package com.openxc.messages.streamers;
import android.util.Log;
import com.google.common.base.CharMatcher;
import com.openxc.messages.UnrecognizedMessageTypeException;
import com.openxc.messages.VehicleMessage;
import com.openxc.messages.formatters.JsonFormatter;
/**
* A class to deserialize and serialize JSON-formatted vehicle messages from
* byte streams.
*
* The JsonStreamer wraps the JsonFormatter and handles messages delimiting.
* It uses a \u0000 delimiter as specified by the OpenXC message format.
*
* Unlike the JsonFormatter, the JsonStreamer is not stateless. It maintains
* an internal buffer of bytes so that if partial messages is received it can
* eventually receive an parse the entire thing.
*/
public class JsonStreamer extends VehicleMessageStreamer {
private static String TAG = "JsonStreamer";
private final static String DELIMITER = "\u0000";
private StringBuffer mBuffer = new StringBuffer();
/**
* Return true if the buffer *most likely* contains JSON (as opposed to a
* protobuf).
*/
public static boolean containsJson(String buffer) {
return CharMatcher.ASCII
// We need to allow the \u0000 delimiter for JSON messages, so we
// can't use the JAVA_ISO_CONTROL character set and must build the
// range manually (minus \u0000)
.and(CharMatcher.inRange('\u0001', '\u001f').negate())
.and(CharMatcher.inRange('\u007f', '\u009f').negate())
.and(CharMatcher.ASCII)
.matchesAllOf(buffer);
}
@Override
public VehicleMessage parseNextMessage() {
String line = readToDelimiter();
if(line != null) {
try {
return JsonFormatter.deserialize(line);
} catch(UnrecognizedMessageTypeException e) {
Log.w(TAG, "Unable to deserialize JSON", e);
}
}
return null;
}
@Override
public void receive(byte[] bytes, int length) {
super.receive(bytes, length);
// Creating a new String object for each message causes the
// GC to go a little crazy, but I don't see another obvious way
// of converting the byte[] to something the StringBuilder can
// accept (either char[] or String). See #151.
mBuffer.append(new String(bytes, 0, length));
}
@Override
public byte[] serializeForStream(VehicleMessage message) {
return (JsonFormatter.serialize(message) + DELIMITER).getBytes();
}
/**
* Parse the current byte buffer to find the next potential message.
*
* The first potential serialized message data in the buffer is removed and
* returned. Any delimiters at the start of the buffer will be cleared.
*
* @return A potential serialized JSON message or null if none found.
*/
private String readToDelimiter() {
String line = null;
while(line == null || line.isEmpty()) {
int delimiterIndex = mBuffer.indexOf(DELIMITER);
if(delimiterIndex != -1) {
line = mBuffer.substring(0, delimiterIndex);
mBuffer.delete(0, delimiterIndex + 1);
} else {
line = null;
break;
}
}
return line;
}
}