1 /*
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 
17 package util.build;
18 
19 import java.io.File;
20 import java.io.FileInputStream;
21 import java.io.FileNotFoundException;
22 import java.io.FileOutputStream;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.io.OutputStream;
26 import java.io.PrintStream;
27 import java.io.StreamTokenizer;
28 import java.io.StringReader;
29 import java.util.ArrayList;
30 import java.util.logging.Level;
31 import java.util.logging.Logger;
32 
33 import javax.annotation.CheckForNull;
34 import javax.annotation.Nonnull;
35 
36 /**
37  * Class to handle the execution of an external process
38  */
39 public 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 
setErr(@onnull File file)62   public void setErr(@Nonnull File file) throws FileNotFoundException {
63     errStream = new FileOutputStream(file);
64     errToBeClose = true;
65   }
66 
setOut(@onnull File file)67   public void setOut(@Nonnull File file) throws FileNotFoundException {
68     outStream = new FileOutputStream(file);
69     outToBeClose = true;
70   }
71 
setIn(@onnull File file)72   public void setIn(@Nonnull File file) throws FileNotFoundException {
73     inStream = new FileInputStream(file);
74     inToBeClose = true;
75   }
76 
setErr(@onnull OutputStream stream)77   public void setErr(@Nonnull OutputStream stream) {
78     errStream = stream;
79   }
80 
setOut(@onnull OutputStream stream)81   public void setOut(@Nonnull OutputStream stream) {
82     outStream = stream;
83   }
84 
setIn(@onnull InputStream stream)85   public void setIn(@Nonnull InputStream stream) {
86     inStream = stream;
87   }
88 
setWorkingDir(@onnull File dir, boolean create)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 
setVerbose(boolean verbose)103   public void setVerbose(boolean verbose) {
104     this.verbose = verbose;
105   }
106 
ExecuteFile(@onnull File exec, @Nonnull String[] args)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 
ExecuteFile(@onnull String exec, @Nonnull String[] args)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 
ExecuteFile(@onnull File exec)121   public ExecuteFile(@Nonnull File exec) {
122     cmdLine = new String[1];
123     cmdLine[0] = exec.getAbsolutePath();
124   }
125 
ExecuteFile(@onnull String[] cmdLine)126   public ExecuteFile(@Nonnull String[] cmdLine) {
127     this.cmdLine = cmdLine.clone();
128   }
129 
ExecuteFile(@onnull String cmdLine)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 
run()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 
ThreadBytesStreamSucker(@onnull InputStream is, @Nonnull OutputStream os, boolean toBeClose)243     public ThreadBytesStreamSucker(@Nonnull InputStream is, @Nonnull OutputStream os,
244         boolean toBeClose) {
245       super(is, os, toBeClose);
246     }
247 
248     @Override
run()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 
ThreadCharactersStreamSucker(@onnull InputStream is, @Nonnull PrintStream ps, boolean toBeClose)261     public ThreadCharactersStreamSucker(@Nonnull InputStream is, @Nonnull PrintStream ps,
262         boolean toBeClose) {
263       super(is, ps, toBeClose);
264     }
265 
266     @Override
run()267     public void run() {
268       try {
269         suck();
270       } catch (IOException e) {
271         // Best effort
272       }
273     }
274   }
275 }