blob: 72f32f2f175b5ef6827b63edd08a30ff03293783 [file] [log] [blame]
/*
* 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);
}
}