JsonReader.java
package zserio.runtime.json;
import java.io.IOException;
import java.io.Reader;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
import zserio.runtime.ZserioError;
import zserio.runtime.creator.ZserioTreeCreator;
import zserio.runtime.io.BitBuffer;
import zserio.runtime.typeinfo.ItemInfo;
import zserio.runtime.typeinfo.JavaType;
import zserio.runtime.typeinfo.TypeInfo;
/**
* Reads zserio object tree defined by a type info from a text stream.
*/
public final class JsonReader implements AutoCloseable
{
/**
* Constructor.
*
* @param reader Text stream to read.
*/
public JsonReader(Reader reader)
{
this.reader = reader;
this.creatorAdapter = new CreatorAdapter();
this.parser = new JsonParser(reader, creatorAdapter);
}
@Override
public void close() throws IOException
{
reader.close();
}
/**
* Reads a zserio object tree defined by the given type info from the text steam.
*
* @param typeInfo Type info defining the expected zserio object tree.
* @param arguments Arguments of type defining the expected zserio object tree.
*
* @return Zserio object tree initialized using the JSON data.
*/
public Object read(TypeInfo typeInfo, Object... arguments)
{
creatorAdapter.setType(typeInfo, arguments);
try
{
parser.parse();
}
catch (JsonParserError excpt)
{
throw excpt;
}
catch (ZserioError excpt)
{
throw new ZserioError(
excpt.getMessage() + " (JsonParser:" + parser.getLine() + ":" + parser.getColumn() + ")");
}
return creatorAdapter.get();
}
/**
* Adapter for values which are encoded as a JSON object.
*/
private interface ObjectValueAdapter extends JsonParser.Observer
{
/**
* Gets the parsed value.
*/
Object get();
}
/**
* The adapter which allows to parse Bit Buffer object from JSON.
*/
private static class BitBufferAdapter implements ObjectValueAdapter
{
/**
* Constructor.
*/
public BitBufferAdapter()
{
state = State.VISIT_KEY;
buffer = null;
bitSize = null;
}
/**
* Gets the created Bit Buffer object.
*
* @return Parsed Bit Buffer object.
*/
@Override
public BitBuffer get()
{
if (buffer == null || bitSize == null)
throw new ZserioError("JsonReader: Unexpected end in Bit Buffer!");
final int byteSize = buffer.size();
final byte[] array = new byte[byteSize];
for (int i = 0; i < byteSize; ++i)
array[i] = buffer.get(i);
return new BitBuffer(array, bitSize);
}
@Override
public void beginObject()
{
throw new ZserioError("JsonReader: Unexpected begin object in Bit Buffer!");
}
@Override
public void endObject()
{
throw new ZserioError("JsonReader: Unexpected end object in Bit Buffer!");
}
@Override
public void beginArray()
{
if (state == State.BEGIN_ARRAY_BUFFER)
{
state = State.VISIT_VALUE_BUFFER;
buffer = new ArrayList<Byte>();
}
else
{
throw new ZserioError("JsonReader: Unexpected begin array in Bit Buffer!");
}
}
@Override
public void endArray()
{
if (state == State.VISIT_VALUE_BUFFER)
{
state = State.VISIT_KEY;
}
else
{
throw new ZserioError("JsonReader: Unexpected end array in Bit Buffer!");
}
}
@Override
public void visitKey(String key)
{
if (state == State.VISIT_KEY)
{
if (key.equals("buffer"))
state = State.BEGIN_ARRAY_BUFFER;
else if (key.equals("bitSize"))
state = State.VISIT_VALUE_BITSIZE;
else
throw new ZserioError("JsonReader: Unknown key '" + key + "' in Bit Buffer!");
}
else
{
throw new ZserioError("JsonReader: Unexpected key '" + key + "' in Bit Buffer!");
}
}
@Override
public void visitValue(Object value)
{
if (state == State.VISIT_VALUE_BUFFER && value instanceof BigInteger)
{
// bit buffer stores 8-bit unsigned values in byte type
final BigInteger intValue = (BigInteger)value;
if (intValue.compareTo(BigInteger.ZERO) < 0 || intValue.compareTo(BigInteger.valueOf(255)) > 0)
{
throw new ZserioError("JsonReader: Cannot create byte for Bit Buffer from value '" +
value.toString() + "'!");
}
buffer.add(((BigInteger)value).byteValue());
}
else if (state == State.VISIT_VALUE_BITSIZE && value instanceof BigInteger)
{
try
{
bitSize = ((BigInteger)value).longValueExact();
}
catch (ArithmeticException excpt)
{
throw new ZserioError("JsonReader: Cannot create long for Bit Buffer size from value '" +
value.toString() + "'!",
excpt);
}
state = State.VISIT_KEY;
}
else
{
throw new ZserioError("JsonReader: Unexpected value '" + value + "' in Bit Buffer!");
}
}
private enum State
{
VISIT_KEY,
BEGIN_ARRAY_BUFFER,
VISIT_VALUE_BUFFER,
VISIT_VALUE_BITSIZE
}
private State state;
private List<Byte> buffer;
private Long bitSize;
}
/**
* The adapter which allows to parse bytes object from JSON.
*/
private static class BytesAdapter implements ObjectValueAdapter
{
/**
* Constructor.
*/
public BytesAdapter()
{
state = State.VISIT_KEY;
buffer = null;
}
/**
* Gets the created bytes object.
*
* @return Parsed bytes object.
*/
@Override
public byte[] get()
{
if (buffer == null)
throw new ZserioError("JsonReader: Unexpected end in bytes!");
final byte[] bytes = new byte[buffer.size()];
for (int i = 0; i < bytes.length; ++i)
bytes[i] = buffer.get(i);
return bytes;
}
@Override
public void beginObject()
{
throw new ZserioError("JsonReader: Unexpected begin object in bytes!");
}
@Override
public void endObject()
{
throw new ZserioError("JsonReader: Unexpected end object in bytes!");
}
@Override
public void beginArray()
{
if (state == State.BEGIN_ARRAY_BUFFER)
{
state = State.VISIT_VALUE_BUFFER;
buffer = new ArrayList<Byte>();
}
else
{
throw new ZserioError("JsonReader: Unexpected begin array in bytes!");
}
}
@Override
public void endArray()
{
if (state == State.VISIT_VALUE_BUFFER)
{
state = State.VISIT_KEY;
}
else
{
throw new ZserioError("JsonReader: Unexpected end array in bytes!");
}
}
@Override
public void visitKey(String key)
{
if (state == State.VISIT_KEY)
{
if (key.equals("buffer"))
state = State.BEGIN_ARRAY_BUFFER;
else
throw new ZserioError("JsonReader: Unknown key '" + key + "' in bytes!");
}
else
{
throw new ZserioError("JsonReader: Unexpected key '" + key + "' in bytes!");
}
}
@Override
public void visitValue(Object value)
{
if (state == State.VISIT_VALUE_BUFFER && value instanceof BigInteger)
{
// bit buffer stores 8-bit unsigned values in byte type
final BigInteger intValue = (BigInteger)value;
if (intValue.compareTo(BigInteger.ZERO) < 0 || intValue.compareTo(BigInteger.valueOf(255)) > 0)
{
throw new ZserioError(
"JsonReader: Cannot create byte for bytes from value '" + value.toString() + "'!");
}
buffer.add(((BigInteger)value).byteValue());
}
else
{
throw new ZserioError("JsonReader: Unexpected value '" + value + "' in bytes!");
}
}
private enum State
{
VISIT_KEY,
BEGIN_ARRAY_BUFFER,
VISIT_VALUE_BUFFER,
}
private State state;
private List<Byte> buffer;
}
/**
* The adapter which allows to use ZserioTreeCreator as an JsonReader observer.
*/
private static class CreatorAdapter implements JsonParser.Observer
{
/**
* Constructor.
*/
public CreatorAdapter()
{
creator = null;
keyStack = new Stack<String>();
object = null;
objectValueAdapter = null;
}
/**
* Sets type which shall be created next. Resets the current object.
*
* @param typeInfo Type info of the type which is to be created.
* @param arguments Arguments of type defining the expected zserio object tree.
*/
public void setType(TypeInfo typeInfo, Object... arguments)
{
creator = new ZserioTreeCreator(typeInfo, arguments);
object = null;
}
/**
* Gets the created zserio object tree.
*
* @return Zserio object tree.
*/
public Object get()
{
if (object == null)
throw new ZserioError("JsonReader: Zserio tree not created!");
return object;
}
@Override
public void beginObject()
{
if (objectValueAdapter != null)
{
objectValueAdapter.beginObject();
}
else
{
if (creator == null)
throw new ZserioError("JsonReader: Adapter not initialized!");
if (keyStack.isEmpty())
{
creator.beginRoot();
}
else
{
final String lastKey = keyStack.peek();
if (!lastKey.isEmpty())
{
final JavaType javaType = creator.getFieldType(lastKey).getJavaType();
if (javaType == JavaType.BIT_BUFFER)
objectValueAdapter = new BitBufferAdapter();
else if (javaType == JavaType.BYTES)
objectValueAdapter = new BytesAdapter();
else
creator.beginCompound(lastKey);
}
else
{
final JavaType javaType = creator.getElementType().getJavaType();
if (javaType == JavaType.BIT_BUFFER)
objectValueAdapter = new BitBufferAdapter();
else if (javaType == JavaType.BYTES)
objectValueAdapter = new BytesAdapter();
else
creator.beginCompoundElement();
}
}
}
}
@Override
public void endObject()
{
if (objectValueAdapter != null)
{
final Object objectValue = objectValueAdapter.get();
objectValueAdapter = null;
visitValue(objectValue);
}
else
{
if (creator == null)
throw new ZserioError("JsonReader: Adapter not initialized!");
if (keyStack.isEmpty())
{
object = creator.endRoot();
creator = null;
}
else
{
final String lastKey = keyStack.peek();
if (!lastKey.isEmpty())
{
creator.endCompound();
keyStack.pop(); // finish member
}
else
{
creator.endCompoundElement();
}
}
}
}
@Override
public void beginArray()
{
if (objectValueAdapter != null)
{
objectValueAdapter.beginArray();
}
else
{
if (creator == null)
throw new ZserioError("JsonReader: Adapter not initialized!");
if (keyStack.isEmpty())
throw new ZserioError("JsonReader: ZserioTreeCreator expects json object!");
creator.beginArray(keyStack.peek());
keyStack.push("");
}
}
@Override
public void endArray()
{
if (objectValueAdapter != null)
{
objectValueAdapter.endArray();
}
else
{
if (creator == null)
throw new ZserioError("JsonReader: Adapter not initialized!");
creator.endArray();
keyStack.pop(); // finish array
keyStack.pop(); // finish member
}
}
@Override
public void visitKey(String key)
{
if (objectValueAdapter != null)
{
objectValueAdapter.visitKey(key);
}
else
{
if (creator == null)
throw new ZserioError("JsonReader: Adapter not initialized!");
keyStack.push(key);
}
}
@Override
public void visitValue(Object value)
{
if (objectValueAdapter != null)
{
objectValueAdapter.visitValue(value);
}
else
{
if (creator == null)
throw new ZserioError("JsonReader: Adapter not initialized!");
if (keyStack.isEmpty())
throw new ZserioError("JsonReader: ZserioTreeCreator expects json object!");
final String lastKey = keyStack.peek();
if (!lastKey.isEmpty())
{
final TypeInfo expectedTypeInfo = creator.getFieldType(lastKey);
creator.setValue(lastKey, convertValue(value, expectedTypeInfo));
keyStack.pop(); // finish member
}
else
{
final TypeInfo expectedTypeInfo = creator.getElementType();
creator.addValueElement(convertValue(value, expectedTypeInfo));
}
}
}
private static Object convertValue(Object value, TypeInfo typeInfo)
{
if (value == null)
return null;
final JavaType expectedJavaType = typeInfo.getJavaType();
switch (expectedJavaType)
{
case ENUM:
if (value instanceof String)
return createEnum((String)value, typeInfo);
if (value instanceof BigInteger)
return createEnum((BigInteger)value, typeInfo);
break;
case BITMASK:
if (value instanceof String)
return createBitmask((String)value, typeInfo);
if (value instanceof BigInteger)
return createBitmask((BigInteger)value, typeInfo);
break;
case FLOAT:
if (value instanceof Double)
return ((Double)value).floatValue();
break;
default:
if (value instanceof BigInteger)
return convertNumber((BigInteger)value, typeInfo, expectedJavaType);
break;
}
// possible type mismatch => just leave it to creator, it will check and report better message
return value;
}
private static Object createEnum(String stringValue, TypeInfo typeInfo)
{
if (!stringValue.isEmpty())
{
final char firstChar = stringValue.charAt(0);
if ((firstChar >= 'A' && firstChar <= 'Z') || (firstChar >= 'a' && firstChar <= 'z') ||
firstChar == '_')
{
final BigInteger value = parseEnumStringValue(stringValue, typeInfo);
if (value != null)
return createEnum(value, typeInfo);
}
// else it's a no match
}
throw new ZserioError("JsonReader: Cannot create enum '" + typeInfo.getSchemaName() +
"' from string value '" + stringValue + "'!");
}
private static Object createEnum(BigInteger value, TypeInfo typeInfo)
{
try
{
final Class<?> enumClass = typeInfo.getJavaClass();
final TypeInfo enumUnderlyingTypeInfo = typeInfo.getUnderlyingType();
final Class<?> enumUnderlyingClass = enumUnderlyingTypeInfo.getJavaClass();
final Method toEnum = enumClass.getMethod("toEnum", enumUnderlyingClass);
final JavaType enumUnderlyingJavaType = enumUnderlyingTypeInfo.getJavaType();
return toEnum.invoke(null, convertNumber(value, typeInfo, enumUnderlyingJavaType));
}
catch (ClassCastException | SecurityException | IllegalAccessException | IllegalArgumentException |
InvocationTargetException | NoSuchMethodException excpt)
{
throw new ZserioError("JsonReader: Cannot create enum '" + typeInfo.getSchemaName() +
"' from value '" + value.toString() + "'!",
excpt);
}
}
private static Object createBitmask(String stringValue, TypeInfo typeInfo)
{
if (!stringValue.isEmpty())
{
final char firstChar = stringValue.charAt(0);
if ((firstChar >= 'A' && firstChar <= 'Z') || (firstChar >= 'a' && firstChar <= 'z') ||
firstChar == '_')
{
final BigInteger value = parseBitmaskStringValue(stringValue, typeInfo);
if (value != null)
return createBitmask(value, typeInfo);
}
else if (firstChar >= '0' && firstChar <= '9') // bitmask can be only unsigned
{
final BigInteger value = parseBitmaskNumericStringValue(stringValue);
if (value != null)
return createBitmask(value, typeInfo);
}
}
throw new ZserioError("JsonReader: Cannot create bitmask '" + typeInfo.getSchemaName() +
"' from string value '" + stringValue + "'!");
}
private static Object createBitmask(BigInteger value, TypeInfo typeInfo)
{
try
{
final Class<?> bitmaskClass = typeInfo.getJavaClass();
final TypeInfo bitmaskUnderlyingTypeInfo = typeInfo.getUnderlyingType();
final Class<?> bitmaskUnderlyingClass = bitmaskUnderlyingTypeInfo.getJavaClass();
final Class<?>[] parameterType = new Class<?>[] {bitmaskUnderlyingClass};
final Constructor<?> constructor = bitmaskClass.getConstructor(parameterType);
final JavaType bitmaskUnderlyingJavaType = bitmaskUnderlyingTypeInfo.getJavaType();
return constructor.newInstance(convertNumber(value, typeInfo, bitmaskUnderlyingJavaType));
}
catch (ClassCastException | InstantiationException | SecurityException | IllegalAccessException |
IllegalArgumentException | InvocationTargetException | NoSuchMethodException excpt)
{
throw new ZserioError("JsonReader: Cannot create bitmask '" + typeInfo.getSchemaName() +
"' from value '" + value.toString() + "'!",
excpt);
}
}
private static BigInteger parseEnumStringValue(String stringValue, TypeInfo typeInfo)
{
for (ItemInfo itemInfo : typeInfo.getEnumItems())
{
if (stringValue.equals(itemInfo.getSchemaName()))
return itemInfo.getValue();
}
return null;
}
private static BigInteger parseBitmaskStringValue(String stringValue, TypeInfo typeInfo)
{
BigInteger value = BigInteger.ZERO;
final String[] identifiers = stringValue.split("\\|");
for (String identifierWithSpaces : identifiers)
{
final String identifier = identifierWithSpaces.trim();
boolean match = false;
for (ItemInfo itemInfo : typeInfo.getBitmaskValues())
{
if (identifier.equals(itemInfo.getSchemaName()))
{
match = true;
value = value.or(itemInfo.getValue());
break;
}
}
if (!match)
return null;
}
return value;
}
private static BigInteger parseBitmaskNumericStringValue(String stringValue)
{
int numberLen = 1;
while (stringValue.charAt(numberLen) >= '0' && stringValue.charAt(numberLen) <= '9')
numberLen++;
try
{
return new BigInteger(stringValue.substring(0, numberLen));
}
catch (NumberFormatException excpt)
{
return null;
}
}
private static Object convertNumber(BigInteger value, TypeInfo typeInfo, JavaType expectedJavaType)
{
switch (expectedJavaType)
{
case BYTE:
return createByte(value, typeInfo);
case SHORT:
return createShort(value, typeInfo);
case INT:
return createInt(value, typeInfo);
case LONG:
return createLong(value, typeInfo);
default:
return value;
}
}
private static byte createByte(BigInteger value, TypeInfo typeInfo)
{
try
{
return value.byteValueExact();
}
catch (ArithmeticException excpt)
{
throw new ZserioError("JsonReader: Cannot create byte '" + typeInfo.getSchemaName() +
"' from value '" + value.toString() + "'!",
excpt);
}
}
private static short createShort(BigInteger value, TypeInfo typeInfo)
{
try
{
return value.shortValueExact();
}
catch (ArithmeticException excpt)
{
throw new ZserioError("JsonReader: Cannot create short '" + typeInfo.getSchemaName() +
"' from value '" + value.toString() + "'!",
excpt);
}
}
private static int createInt(BigInteger value, TypeInfo typeInfo)
{
try
{
return value.intValueExact();
}
catch (ArithmeticException excpt)
{
throw new ZserioError("JsonReader: Cannot create int '" + typeInfo.getSchemaName() +
"' from value '" + value.toString() + "'!",
excpt);
}
}
private static long createLong(BigInteger value, TypeInfo typeInfo)
{
try
{
return value.longValueExact();
}
catch (ArithmeticException excpt)
{
throw new ZserioError("JsonReader: Cannot create long '" + typeInfo.getSchemaName() +
"' from value '" + value.toString() + "'!",
excpt);
}
}
private ZserioTreeCreator creator;
private final Stack<String> keyStack;
private Object object;
private ObjectValueAdapter objectValueAdapter;
}
private final Reader reader;
private final CreatorAdapter creatorAdapter;
private final JsonParser parser;
}