/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.apache.tools.ant.types; import java.util.Enumeration; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import java.util.Properties; import java.util.Vector; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Project; import org.apache.tools.ant.util.JavaEnvUtils; /** * A representation of a Java command line that is * a composite of 2 <tt>Commandline</tt>s. One is used for the * vm/options and one for the classname/arguments. It provides * specific methods for a Java command line. * */ public class CommandlineJava implements Cloneable { /** * commands to the JVM */ private Commandline vmCommand = new Commandline(); /** * actual java commands */ private Commandline javaCommand = new Commandline(); /** * properties to add using -D */ private SysProperties sysProperties = new SysProperties(); private Path classpath = null; private Path bootclasspath = null; private Path modulepath = null; private Path upgrademodulepath = null; private String vmVersion; private String maxMemory = null; /** * any assertions to make? Currently only supported in forked JVMs */ private Assertions assertions = null; /** * Indicate whether it will execute a jar file, module or main class. * In this case of jar the first vm option must be a -jar and the 'executable' is a jar file. * In case of module the first vm option is -m and the 'executable' is 'module/mainClass'. */ private ExecutableType executableType; /** * Whether system properties and bootclasspath shall be cloned. * @since Ant 1.7 */ private boolean cloneVm = false; /** * Specialized Environment class for System properties. */ public static class SysProperties extends Environment implements Cloneable { // CheckStyle:VisibilityModifier OFF - bc /** the system properties. */ Properties sys = null; // CheckStyle:VisibilityModifier ON private Vector<PropertySet> propertySets = new Vector<>(); /** * Get the properties as an array; this is an override of the * superclass, as it evaluates all the properties. * @return the array of definitions; may be null. * @throws BuildException on error. */ @Override public String[] getVariables() throws BuildException { List<String> definitions = new LinkedList<>(); addDefinitionsToList(definitions.listIterator()); if (definitions.isEmpty()) { return null; } return definitions.toArray(new String[definitions.size()]); } /** * Add all definitions (including property sets) to a list. * @param listIt list iterator supporting add method. */ public void addDefinitionsToList(ListIterator<String> listIt) { String[] props = super.getVariables(); if (props != null) { for (int i = 0; i < props.length; i++) { listIt.add("-D" + props[i]); } } Properties propertySetProperties = mergePropertySets(); for (Enumeration<?> e = propertySetProperties.keys(); e.hasMoreElements();) { String key = (String) e.nextElement(); String value = propertySetProperties.getProperty(key); listIt.add("-D" + key + "=" + value); } } /** * Get the size of the sysproperties instance. This merges all * property sets, so is not an O(1) operation. * @return the size of the sysproperties instance. */ public int size() { Properties p = mergePropertySets(); return variables.size() + p.size(); } /** * Cache the system properties and set the system properties to the * new values. * @throws BuildException if Security prevented this operation. */ public void setSystem() throws BuildException { try { sys = System.getProperties(); Properties p = new Properties(); for (Enumeration<?> e = sys.propertyNames(); e.hasMoreElements();) { String name = (String) e.nextElement(); String value = sys.getProperty(name); if (name != null && value != null) { p.put(name, value); } } p.putAll(mergePropertySets()); for (Environment.Variable v : variables) { v.validate(); p.put(v.getKey(), v.getValue()); } System.setProperties(p); } catch (SecurityException e) { throw new BuildException("Cannot modify system properties", e); } } /** * Restore the system properties to the cached value. * @throws BuildException if Security prevented this operation, or * there were no system properties to restore. */ public void restoreSystem() throws BuildException { if (sys == null) { throw new BuildException("Unbalanced nesting of SysProperties"); } try { System.setProperties(sys); sys = null; } catch (SecurityException e) { throw new BuildException("Cannot modify system properties", e); } } /** * Create a deep clone. * @return a cloned instance of SysProperties. * @exception CloneNotSupportedException for signature. */ @SuppressWarnings("unchecked") @Override public Object clone() throws CloneNotSupportedException { try { SysProperties c = (SysProperties) super.clone(); c.variables = (Vector<Environment.Variable>) variables.clone(); c.propertySets = (Vector<PropertySet>) propertySets.clone(); return c; } catch (CloneNotSupportedException e) { return null; } } /** * Add a propertyset to the total set. * @param ps the new property set. */ public void addSyspropertyset(PropertySet ps) { propertySets.addElement(ps); } /** * Add a propertyset to the total set. * @param ps the new property set. * @since Ant 1.6.3 */ public void addSysproperties(SysProperties ps) { variables.addAll(ps.variables); propertySets.addAll(ps.propertySets); } /** * Merge all property sets into a single Properties object. * @return the merged object. */ private Properties mergePropertySets() { Properties p = new Properties(); for (PropertySet ps : propertySets) { p.putAll(ps.getProperties()); } return p; } } /** * Constructor uses the VM we are running on now. */ public CommandlineJava() { setVm(JavaEnvUtils.getJreExecutable("java")); setVmversion(JavaEnvUtils.getJavaVersion()); } /** * Create a new argument to the java program. * @return an argument to be configured. */ public Commandline.Argument createArgument() { return javaCommand.createArgument(); } /** * Create a new JVM argument. * @return an argument to be configured. */ public Commandline.Argument createVmArgument() { return vmCommand.createArgument(); } /** * Add a system property. * @param sysp a property to be set in the JVM. */ public void addSysproperty(Environment.Variable sysp) { sysProperties.addVariable(sysp); } /** * Add a set of system properties. * @param sysp a set of properties. */ public void addSyspropertyset(PropertySet sysp) { sysProperties.addSyspropertyset(sysp); } /** * Add a set of system properties. * @param sysp a set of properties. * @since Ant 1.6.3 */ public void addSysproperties(SysProperties sysp) { sysProperties.addSysproperties(sysp); } /** * Set the executable used to start the new JVM. * @param vm the executable to use. */ public void setVm(String vm) { vmCommand.setExecutable(vm); } /** * Set the JVM version required. * @param value the version required. */ public void setVmversion(String value) { vmVersion = value; } /** * Set whether system properties will be copied to the cloned VM--as * well as the bootclasspath unless you have explicitly specified * a bootclasspath. * @param cloneVm if true copy the system properties. * @since Ant 1.7 */ public void setCloneVm(boolean cloneVm) { this.cloneVm = cloneVm; } /** * Get the current assertions. * @return assertions or null. */ public Assertions getAssertions() { return assertions; } /** * Add an assertion set to the command. * @param assertions assertions to make. */ public void setAssertions(Assertions assertions) { this.assertions = assertions; } /** * Set a jar file to execute via the -jar option. * @param jarpathname the pathname of the jar to execute. */ public void setJar(String jarpathname) { javaCommand.setExecutable(jarpathname); executableType = ExecutableType.JAR; } /** * Get the name of the jar to be run. * @return the pathname of the jar file to run via -jar option * or <tt>null</tt> if there is no jar to run. * @see #getClassname() */ public String getJar() { if (executableType == ExecutableType.JAR) { return javaCommand.getExecutable(); } return null; } /** * Set the classname to execute. * @param classname the fully qualified classname. */ public void setClassname(String classname) { if (executableType == ExecutableType.MODULE) { javaCommand.setExecutable(createModuleClassPair( parseModuleFromModuleClassPair(javaCommand.getExecutable()), classname), false); } else { javaCommand.setExecutable(classname); executableType = ExecutableType.CLASS; } } /** * Get the name of the class to be run. * @return the name of the class to run or <tt>null</tt> if there is no class. * @see #getJar() */ public String getClassname() { if (executableType != null) { switch (executableType) { case CLASS: return javaCommand.getExecutable(); case MODULE: return parseClassFromModuleClassPair(javaCommand.getExecutable()); default: } } return null; } /** * Set the module to execute. * @param module the module name. * @since 1.9.7 */ public void setModule(final String module) { if (executableType == null) { javaCommand.setExecutable(module); } else { switch (executableType) { case JAR: javaCommand.setExecutable(module, false); break; case CLASS: javaCommand.setExecutable(createModuleClassPair( module, javaCommand.getExecutable()), false); break; case MODULE: javaCommand.setExecutable(createModuleClassPair( module, parseClassFromModuleClassPair(javaCommand.getExecutable())), false); break; default: } } executableType = ExecutableType.MODULE; } /** * Get the name of the module to be run. * @return the name of the module to run or <tt>null</tt> if there is no module. * @see #getJar() * @see #getClassname() * @since 1.9.7 */ public String getModule() { if (executableType == ExecutableType.MODULE) { return parseModuleFromModuleClassPair(javaCommand.getExecutable()); } return null; } /** * Create a classpath. * @param p the project to use to create the path. * @return a path to be configured. */ public Path createClasspath(Project p) { if (classpath == null) { classpath = new Path(p); } return classpath; } /** * Create a boot classpath. * @param p the project to use to create the path. * @return a path to be configured. * @since Ant 1.6 */ public Path createBootclasspath(Project p) { if (bootclasspath == null) { bootclasspath = new Path(p); } return bootclasspath; } /** * Create a modulepath. * @param p the project to use to create the path. * @return a path to be configured. * @since 1.9.7 */ public Path createModulepath(Project p) { if (modulepath == null) { modulepath = new Path(p); } return modulepath; } /** * Create an upgrademodulepath. * @param p the project to use to create the path. * @return a path to be configured. * @since 1.9.7 */ public Path createUpgrademodulepath(Project p) { if (upgrademodulepath == null) { upgrademodulepath = new Path(p); } return upgrademodulepath; } /** * Get the vm version. * @return the vm version. */ public String getVmversion() { return vmVersion; } /** * Get the command line to run a Java vm. * @return the list of all arguments necessary to run the vm. */ public String[] getCommandline() { //create the list List<String> commands = new LinkedList<>(); //fill it addCommandsToList(commands.listIterator()); //convert to an array return commands.toArray(new String[commands.size()]); } /** * Add all the commands to a list identified by the iterator passed in. * @param listIterator an iterator that supports the add method. * @since Ant 1.6 */ private void addCommandsToList(final ListIterator<String> listIterator) { //create the command to run Java, including user specified options getActualVMCommand().addCommandToList(listIterator); // properties are part of the vm options... sysProperties.addDefinitionsToList(listIterator); if (isCloneVm()) { SysProperties clonedSysProperties = new SysProperties(); PropertySet ps = new PropertySet(); PropertySet.BuiltinPropertySetName sys = new PropertySet.BuiltinPropertySetName(); sys.setValue("system"); ps.appendBuiltin(sys); clonedSysProperties.addSyspropertyset(ps); clonedSysProperties.addDefinitionsToList(listIterator); } //boot classpath Path bcp = calculateBootclasspath(true); if (bcp.size() > 0) { listIterator.add("-Xbootclasspath:" + bcp.toString()); } //main classpath if (haveClasspath()) { listIterator.add("-classpath"); listIterator.add( classpath.concatSystemClasspath("ignore").toString()); } //module path if (haveModulepath()) { listIterator.add("--module-path"); listIterator.add( modulepath.concatSystemClasspath("ignore").toString()); } //upgrade module path if (haveUpgrademodulepath()) { listIterator.add("--upgrade-module-path"); listIterator.add( upgrademodulepath.concatSystemClasspath("ignore").toString()); } //now any assertions are added if (getAssertions() != null) { getAssertions().applyAssertions(listIterator); } // JDK usage command line says that -jar must be the first option, as there is // a bug in JDK < 1.4 that forces the jvm type to be specified as the first // option, it is appended here as specified in the docs even though there is // in fact no order. if (executableType == ExecutableType.JAR) { listIterator.add("-jar"); } else if (executableType == ExecutableType.MODULE) { listIterator.add("-m"); } // this is the classname to run as well as its arguments. // in case of ExecutableType.JAR, the executable is a jar file, // in case of ExecutableType.MODULE, the executable is a module name, portentially including a class name. javaCommand.addCommandToList(listIterator); } /** * Specify max memory of the JVM. * -mx or -Xmx depending on VM version. * @param max the string to pass to the jvm to specify the max memory. */ public void setMaxmemory(String max) { this.maxMemory = max; } /** * Get a string description. * @return the command line as a string. */ @Override public String toString() { return Commandline.toString(getCommandline()); } /** * Return a String that describes the command and arguments suitable for * verbose output before a call to <code>Runtime.exec(String[])<code>. * @return the description string. * @since Ant 1.5 */ public String describeCommand() { return Commandline.describeCommand(getCommandline()); } /** * Return a String that describes the java command and arguments * for in-VM executions. * * <p>The class name is the executable in this context.</p> * @return the description string. * @since Ant 1.5 */ public String describeJavaCommand() { return Commandline.describeCommand(getJavaCommand()); } /** * Get the VM command parameters, including memory settings. * @return the VM command parameters. */ protected Commandline getActualVMCommand() { Commandline actualVMCommand = (Commandline) vmCommand.clone(); if (maxMemory != null) { if (vmVersion.startsWith("1.1")) { actualVMCommand.createArgument().setValue("-mx" + maxMemory); } else { actualVMCommand.createArgument().setValue("-Xmx" + maxMemory); } } return actualVMCommand; } /** * Get the size of the java command line. This is a fairly intensive * operation, as it has to evaluate the size of many components. * @return the total number of arguments in the java command line. * @see #getCommandline() * @deprecated since 1.7. * Please dont use this, it effectively creates the * entire command. */ @Deprecated public int size() { int size = getActualVMCommand().size() + javaCommand.size() + sysProperties.size(); // cloned system properties if (isCloneVm()) { size += System.getProperties().size(); } // classpath is "-classpath <classpath>" -> 2 args if (haveClasspath()) { size += 2; } // bootclasspath is "-Xbootclasspath:<classpath>" -> 1 arg if (calculateBootclasspath(true).size() > 0) { size++; } // jar execution requires an additional -jar option if (executableType == ExecutableType.JAR || executableType == ExecutableType.MODULE) { size++; } //assertions take up space too if (getAssertions() != null) { size += getAssertions().size(); } return size; } /** * Get the Java command to be used. * @return the java command--not a clone. */ public Commandline getJavaCommand() { return javaCommand; } /** * Get the VM command, including memory. * @return A deep clone of the instance's VM command, with memory settings added. */ public Commandline getVmCommand() { return getActualVMCommand(); } /** * Get the classpath for the command. * @return the classpath or null. */ public Path getClasspath() { return classpath; } /** * Get the boot classpath. * @return boot classpath or null. */ public Path getBootclasspath() { return bootclasspath; } /** * Get the modulepath. * @return modulepath or null. * @since 1.9.7 */ public Path getModulepath() { return modulepath; } /** * Get the upgrademodulepath. * @return upgrademodulepath or null. * @since 1.9.7 */ public Path getUpgrademodulepath() { return upgrademodulepath; } /** * Cache current system properties and set them to those in this * Java command. * @throws BuildException if Security prevented this operation. */ public void setSystemProperties() throws BuildException { sysProperties.setSystem(); } /** * Restore the cached system properties. * @throws BuildException if Security prevented this operation, or * there was no system properties to restore */ public void restoreSystemProperties() throws BuildException { sysProperties.restoreSystem(); } /** * Get the system properties object. * @return The system properties object. */ public SysProperties getSystemProperties() { return sysProperties; } /** * Deep clone the object. * @return a CommandlineJava object. * @throws BuildException if anything went wrong. * @throws CloneNotSupportedException never. */ @Override public CommandlineJava clone() throws CloneNotSupportedException { try { CommandlineJava c = (CommandlineJava) super.clone(); c.vmCommand = (Commandline) vmCommand.clone(); c.javaCommand = (Commandline) javaCommand.clone(); c.sysProperties = (SysProperties) sysProperties.clone(); if (classpath != null) { c.classpath = (Path) classpath.clone(); } if (bootclasspath != null) { c.bootclasspath = (Path) bootclasspath.clone(); } if (modulepath != null) { c.modulepath = (Path) modulepath.clone(); } if (upgrademodulepath != null) { c.upgrademodulepath = (Path) upgrademodulepath.clone(); } if (assertions != null) { c.assertions = (Assertions) assertions.clone(); } return c; } catch (CloneNotSupportedException e) { throw new BuildException(e); } } /** * Clear out the java arguments. */ public void clearJavaArgs() { javaCommand.clearArgs(); } /** * Determine whether the classpath has been specified, and whether it shall * really be used or be nulled by build.sysclasspath. * @return true if the classpath is to be used. * @since Ant 1.6 */ public boolean haveClasspath() { Path fullClasspath = classpath != null ? classpath.concatSystemClasspath("ignore") : null; return fullClasspath != null && fullClasspath.toString().trim().length() > 0; } /** * Determine whether the bootclasspath has been specified, and whether it * shall really be used (build.sysclasspath could be set or the VM may not * support it). * * @param log whether to log a warning if a bootclasspath has been * specified but will be ignored. * @return true if the bootclasspath is to be used. * @since Ant 1.6 */ protected boolean haveBootclasspath(boolean log) { return calculateBootclasspath(log).size() > 0; } /** * Determine whether the modulepath has been specified. * @return true if the modulepath is to be used. * @since 1.9.7 */ public boolean haveModulepath() { Path fullClasspath = modulepath != null ? modulepath.concatSystemClasspath("ignore") : null; return fullClasspath != null && !fullClasspath.toString().trim().isEmpty(); } /** * Determine whether the upgrademodulepath has been specified. * @return true if the upgrademodulepath is to be used. * @since 1.9.7 */ public boolean haveUpgrademodulepath() { Path fullClasspath = upgrademodulepath != null ? upgrademodulepath.concatSystemClasspath("ignore") : null; return fullClasspath != null && !fullClasspath.toString().trim().isEmpty(); } /** * Calculate the bootclasspath based on the bootclasspath * specified, the build.sysclasspath and ant.build.clonevm magic * properties as well as the cloneVm attribute. * @param log whether to write messages to the log. * @since Ant 1.7 */ private Path calculateBootclasspath(boolean log) { if (vmVersion.startsWith("1.1")) { if (bootclasspath != null && log) { bootclasspath.log("Ignoring bootclasspath as " + "the target VM doesn't support it."); } } else { Path b = bootclasspath; if (b == null) { b = new Path(null); } // even with no user-supplied bootclasspath // build.sysclasspath could be set to something other than // "ignore" and thus create one return b.concatSystemBootClasspath(isCloneVm() ? "last" : "ignore"); } return new Path(null); } /** * Find out whether either of the cloneVm attribute or the magic property * ant.build.clonevm has been set. * @return <code>boolean</code>. * @since 1.7 */ private boolean isCloneVm() { return cloneVm || Boolean.parseBoolean(System.getProperty("ant.build.clonevm")); } /** * Creates JDK 9 main module command line argument. * @param module the module name. * @param classname the classname or <code>null</code>. * @return the main module with optional classname command line argument. * @since 1.9.7 */ private static String createModuleClassPair(final String module, final String classname) { return classname == null ? module : String.format("%s/%s", module, classname); //NOI18N } /** * Parses a module name from JDK 9 main module command line argument. * @param moduleClassPair a module with optional classname or <code>null</code>. * @return the module name or <code>null</code>. * @since 1.9.7 */ private static String parseModuleFromModuleClassPair(final String moduleClassPair) { if (moduleClassPair == null) { return null; } final String[] moduleAndClass = moduleClassPair.split("/"); //NOI18N return moduleAndClass[0]; } /** * Parses a classname from JDK 9 main module command line argument. * @param moduleClassPair a module with optional classname or <code>null</code>. * @return the classname or <code>null</code>. * @since 1.9.7 */ private static String parseClassFromModuleClassPair(final String moduleClassPair) { if (moduleClassPair == null) { return null; } final String[] moduleAndClass = moduleClassPair.split("/"); //NOI18N return moduleAndClass.length == 2 ? moduleAndClass[1] : null; } /** * Type of execution. * @since 1.9.7 */ private enum ExecutableType { /** * Main class execution. */ CLASS, /** * Jar file execution. */ JAR, /** * Module execution. */ MODULE } }