View Javadoc
1   package org.codehaus.plexus.util.cli;
2   
3   /*
4    * Copyright The Codehaus Foundation.
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  import java.io.IOException;
20  import java.io.InputStream;
21  import java.util.Locale;
22  import java.util.Map;
23  import java.util.Properties;
24  import java.util.StringTokenizer;
25  import java.util.Vector;
26  
27  import org.codehaus.plexus.util.Os;
28  import org.codehaus.plexus.util.StringUtils;
29  import org.kuali.common.util.Assert;
30  
31  public abstract class CommandLineUtils {
32  
33  	public static int executeCommandLine(Commandline cl, StreamConsumer systemOut, StreamConsumer systemErr) throws CommandLineException {
34  		return executeCommandLine(cl, null, systemOut, systemErr, 0);
35  	}
36  
37  	public static int executeCommandLine(Commandline cl, StreamConsumer systemOut, StreamConsumer systemErr, int timeoutInSeconds) throws CommandLineException {
38  		return executeCommandLine(cl, null, systemOut, systemErr, timeoutInSeconds);
39  	}
40  
41  	public static int executeCommandLine(Commandline cl, InputStream systemIn, StreamConsumer systemOut, StreamConsumer systemErr) throws CommandLineException {
42  		return executeCommandLine(cl, systemIn, systemOut, systemErr, 0);
43  	}
44  
45  	/**
46  	 * @param cl
47  	 *            The command line to execute
48  	 * @param systemIn
49  	 *            The input to read from, must be thread safe
50  	 * @param systemOut
51  	 *            A consumer that receives output, must be thread safe
52  	 * @param systemErr
53  	 *            A consumer that receives system error stream output, must be thread safe
54  	 * @param timeoutInSeconds
55  	 *            Positive integer to specify timeout, zero and negative integers for no timeout.
56  	 * @return A return value, see {@link Process#exitValue()}
57  	 * @throws CommandLineException
58  	 *             or CommandLineTimeOutException if time out occurs
59  	 * @noinspection ThrowableResultOfMethodCallIgnored
60  	 */
61  	public static int executeCommandLine(Commandline cl, InputStream systemIn, StreamConsumer systemOut, StreamConsumer systemErr, int timeoutInSeconds)
62  			throws CommandLineException {
63  		final CommandLineCallable future = executeCommandLineAsCallable(cl, systemIn, systemOut, systemErr, timeoutInSeconds);
64  		return future.call();
65  	}
66  
67  	/**
68  	 * Immediately forks a process, returns a callable that will block until process is complete.
69  	 * 
70  	 * @param cl
71  	 *            The command line to execute
72  	 * @param systemIn
73  	 *            The input to read from, must be thread safe
74  	 * @param systemOut
75  	 *            A consumer that receives output, must be thread safe
76  	 * @param systemErr
77  	 *            A consumer that receives system error stream output, must be thread safe
78  	 * @param timeoutInSeconds
79  	 *            Positive integer to specify timeout, zero and negative integers for no timeout.
80  	 * @return A CommandLineCallable that provides the process return value, see {@link Process#exitValue()}. "call" must be called on this to be sure the forked process has
81  	 *         terminated, no guarantees is made about any internal state before the completion of the call statement
82  	 * @throws CommandLineException
83  	 *             or CommandLineTimeOutException if time out occurs
84  	 * @noinspection ThrowableResultOfMethodCallIgnored
85  	 */
86  	public static CommandLineCallable executeCommandLineAsCallable(Commandline cl, InputStream systemIn, StreamConsumer systemOut, StreamConsumer systemErr, int timeoutInSeconds)
87  			throws CommandLineException {
88  
89  		Assert.noNulls(cl);
90  
91  		final Process p = cl.execute();
92  		final StreamFeeder inputFeeder = systemIn != null ? new StreamFeeder(systemIn, p.getOutputStream()) : null;
93  		final StreamPumper outputPumper = new StreamPumper(p.getInputStream(), systemOut);
94  		final StreamPumper errorPumper = new StreamPumper(p.getErrorStream(), systemErr);
95  
96  		if (inputFeeder != null) {
97  			inputFeeder.start();
98  		}
99  
100 		outputPumper.start();
101 		errorPumper.start();
102 
103 		final ProcessHook processHook = new ProcessHook(p);
104 
105 		ShutdownHookUtils.addShutDownHook(processHook);
106 
107 		return new CLICallable(timeoutInSeconds, inputFeeder, outputPumper, errorPumper, p, processHook);
108 
109 	}
110 
111 	public static void waitForAllPumpers(StreamFeeder inputFeeder, StreamPumper outputPumper, StreamPumper errorPumper) throws InterruptedException {
112 		if (inputFeeder != null) {
113 			inputFeeder.waitUntilDone();
114 		}
115 
116 		outputPumper.waitUntilDone();
117 		errorPumper.waitUntilDone();
118 	}
119 
120 	/**
121 	 * Gets the shell environment variables for this process. Note that the returned mapping from variable names to values will always be case-sensitive regardless of the platform,
122 	 * i.e. <code>getSystemEnvVars().get("path")</code> and <code>getSystemEnvVars().get("PATH")</code> will in general return different values. However, on platforms with
123 	 * case-insensitive environment variables like Windows, all variable names will be normalized to upper case.
124 	 * 
125 	 * @return The shell environment variables, can be empty but never <code>null</code>.
126 	 * @throws IOException
127 	 *             If the environment variables could not be queried from the shell.
128 	 * @see System#getenv() System.getenv() API, new in JDK 5.0, to get the same result <b>since 2.0.2 System#getenv() will be used if available in the current running jvm.</b>
129 	 */
130 	public static Properties getSystemEnvVars() throws IOException {
131 		return getSystemEnvVars(!Os.isFamily(Os.FAMILY_WINDOWS));
132 	}
133 
134 	/**
135 	 * Return the shell environment variables. If <code>caseSensitive == true</code>, then envar keys will all be upper-case.
136 	 * 
137 	 * @param caseSensitive
138 	 *            Whether environment variable keys should be treated case-sensitively.
139 	 * @return Properties object of (possibly modified) envar keys mapped to their values.
140 	 * @throws IOException .
141 	 * @see System#getenv() System.getenv() API, new in JDK 5.0, to get the same result <b>since 2.0.2 System#getenv() will be used if available in the current running jvm.</b>
142 	 */
143 	public static Properties getSystemEnvVars(boolean caseSensitive) throws IOException {
144 		Properties envVars = new Properties();
145 		Map<String, String> envs = System.getenv();
146 		for (String key : envs.keySet()) {
147 			String value = envs.get(key);
148 			if (!caseSensitive) {
149 				key = key.toUpperCase(Locale.ENGLISH);
150 			}
151 			envVars.put(key, value);
152 		}
153 		return envVars;
154 	}
155 
156 	public static boolean isAlive(Process p) {
157 		if (p == null) {
158 			return false;
159 		}
160 
161 		try {
162 			p.exitValue();
163 			return false;
164 		} catch (IllegalThreadStateException e) {
165 			return true;
166 		}
167 	}
168 
169 	public static String[] translateCommandline(String toProcess) throws Exception {
170 		if ((toProcess == null) || (toProcess.length() == 0)) {
171 			return new String[0];
172 		}
173 
174 		// parse with a simple finite state machine
175 
176 		final int normal = 0;
177 		final int inQuote = 1;
178 		final int inDoubleQuote = 2;
179 		int state = normal;
180 		StringTokenizer tok = new StringTokenizer(toProcess, "\"\' ", true);
181 		Vector<String> v = new Vector<String>();
182 		StringBuilder current = new StringBuilder();
183 
184 		while (tok.hasMoreTokens()) {
185 			String nextTok = tok.nextToken();
186 			switch (state) {
187 			case inQuote:
188 				if ("\'".equals(nextTok)) {
189 					state = normal;
190 				} else {
191 					current.append(nextTok);
192 				}
193 				break;
194 			case inDoubleQuote:
195 				if ("\"".equals(nextTok)) {
196 					state = normal;
197 				} else {
198 					current.append(nextTok);
199 				}
200 				break;
201 			default:
202 				if ("\'".equals(nextTok)) {
203 					state = inQuote;
204 				} else if ("\"".equals(nextTok)) {
205 					state = inDoubleQuote;
206 				} else if (" ".equals(nextTok)) {
207 					if (current.length() != 0) {
208 						v.addElement(current.toString());
209 						current.setLength(0);
210 					}
211 				} else {
212 					current.append(nextTok);
213 				}
214 				break;
215 			}
216 		}
217 
218 		if (current.length() != 0) {
219 			v.addElement(current.toString());
220 		}
221 
222 		if ((state == inQuote) || (state == inDoubleQuote)) {
223 			throw new CommandLineException("unbalanced quotes in " + toProcess);
224 		}
225 
226 		String[] args = new String[v.size()];
227 		v.copyInto(args);
228 		return args;
229 	}
230 
231 	/**
232 	 * <p>
233 	 * Put quotes around the given String if necessary.
234 	 * </p>
235 	 * <p>
236 	 * If the argument doesn't include spaces or quotes, return it as is. If it contains double quotes, use single quotes - else surround the argument by double quotes.
237 	 * </p>
238 	 * 
239 	 * @throws CommandLineException
240 	 *             if the argument contains both, single and double quotes.
241 	 * @deprecated Use {@link StringUtils#quoteAndEscape(String, char, char[], char[], char, boolean)}, {@link StringUtils#quoteAndEscape(String, char, char[], char, boolean)}, or
242 	 *             {@link StringUtils#quoteAndEscape(String, char)} instead.
243 	 */
244 	@Deprecated
245 	public static String quote(String argument) throws CommandLineException {
246 		return quote(argument, false, false, true);
247 	}
248 
249 	/**
250 	 * <p>
251 	 * Put quotes around the given String if necessary.
252 	 * </p>
253 	 * <p>
254 	 * If the argument doesn't include spaces or quotes, return it as is. If it contains double quotes, use single quotes - else surround the argument by double quotes.
255 	 * </p>
256 	 * 
257 	 * @throws CommandLineException
258 	 *             if the argument contains both, single and double quotes.
259 	 * @deprecated Use {@link StringUtils#quoteAndEscape(String, char, char[], char[], char, boolean)}, {@link StringUtils#quoteAndEscape(String, char, char[], char, boolean)}, or
260 	 *             {@link StringUtils#quoteAndEscape(String, char)} instead.
261 	 */
262 	@Deprecated
263 	public static String quote(String argument, boolean wrapExistingQuotes) throws CommandLineException {
264 		return quote(argument, false, false, wrapExistingQuotes);
265 	}
266 
267 	/**
268 	 * @deprecated Use {@link StringUtils#quoteAndEscape(String, char, char[], char[], char, boolean)}, {@link StringUtils#quoteAndEscape(String, char, char[], char, boolean)}, or
269 	 *             {@link StringUtils#quoteAndEscape(String, char)} instead.
270 	 */
271 	@Deprecated
272 	public static String quote(String argument, boolean escapeSingleQuotes, boolean escapeDoubleQuotes, boolean wrapExistingQuotes) throws CommandLineException {
273 		if (argument.contains("\"")) {
274 			if (argument.contains("\'")) {
275 				throw new CommandLineException("Can't handle single and double quotes in same argument");
276 			} else {
277 				if (escapeSingleQuotes) {
278 					return "\\\'" + argument + "\\\'";
279 				} else if (wrapExistingQuotes) {
280 					return '\'' + argument + '\'';
281 				}
282 			}
283 		} else if (argument.contains("\'")) {
284 			if (escapeDoubleQuotes) {
285 				return "\\\"" + argument + "\\\"";
286 			} else if (wrapExistingQuotes) {
287 				return '\"' + argument + '\"';
288 			}
289 		} else if (argument.contains(" ")) {
290 			if (escapeDoubleQuotes) {
291 				return "\\\"" + argument + "\\\"";
292 			} else {
293 				return '\"' + argument + '\"';
294 			}
295 		}
296 
297 		return argument;
298 	}
299 
300 	public static String toString(String[] line) {
301 		// empty path return empty string
302 		if ((line == null) || (line.length == 0)) {
303 			return "";
304 		}
305 
306 		// path containing one or more elements
307 		final StringBuilder result = new StringBuilder();
308 		for (int i = 0; i < line.length; i++) {
309 			if (i > 0) {
310 				result.append(' ');
311 			}
312 			try {
313 				result.append(StringUtils.quoteAndEscape(line[i], '\"'));
314 			} catch (Exception e) {
315 				System.err.println("Error quoting argument: " + e.getMessage());
316 			}
317 		}
318 		return result.toString();
319 	}
320 
321 }