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 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 }