/* Copyright (C) 2009 Mobile Sorcery AB
This program is free software; you can redistribute it and/or modify it
under the terms of the Eclipse Public License v1.0.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the Eclipse Public License v1.0 for
more details.
You should have received a copy of the Eclipse Public License v1.0 along
with this program. It is also available at http://www.eclipse.org/legal/epl-v10.html
*/
package com.mobilesorcery.sdk.internal;
import java.io.ByteArrayInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import com.mobilesorcery.sdk.core.CoreMoSyncPlugin;
import com.mobilesorcery.sdk.core.SimpleQueue;
import com.mobilesorcery.sdk.core.Util;
/**
* Class that parses binary data from the emulator.
*/
public class EmulatorOutputParser {
public static class ParseEvent {
public int type;
public String message;
public int ip;
public int[] stack;
public ParseEvent(int type) {
this(type, null, -1, null);
}
public ParseEvent(int type, int ip) {
this(type, null, ip, null);
}
public ParseEvent(int type, String message, int ip, int[] stack) {
this.type = type;
this.message = message;
this.ip = ip;
this.stack = stack;
}
public String toString() {
String stackStr = "";
if (stack != null) {
String[] iStack = new String[stack.length];
for (int i = 0; i < stack.length; i++) {
iStack[i] = Integer.toHexString(stack[i]);
}
stackStr = Util.join(iStack, ",");
}
return "TYPE: " + type + ", MESSAGE: " + message + ", IP: "
+ Integer.toHexString(ip) + ", STACK: " + stackStr;
}
}
public interface IParseEventHandler {
public void handleEvent(ParseEvent event);
}
public static final int REPORT_STRING = 0x1;
public static final int REPORT_IP = 0x2;
public static final int REPORT_EXIT_STRING = 0x3;
public static final int REPORT_LOAD_PROGRAM = 0x4;
public static final int REPORT_CALL_STACK = 0x5;
public static final int REPORT_RELOAD = 0x6;
//public static final int CLOSE = 0xffff;
private static final int MAX_BUFFER = 65536;
private byte[] buffer;
private int id;
private IParseEventHandler handler;
private SimpleQueue handlerQueue = new SimpleQueue(true);
private boolean dieAfterNextReportIp = false;
public EmulatorOutputParser(int id, IParseEventHandler handler) {
this.id = id;
this.handler = handler;
buffer = new byte[MAX_BUFFER];
}
public void parse(InputStream input) throws IOException {
try {
while (parseNext(input)) {
;
}
} catch (EOFException e) {
// Ok.
}
}
private boolean parseNext(InputStream input) throws IOException {
int opcode = readInt(input);
int size = readInt(input);
if (size > MAX_BUFFER) {
throw new IOException("Invalid emulator info");
}
switch (opcode) {
case REPORT_STRING:
case REPORT_EXIT_STRING:
/* It is important to read the number of bytes specified in
* the message. Otherwise, the pipe that we read from will
* be blocked by the remaining data and cause the writer to hang.
*/
readAllBytes(input, buffer, size);
CoreMoSyncPlugin.getDefault().getEmulatorProcessManager().dataStreamed(id, buffer, 0, size);
String message = new String(buffer, 0, size, "UTF8");
handle(new ParseEvent(opcode, message, -1, null));
break;
case REPORT_IP:
int ip = readInt(input);
handle(new ParseEvent(REPORT_IP, ip));
break;
case REPORT_CALL_STACK:
int stackDepth = size >> 2;
int[] ips = new int[stackDepth];
for (int i = 0; i < ips.length; i++) {
ips[i] = readInt(input);
}
handle(new ParseEvent(REPORT_CALL_STACK, null, -1, ips));
case REPORT_LOAD_PROGRAM:
case REPORT_RELOAD:
handle(new ParseEvent(opcode));
}
if (opcode == REPORT_EXIT_STRING) {
dieAfterNextReportIp = true;
}
return opcode != REPORT_IP || !dieAfterNextReportIp;
}
private void handle(final ParseEvent event) {
final IParseEventHandler handler = this.handler;
if (handler != null) {
handlerQueue.execute(new Runnable() {
public void run() {
if (handler != null) {
handler.handleEvent(event);
}
}
});
}
}
public void awaitParseEventsToBeHandled(int timeout) {
handlerQueue.awaitShutdown(timeout);
}
/**
* Reads a single integer from the given input stream.
*
* Note: This method will always read 4 bytes from the
* input stream.
*
* @param input The input stream to read from.
*
* @return The integer that was read.
*
* @throws IOException
*/
private int readInt(InputStream input) throws IOException {
return Util.readInt(input);
}
/**
* Reads from the given input stream and blocks until the
* specified number of bytes has been read.
*
* @param input The input stream to read from.
* @param buffer The buffer that will contain the read data.
* @param bytesToRead The number of bytes to read.
*
* @throws IOException If an internal error occurs.
*/
private void readAllBytes(InputStream input, byte[] buffer, int bytesToRead)
throws IOException {
int bytesLeft = bytesToRead;
int offset = 0;
while(bytesLeft > 0) {
int bytesRead = input.read(buffer, offset, bytesLeft);
if(bytesRead > 0) {
bytesLeft -= bytesRead;
offset += bytesRead;
}
}
}
public static void main(String[] args) throws Exception {
// String hex =
// "0100000013000000456d756c61746f7220636f6e6e65637465642e0100000010000000457869742040204950203078363265310200000004000000e1620000"
// ;
String hex = "0100000013000000456d756c61746f7220636f6e6e65637465642e"
+ "0300000010000000557365722050616e69633a2022525422"
+ "010000000e0000004578697420402049502030783330"
+ "020000000400000030000000";
byte[] bytes = convertHex(hex);
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
EmulatorOutputParser self = new EmulatorOutputParser(0, null);
self.parse(bis);
}
private static byte[] convertHex(String hex) {
char[] chars = hex.toCharArray();
byte[] result = new byte[chars.length / 2];
for (int i = 0; i < result.length; i++) {
int value = Integer.parseInt("" + chars[2 * i] + chars[2 * i + 1],
16);
result[i] = (byte) value;
}
return result;
}
}