/* * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package util.build; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintStream; import java.io.StreamTokenizer; import java.io.StringReader; import java.util.ArrayList; import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.CheckForNull; import javax.annotation.Nonnull; /** * Class to handle the execution of an external process */ public class ExecuteFile { @Nonnull private final String[] cmdLine; @CheckForNull private File workDir; @CheckForNull private InputStream inStream; private boolean inToBeClose; @CheckForNull private OutputStream outStream; private boolean outToBeClose; @CheckForNull private OutputStream errStream; private boolean errToBeClose; private boolean verbose; @Nonnull private final Logger logger = Logger.getLogger(this.getClass().getName()); public void setErr(@Nonnull File file) throws FileNotFoundException { errStream = new FileOutputStream(file); errToBeClose = true; } public void setOut(@Nonnull File file) throws FileNotFoundException { outStream = new FileOutputStream(file); outToBeClose = true; } public void setIn(@Nonnull File file) throws FileNotFoundException { inStream = new FileInputStream(file); inToBeClose = true; } public void setErr(@Nonnull OutputStream stream) { errStream = stream; } public void setOut(@Nonnull OutputStream stream) { outStream = stream; } public void setIn(@Nonnull InputStream stream) { inStream = stream; } public void setWorkingDir(@Nonnull File dir, boolean create) throws IOException { if (!dir.isDirectory()) { if (create && !dir.exists()) { if (!dir.mkdirs()) { throw new IOException("Directory creation failed"); } } else { throw new FileNotFoundException(dir.getPath() + " is not a directory"); } } workDir = dir; } public void setVerbose(boolean verbose) { this.verbose = verbose; } public ExecuteFile(@Nonnull File exec, @Nonnull String[] args) { cmdLine = new String[args.length + 1]; System.arraycopy(args, 0, cmdLine, 1, args.length); cmdLine[0] = exec.getAbsolutePath(); } public ExecuteFile(@Nonnull String exec, @Nonnull String[] args) { cmdLine = new String[args.length + 1]; System.arraycopy(args, 0, cmdLine, 1, args.length); cmdLine[0] = exec; } public ExecuteFile(@Nonnull File exec) { cmdLine = new String[1]; cmdLine[0] = exec.getAbsolutePath(); } public ExecuteFile(@Nonnull String[] cmdLine) { this.cmdLine = cmdLine.clone(); } public ExecuteFile(@Nonnull String cmdLine) throws IOException { StringReader reader = new StringReader(cmdLine); StreamTokenizer tokenizer = new StreamTokenizer(reader); tokenizer.resetSyntax(); // Only standard spaces are recognized as whitespace chars tokenizer.whitespaceChars(' ', ' '); // Matches alphanumerical and common special symbols like '(' and ')' tokenizer.wordChars('!', 'z'); // Quote chars will be ignored when parsing strings tokenizer.quoteChar('\''); tokenizer.quoteChar('\"'); ArrayList tokens = new ArrayList(); while (tokenizer.nextToken() != StreamTokenizer.TT_EOF) { String token = tokenizer.sval; if (token != null) { tokens.add(token); } } this.cmdLine = tokens.toArray(new String[0]); } public boolean run() { int ret; Process proc = null; Thread suckOut = null; Thread suckErr = null; Thread suckIn = null; try { StringBuilder cmdLineBuilder = new StringBuilder(); for (String arg : cmdLine) { cmdLineBuilder.append(arg).append(' '); } if (verbose) { PrintStream printStream; if (outStream instanceof PrintStream) { printStream = (PrintStream) outStream; } else { printStream = System.out; } if (printStream != null) { printStream.println(cmdLineBuilder); } } else { logger.log(Level.FINE, "Execute: {0}", cmdLineBuilder); } proc = Runtime.getRuntime().exec(cmdLine, null, workDir); InputStream localInStream = inStream; if (localInStream != null) { suckIn = new Thread( new ThreadBytesStreamSucker(localInStream, proc.getOutputStream(), inToBeClose)); } else { proc.getOutputStream().close(); } OutputStream localOutStream = outStream; if (localOutStream != null) { if (localOutStream instanceof PrintStream) { suckOut = new Thread(new ThreadCharactersStreamSucker(proc.getInputStream(), (PrintStream) localOutStream, outToBeClose)); } else { suckOut = new Thread( new ThreadBytesStreamSucker(proc.getInputStream(), localOutStream, outToBeClose)); } } OutputStream localErrStream = errStream; if (localErrStream != null) { if (localErrStream instanceof PrintStream) { suckErr = new Thread(new ThreadCharactersStreamSucker(proc.getErrorStream(), (PrintStream) localErrStream, errToBeClose)); } else { suckErr = new Thread( new ThreadBytesStreamSucker(proc.getErrorStream(), localErrStream, errToBeClose)); } } if (suckIn != null) { suckIn.start(); } if (suckOut != null) { suckOut.start(); } if (suckErr != null) { suckErr.start(); } proc.waitFor(); if (suckIn != null) { suckIn.join(); } if (suckOut != null) { suckOut.join(); } if (suckErr != null) { suckErr.join(); } ret = proc.exitValue(); proc.destroy(); return ret == 0; } catch (Throwable e) { e.printStackTrace(); return false; } } private static class ThreadBytesStreamSucker extends BytesStreamSucker implements Runnable { public ThreadBytesStreamSucker(@Nonnull InputStream is, @Nonnull OutputStream os, boolean toBeClose) { super(is, os, toBeClose); } @Override public void run() { try { suck(); } catch (IOException e) { // Best effort } } } private static class ThreadCharactersStreamSucker extends CharactersStreamSucker implements Runnable { public ThreadCharactersStreamSucker(@Nonnull InputStream is, @Nonnull PrintStream ps, boolean toBeClose) { super(is, ps, toBeClose); } @Override public void run() { try { suck(); } catch (IOException e) { // Best effort } } } }