1. /*
  2. * Copyright 2000-2004 The Apache Software Foundation
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. *
  16. */
  17. package org.apache.tools.ant.types;
  18. import java.util.Enumeration;
  19. import java.util.Properties;
  20. import java.util.Vector;
  21. import java.util.List;
  22. import java.util.LinkedList;
  23. import java.util.ListIterator;
  24. import org.apache.tools.ant.BuildException;
  25. import org.apache.tools.ant.Project;
  26. import org.apache.tools.ant.util.JavaEnvUtils;
  27. /**
  28. * A representation of a Java command line that is
  29. * a composite of 2 <tt>Commandline</tt>. One is used for the
  30. * vm/options and one for the classname/arguments. It provides
  31. * specific methods for a java command line.
  32. *
  33. */
  34. public class CommandlineJava implements Cloneable {
  35. /**
  36. * commands to the JVM
  37. */
  38. private Commandline vmCommand = new Commandline();
  39. /**
  40. * actual java commands
  41. */
  42. private Commandline javaCommand = new Commandline();
  43. /**
  44. * properties to add using -D
  45. */
  46. private SysProperties sysProperties = new SysProperties();
  47. private Path classpath = null;
  48. private Path bootclasspath = null;
  49. private String vmVersion;
  50. private String maxMemory = null;
  51. /**
  52. * any assertions to make? Currently only supported in forked JVMs
  53. */
  54. private Assertions assertions = null;
  55. /**
  56. * Indicate whether it will execute a jar file or not, in this case
  57. * the first vm option must be a -jar and the 'executable' is a jar file.
  58. */
  59. private boolean executeJar = false;
  60. /**
  61. * Specialized Environment class for System properties
  62. */
  63. public static class SysProperties extends Environment implements Cloneable {
  64. Properties sys = null;
  65. private Vector propertySets = new Vector();
  66. /**
  67. * get the properties as an array; this is an override of the
  68. * superclass, as it evaluates all the properties
  69. * @return the array of definitions; may be null
  70. * @throws BuildException
  71. */
  72. public String[] getVariables() throws BuildException {
  73. List definitions = new LinkedList();
  74. ListIterator list = definitions.listIterator();
  75. addDefinitionsToList(list);
  76. if (definitions.size() == 0) {
  77. return null;
  78. } else {
  79. return (String[]) definitions.toArray(new String[0]);
  80. }
  81. }
  82. /**
  83. * add all definitions (including property sets) to a list
  84. * @param listIt list iterator supporting add method
  85. */
  86. public void addDefinitionsToList(ListIterator listIt) {
  87. String[] props = super.getVariables();
  88. if (props != null) {
  89. for (int i = 0; i < props.length; i++) {
  90. listIt.add("-D" + props[i]);
  91. }
  92. }
  93. Properties propertySets = mergePropertySets();
  94. for (Enumeration e = propertySets.keys(); e.hasMoreElements();) {
  95. String key = (String) e.nextElement();
  96. String value = propertySets.getProperty(key);
  97. listIt.add("-D" + key + "=" + value);
  98. }
  99. }
  100. /**
  101. * This method gets the size of the sysproperties instance. This merges all
  102. * property sets, so is not an O(1) operation.
  103. * @return the size of the sysproperties instance
  104. */
  105. public int size() {
  106. Properties p = mergePropertySets();
  107. return variables.size() + p.size();
  108. }
  109. /**
  110. * cache the system properties and set the system properties to the
  111. * new values
  112. * @throws BuildException if Security prevented this operation
  113. */
  114. public void setSystem() throws BuildException {
  115. try {
  116. sys = System.getProperties();
  117. Properties p = new Properties();
  118. for (Enumeration e = sys.keys(); e.hasMoreElements();) {
  119. Object o = e.nextElement();
  120. p.put(o, sys.get(o));
  121. }
  122. p.putAll(mergePropertySets());
  123. for (Enumeration e = variables.elements(); e.hasMoreElements();) {
  124. Environment.Variable v = (Environment.Variable) e.nextElement();
  125. p.put(v.getKey(), v.getValue());
  126. }
  127. System.setProperties(p);
  128. } catch (SecurityException e) {
  129. throw new BuildException("Cannot modify system properties", e);
  130. }
  131. }
  132. /**
  133. * restore the system properties to the cached value
  134. * @throws BuildException if Security prevented this operation, or
  135. * there was no system properties to restore
  136. */
  137. public void restoreSystem() throws BuildException {
  138. if (sys == null) {
  139. throw new BuildException("Unbalanced nesting of SysProperties");
  140. }
  141. try {
  142. System.setProperties(sys);
  143. sys = null;
  144. } catch (SecurityException e) {
  145. throw new BuildException("Cannot modify system properties", e);
  146. }
  147. }
  148. /**
  149. * deep clone
  150. * @return a cloned instance of SysProperties
  151. */
  152. public Object clone() {
  153. try {
  154. SysProperties c = (SysProperties) super.clone();
  155. c.variables = (Vector) variables.clone();
  156. c.propertySets = (Vector) propertySets.clone();
  157. return c;
  158. } catch (CloneNotSupportedException e) {
  159. return null;
  160. }
  161. }
  162. /**
  163. * add a propertyset to the total set
  164. * @param ps the new property set
  165. */
  166. public void addSyspropertyset(PropertySet ps) {
  167. propertySets.addElement(ps);
  168. }
  169. /**
  170. * merge all property sets into a single Properties object
  171. * @return the merged object
  172. */
  173. private Properties mergePropertySets() {
  174. Properties p = new Properties();
  175. for (Enumeration e = propertySets.elements();
  176. e.hasMoreElements();) {
  177. PropertySet ps = (PropertySet) e.nextElement();
  178. p.putAll(ps.getProperties());
  179. }
  180. return p;
  181. }
  182. }
  183. /**
  184. * constructor uses the VM we are running on now.
  185. */
  186. public CommandlineJava() {
  187. setVm(JavaEnvUtils.getJreExecutable("java"));
  188. setVmversion(JavaEnvUtils.getJavaVersion());
  189. }
  190. /**
  191. * create a new argument to the java program
  192. * @return an argument to be configured
  193. */
  194. public Commandline.Argument createArgument() {
  195. return javaCommand.createArgument();
  196. }
  197. /**
  198. * create a new JVM argument
  199. * @return an argument to be configured
  200. */
  201. public Commandline.Argument createVmArgument() {
  202. return vmCommand.createArgument();
  203. }
  204. /**
  205. * add a system property
  206. * @param sysp a property to be set in the JVM
  207. */
  208. public void addSysproperty(Environment.Variable sysp) {
  209. sysProperties.addVariable(sysp);
  210. }
  211. /**
  212. * add a set of system properties
  213. * @param sysp a set of properties
  214. */
  215. public void addSyspropertyset(PropertySet sysp) {
  216. sysProperties.addSyspropertyset(sysp);
  217. }
  218. /**
  219. * set the executable used to start the new JVM
  220. * @param vm
  221. */
  222. public void setVm(String vm) {
  223. vmCommand.setExecutable(vm);
  224. }
  225. /**
  226. * set the JVM version required
  227. * @param value
  228. */
  229. public void setVmversion(String value) {
  230. vmVersion = value;
  231. }
  232. /**
  233. * get the current assertions
  234. * @return assertions or null
  235. */
  236. public Assertions getAssertions() {
  237. return assertions;
  238. }
  239. /**
  240. * add an assertion set to the command
  241. * @param assertions assertions to make
  242. */
  243. public void setAssertions(Assertions assertions) {
  244. this.assertions = assertions;
  245. }
  246. /**
  247. * set a jar file to execute via the -jar option.
  248. * @param jarpathname the pathname of the jar to execute
  249. */
  250. public void setJar(String jarpathname) {
  251. javaCommand.setExecutable(jarpathname);
  252. executeJar = true;
  253. }
  254. /**
  255. * @return the pathname of the jar file to run via -jar option
  256. * or <tt>null</tt> if there is no jar to run.
  257. * @see #getClassname()
  258. */
  259. public String getJar() {
  260. if (executeJar) {
  261. return javaCommand.getExecutable();
  262. }
  263. return null;
  264. }
  265. /**
  266. * set the classname to execute
  267. * @param classname the fully qualified classname.
  268. */
  269. public void setClassname(String classname) {
  270. javaCommand.setExecutable(classname);
  271. executeJar = false;
  272. }
  273. /**
  274. * @return the name of the class to run or <tt>null</tt> if there is no class.
  275. * @see #getJar()
  276. */
  277. public String getClassname() {
  278. if (!executeJar) {
  279. return javaCommand.getExecutable();
  280. }
  281. return null;
  282. }
  283. public Path createClasspath(Project p) {
  284. if (classpath == null) {
  285. classpath = new Path(p);
  286. }
  287. return classpath;
  288. }
  289. /**
  290. * @since Ant 1.6
  291. */
  292. public Path createBootclasspath(Project p) {
  293. if (bootclasspath == null) {
  294. bootclasspath = new Path(p);
  295. }
  296. return bootclasspath;
  297. }
  298. public String getVmversion() {
  299. return vmVersion;
  300. }
  301. /**
  302. * get the command line to run a java vm.
  303. * @return the list of all arguments necessary to run the vm.
  304. */
  305. public String[] getCommandline() {
  306. //create the list
  307. List commands = new LinkedList();
  308. final ListIterator listIterator = commands.listIterator();
  309. //fill it
  310. addCommandsToList(listIterator);
  311. //convert to an array
  312. return (String[]) commands.toArray(new String[0]);
  313. }
  314. /**
  315. * add all the commands to a list identified by the iterator passed in
  316. * @param listIterator an iterator that supports the add method
  317. * @since Ant1.6
  318. */
  319. private void addCommandsToList(final ListIterator listIterator) {
  320. //create the command to run Java, including user specified options
  321. getActualVMCommand().addCommandToList(listIterator);
  322. // properties are part of the vm options...
  323. sysProperties.addDefinitionsToList(listIterator);
  324. //boot classpath
  325. if (haveBootclasspath(true)) {
  326. listIterator.add("-Xbootclasspath:" + bootclasspath.toString());
  327. }
  328. //main classpath
  329. if (haveClasspath()) {
  330. listIterator.add("-classpath");
  331. listIterator.add(
  332. classpath.concatSystemClasspath("ignore").toString());
  333. }
  334. //now any assertions are added
  335. if (getAssertions() != null) {
  336. getAssertions().applyAssertions(listIterator);
  337. }
  338. // JDK usage command line says that -jar must be the first option, as there is
  339. // a bug in JDK < 1.4 that forces the jvm type to be specified as the first
  340. // option, it is appended here as specified in the docs even though there is
  341. // in fact no order.
  342. if (executeJar) {
  343. listIterator.add("-jar");
  344. }
  345. // this is the classname to run as well as its arguments.
  346. // in case of 'executeJar', the executable is a jar file.
  347. javaCommand.addCommandToList(listIterator);
  348. }
  349. /**
  350. * Specify max memory of the JVM
  351. * -mx or -Xmx depending on VM version
  352. */
  353. public void setMaxmemory(String max) {
  354. this.maxMemory = max;
  355. }
  356. /**
  357. * get a string description.
  358. * @return the command line as a string
  359. */
  360. public String toString() {
  361. return Commandline.toString(getCommandline());
  362. }
  363. /**
  364. * Returns a String that describes the command and arguments
  365. * suitable for verbose output before a call to
  366. * <code>Runtime.exec(String[])<code>
  367. *
  368. * @since Ant 1.5
  369. */
  370. public String describeCommand() {
  371. return Commandline.describeCommand(getCommandline());
  372. }
  373. /**
  374. * Returns a String that describes the java command and arguments
  375. * for in VM executions.
  376. *
  377. * <p>The class name is the executable in this context.</p>
  378. *
  379. * @since Ant 1.5
  380. */
  381. public String describeJavaCommand() {
  382. return Commandline.describeCommand(getJavaCommand());
  383. }
  384. /**
  385. * Get the VM command parameters, including memory settings
  386. * @return the VM command parameters
  387. */
  388. protected Commandline getActualVMCommand() {
  389. Commandline actualVMCommand = (Commandline) vmCommand.clone();
  390. if (maxMemory != null) {
  391. if (vmVersion.startsWith("1.1")) {
  392. actualVMCommand.createArgument().setValue("-mx" + maxMemory);
  393. } else {
  394. actualVMCommand.createArgument().setValue("-Xmx" + maxMemory);
  395. }
  396. }
  397. return actualVMCommand;
  398. }
  399. /**
  400. * The size of the java command line. This is a fairly intensive
  401. * operation, as it has to evaluate the size of many components.
  402. * @return the total number of arguments in the java command line.
  403. * @see #getCommandline()
  404. * @deprecated please dont use this -it effectively creates the entire command.
  405. */
  406. public int size() {
  407. int size = getActualVMCommand().size() + javaCommand.size()
  408. + sysProperties.size();
  409. // classpath is "-classpath <classpath>" -> 2 args
  410. if (haveClasspath()) {
  411. size += 2;
  412. }
  413. // bootclasspath is "-Xbootclasspath:<classpath>" -> 1 arg
  414. if (haveBootclasspath(true)) {
  415. size++;
  416. }
  417. // jar execution requires an additional -jar option
  418. if (executeJar) {
  419. size++;
  420. }
  421. //assertions take up space too
  422. if (getAssertions() != null) {
  423. size += getAssertions().size();
  424. }
  425. return size;
  426. }
  427. /**
  428. * get the Java command to be used.
  429. * @return the java command -not a clone.
  430. */
  431. public Commandline getJavaCommand() {
  432. return javaCommand;
  433. }
  434. /**
  435. * Get the VM command, including memory.
  436. * @return A deep clone of the instance's VM command, with memory settings added
  437. */
  438. public Commandline getVmCommand() {
  439. return getActualVMCommand();
  440. }
  441. /**
  442. * get the classpath for the command
  443. * @return the classpath or null
  444. */
  445. public Path getClasspath() {
  446. return classpath;
  447. }
  448. /**
  449. * get the boot classpath
  450. * @return boot classpath or null
  451. */
  452. public Path getBootclasspath() {
  453. return bootclasspath;
  454. }
  455. /**
  456. * cache current system properties and set them to those in this
  457. * java command
  458. * @throws BuildException if Security prevented this operation
  459. */
  460. public void setSystemProperties() throws BuildException {
  461. sysProperties.setSystem();
  462. }
  463. /**
  464. * @throws BuildException if Security prevented this operation, or
  465. * there was no system properties to restore
  466. */
  467. public void restoreSystemProperties() throws BuildException {
  468. sysProperties.restoreSystem();
  469. }
  470. /**
  471. * get the system properties object
  472. * @return The system properties object
  473. */
  474. public SysProperties getSystemProperties() {
  475. return sysProperties;
  476. }
  477. /**
  478. * clone the object; clone of all fields in the class
  479. * @return a CommandlineJava object
  480. */
  481. public Object clone() {
  482. try {
  483. CommandlineJava c = (CommandlineJava) super.clone();
  484. c.vmCommand = (Commandline) vmCommand.clone();
  485. c.javaCommand = (Commandline) javaCommand.clone();
  486. c.sysProperties = (SysProperties) sysProperties.clone();
  487. if (classpath != null) {
  488. c.classpath = (Path) classpath.clone();
  489. }
  490. if (bootclasspath != null) {
  491. c.bootclasspath = (Path) bootclasspath.clone();
  492. }
  493. if (assertions != null) {
  494. c.assertions = (Assertions) assertions.clone();
  495. }
  496. return c;
  497. } catch (CloneNotSupportedException e) {
  498. throw new BuildException(e);
  499. }
  500. }
  501. /**
  502. * Clear out the java arguments.
  503. */
  504. public void clearJavaArgs() {
  505. javaCommand.clearArgs();
  506. }
  507. /**
  508. * Has the classpath been specified and shall it really be used or
  509. * will build.sysclasspath null it?
  510. *
  511. * @since Ant 1.6
  512. */
  513. protected boolean haveClasspath() {
  514. Path fullClasspath = classpath != null
  515. ? classpath.concatSystemClasspath("ignore") : null;
  516. return fullClasspath != null
  517. && fullClasspath.toString().trim().length() > 0;
  518. }
  519. /**
  520. * Has the bootclasspath been specified and shall it really be
  521. * used (build.sysclasspath could be set or the VM may not support
  522. * it)?
  523. *
  524. * @param log whether to log a warning if a bootclasspath has been
  525. * specified but will be ignored.
  526. *
  527. * @since Ant 1.6
  528. */
  529. protected boolean haveBootclasspath(boolean log) {
  530. if (bootclasspath != null
  531. && bootclasspath.toString().trim().length() > 0) {
  532. /*
  533. * XXX - need to log something, but there is no ProjectComponent
  534. * around to log to.
  535. */
  536. if (!bootclasspath.toString()
  537. .equals(bootclasspath.concatSystemClasspath("ignore")
  538. .toString())) {
  539. if (log) {
  540. System.out.println("Ignoring bootclasspath as "
  541. + "build.sysclasspath has been set.");
  542. }
  543. } else if (vmVersion.startsWith("1.1")) {
  544. if (log) {
  545. System.out.println("Ignoring bootclasspath as "
  546. + "the target VM doesn't support it.");
  547. }
  548. } else {
  549. return true;
  550. }
  551. }
  552. return false;
  553. }
  554. }