All Downloads are FREE. Search and download functionalities are using the official Maven repository.

hudson.util.ArgumentListBuilder Maven / Gradle / Ivy

The newest version!
/*******************************************************************************
 *
 * Copyright (c) 2004-2010 Oracle Corporation.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *
 *    Kohsuke Kawaguchi,   Alan Harder, Yahoo! Inc.
 *
 *
 *******************************************************************************/ 

package hudson.util;

import hudson.Util;

import java.util.ArrayList;
import java.util.List;
import java.util.Arrays;
import java.util.Map;
import java.util.BitSet;
import java.util.Properties;
import java.util.Map.Entry;
import java.io.Serializable;
import java.io.File;
import java.io.IOException;
import java.util.Set;

/**
 * Used to build up arguments for a process invocation.
 *
 * @author Kohsuke Kawaguchi
 */
public class ArgumentListBuilder implements Serializable {

    private final List args = new ArrayList();
    /**
     * Bit mask indicating arguments that shouldn't be echoed-back (e.g.,
     * password)
     */
    private BitSet mask = new BitSet();

    public ArgumentListBuilder() {
    }

    public ArgumentListBuilder(String... args) {
        add(args);
    }

    public ArgumentListBuilder add(Object a) {
        return add(a.toString(), false);
    }

    /**
     * @since 1.378
     */
    public ArgumentListBuilder add(Object a, boolean mask) {
        return add(a.toString(), mask);
    }

    public ArgumentListBuilder add(File f) {
        return add(f.getAbsolutePath(), false);
    }

    public ArgumentListBuilder add(String a) {
        return add(a, false);
    }

    /**
     * @since 1.378
     */
    public ArgumentListBuilder add(String a, boolean mask) {
        if (a != null) {
            if (mask) {
                this.mask.set(args.size());
            }
            args.add(a);
        }
        return this;
    }

    public ArgumentListBuilder prepend(String... args) {
        // left-shift the mask
        BitSet nm = new BitSet(this.args.size() + args.length);
        for (int i = 0; i < this.args.size(); i++) {
            nm.set(i + args.length, mask.get(i));
        }
        mask = nm;

        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 + '"', false);
    }

    /**
     * @since 1.378
     */
    public ArgumentListBuilder addQuoted(String a, boolean mask) {
        return add('"' + a + '"', mask);
    }

    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;
        }
        add(Util.tokenize(s));
        return this;
    }

    /**
     * @since 1.378
     */
    public ArgumentListBuilder addKeyValuePair(String prefix, String key, String value, boolean mask) {
        if (key == null) {
            return this;
        }
        add(((prefix == null) ? "-D" : prefix) + key + '=' + value, mask);
        return this;
    }

    /**
     * Adds key value pairs as "-Dkey=value -Dkey=value ..."
     *
     * -D portion is configurable as the 'prefix' parameter.
     *
     * @since 1.114
     */
    public ArgumentListBuilder addKeyValuePairs(String prefix, Map props) {
        for (Entry e : props.entrySet()) {
            addKeyValuePair(prefix, e.getKey(), e.getValue(), false);
        }
        return this;
    }

    /**
     * Adds key value pairs as "-Dkey=value -Dkey=value ..." with masking.
     *
     * @param prefix Configures the -D portion of the example. Defaults to -D if
     * null.
     * @param props The map of key/value pairs to add
     * @param propsToMask Set containing key names to mark as masked in the
     * argument list. Key names that do not exist in the set will be added
     * unmasked.
     * @since 1.378
     */
    public ArgumentListBuilder addKeyValuePairs(String prefix, Map props, Set propsToMask) {
        for (Entry e : props.entrySet()) {
            addKeyValuePair(prefix, e.getKey(), e.getValue(), (propsToMask == null) ? false : propsToMask.contains(e.getKey()));
        }
        return this;
    }

    /**
     * Adds key value pairs as "-Dkey=value -Dkey=value ..." by parsing a given
     * string using {@link Properties}.
     *
     * @param prefix The '-D' portion of the example. Defaults to -D if null.
     * @param properties The persisted form of {@link Properties}. For example,
     * "abc=def\nghi=jkl". Can be null, in which case this method becomes no-op.
     * @param vr {@link VariableResolver} to be performed on the values.
     * @since 1.262
     */
    public ArgumentListBuilder addKeyValuePairsFromPropertyString(String prefix, String properties, VariableResolver vr) throws IOException {
        if (properties == null) {
            return this;
        }

        for (Entry entry : Util.loadProperties(properties).entrySet()) {
            addKeyValuePair(prefix, (String) entry.getKey(), Util.replaceMacro(entry.getValue().toString(), vr), false);
        }
        return this;
    }

    /**
     * Adds key value pairs as "-Dkey=value -Dkey=value ..." by parsing a given
     * string using {@link Properties} with masking.
     *
     * @param prefix The '-D' portion of the example. Defaults to -D if null.
     * @param properties The persisted form of {@link Properties}. For example,
     * "abc=def\nghi=jkl". Can be null, in which case this method becomes no-op.
     * @param vr {@link VariableResolver} to be performed on the values.
     * @param propsToMask Set containing key names to mark as masked in the
     * argument list. Key names that do not exist in the set will be added
     * unmasked.
     * @since 1.378
     */
    public ArgumentListBuilder addKeyValuePairsFromPropertyString(String prefix, String properties, VariableResolver vr, Set propsToMask) throws IOException {
        if (properties == null) {
            return this;
        }

        for (Entry entry : Util.loadProperties(properties).entrySet()) {
            addKeyValuePair(prefix, (String) entry.getKey(), Util.replaceMacro(entry.getValue().toString(), vr), (propsToMask == null) ? false : propsToMask.contains((String) entry.getKey()));
        }
        return this;
    }

    @Override
    public String toString() {
        return args.toString();
    }

    public String[] toCommandArray() {
        return args.toArray(new String[args.size()]);
    }

    @Override
    public ArgumentListBuilder clone() {
        ArgumentListBuilder r = new ArgumentListBuilder();
        r.args.addAll(this.args);
        r.mask = (BitSet) this.mask.clone();
        return r;
    }

    /**
     * Re-initializes the arguments list.
     */
    public void clear() {
        args.clear();
        mask.clear();
    }

    public List toList() {
        return args;
    }

    /**
     * Just adds quotes around args containing spaces, but no other special
     * characters, so this method should generally be used only for
     * informational/logging purposes.
     */
    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();
    }

    /**
     * Wrap command in a CMD.EXE call so we can return the exit code
     * (ERRORLEVEL). This method takes care of escaping special characters in
     * the command, which is needed since the command is now passed as a string
     * to the CMD.EXE shell. This is done as follows: Wrap arguments in double
     * quotes if they contain any of: space *?,;^&<>|" or % followed by a
     * letter. 
When testing from command prompt, these characters also * need to be prepended with a ^ character: ^&<>| -- however, invoking * cmd.exe from Hudson does not seem to require this extra escaping so it is * not added by this method.
A " is prepended with another " * character. Note: Windows has issues escaping some combinations of quotes * and spaces. Quotes should be avoided.
A % followed by a letter has * that letter wrapped in double quotes, to avoid possible variable * expansion. ie, %foo% becomes "%"f"oo%". The second % does not need * special handling because it is not followed by a letter.
Example: * "-Dfoo=*abc?def;ghi^jkl&mnostu|vwx""yz%"e"nd" * * @return new ArgumentListBuilder that runs given command through cmd.exe * /C * @since 1.386 */ public ArgumentListBuilder toWindowsCommand() { StringBuilder quotedArgs = new StringBuilder(); boolean quoted, percent; for (String arg : args) { quoted = percent = false; for (int i = 0; i < arg.length(); i++) { char c = arg.charAt(i); if (!quoted && (c == ' ' || c == '*' || c == '?' || c == ',' || c == ';')) { quoted = startQuoting(quotedArgs, arg, i); } else if (c == '^' || c == '&' || c == '<' || c == '>' || c == '|') { if (!quoted) { quoted = startQuoting(quotedArgs, arg, i); } // quotedArgs.append('^'); See note in javadoc above } else if (c == '"') { if (!quoted) { quoted = startQuoting(quotedArgs, arg, i); } quotedArgs.append('"'); } else if (percent && ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'))) { if (!quoted) { quoted = startQuoting(quotedArgs, arg, i); } quotedArgs.append('"').append(c); c = '"'; } percent = (c == '%'); if (quoted) { quotedArgs.append(c); } } if (quoted) { quotedArgs.append('"'); } else { quotedArgs.append(arg); } quotedArgs.append(' '); } // (comment copied from old code in hudson.tasks.Ant) // on Windows, executing batch file can't return the correct error code, // so we need to wrap it into cmd.exe. // double %% is needed because we want ERRORLEVEL to be expanded after // batch file executed, not before. This alone shows how broken Windows is... quotedArgs.append("&& exit %%ERRORLEVEL%%"); return new ArgumentListBuilder().add("cmd.exe", "/C").addQuoted(quotedArgs.toString()); } private static boolean startQuoting(StringBuilder buf, String arg, int atIndex) { buf.append('"').append(arg.substring(0, atIndex)); return true; } /** * Returns true if there are any masked arguments. * * @return true if there are any masked arguments; false otherwise */ public boolean hasMaskedArguments() { return mask.length() > 0; } /** * Returns an array of booleans where the masked arguments are marked as * true * * @return an array of booleans. */ public boolean[] toMaskArray() { boolean[] mask = new boolean[args.size()]; for (int i = 0; i < mask.length; i++) { mask[i] = this.mask.get(i); } return mask; } /** * Add a masked argument * * @param string the argument */ public void addMasked(String string) { add(string, true); } private static final long serialVersionUID = 1L; }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy