Index: main/core/src/main/java/hudson/util/ArgumentListBuilder.java =================================================================== --- main/core/src/main/java/hudson/util/ArgumentListBuilder.java (revision 8614) +++ main/core/src/main/java/hudson/util/ArgumentListBuilder.java (working copy) @@ -13,85 +13,126 @@ * @author Kohsuke Kawaguchi */ public class ArgumentListBuilder { - private final List args = new ArrayList(); + private final List args = new ArrayList(); + private static final String fDOUBLE_QUOTE = "\""; + //the parser flips between these two sets of delimiters + private static final String fWHITESPACE_AND_QUOTES = " \t\r\n\""; + private static final String fQUOTES_ONLY = "\""; - public ArgumentListBuilder add(String a) { - if(a!=null) - args.add(a); - return this; - } + public ArgumentListBuilder add(String a) { + if (a != null) + args.add(a); + return this; + } - public ArgumentListBuilder prepend(String... args) { - this.args.addAll(0, Arrays.asList(args)); - return this; - } + public ArgumentListBuilder prepend(String... args) { + this.args.addAll(0, Arrays.asList(args)); + return this; + } - /** - * Adds an argument by quoting it. - * This is necessary only in a rare circumstance, - * such as when adding argument for ssh and rsh. - * - * Normal process invocations don't need it, because each - * argument is treated as its own string and never merged into one. - */ - public ArgumentListBuilder addQuoted(String a) { - return add('"'+a+'"'); - } + /** + * Adds an argument by quoting it. + * This is necessary only in a rare circumstance, + * such as when adding argument for ssh and rsh. + * + * Normal process invocations don't need it, because each + * argument is treated as its own string and never merged into one. + */ + public ArgumentListBuilder addQuoted(String a) { + return add('"' + a + '"'); + } - public ArgumentListBuilder add(String... args) { - for (String arg : args) { - add(arg); - } - return this; - } + public ArgumentListBuilder add(String... args) { + for (String arg : args) { + add(arg); + } + return this; + } - /** - * Decomposes the given token into multiple arguments by splitting via whitespace. - */ - public ArgumentListBuilder addTokenized(String s) { - if(s==null) return this; - StringTokenizer tokens = new StringTokenizer(s); - while(tokens.hasMoreTokens()) - add(tokens.nextToken()); - return this; - } + /** + * Decomposes the given token into multiple arguments. An Argument is defined by + * a lone word that is surrounded by whitespace or a group of word surrounded + * double quotes + */ + public ArgumentListBuilder addTokenized(String s) { + if (s == null) + return this; - /** - * Adds key value pairs as "-Dkey=value -Dkey=value"... - * - * -D portion is configurable. - * @since 1.114 - */ - public ArgumentListBuilder addKeyValuePairs(String prefix, Map props) { - for (Entry e : props.entrySet()) - add(prefix+e.getKey()+'='+e.getValue()); - return this; - } + boolean returnTokens = true; + String currentDelims = fWHITESPACE_AND_QUOTES; + StringTokenizer parser = new StringTokenizer(s, currentDelims, + returnTokens); - public String[] toCommandArray() { - return args.toArray(new String[args.size()]); - } - - public ArgumentListBuilder clone() { - ArgumentListBuilder r = new ArgumentListBuilder(); - r.args.addAll(this.args); - return r; - } + String token = null; + while (parser.hasMoreTokens()) { + token = parser.nextToken(currentDelims); + if (!isDoubleQuote(token)) { + if (textHasContent(token)) { + add(token); + } + } else { + currentDelims = flipDelimiters(currentDelims); + } + } + return this; + } - public List toList() { - return args; - } + private boolean textHasContent(String aText) { + return (aText != null) && (!aText.trim().equals("")); + } - public String toStringWithQuote() { - StringBuilder buf = new StringBuilder(); - for (String arg : args) { - if(buf.length()>0) buf.append(' '); + private boolean isDoubleQuote(String aToken) { + return aToken.equals(fDOUBLE_QUOTE); + } - if(arg.indexOf(' ')>=0 || arg.length()==0) - buf.append('"').append(arg).append('"'); - else - buf.append(arg); - } - return buf.toString(); - } + private String flipDelimiters(String aCurrentDelims) { + String result = null; + if (aCurrentDelims.equals(fWHITESPACE_AND_QUOTES)) { + result = fQUOTES_ONLY; + } else { + result = fWHITESPACE_AND_QUOTES; + } + return result; + } + + /** + * Adds key value pairs as "-Dkey=value -Dkey=value"... + * + * -D portion is configurable. + * @since 1.114 + */ + public ArgumentListBuilder addKeyValuePairs(String prefix, + Map props) { + for (Entry e : props.entrySet()) + add(prefix + e.getKey() + '=' + e.getValue()); + return this; + } + + public String[] toCommandArray() { + return args.toArray(new String[args.size()]); + } + + public ArgumentListBuilder clone() { + ArgumentListBuilder r = new ArgumentListBuilder(); + r.args.addAll(this.args); + return r; + } + + public List toList() { + return args; + } + + public String toStringWithQuote() { + StringBuilder buf = new StringBuilder(); + for (String arg : args) { + if (buf.length() > 0) + buf.append(' '); + + if (arg.indexOf(' ') >= 0 || arg.length() == 0) + buf.append('"').append(arg).append('"'); + else + buf.append(arg); + } + return buf.toString(); + } } Index: main/core/src/test/java/hudson/util/ArgumentListBuilderTest.java =================================================================== --- main/core/src/test/java/hudson/util/ArgumentListBuilderTest.java (revision 0) +++ main/core/src/test/java/hudson/util/ArgumentListBuilderTest.java (revision 0) @@ -0,0 +1,66 @@ +/** + * + */ +package hudson.util; + +import junit.framework.TestCase; + +/** + * @author bfitzgerald + * + */ +public class ArgumentListBuilderTest extends TestCase { + + public void testOneQuotedArg() throws Exception { + ArgumentListBuilder arguments = new ArgumentListBuilder(); + arguments.addTokenized("clean \"install\""); + assertEquals("clean", arguments.toList().get(0)); + assertEquals("install", arguments.toList().get(1)); + + } + + public void testNoQuotedArgs() throws Exception { + ArgumentListBuilder arguments = new ArgumentListBuilder(); + arguments.addTokenized("clean install"); + assertEquals("clean", arguments.toList().get(0)); + assertEquals("install", arguments.toList().get(1)); + + } + + public void testQuotedAndEquals() throws Exception { + ArgumentListBuilder arguments = new ArgumentListBuilder(); + arguments.addTokenized("clean \"install=hello\""); + assertEquals("clean", arguments.toList().get(0)); + assertEquals("install=hello", arguments.toList().get(1)); + } + + public void testArgumentsQuotedAndSpaces() throws Exception { + ArgumentListBuilder arguments = new ArgumentListBuilder(); + arguments.addTokenized("clean \"install build\""); + assertEquals("clean", arguments.toList().get(0)); + assertEquals("install build", arguments.toList().get(1)); + } + + public void testQuotedEqualsAndSpaces() throws Exception { + ArgumentListBuilder arguments = new ArgumentListBuilder(); + arguments.addTokenized("clean \"install=hello goodbye\""); + assertEquals("clean", arguments.toList().get(0)); + assertEquals("install=hello goodbye", arguments.toList().get(1)); + } + + public void testMultipleQuoted() throws Exception { + ArgumentListBuilder arguments = new ArgumentListBuilder(); + arguments.addTokenized("clean \"install=hello goodbye\" \"foo=bar\""); + assertEquals("clean", arguments.toList().get(0)); + assertEquals("install=hello goodbye", arguments.toList().get(1)); + } + +// //Doesn't Work but Should! +// public void testArgumentContaingQuotes() throws Exception { +// ArgumentListBuilder arguments = new ArgumentListBuilder(); +// arguments.addTokenized("clean install=\"hello goodbye\""); +// assertEquals("clean", arguments.toList().get(0)); +// assertEquals("install=hello goodbye", arguments.toList().get(1)); +// } + +}