001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018:
019: package org.apache.tools.ant.types;
020:
021: import java.io.File;
022: import java.util.StringTokenizer;
023: import java.util.Vector;
024: import java.util.ArrayList;
025: import java.util.List;
026: import java.util.ListIterator;
027: import java.util.LinkedList;
028: import java.util.Iterator;
029:
030: import org.apache.tools.ant.BuildException;
031: import org.apache.tools.ant.ProjectComponent;
032: import org.apache.tools.ant.util.StringUtils;
033: import org.apache.tools.ant.taskdefs.condition.Os;
034:
035: /**
036: * Commandline objects help handling command lines specifying processes to
037: * execute.
038: *
039: * The class can be used to define a command line as nested elements or as a
040: * helper to define a command line by an application.
041: * <p>
042: * <code>
043: * <someelement><br>
044: * <acommandline executable="/executable/to/run"><br>
045: * <argument value="argument 1" /><br>
046: * <argument line="argument_1 argument_2 argument_3" /><br>
047: * <argument value="argument 4" /><br>
048: * </acommandline><br>
049: * </someelement><br>
050: * </code>
051: * The element <code>someelement</code> must provide a method
052: * <code>createAcommandline</code> which returns an instance of this class.
053: *
054: */
055: public class Commandline implements Cloneable {
056: /** win9x uses a (shudder) bat file (antRun.bat) for executing commands */
057: private static final boolean IS_WIN_9X = Os.isFamily("win9x");
058:
059: /**
060: * The arguments of the command
061: */
062: private Vector arguments = new Vector();
063:
064: /**
065: * the program to execute
066: */
067: private String executable = null;
068:
069: protected static final String DISCLAIMER = StringUtils.LINE_SEP
070: + "The \' characters around the executable and arguments are"
071: + StringUtils.LINE_SEP + "not part of the command."
072: + StringUtils.LINE_SEP;
073:
074: /**
075: * Create a command line from a string.
076: * @param toProcess the line: the first element becomes the executable, the rest
077: * the arguments.
078: */
079: public Commandline(String toProcess) {
080: super ();
081: String[] tmp = translateCommandline(toProcess);
082: if (tmp != null && tmp.length > 0) {
083: setExecutable(tmp[0]);
084: for (int i = 1; i < tmp.length; i++) {
085: createArgument().setValue(tmp[i]);
086: }
087: }
088: }
089:
090: /**
091: * Create an empty command line.
092: */
093: public Commandline() {
094: super ();
095: }
096:
097: /**
098: * Used for nested xml command line definitions.
099: */
100: public static class Argument extends ProjectComponent {
101:
102: private String[] parts;
103:
104: /**
105: * Set a single commandline argument.
106: *
107: * @param value a single commandline argument.
108: */
109: public void setValue(String value) {
110: parts = new String[] { value };
111: }
112:
113: /**
114: * Set the line to split into several commandline arguments.
115: *
116: * @param line line to split into several commandline arguments.
117: */
118: public void setLine(String line) {
119: if (line == null) {
120: return;
121: }
122: parts = translateCommandline(line);
123: }
124:
125: /**
126: * Set a single commandline argument and treats it like a
127: * PATH--ensuring the right separator for the local platform
128: * is used.
129: *
130: * @param value a single commandline argument.
131: */
132: public void setPath(Path value) {
133: parts = new String[] { value.toString() };
134: }
135:
136: /**
137: * Set a single commandline argument from a reference to a
138: * path--ensuring the right separator for the local platform
139: * is used.
140: *
141: * @param value a single commandline argument.
142: */
143: public void setPathref(Reference value) {
144: Path p = new Path(getProject());
145: p.setRefid(value);
146: parts = new String[] { p.toString() };
147: }
148:
149: /**
150: * Set a single commandline argument to the absolute filename
151: * of the given file.
152: *
153: * @param value a single commandline argument.
154: */
155: public void setFile(File value) {
156: parts = new String[] { value.getAbsolutePath() };
157: }
158:
159: /**
160: * Return the constituent parts of this Argument.
161: * @return an array of strings.
162: */
163: public String[] getParts() {
164: return parts;
165: }
166: }
167:
168: /**
169: * Class to keep track of the position of an Argument.
170: <p>This class is there to support the srcfile and targetfile
171: elements of <execon> and <transform> - don't know
172: whether there might be additional use cases.</p> --SB
173: */
174: public class Marker {
175:
176: private int position;
177: private int realPos = -1;
178:
179: /**
180: * Construct a marker for the specified position.
181: * @param position the position to mark.
182: */
183: Marker(int position) {
184: this .position = position;
185: }
186:
187: /**
188: * Return the number of arguments that preceded this marker.
189: *
190: * <p>The name of the executable -- if set -- is counted as the
191: * first argument.</p>
192: * @return the position of this marker.
193: */
194: public int getPosition() {
195: if (realPos == -1) {
196: realPos = (executable == null ? 0 : 1);
197: for (int i = 0; i < position; i++) {
198: Argument arg = (Argument) arguments.elementAt(i);
199: realPos += arg.getParts().length;
200: }
201: }
202: return realPos;
203: }
204: }
205:
206: /**
207: * Create an argument object.
208: *
209: * <p>Each commandline object has at most one instance of the
210: * argument class. This method calls
211: * <code>this.createArgument(false)</code>.</p>
212: *
213: * @see #createArgument(boolean)
214: * @return the argument object.
215: */
216: public Argument createArgument() {
217: return this .createArgument(false);
218: }
219:
220: /**
221: * Create an argument object and add it to our list of args.
222: *
223: * <p>Each commandline object has at most one instance of the
224: * argument class.</p>
225: *
226: * @param insertAtStart if true, the argument is inserted at the
227: * beginning of the list of args, otherwise it is appended.
228: * @return an argument to be configured
229: */
230: public Argument createArgument(boolean insertAtStart) {
231: Argument argument = new Argument();
232: if (insertAtStart) {
233: arguments.insertElementAt(argument, 0);
234: } else {
235: arguments.addElement(argument);
236: }
237: return argument;
238: }
239:
240: /**
241: * Set the executable to run. All file separators in the string
242: * are converted to the platform specific value.
243: * @param executable the String executable name.
244: */
245: public void setExecutable(String executable) {
246: if (executable == null || executable.length() == 0) {
247: return;
248: }
249: this .executable = executable.replace('/', File.separatorChar)
250: .replace('\\', File.separatorChar);
251: }
252:
253: /**
254: * Get the executable.
255: * @return the program to run--null if not yet set.
256: */
257: public String getExecutable() {
258: return executable;
259: }
260:
261: /**
262: * Append the arguments to the existing command.
263: * @param line an array of arguments to append.
264: */
265: public void addArguments(String[] line) {
266: for (int i = 0; i < line.length; i++) {
267: createArgument().setValue(line[i]);
268: }
269: }
270:
271: /**
272: * Return the executable and all defined arguments.
273: * @return the commandline as an array of strings.
274: */
275: public String[] getCommandline() {
276: List commands = new LinkedList();
277: ListIterator list = commands.listIterator();
278: addCommandToList(list);
279: final String[] result = new String[commands.size()];
280: return (String[]) commands.toArray(result);
281: }
282:
283: /**
284: * Add the entire command, including (optional) executable to a list.
285: * @param list the list to add to.
286: * @since Ant 1.6
287: */
288: public void addCommandToList(ListIterator list) {
289: if (executable != null) {
290: list.add(executable);
291: }
292: addArgumentsToList(list);
293: }
294:
295: /**
296: * Returns all arguments defined by <code>addLine</code>,
297: * <code>addValue</code> or the argument object.
298: * @return the arguments as an array of strings.
299: */
300: public String[] getArguments() {
301: List result = new ArrayList(arguments.size() * 2);
302: addArgumentsToList(result.listIterator());
303: String[] res = new String[result.size()];
304: return (String[]) result.toArray(res);
305: }
306:
307: /**
308: * Append all the arguments to the tail of a supplied list.
309: * @param list the list of arguments.
310: * @since Ant 1.6
311: */
312: public void addArgumentsToList(ListIterator list) {
313: for (int i = 0; i < arguments.size(); i++) {
314: Argument arg = (Argument) arguments.elementAt(i);
315: String[] s = arg.getParts();
316: if (s != null) {
317: for (int j = 0; j < s.length; j++) {
318: list.add(s[j]);
319: }
320: }
321: }
322: }
323:
324: /**
325: * Return the command line as a string.
326: * @return the command line.
327: */
328: public String toString() {
329: return toString(getCommandline());
330: }
331:
332: /**
333: * Put quotes around the given String if necessary.
334: *
335: * <p>If the argument doesn't include spaces or quotes, return it
336: * as is. If it contains double quotes, use single quotes - else
337: * surround the argument by double quotes.</p>
338: * @param argument the argument to quote if necessary.
339: * @return the quoted argument.
340: * @exception BuildException if the argument contains both, single
341: * and double quotes.
342: */
343: public static String quoteArgument(String argument) {
344: if (argument.indexOf("\"") > -1) {
345: if (argument.indexOf("\'") > -1) {
346: throw new BuildException(
347: "Can\'t handle single and double"
348: + " quotes in same argument");
349: } else {
350: return '\'' + argument + '\'';
351: }
352: } else if (argument.indexOf("\'") > -1
353: || argument.indexOf(" ") > -1
354: // WIN9x uses a bat file for executing commands
355: || (IS_WIN_9X && argument.indexOf(';') != -1)) {
356: return '\"' + argument + '\"';
357: } else {
358: return argument;
359: }
360: }
361:
362: /**
363: * Quote the parts of the given array in way that makes them
364: * usable as command line arguments.
365: * @param line the list of arguments to quote.
366: * @return empty string for null or no command, else every argument split
367: * by spaces and quoted by quoting rules.
368: */
369: public static String toString(String[] line) {
370: // empty path return empty string
371: if (line == null || line.length == 0) {
372: return "";
373: }
374: // path containing one or more elements
375: final StringBuffer result = new StringBuffer();
376: for (int i = 0; i < line.length; i++) {
377: if (i > 0) {
378: result.append(' ');
379: }
380: result.append(quoteArgument(line[i]));
381: }
382: return result.toString();
383: }
384:
385: /**
386: * Crack a command line.
387: * @param toProcess the command line to process.
388: * @return the command line broken into strings.
389: * An empty or null toProcess parameter results in a zero sized array.
390: */
391: public static String[] translateCommandline(String toProcess) {
392: if (toProcess == null || toProcess.length() == 0) {
393: //no command? no string
394: return new String[0];
395: }
396: // parse with a simple finite state machine
397:
398: final int normal = 0;
399: final int inQuote = 1;
400: final int inDoubleQuote = 2;
401: int state = normal;
402: StringTokenizer tok = new StringTokenizer(toProcess, "\"\' ",
403: true);
404: Vector v = new Vector();
405: StringBuffer current = new StringBuffer();
406: boolean lastTokenHasBeenQuoted = false;
407:
408: while (tok.hasMoreTokens()) {
409: String nextTok = tok.nextToken();
410: switch (state) {
411: case inQuote:
412: if ("\'".equals(nextTok)) {
413: lastTokenHasBeenQuoted = true;
414: state = normal;
415: } else {
416: current.append(nextTok);
417: }
418: break;
419: case inDoubleQuote:
420: if ("\"".equals(nextTok)) {
421: lastTokenHasBeenQuoted = true;
422: state = normal;
423: } else {
424: current.append(nextTok);
425: }
426: break;
427: default:
428: if ("\'".equals(nextTok)) {
429: state = inQuote;
430: } else if ("\"".equals(nextTok)) {
431: state = inDoubleQuote;
432: } else if (" ".equals(nextTok)) {
433: if (lastTokenHasBeenQuoted || current.length() != 0) {
434: v.addElement(current.toString());
435: current = new StringBuffer();
436: }
437: } else {
438: current.append(nextTok);
439: }
440: lastTokenHasBeenQuoted = false;
441: break;
442: }
443: }
444: if (lastTokenHasBeenQuoted || current.length() != 0) {
445: v.addElement(current.toString());
446: }
447: if (state == inQuote || state == inDoubleQuote) {
448: throw new BuildException("unbalanced quotes in "
449: + toProcess);
450: }
451: String[] args = new String[v.size()];
452: v.copyInto(args);
453: return args;
454: }
455:
456: /**
457: * Size operator. This actually creates the command line, so it is not
458: * a zero cost operation.
459: * @return number of elements in the command, including the executable.
460: */
461: public int size() {
462: return getCommandline().length;
463: }
464:
465: /**
466: * Generate a deep clone of the contained object.
467: * @return a clone of the contained object
468: */
469: public Object clone() {
470: try {
471: Commandline c = (Commandline) super .clone();
472: c.arguments = (Vector) arguments.clone();
473: return c;
474: } catch (CloneNotSupportedException e) {
475: throw new BuildException(e);
476: }
477: }
478:
479: /**
480: * Clear out the whole command line.
481: */
482: public void clear() {
483: executable = null;
484: arguments.removeAllElements();
485: }
486:
487: /**
488: * Clear out the arguments but leave the executable in place for
489: * another operation.
490: */
491: public void clearArgs() {
492: arguments.removeAllElements();
493: }
494:
495: /**
496: * Return a marker.
497: *
498: * <p>This marker can be used to locate a position on the
499: * commandline--to insert something for example--when all
500: * parameters have been set.</p>
501: * @return a marker
502: */
503: public Marker createMarker() {
504: return new Marker(arguments.size());
505: }
506:
507: /**
508: * Return a String that describes the command and arguments suitable for
509: * verbose output before a call to <code>Runtime.exec(String[])<code>.
510: * @return a string that describes the command and arguments.
511: * @since Ant 1.5
512: */
513: public String describeCommand() {
514: return describeCommand(this );
515: }
516:
517: /**
518: * Return a String that describes the arguments suitable for
519: * verbose output before a call to <code>Runtime.exec(String[])<code>.
520: * @return a string that describes the arguments.
521: * @since Ant 1.5
522: */
523: public String describeArguments() {
524: return describeArguments(this );
525: }
526:
527: /**
528: * Return a String that describes the command and arguments suitable for
529: * verbose output before a call to <code>Runtime.exec(String[])<code>.
530: * @param line the Commandline to describe.
531: * @return a string that describes the command and arguments.
532: * @since Ant 1.5
533: */
534: public static String describeCommand(Commandline line) {
535: return describeCommand(line.getCommandline());
536: }
537:
538: /**
539: * Return a String that describes the arguments suitable for
540: * verbose output before a call to <code>Runtime.exec(String[])<code>.
541: * @param line the Commandline whose arguments to describe.
542: * @return a string that describes the arguments.
543: * @since Ant 1.5
544: */
545: public static String describeArguments(Commandline line) {
546: return describeArguments(line.getArguments());
547: }
548:
549: /**
550: * Return a String that describes the command and arguments suitable for
551: * verbose output before a call to <code>Runtime.exec(String[])<code>.
552: *
553: * <p>This method assumes that the first entry in the array is the
554: * executable to run.</p>
555: * @param args the command line to describe as an array of strings
556: * @return a string that describes the command and arguments.
557: * @since Ant 1.5
558: */
559: public static String describeCommand(String[] args) {
560: if (args == null || args.length == 0) {
561: return "";
562: }
563: StringBuffer buf = new StringBuffer("Executing \'");
564: buf.append(args[0]);
565: buf.append("\'");
566: if (args.length > 1) {
567: buf.append(" with ");
568: buf.append(describeArguments(args, 1));
569: } else {
570: buf.append(DISCLAIMER);
571: }
572: return buf.toString();
573: }
574:
575: /**
576: * Return a String that describes the arguments suitable for
577: * verbose output before a call to <code>Runtime.exec(String[])<code>.
578: * @param args the command line to describe as an array of strings.
579: * @return a string that describes the arguments.
580: * @since Ant 1.5
581: */
582: public static String describeArguments(String[] args) {
583: return describeArguments(args, 0);
584: }
585:
586: /**
587: * Return a String that describes the arguments suitable for
588: * verbose output before a call to <code>Runtime.exec(String[])<code>.
589: *
590: * @param args the command line to describe as an array of strings.
591: * @param offset ignore entries before this index.
592: * @return a string that describes the arguments
593: *
594: * @since Ant 1.5
595: */
596: protected static String describeArguments(String[] args, int offset) {
597: if (args == null || args.length <= offset) {
598: return "";
599: }
600: StringBuffer buf = new StringBuffer("argument");
601: if (args.length > offset) {
602: buf.append("s");
603: }
604: buf.append(":").append(StringUtils.LINE_SEP);
605: for (int i = offset; i < args.length; i++) {
606: buf.append("\'").append(args[i]).append("\'").append(
607: StringUtils.LINE_SEP);
608: }
609: buf.append(DISCLAIMER);
610: return buf.toString();
611: }
612:
613: /**
614: * Get an iterator to the arguments list.
615: * @since Ant 1.7
616: * @return an Iterator.
617: */
618: public Iterator iterator() {
619: return arguments.iterator();
620: }
621: }
|