| /* |
| * Copyright (C) 2010 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 vogar.monitor; |
| |
| import com.google.gson.JsonObject; |
| import java.io.BufferedInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.net.ConnectException; |
| import java.net.Socket; |
| import java.net.SocketException; |
| import java.nio.charset.Charset; |
| import vogar.Log; |
| import vogar.Outcome; |
| import vogar.Result; |
| import vogar.util.IoUtils; |
| |
| /** |
| * Connects to a target process to monitor its action using XML over raw |
| * sockets. |
| */ |
| public final class HostMonitor { |
| private static final Charset UTF8 = Charset.forName("UTF-8"); |
| |
| private Log log; |
| private Handler handler; |
| private final String marker = "//00xx"; |
| |
| public HostMonitor(Log log, Handler handler) { |
| this.log = log; |
| this.handler = handler; |
| } |
| |
| /** |
| * Returns true if the target process completed normally. |
| */ |
| public boolean attach(int port) throws IOException { |
| for (int attempt = 0; true; attempt++) { |
| Socket socket = null; |
| try { |
| socket = new Socket("localhost", port); |
| InputStream in = new BufferedInputStream(socket.getInputStream()); |
| if (checkStream(in)) { |
| log.verbose("action monitor connected to " + socket.getRemoteSocketAddress()); |
| return followStream(in); |
| } |
| } catch (ConnectException ignored) { |
| } catch (SocketException ignored) { |
| } finally { |
| IoUtils.closeQuietly(socket); |
| } |
| |
| log.verbose("connection " + attempt + " to localhost:" |
| + port + " failed; retrying in 1s"); |
| try { |
| Thread.sleep(1000); |
| } catch (InterruptedException ignored) { |
| } |
| } |
| } |
| |
| /** |
| * Somewhere between the host and client process, broken socket connections |
| * are being accepted. Before we try to do any work on such a connection, |
| * check it to make sure it's not dead! |
| * |
| * TODO: file a bug (against adb?) for this |
| */ |
| private boolean checkStream(InputStream in) throws IOException { |
| in.mark(1); |
| if (in.read() == -1) { |
| return false; |
| } else { |
| in.reset(); |
| return true; |
| } |
| } |
| |
| public boolean followStream(InputStream in) throws IOException { |
| return followProcess(new InterleavedReader(marker, new InputStreamReader(in, UTF8))); |
| } |
| |
| /** |
| * Our wire format is a mix of strings and the JSON values like the following: |
| * |
| * {"outcome"="java.util.FormatterMain"} |
| * {"result"="SUCCESS"} |
| * {"outcome"="java.util.FormatterTest#testBar" runner="vogar.target.junit.JUnitRunner"} |
| * {"result"="SUCCESS"} |
| * {"completedNormally"=true} |
| */ |
| private boolean followProcess(InterleavedReader reader) throws IOException { |
| String currentOutcome = null; |
| StringBuilder output = new StringBuilder(); |
| boolean completedNormally = false; |
| |
| Object o; |
| while ((o = reader.read()) != null) { |
| if (o instanceof String) { |
| String text = (String) o; |
| if (currentOutcome != null) { |
| output.append(text); |
| handler.output(currentOutcome, text); |
| } else { |
| handler.print(text); |
| } |
| } else if (o instanceof JsonObject) { |
| JsonObject jsonObject = (JsonObject) o; |
| if (jsonObject.get("outcome") != null) { |
| currentOutcome = jsonObject.get("outcome").getAsString(); |
| handler.output(currentOutcome, ""); |
| handler.start(currentOutcome); |
| } else if (jsonObject.get("result") != null) { |
| Result currentResult = Result.valueOf(jsonObject.get("result").getAsString()); |
| handler.finish(new Outcome(currentOutcome, currentResult, output.toString())); |
| output.delete(0, output.length()); |
| currentOutcome = null; |
| } else if (jsonObject.get("completedNormally") != null) { |
| completedNormally = jsonObject.get("completedNormally").getAsBoolean(); |
| } |
| } else { |
| throw new IllegalStateException("Unexpected object: " + o); |
| } |
| } |
| |
| return completedNormally; |
| } |
| |
| |
| /** |
| * Handles updates on the outcomes of a target process. |
| */ |
| public interface Handler { |
| |
| /** |
| * Receive notification that an outcome is pending. |
| */ |
| void start(String outcomeName); |
| |
| /** |
| * Receive a completed outcome. |
| */ |
| void finish(Outcome outcome); |
| |
| /** |
| * Receive partial output from an action being executed. |
| */ |
| void output(String outcomeName, String output); |
| |
| /** |
| * Receive a string to print immediately |
| */ |
| void print(String string); |
| } |
| } |