Home | History | Annotate | Download | only in build
      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 }