blob: 128e4775e04e5d89d5a47681e1c5435ffb813b96 [file] [log] [blame]
Yohann Rousselc48d4ce2016-01-20 17:51:55 +01001/*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package util.build;
18
19import java.io.File;
20import java.io.FileInputStream;
21import java.io.FileNotFoundException;
22import java.io.FileOutputStream;
23import java.io.IOException;
24import java.io.InputStream;
25import java.io.OutputStream;
26import java.io.PrintStream;
27import java.io.StreamTokenizer;
28import java.io.StringReader;
29import java.util.ArrayList;
30import java.util.logging.Level;
31import java.util.logging.Logger;
32
33import javax.annotation.CheckForNull;
34import javax.annotation.Nonnull;
35
36/**
37 * Class to handle the execution of an external process
38 */
39public class ExecuteFile {
40 @Nonnull
41 private final String[] cmdLine;
42
43 @CheckForNull
44 private File workDir;
45
46 @CheckForNull
47 private InputStream inStream;
48 private boolean inToBeClose;
49
50 @CheckForNull
51 private OutputStream outStream;
52 private boolean outToBeClose;
53
54 @CheckForNull
55 private OutputStream errStream;
56 private boolean errToBeClose;
57 private boolean verbose;
58
59 @Nonnull
60 private final Logger logger = Logger.getLogger(this.getClass().getName());
61
62 public void setErr(@Nonnull File file) throws FileNotFoundException {
63 errStream = new FileOutputStream(file);
64 errToBeClose = true;
65 }
66
67 public void setOut(@Nonnull File file) throws FileNotFoundException {
68 outStream = new FileOutputStream(file);
69 outToBeClose = true;
70 }
71
72 public void setIn(@Nonnull File file) throws FileNotFoundException {
73 inStream = new FileInputStream(file);
74 inToBeClose = true;
75 }
76
77 public void setErr(@Nonnull OutputStream stream) {
78 errStream = stream;
79 }
80
81 public void setOut(@Nonnull OutputStream stream) {
82 outStream = stream;
83 }
84
85 public void setIn(@Nonnull InputStream stream) {
86 inStream = stream;
87 }
88
89 public void setWorkingDir(@Nonnull File dir, boolean create) throws IOException {
90 if (!dir.isDirectory()) {
91 if (create && !dir.exists()) {
92 if (!dir.mkdirs()) {
93 throw new IOException("Directory creation failed");
94 }
95 } else {
96 throw new FileNotFoundException(dir.getPath() + " is not a directory");
97 }
98 }
99
100 workDir = dir;
101 }
102
103 public void setVerbose(boolean verbose) {
104 this.verbose = verbose;
105 }
106
107 public ExecuteFile(@Nonnull File exec, @Nonnull String[] args) {
108 cmdLine = new String[args.length + 1];
109 System.arraycopy(args, 0, cmdLine, 1, args.length);
110
111 cmdLine[0] = exec.getAbsolutePath();
112 }
113
114 public ExecuteFile(@Nonnull String exec, @Nonnull String[] args) {
115 cmdLine = new String[args.length + 1];
116 System.arraycopy(args, 0, cmdLine, 1, args.length);
117
118 cmdLine[0] = exec;
119 }
120
121 public ExecuteFile(@Nonnull File exec) {
122 cmdLine = new String[1];
123 cmdLine[0] = exec.getAbsolutePath();
124 }
125
126 public ExecuteFile(@Nonnull String[] cmdLine) {
127 this.cmdLine = cmdLine.clone();
128 }
129
130 public ExecuteFile(@Nonnull String cmdLine) throws IOException {
131 StringReader reader = new StringReader(cmdLine);
132 StreamTokenizer tokenizer = new StreamTokenizer(reader);
133 tokenizer.resetSyntax();
134 // Only standard spaces are recognized as whitespace chars
135 tokenizer.whitespaceChars(' ', ' ');
136 // Matches alphanumerical and common special symbols like '(' and ')'
137 tokenizer.wordChars('!', 'z');
138 // Quote chars will be ignored when parsing strings
139 tokenizer.quoteChar('\'');
140 tokenizer.quoteChar('\"');
141 ArrayList<String> tokens = new ArrayList<String>();
142 while (tokenizer.nextToken() != StreamTokenizer.TT_EOF) {
143 String token = tokenizer.sval;
144 if (token != null) {
145 tokens.add(token);
146 }
147 }
148 this.cmdLine = tokens.toArray(new String[0]);
149 }
150
151 public boolean run() {
152 int ret;
153 Process proc = null;
154 Thread suckOut = null;
155 Thread suckErr = null;
156 Thread suckIn = null;
157
158 try {
159 StringBuilder cmdLineBuilder = new StringBuilder();
160 for (String arg : cmdLine) {
161 cmdLineBuilder.append(arg).append(' ');
162 }
163 if (verbose) {
164 PrintStream printStream;
165 if (outStream instanceof PrintStream) {
166 printStream = (PrintStream) outStream;
167 } else {
168 printStream = System.out;
169 }
170
171 if (printStream != null) {
172 printStream.println(cmdLineBuilder);
173 }
174 } else {
175 logger.log(Level.FINE, "Execute: {0}", cmdLineBuilder);
176 }
177
178 proc = Runtime.getRuntime().exec(cmdLine, null, workDir);
179
180 InputStream localInStream = inStream;
181 if (localInStream != null) {
182 suckIn = new Thread(
183 new ThreadBytesStreamSucker(localInStream, proc.getOutputStream(), inToBeClose));
184 } else {
185 proc.getOutputStream().close();
186 }
187
188 OutputStream localOutStream = outStream;
189 if (localOutStream != null) {
190 if (localOutStream instanceof PrintStream) {
191 suckOut = new Thread(new ThreadCharactersStreamSucker(proc.getInputStream(),
192 (PrintStream) localOutStream, outToBeClose));
193 } else {
194 suckOut = new Thread(
195 new ThreadBytesStreamSucker(proc.getInputStream(), localOutStream, outToBeClose));
196 }
197 }
198
199 OutputStream localErrStream = errStream;
200 if (localErrStream != null) {
201 if (localErrStream instanceof PrintStream) {
202 suckErr = new Thread(new ThreadCharactersStreamSucker(proc.getErrorStream(),
203 (PrintStream) localErrStream, errToBeClose));
204 } else {
205 suckErr = new Thread(
206 new ThreadBytesStreamSucker(proc.getErrorStream(), localErrStream, errToBeClose));
207 }
208 }
209
210 if (suckIn != null) {
211 suckIn.start();
212 }
213 if (suckOut != null) {
214 suckOut.start();
215 }
216 if (suckErr != null) {
217 suckErr.start();
218 }
219
220 proc.waitFor();
221 if (suckIn != null) {
222 suckIn.join();
223 }
224 if (suckOut != null) {
225 suckOut.join();
226 }
227 if (suckErr != null) {
228 suckErr.join();
229 }
230
231 ret = proc.exitValue();
232 proc.destroy();
233
234 return ret == 0;
235 } catch (Throwable e) {
236 e.printStackTrace();
237 return false;
238 }
239 }
240
241 private static class ThreadBytesStreamSucker extends BytesStreamSucker implements Runnable {
242
243 public ThreadBytesStreamSucker(@Nonnull InputStream is, @Nonnull OutputStream os,
244 boolean toBeClose) {
245 super(is, os, toBeClose);
246 }
247
248 @Override
249 public void run() {
250 try {
251 suck();
252 } catch (IOException e) {
253 // Best effort
254 }
255 }
256 }
257
258 private static class ThreadCharactersStreamSucker extends CharactersStreamSucker implements
259 Runnable {
260
261 public ThreadCharactersStreamSucker(@Nonnull InputStream is, @Nonnull PrintStream ps,
262 boolean toBeClose) {
263 super(is, ps, toBeClose);
264 }
265
266 @Override
267 public void run() {
268 try {
269 suck();
270 } catch (IOException e) {
271 // Best effort
272 }
273 }
274 }
275}