blob: 3feb702599160541d5475cfd292012c1d514e0a0 [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>();
Stuart McCulloch2286f232012-06-15 13:27:53 +000015 Map<String,String> variables = new LinkedHashMap<String,String>();
Stuart McCullochbb014372012-06-07 21:57:32 +000016 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
Stuart McCulloch2286f232012-06-15 13:27:53 +000027 public Command() {}
Stuart McCullochbb014372012-06-07 21:57:32 +000028
29 public int execute(Appendable stdout, Appendable stderr) throws Exception {
30 return execute((InputStream) null, stdout, stderr);
31 }
32
33 public int execute(String input, Appendable stdout, Appendable stderr) throws Exception {
34 InputStream in = new ByteArrayInputStream(input.getBytes("UTF-8"));
35 return execute(in, stdout, stderr);
36 }
37
38 public int execute(InputStream in, Appendable stdout, Appendable stderr) throws Exception {
Stuart McCullochbb014372012-06-07 21:57:32 +000039 if (reporter != null) {
40 reporter.trace("executing cmd: %s", arguments);
41 }
42
43 String args[] = arguments.toArray(new String[arguments.size()]);
44 String vars[] = new String[variables.size()];
45 int i = 0;
Stuart McCulloch2286f232012-06-15 13:27:53 +000046 for (Entry<String,String> s : variables.entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +000047 vars[i++] = s.getKey() + "=" + s.getValue();
48 }
49
50 if (fullCommand == null)
51 process = Runtime.getRuntime().exec(args, vars.length == 0 ? null : vars, cwd);
52 else
53 process = Runtime.getRuntime().exec(fullCommand, vars.length == 0 ? null : vars, cwd);
54
Stuart McCullochbb014372012-06-07 21:57:32 +000055 // Make sure the command will not linger when we go
56 Runnable r = new Runnable() {
57 public void run() {
58 process.destroy();
59 }
60 };
61 Thread hook = new Thread(r, arguments.toString());
62 Runtime.getRuntime().addShutdownHook(hook);
63 TimerTask timer = null;
64 OutputStream stdin = process.getOutputStream();
Stuart McCullochbb014372012-06-07 21:57:32 +000065
66 if (timeout != 0) {
67 timer = new TimerTask() {
68 public void run() {
69 timedout = true;
70 process.destroy();
Stuart McCullochbb014372012-06-07 21:57:32 +000071 }
72 };
73 Command.timer.schedule(timer, timeout);
74 }
75
76 InputStream out = process.getInputStream();
77 try {
78 InputStream err = process.getErrorStream();
79 try {
Stuart McCulloch285034f2012-06-12 12:41:16 +000080 Collector cout = new Collector(out, stdout);
81 cout.start();
82 Collector cerr = new Collector(err, stderr);
83 cerr.start();
Stuart McCulloch2286f232012-06-15 13:27:53 +000084
Stuart McCulloch285034f2012-06-12 12:41:16 +000085 try {
86 int c = in.read();
87 while (c >= 0) {
88 stdin.write(c);
Stuart McCulloch2286f232012-06-15 13:27:53 +000089 if (c == '\n')
Stuart McCulloch285034f2012-06-12 12:41:16 +000090 stdin.flush();
91 c = in.read();
92 }
93 }
94 catch (InterruptedIOException e) {
95 // Ignore here
96 }
97 catch (Exception e) {
98 // Who cares?
Stuart McCulloch2286f232012-06-15 13:27:53 +000099 }
100 finally {
Stuart McCulloch285034f2012-06-12 12:41:16 +0000101 stdin.close();
102 }
Stuart McCulloch2286f232012-06-15 13:27:53 +0000103
Stuart McCullochbb014372012-06-07 21:57:32 +0000104 if (reporter != null)
Stuart McCulloch285034f2012-06-12 12:41:16 +0000105 reporter.trace("exited process");
Stuart McCulloch2286f232012-06-15 13:27:53 +0000106
Stuart McCulloch285034f2012-06-12 12:41:16 +0000107 cerr.join();
108 cout.join();
109 if (reporter != null)
110 reporter.trace("stdout/stderr streams have finished");
Stuart McCullochbb014372012-06-07 21:57:32 +0000111 }
112 finally {
113 err.close();
114 }
115 }
116 finally {
117 out.close();
118 if (timer != null)
119 timer.cancel();
120 Runtime.getRuntime().removeShutdownHook(hook);
Stuart McCullochbb014372012-06-07 21:57:32 +0000121 }
Stuart McCulloch2286f232012-06-15 13:27:53 +0000122
Stuart McCulloch285034f2012-06-12 12:41:16 +0000123 byte exitValue = (byte) process.waitFor();
Stuart McCullochbb014372012-06-07 21:57:32 +0000124 if (reporter != null)
Stuart McCulloch2286f232012-06-15 13:27:53 +0000125 reporter.trace("cmd %s executed with result=%d, result: %s/%s, timedout=%s", arguments, exitValue, stdout,
126 stderr, timedout);
Stuart McCullochbb014372012-06-07 21:57:32 +0000127
128 if (timedout)
129 return Integer.MIN_VALUE;
Stuart McCulloch2286f232012-06-15 13:27:53 +0000130
Stuart McCullochbb014372012-06-07 21:57:32 +0000131 return exitValue;
132 }
133
134 public void add(String... args) {
135 for (String arg : args)
136 arguments.add(arg);
137 }
138
139 public void addAll(Collection<String> args) {
140 arguments.addAll(args);
141 }
142
143 public void setTimeout(long duration, TimeUnit unit) {
144 timeout = unit.toMillis(duration);
145 }
146
147 public void setTrace() {
148 this.trace = true;
149 }
150
151 public void setReporter(Reporter reporter) {
152 this.reporter = reporter;
153 }
154
155 public void setCwd(File dir) {
156 if (!dir.isDirectory())
157 throw new IllegalArgumentException("Working directory must be a directory: " + dir);
158
159 this.cwd = dir;
160 }
161
162 public void cancel() {
163 process.destroy();
164 }
165
166 class Collector extends Thread {
167 final InputStream in;
168 final Appendable sb;
169
170 Collector(InputStream inputStream, Appendable sb) {
171 this.in = inputStream;
172 this.sb = sb;
173 setDaemon(true);
174 }
175
176 public void run() {
177 try {
178 int c = in.read();
179 while (c >= 0) {
180 sb.append((char) c);
181 c = in.read();
182 }
183 }
Stuart McCulloch2286f232012-06-15 13:27:53 +0000184 catch (IOException e) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000185 // We assume the socket is closed
186 }
187 catch (Exception e) {
188 try {
189 sb.append("\n**************************************\n");
190 sb.append(e.toString());
191 sb.append("\n**************************************\n");
192 }
Stuart McCulloch2286f232012-06-15 13:27:53 +0000193 catch (IOException e1) {}
Stuart McCullochbb014372012-06-07 21:57:32 +0000194 if (reporter != null) {
195 reporter.trace("cmd exec: %s", e);
196 }
197 }
198 }
199 }
200
Stuart McCullochbb014372012-06-07 21:57:32 +0000201 public Command var(String name, String value) {
202 variables.put(name, value);
203 return this;
204 }
205
206 public Command arg(String... args) {
207 add(args);
208 return this;
209 }
210
211 public Command full(String full) {
212 fullCommand = full;
213 return this;
214 }
215
216 public void inherit() {
217 ProcessBuilder pb = new ProcessBuilder();
Stuart McCulloch2286f232012-06-15 13:27:53 +0000218 for (Entry<String,String> e : pb.environment().entrySet()) {
Stuart McCullochbb014372012-06-07 21:57:32 +0000219 var(e.getKey(), e.getValue());
220 }
221 }
222
223 public String var(String name) {
224 return variables.get(name);
225 }
226
227 public String toString() {
228 StringBuilder sb = new StringBuilder();
229 String del = "";
230
231 for (String argument : arguments) {
232 sb.append(del);
233 sb.append(argument);
234 del = " ";
235 }
236 return sb.toString();
237 }
238}