blob: 0a79a5859351bc77fbab4b0d1726ebf097a393f0 [file] [log] [blame]
Stuart McCullochbb014372012-06-07 21:57:32 +00001package aQute.libg.command;
2
3import java.io.*;
4import java.util.*;
5import java.util.Map.Entry;
6import java.util.concurrent.*;
7
8import aQute.libg.reporter.*;
9
10public class Command {
11
12 boolean trace;
13 Reporter reporter;
14 List<String> arguments = new ArrayList<String>();
15 Map<String, String> variables = new LinkedHashMap<String, String>();
16 long timeout = 0;
17 File cwd = new File("").getAbsoluteFile();
18 static Timer timer = new Timer(Command.class.getName(), true);
19 Process process;
20 volatile boolean timedout;
21 String fullCommand;
22
23 public Command(String fullCommand) {
24 this.fullCommand = fullCommand;
25 }
26
27 public Command() {
28 }
29
30 public int execute(Appendable stdout, Appendable stderr) throws Exception {
31 return execute((InputStream) null, stdout, stderr);
32 }
33
34 public int execute(String input, Appendable stdout, Appendable stderr) throws Exception {
35 InputStream in = new ByteArrayInputStream(input.getBytes("UTF-8"));
36 return execute(in, stdout, stderr);
37 }
38
39 public int execute(InputStream in, Appendable stdout, Appendable stderr) throws Exception {
40 int result;
41 if (reporter != null) {
42 reporter.trace("executing cmd: %s", arguments);
43 }
44
45 String args[] = arguments.toArray(new String[arguments.size()]);
46 String vars[] = new String[variables.size()];
47 int i = 0;
48 for (Entry<String, String> s : variables.entrySet()) {
49 vars[i++] = s.getKey() + "=" + s.getValue();
50 }
51
52 if (fullCommand == null)
53 process = Runtime.getRuntime().exec(args, vars.length == 0 ? null : vars, cwd);
54 else
55 process = Runtime.getRuntime().exec(fullCommand, vars.length == 0 ? null : vars, cwd);
56
57
58 // Make sure the command will not linger when we go
59 Runnable r = new Runnable() {
60 public void run() {
61 process.destroy();
62 }
63 };
64 Thread hook = new Thread(r, arguments.toString());
65 Runtime.getRuntime().addShutdownHook(hook);
66 TimerTask timer = null;
67 OutputStream stdin = process.getOutputStream();
68 final InputStreamHandler handler = in != null ? new InputStreamHandler(in, stdin) : null;
69
70 if (timeout != 0) {
71 timer = new TimerTask() {
72 public void run() {
73 timedout = true;
74 process.destroy();
75 if (handler != null)
76 handler.interrupt();
77 }
78 };
79 Command.timer.schedule(timer, timeout);
80 }
81
82 InputStream out = process.getInputStream();
83 try {
84 InputStream err = process.getErrorStream();
85 try {
86 new Collector(out, stdout).start();
87 new Collector(err, stderr).start();
88 if (handler != null)
89 handler.start();
90
91 result = process.waitFor();
92 if (reporter != null)
93 reporter.trace("exited process.waitFor, %s", result);
94
95 }
96 finally {
97 err.close();
98 }
99 }
100 finally {
101 out.close();
102 if (timer != null)
103 timer.cancel();
104 Runtime.getRuntime().removeShutdownHook(hook);
105 if (handler != null)
106 handler.interrupt();
107 }
108 if (reporter != null)
109 reporter.trace("cmd %s executed with result=%d, result: %s/%s", arguments, result,
110 stdout, stderr);
111
112 if (timedout)
113 return Integer.MIN_VALUE;
114 byte exitValue = (byte) process.exitValue();
115 return exitValue;
116 }
117
118 public void add(String... args) {
119 for (String arg : args)
120 arguments.add(arg);
121 }
122
123 public void addAll(Collection<String> args) {
124 arguments.addAll(args);
125 }
126
127 public void setTimeout(long duration, TimeUnit unit) {
128 timeout = unit.toMillis(duration);
129 }
130
131 public void setTrace() {
132 this.trace = true;
133 }
134
135 public void setReporter(Reporter reporter) {
136 this.reporter = reporter;
137 }
138
139 public void setCwd(File dir) {
140 if (!dir.isDirectory())
141 throw new IllegalArgumentException("Working directory must be a directory: " + dir);
142
143 this.cwd = dir;
144 }
145
146 public void cancel() {
147 process.destroy();
148 }
149
150 class Collector extends Thread {
151 final InputStream in;
152 final Appendable sb;
153
154 Collector(InputStream inputStream, Appendable sb) {
155 this.in = inputStream;
156 this.sb = sb;
157 setDaemon(true);
158 }
159
160 public void run() {
161 try {
162 int c = in.read();
163 while (c >= 0) {
164 sb.append((char) c);
165 c = in.read();
166 }
167 }
168 catch( IOException e) {
169 // We assume the socket is closed
170 }
171 catch (Exception e) {
172 try {
173 sb.append("\n**************************************\n");
174 sb.append(e.toString());
175 sb.append("\n**************************************\n");
176 }
177 catch (IOException e1) {
178 }
179 if (reporter != null) {
180 reporter.trace("cmd exec: %s", e);
181 }
182 }
183 }
184 }
185
186 static class InputStreamHandler extends Thread {
187 final InputStream in;
188 final OutputStream stdin;
189
190 InputStreamHandler(InputStream in, OutputStream stdin) {
191 this.stdin = stdin;
192 this.in = in;
193 setDaemon(true);
194 }
195
196 public void run() {
197 try {
198 int c = in.read();
199 while (c >= 0) {
200 stdin.write(c);
201 stdin.flush();
202 c = in.read();
203 }
204 }
205 catch (InterruptedIOException e) {
206 // Ignore here
207 }
208 catch (Exception e) {
209 // Who cares?
210 }
211 finally {
212 try {
213 stdin.close();
214 }
215 catch (IOException e) {
216 // Who cares?
217 }
218 }
219 }
220 }
221
222 public Command var(String name, String value) {
223 variables.put(name, value);
224 return this;
225 }
226
227 public Command arg(String... args) {
228 add(args);
229 return this;
230 }
231
232 public Command full(String full) {
233 fullCommand = full;
234 return this;
235 }
236
237 public void inherit() {
238 ProcessBuilder pb = new ProcessBuilder();
239 for (Entry<String, String> e : pb.environment().entrySet()) {
240 var(e.getKey(), e.getValue());
241 }
242 }
243
244 public String var(String name) {
245 return variables.get(name);
246 }
247
248 public String toString() {
249 StringBuilder sb = new StringBuilder();
250 String del = "";
251
252 for (String argument : arguments) {
253 sb.append(del);
254 sb.append(argument);
255 del = " ";
256 }
257 return sb.toString();
258 }
259}