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.io.File;
  19. import java.util.StringTokenizer;
  20. import java.util.Vector;
  21. import java.util.ArrayList;
  22. import java.util.List;
  23. import java.util.ListIterator;
  24. import java.util.LinkedList;
  25. import org.apache.tools.ant.BuildException;
  26. import org.apache.tools.ant.ProjectComponent;
  27. import org.apache.tools.ant.util.StringUtils;
  28. /**
  29. * Commandline objects help handling command lines specifying processes to
  30. * execute.
  31. *
  32. * The class can be used to define a command line as nested elements or as a
  33. * helper to define a command line by an application.
  34. * <p>
  35. * <code>
  36. * <someelement><br>
  37. *   <acommandline executable="/executable/to/run"><br>
  38. *     <argument value="argument 1" /><br>
  39. *     <argument line="argument_1 argument_2 argument_3" /><br>
  40. *     <argument value="argument 4" /><br>
  41. *   </acommandline><br>
  42. * </someelement><br>
  43. * </code>
  44. * The element <code>someelement</code> must provide a method
  45. * <code>createAcommandline</code> which returns an instance of this class.
  46. *
  47. */
  48. public class Commandline implements Cloneable {
  49. /**
  50. * The arguments of the command
  51. */
  52. private Vector arguments = new Vector();
  53. /**
  54. * the program to execute
  55. */
  56. private String executable = null;
  57. protected static final String DISCLAIMER =
  58. StringUtils.LINE_SEP
  59. + "The \' characters around the executable and arguments are"
  60. + StringUtils.LINE_SEP
  61. + "not part of the command."
  62. + StringUtils.LINE_SEP;
  63. /**
  64. * create a command line from a string
  65. * @param toProcess the line: the first element becomes the executable, the rest
  66. * the arguments
  67. */
  68. public Commandline(String toProcess) {
  69. super();
  70. String[] tmp = translateCommandline(toProcess);
  71. if (tmp != null && tmp.length > 0) {
  72. setExecutable(tmp[0]);
  73. for (int i = 1; i < tmp.length; i++) {
  74. createArgument().setValue(tmp[i]);
  75. }
  76. }
  77. }
  78. /**
  79. * Create an empty command line
  80. */
  81. public Commandline() {
  82. super();
  83. }
  84. /**
  85. * Used for nested xml command line definitions.
  86. */
  87. public static class Argument extends ProjectComponent {
  88. private String[] parts;
  89. /**
  90. * Sets a single commandline argument.
  91. *
  92. * @param value a single commandline argument.
  93. */
  94. public void setValue(String value) {
  95. parts = new String[] {value};
  96. }
  97. /**
  98. * Line to split into several commandline arguments.
  99. *
  100. * @param line line to split into several commandline arguments
  101. */
  102. public void setLine(String line) {
  103. if (line == null) {
  104. return;
  105. }
  106. parts = translateCommandline(line);
  107. }
  108. /**
  109. * Sets a single commandline argument and treats it like a
  110. * PATH - ensures the right separator for the local platform
  111. * is used.
  112. *
  113. * @param value a single commandline argument.
  114. */
  115. public void setPath(Path value) {
  116. parts = new String[] {value.toString()};
  117. }
  118. /**
  119. * Sets a single commandline argument from a reference to a
  120. * path - ensures the right separator for the local platform
  121. * is used.
  122. *
  123. * @param value a single commandline argument.
  124. */
  125. public void setPathref(Reference value) {
  126. Path p = new Path(getProject());
  127. p.setRefid(value);
  128. parts = new String[] {p.toString()};
  129. }
  130. /**
  131. * Sets a single commandline argument to the absolute filename
  132. * of the given file.
  133. *
  134. * @param value a single commandline argument.
  135. */
  136. public void setFile(File value) {
  137. parts = new String[] {value.getAbsolutePath()};
  138. }
  139. /**
  140. * Returns the parts this Argument consists of.
  141. */
  142. public String[] getParts() {
  143. return parts;
  144. }
  145. }
  146. /**
  147. * Class to keep track of the position of an Argument.
  148. <p>This class is there to support the srcfile and targetfile
  149. elements of <execon> and <transform> - don't know
  150. whether there might be additional use cases.</p> --SB
  151. */
  152. public class Marker {
  153. private int position;
  154. private int realPos = -1;
  155. Marker(int position) {
  156. this.position = position;
  157. }
  158. /**
  159. * Return the number of arguments that preceeded this marker.
  160. *
  161. * <p>The name of the executable - if set - is counted as the
  162. * very first argument.</p>
  163. */
  164. public int getPosition() {
  165. if (realPos == -1) {
  166. realPos = (executable == null ? 0 : 1);
  167. for (int i = 0; i < position; i++) {
  168. Argument arg = (Argument) arguments.elementAt(i);
  169. realPos += arg.getParts().length;
  170. }
  171. }
  172. return realPos;
  173. }
  174. }
  175. /**
  176. * Creates an argument object.
  177. *
  178. * <p>Each commandline object has at most one instance of the
  179. * argument class. This method calls
  180. * <code>this.createArgument(false)</code>.</p>
  181. *
  182. * @see #createArgument(boolean)
  183. * @return the argument object.
  184. */
  185. public Argument createArgument() {
  186. return this.createArgument(false);
  187. }
  188. /**
  189. * Creates an argument object and adds it to our list of args.
  190. *
  191. * <p>Each commandline object has at most one instance of the
  192. * argument class.</p>
  193. *
  194. * @param insertAtStart if true, the argument is inserted at the
  195. * beginning of the list of args, otherwise it is appended.
  196. */
  197. public Argument createArgument(boolean insertAtStart) {
  198. Argument argument = new Argument();
  199. if (insertAtStart) {
  200. arguments.insertElementAt(argument, 0);
  201. } else {
  202. arguments.addElement(argument);
  203. }
  204. return argument;
  205. }
  206. /**
  207. * Sets the executable to run. All file separators in the string
  208. * are converted to the platform specific value
  209. */
  210. public void setExecutable(String executable) {
  211. if (executable == null || executable.length() == 0) {
  212. return;
  213. }
  214. this.executable = executable.replace('/', File.separatorChar)
  215. .replace('\\', File.separatorChar);
  216. }
  217. /**
  218. * get the executable
  219. * @return the program to run -null if not yet set
  220. */
  221. public String getExecutable() {
  222. return executable;
  223. }
  224. /**
  225. * append the arguments to the existing command
  226. * @param line an array of arguments to append
  227. */
  228. public void addArguments(String[] line) {
  229. for (int i = 0; i < line.length; i++) {
  230. createArgument().setValue(line[i]);
  231. }
  232. }
  233. /**
  234. * Returns the executable and all defined arguments.
  235. */
  236. public String[] getCommandline() {
  237. List commands = new LinkedList();
  238. ListIterator list = commands.listIterator();
  239. addCommandToList(list);
  240. final String[] result = new String[commands.size()];
  241. return (String[]) commands.toArray(result);
  242. }
  243. /**
  244. * add the entire command, including (optional) executable to a list
  245. * @param list
  246. * @since Ant 1.6
  247. */
  248. public void addCommandToList(ListIterator list) {
  249. if (executable != null) {
  250. list.add(executable);
  251. }
  252. addArgumentsToList(list);
  253. }
  254. /**
  255. * Returns all arguments defined by <code>addLine</code>,
  256. * <code>addValue</code> or the argument object.
  257. */
  258. public String[] getArguments() {
  259. List result = new ArrayList(arguments.size() * 2);
  260. addArgumentsToList(result.listIterator());
  261. String [] res = new String[result.size()];
  262. return (String[]) result.toArray(res);
  263. }
  264. /**
  265. * append all the arguments to the tail of a supplied list
  266. * @param list
  267. * @since Ant 1.6
  268. */
  269. public void addArgumentsToList(ListIterator list) {
  270. for (int i = 0; i < arguments.size(); i++) {
  271. Argument arg = (Argument) arguments.elementAt(i);
  272. String[] s = arg.getParts();
  273. if (s != null) {
  274. for (int j = 0; j < s.length; j++) {
  275. list.add(s[j]);
  276. }
  277. }
  278. }
  279. }
  280. /**
  281. * stringify operator returns the command line as a string
  282. * @return the command line
  283. */
  284. public String toString() {
  285. return toString(getCommandline());
  286. }
  287. /**
  288. * Put quotes around the given String if necessary.
  289. *
  290. * <p>If the argument doesn't include spaces or quotes, return it
  291. * as is. If it contains double quotes, use single quotes - else
  292. * surround the argument by double quotes.</p>
  293. *
  294. * @exception BuildException if the argument contains both, single
  295. * and double quotes.
  296. */
  297. public static String quoteArgument(String argument) {
  298. if (argument.indexOf("\"") > -1) {
  299. if (argument.indexOf("\'") > -1) {
  300. throw new BuildException("Can\'t handle single and double"
  301. + " quotes in same argument");
  302. } else {
  303. return '\'' + argument + '\'';
  304. }
  305. } else if (argument.indexOf("\'") > -1 || argument.indexOf(" ") > -1) {
  306. return '\"' + argument + '\"';
  307. } else {
  308. return argument;
  309. }
  310. }
  311. /**
  312. * Quotes the parts of the given array in way that makes them
  313. * usable as command line arguments.
  314. * @return empty string for null or no command, else every argument split
  315. * by spaces and quoted by quoting rules
  316. */
  317. public static String toString(String [] line) {
  318. // empty path return empty string
  319. if (line == null || line.length == 0) {
  320. return "";
  321. }
  322. // path containing one or more elements
  323. final StringBuffer result = new StringBuffer();
  324. for (int i = 0; i < line.length; i++) {
  325. if (i > 0) {
  326. result.append(' ');
  327. }
  328. result.append(quoteArgument(line[i]));
  329. }
  330. return result.toString();
  331. }
  332. /**
  333. * crack a command line
  334. * @param toProcess the command line to process
  335. * @return the command line broken into strings.
  336. * An empty or null toProcess parameter results in a zero sized array
  337. */
  338. public static String[] translateCommandline(String toProcess) {
  339. if (toProcess == null || toProcess.length() == 0) {
  340. //no command? no string
  341. return new String[0];
  342. }
  343. // parse with a simple finite state machine
  344. final int normal = 0;
  345. final int inQuote = 1;
  346. final int inDoubleQuote = 2;
  347. int state = normal;
  348. StringTokenizer tok = new StringTokenizer(toProcess, "\"\' ", true);
  349. Vector v = new Vector();
  350. StringBuffer current = new StringBuffer();
  351. boolean lastTokenHasBeenQuoted = false;
  352. while (tok.hasMoreTokens()) {
  353. String nextTok = tok.nextToken();
  354. switch (state) {
  355. case inQuote:
  356. if ("\'".equals(nextTok)) {
  357. lastTokenHasBeenQuoted = true;
  358. state = normal;
  359. } else {
  360. current.append(nextTok);
  361. }
  362. break;
  363. case inDoubleQuote:
  364. if ("\"".equals(nextTok)) {
  365. lastTokenHasBeenQuoted = true;
  366. state = normal;
  367. } else {
  368. current.append(nextTok);
  369. }
  370. break;
  371. default:
  372. if ("\'".equals(nextTok)) {
  373. state = inQuote;
  374. } else if ("\"".equals(nextTok)) {
  375. state = inDoubleQuote;
  376. } else if (" ".equals(nextTok)) {
  377. if (lastTokenHasBeenQuoted || current.length() != 0) {
  378. v.addElement(current.toString());
  379. current = new StringBuffer();
  380. }
  381. } else {
  382. current.append(nextTok);
  383. }
  384. lastTokenHasBeenQuoted = false;
  385. break;
  386. }
  387. }
  388. if (lastTokenHasBeenQuoted || current.length() != 0) {
  389. v.addElement(current.toString());
  390. }
  391. if (state == inQuote || state == inDoubleQuote) {
  392. throw new BuildException("unbalanced quotes in " + toProcess);
  393. }
  394. String[] args = new String[v.size()];
  395. v.copyInto(args);
  396. return args;
  397. }
  398. /**
  399. * size operator. This actually creates the command line, so it is not
  400. * a zero cost operation.
  401. * @return number of elements in the command, including the executable
  402. */
  403. public int size() {
  404. return getCommandline().length;
  405. }
  406. /**
  407. * Generate a deep clone of the contained object.
  408. * @return a clone of the contained object
  409. */
  410. public Object clone() {
  411. try {
  412. Commandline c = (Commandline) super.clone();
  413. c.arguments = (Vector) arguments.clone();
  414. return c;
  415. } catch (CloneNotSupportedException e) {
  416. throw new BuildException(e);
  417. }
  418. }
  419. /**
  420. * Clear out the whole command line. */
  421. public void clear() {
  422. executable = null;
  423. arguments.removeAllElements();
  424. }
  425. /**
  426. * Clear out the arguments but leave the executable in place for
  427. * another operation.
  428. */
  429. public void clearArgs() {
  430. arguments.removeAllElements();
  431. }
  432. /**
  433. * Return a marker.
  434. *
  435. * <p>This marker can be used to locate a position on the
  436. * commandline - to insert something for example - when all
  437. * parameters have been set.</p>
  438. */
  439. public Marker createMarker() {
  440. return new Marker(arguments.size());
  441. }
  442. /**
  443. * Returns a String that describes the command and arguments
  444. * suitable for verbose output before a call to
  445. * <code>Runtime.exec(String[])<code>
  446. *
  447. * @since Ant 1.5
  448. */
  449. public String describeCommand() {
  450. return describeCommand(this);
  451. }
  452. /**
  453. * Returns a String that describes the arguments suitable for
  454. * verbose output before a call to
  455. * <code>Runtime.exec(String[])<code>
  456. *
  457. * @since Ant 1.5
  458. */
  459. public String describeArguments() {
  460. return describeArguments(this);
  461. }
  462. /**
  463. * Returns a String that describes the command and arguments
  464. * suitable for verbose output before a call to
  465. * <code>Runtime.exec(String[])<code>
  466. *
  467. * @since Ant 1.5
  468. */
  469. public static String describeCommand(Commandline line) {
  470. return describeCommand(line.getCommandline());
  471. }
  472. /**
  473. * Returns a String that describes the arguments suitable for
  474. * verbose output before a call to
  475. * <code>Runtime.exec(String[])<code>
  476. *
  477. * @since Ant 1.5
  478. */
  479. public static String describeArguments(Commandline line) {
  480. return describeArguments(line.getArguments());
  481. }
  482. /**
  483. * Returns a String that describes the command and arguments
  484. * suitable for verbose output before a call to
  485. * <code>Runtime.exec(String[])<code>.
  486. *
  487. * <p>This method assumes that the first entry in the array is the
  488. * executable to run.</p>
  489. *
  490. * @since Ant 1.5
  491. */
  492. public static String describeCommand(String[] args) {
  493. if (args == null || args.length == 0) {
  494. return "";
  495. }
  496. StringBuffer buf = new StringBuffer("Executing \'");
  497. buf.append(args[0]);
  498. buf.append("\'");
  499. if (args.length > 0) {
  500. buf.append(" with ");
  501. buf.append(describeArguments(args, 1));
  502. } else {
  503. buf.append(DISCLAIMER);
  504. }
  505. return buf.toString();
  506. }
  507. /**
  508. * Returns a String that describes the arguments suitable for
  509. * verbose output before a call to
  510. * <code>Runtime.exec(String[])<code>
  511. *
  512. * @since Ant 1.5
  513. */
  514. public static String describeArguments(String[] args) {
  515. return describeArguments(args, 0);
  516. }
  517. /**
  518. * Returns a String that describes the arguments suitable for
  519. * verbose output before a call to
  520. * <code>Runtime.exec(String[])<code>
  521. *
  522. * @param offset ignore entries before this index
  523. *
  524. * @since Ant 1.5
  525. */
  526. protected static String describeArguments(String[] args, int offset) {
  527. if (args == null || args.length <= offset) {
  528. return "";
  529. }
  530. StringBuffer buf = new StringBuffer("argument");
  531. if (args.length > offset) {
  532. buf.append("s");
  533. }
  534. buf.append(":").append(StringUtils.LINE_SEP);
  535. for (int i = offset; i < args.length; i++) {
  536. buf.append("\'").append(args[i]).append("\'")
  537. .append(StringUtils.LINE_SEP);
  538. }
  539. buf.append(DISCLAIMER);
  540. return buf.toString();
  541. }
  542. }