blob: 1b2587eec94d689584ed2bbe618d63ea9262fdca [file] [log] [blame]
/*
* 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
*
* https://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.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 <code>Commandline</code>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 (String prop : props) {
listIt.add("-D" + prop);
}
}
Properties propertySetProperties = mergePropertySets();
for (String key : propertySetProperties.stringPropertyNames()) {
listIt.add("-D" + key + "=" + propertySetProperties.getProperty(key));
}
}
/**
* 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 (String name : sys.stringPropertyNames()) {
String value = sys.getProperty(name);
if (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 <code>null</code> 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 <code>null</code> 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 source-file, to execute as single file source programs, a feature, available
* since Java 11.
*
* @param sourceFile The path to the source file
* @since Ant 1.10.5
*/
public void setSourceFile(final String sourceFile) {
this.executableType = ExecutableType.SOURCE_FILE;
javaCommand.setExecutable(sourceFile);
}
/**
* @return Returns the source-file to execute, if this command line has
* been {@link #setSourceFile(String) configured for single file source program
* execution}. Else returns null.
* @since Ant 1.10.5
*/
public String getSourceFile() {
return this.executableType == ExecutableType.SOURCE_FILE ? this.javaCommand.getExecutable() : 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 <code>null</code> 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/source-file 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, potentially including a class name.
// in case of ExecutableType.SOURCE_FILE, the executable is a Java source file (ending in .java) or a shebang
// file containing Java source
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 don't 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 Object 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 ? null : classpath.concatSystemClasspath("ignore");
return fullClasspath != null && !fullClasspath.toString().trim().isEmpty();
}
/**
* 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,
/**
* Source file (introduced in Java 11)
*/
SOURCE_FILE,
}
}