Home | History | Annotate | Download | only in monitor
      1 /*
      2  * Copyright (C) 2010 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 vogar.monitor;
     18 
     19 import com.google.gson.JsonObject;
     20 import java.io.BufferedInputStream;
     21 import java.io.IOException;
     22 import java.io.InputStream;
     23 import java.io.InputStreamReader;
     24 import java.net.ConnectException;
     25 import java.net.Socket;
     26 import java.net.SocketException;
     27 import java.nio.charset.Charset;
     28 import vogar.Log;
     29 import vogar.Outcome;
     30 import vogar.Result;
     31 import vogar.util.IoUtils;
     32 
     33 /**
     34  * Connects to a target process to monitor its action using XML over raw
     35  * sockets.
     36  */
     37 public final class HostMonitor {
     38     private static final Charset UTF8 = Charset.forName("UTF-8");
     39 
     40     private Log log;
     41     private Handler handler;
     42     private final String marker = "//00xx";
     43 
     44     public HostMonitor(Log log, Handler handler) {
     45         this.log = log;
     46         this.handler = handler;
     47     }
     48 
     49     /**
     50      * Returns true if the target process completed normally.
     51      */
     52     public boolean attach(int port) throws IOException {
     53         for (int attempt = 0; true; attempt++) {
     54             Socket socket = null;
     55             try {
     56                 socket = new Socket("localhost", port);
     57                 InputStream in = new BufferedInputStream(socket.getInputStream());
     58                 if (checkStream(in)) {
     59                     log.verbose("action monitor connected to " + socket.getRemoteSocketAddress());
     60                     return followStream(in);
     61                 }
     62             } catch (ConnectException ignored) {
     63             } catch (SocketException ignored) {
     64             } finally {
     65                 IoUtils.closeQuietly(socket);
     66             }
     67 
     68             log.verbose("connection " + attempt + " to localhost:"
     69                     + port + " failed; retrying in 1s");
     70             try {
     71                 Thread.sleep(1000);
     72             } catch (InterruptedException ignored) {
     73             }
     74         }
     75     }
     76 
     77     /**
     78      * Somewhere between the host and client process, broken socket connections
     79      * are being accepted. Before we try to do any work on such a connection,
     80      * check it to make sure it's not dead!
     81      *
     82      * TODO: file a bug (against adb?) for this
     83      */
     84     private boolean checkStream(InputStream in) throws IOException {
     85         in.mark(1);
     86         if (in.read() == -1) {
     87             return false;
     88         } else {
     89             in.reset();
     90             return true;
     91         }
     92     }
     93 
     94     public boolean followStream(InputStream in) throws IOException {
     95         return followProcess(new InterleavedReader(marker, new InputStreamReader(in, UTF8)));
     96     }
     97 
     98     /**
     99      * Our wire format is a mix of strings and the JSON values like the following:
    100      *
    101      * {"outcome"="java.util.FormatterMain"}
    102      * {"result"="SUCCESS"}
    103      * {"outcome"="java.util.FormatterTest#testBar" runner="vogar.target.junit.JUnitRunner"}
    104      * {"result"="SUCCESS"}
    105      * {"completedNormally"=true}
    106      */
    107     private boolean followProcess(InterleavedReader reader) throws IOException {
    108         String currentOutcome = null;
    109         StringBuilder output = new StringBuilder();
    110         boolean completedNormally = false;
    111 
    112         Object o;
    113         while ((o = reader.read()) != null) {
    114             if (o instanceof String) {
    115                 String text = (String) o;
    116                 if (currentOutcome != null) {
    117                     output.append(text);
    118                     handler.output(currentOutcome, text);
    119                 } else {
    120                     handler.print(text);
    121                 }
    122             } else if (o instanceof JsonObject) {
    123                 JsonObject jsonObject = (JsonObject) o;
    124                 if (jsonObject.get("outcome") != null) {
    125                     currentOutcome = jsonObject.get("outcome").getAsString();
    126                     handler.output(currentOutcome, "");
    127                     handler.start(currentOutcome);
    128                 } else if (jsonObject.get("result") != null) {
    129                     Result currentResult = Result.valueOf(jsonObject.get("result").getAsString());
    130                     handler.finish(new Outcome(currentOutcome, currentResult, output.toString()));
    131                     output.delete(0, output.length());
    132                     currentOutcome = null;
    133                 } else if (jsonObject.get("completedNormally") != null) {
    134                     completedNormally = jsonObject.get("completedNormally").getAsBoolean();
    135                 }
    136             } else {
    137                 throw new IllegalStateException("Unexpected object: " + o);
    138             }
    139         }
    140 
    141         return completedNormally;
    142     }
    143 
    144 
    145     /**
    146      * Handles updates on the outcomes of a target process.
    147      */
    148     public interface Handler {
    149 
    150         /**
    151          * Receive notification that an outcome is pending.
    152          */
    153         void start(String outcomeName);
    154 
    155         /**
    156          * Receive a completed outcome.
    157          */
    158         void finish(Outcome outcome);
    159 
    160         /**
    161          * Receive partial output from an action being executed.
    162          */
    163         void output(String outcomeName, String output);
    164 
    165         /**
    166          * Receive a string to print immediately
    167          */
    168         void print(String string);
    169     }
    170 }
    171