blob: 420253586b8f2aea2c0cdafb4a32a94915553c24 [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package io.v.v23.vom;
import io.v.v23.vdl.AbstractVdlStruct;
import io.v.v23.vdl.Kind;
import io.v.v23.vdl.NativeTypes;
import io.v.v23.vdl.Types;
import io.v.v23.vdl.VdlAny;
import io.v.v23.vdl.VdlArray;
import io.v.v23.vdl.VdlBool;
import io.v.v23.vdl.VdlByte;
import io.v.v23.vdl.VdlComplex128;
import io.v.v23.vdl.VdlComplex64;
import io.v.v23.vdl.VdlEnum;
import io.v.v23.vdl.VdlField;
import io.v.v23.vdl.VdlFloat32;
import io.v.v23.vdl.VdlFloat64;
import io.v.v23.vdl.VdlInt16;
import io.v.v23.vdl.VdlInt32;
import io.v.v23.vdl.VdlInt64;
import io.v.v23.vdl.VdlOptional;
import io.v.v23.vdl.VdlString;
import io.v.v23.vdl.VdlStruct;
import io.v.v23.vdl.VdlType;
import io.v.v23.vdl.VdlTypeObject;
import io.v.v23.vdl.VdlUint16;
import io.v.v23.vdl.VdlUint32;
import io.v.v23.vdl.VdlUint64;
import io.v.v23.vdl.VdlUnion;
import io.v.v23.vdl.VdlValue;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* BinaryEncoder writes VDL values to {@code OutputStream} in binary VOM format.
*/
public class BinaryEncoder {
private final EncodingStream valueBuffer;
private final EncodingStream typeBuffer;
private final OutputStream out;
private final Map<VdlType, TypeId> visitedTypes;
private TypeId nextTypeId;
private boolean binaryMagicByteWritten;
public BinaryEncoder(OutputStream out) {
this.valueBuffer = new EncodingStream();
this.typeBuffer = new EncodingStream();
this.out = out;
this.visitedTypes = new HashMap<VdlType, TypeId>();
this.nextTypeId = Constants.WIRE_ID_FIRST_USER_TYPE;
this.binaryMagicByteWritten = false;
}
/**
* Encodes a value into binary VOM format.
*
* @param type runtime VDL type of the value
* @param value the value to encode
* @throws IOException
*/
public void encodeValue(VdlType type, Object value) throws IOException {
if (!binaryMagicByteWritten) {
binaryMagicByteWritten = true;
out.write(BinaryUtil.BINARY_MAGIC_BYTE);
}
valueBuffer.reset();
TypeId typeId = getType(type);
writeValue(valueBuffer, value, type);
writeMessage(valueBuffer, typeId.getValue(), BinaryUtil.hasBinaryMsgLen(type));
}
/**
* Encodes a value into binary VOM format.
*
* @param type runtime {@code java.lang.reflectType} of the value
* @param value the value to encode
* @throws IOException
*/
public void encodeValue(Type type, Object value) throws IOException {
encodeValue(Types.getVdlTypeFromReflect(type), value);
}
/**
* Encodes a VDL value into binary VOM format.
*
* @param value the value to encode
* @throws IOException
*/
public void encodeValue(VdlValue value) throws IOException {
encodeValue(value.vdlType(), value);
}
private void writeMessage(ByteArrayOutputStream buffer, long messageId, boolean encodeLength)
throws IOException {
BinaryUtil.encodeInt(out, messageId);
if (encodeLength) {
BinaryUtil.encodeUint(out, buffer.size());
}
buffer.writeTo(out);
}
private TypeId getType(VdlType type) throws IOException {
TypeId typeId = BootstrapType.getBootstrapTypeId(type);
if (typeId != null) {
return typeId;
} else if (visitedTypes.containsKey(type)) {
return visitedTypes.get(type);
} else {
return encodeType(type);
}
}
private TypeId encodeType(VdlType type) throws IOException {
TypeId typeId = nextTypeId;
nextTypeId = new TypeId(nextTypeId.getValue() + 1);
visitedTypes.put(type, typeId);
WireType wireType = convertToWireType(type);
typeBuffer.reset();
writeValue(typeBuffer, wireType, wireType.vdlType());
writeMessage(typeBuffer, -typeId.getValue(), true);
return typeId;
}
private WireType convertToWireType(VdlType type) throws IOException {
switch (type.getKind()) {
case BOOL:
case BYTE:
case UINT16:
case UINT32:
case UINT64:
case INT16:
case INT32:
case INT64:
case FLOAT32:
case FLOAT64:
case COMPLEX64:
case COMPLEX128:
case STRING:
return new WireType.NamedT(new WireNamed(
type.getName(), getType(Types.primitiveTypeFromKind(type.getKind()))));
case ARRAY:
return new WireType.ArrayT(new WireArray(
type.getName(), getType(type.getElem()), new VdlUint64(type.getLength())));
case ENUM:
return new WireType.EnumT(new WireEnum(type.getName(), type.getLabels()));
case LIST:
return new WireType.ListT(new WireList(type.getName(), getType(type.getElem())));
case MAP:
return new WireType.MapT(new WireMap(
type.getName(), getType(type.getKey()), getType(type.getElem())));
case STRUCT:
case UNION:
List<WireField> wireFields = new ArrayList<WireField>();
for (VdlField field : type.getFields()) {
wireFields.add(new WireField(field.getName(), getType(field.getType())));
}
if (type.getKind() == Kind.UNION) {
return new WireType.UnionT(new WireUnion(type.getName(), wireFields));
} else {
return new WireType.StructT(new WireStruct(type.getName(), wireFields));
}
case SET:
return new WireType.SetT(new WireSet(type.getName(), getType(type.getKey())));
case OPTIONAL:
return new WireType.OptionalT(new WireOptional(
type.getName(), getType(type.getElem())));
default:
throw new RuntimeException("Unknown wiretype for kind: " + type.getKind());
}
}
/**
* Writes a value to output stream and returns true iff the value is non-zero.
* The returned value can be used skip encoding of zero fields in structs.
*/
private boolean writeValue(EncodingStream out, Object value, VdlType type) throws IOException {
if (value == null) {
value = VdlValue.zeroValue(type);
}
// Convert native value.
NativeTypes.Converter converter = Types.getNativeTypeConverter(value.getClass());
if (converter != null) {
VdlValue vdlValue = converter.vdlValueFromNative(value);
return writeValue(out, vdlValue, type);
}
switch (type.getKind()) {
case ANY:
return writeVdlAny(out, value);
case ARRAY:
return writeVdlArray(out, value);
case BOOL:
return writeVdlBool(out, value);
case BYTE:
return writeVdlByte(out, value);
case COMPLEX64:
case COMPLEX128:
return writeVdlComplex(out, value);
case ENUM:
return writeVdlEnum(out, value);
case FLOAT32:
case FLOAT64:
return writeVdlFloat(out, value);
case INT16:
case INT32:
case INT64:
return writeVdlInt(out, value);
case LIST:
return writeVdlList(out, value, type);
case MAP:
return writeVdlMap(out, value, type);
case UNION:
return writeVdlUnion(out, value);
case OPTIONAL:
return writeVdlOptional(out, value);
case SET:
return writeVdlSet(out, value, type);
case STRING:
return writeVdlString(out, value);
case STRUCT:
return writeVdlStruct(out, value, type);
case UINT16:
case UINT32:
case UINT64:
return writeVdlUint(out, value);
case TYPEOBJECT:
return writeVdlTypeObject(out, value);
default:
throw new RuntimeException("Unknown kind: " + type.getKind());
}
}
/**
* Writes a VDL any to output stream and returns true iff the value is non-zero.
*/
private boolean writeVdlAny(EncodingStream out, Object value) throws IOException {
expectClass(Kind.ANY, value, VdlAny.class);
VdlAny anyValue = (VdlAny) value;
Object elem = anyValue.getElem();
if (elem != null) {
BinaryUtil.encodeUint(out, getType(anyValue.getElemType()).getValue());
writeValue(out, elem, anyValue.getElemType());
return true;
} else {
writeVdlByte(out, Constants.WIRE_CTRL_NIL);
return false;
}
}
/**
* Writes a VDL array to output stream and returns true iff the value is non-zero.
*/
private boolean writeVdlArray(EncodingStream out, Object value) throws IOException {
expectClass(Kind.ARRAY, value, VdlArray.class);
VdlArray<?> arrayValue = (VdlArray<?>) value;
BinaryUtil.encodeUint(out, 0);
for (Object elem : arrayValue) {
writeValue(out, elem, arrayValue.vdlType().getElem());
}
return arrayValue.size() != 0;
}
/**
* Writes a VDL bool to output stream and returns true iff the value is non-zero.
*/
private boolean writeVdlBool(OutputStream out, Object value) throws IOException {
if (value instanceof VdlBool) {
return BinaryUtil.encodeBoolean(out, ((VdlBool) value).getValue());
} else if (value instanceof Boolean) {
return BinaryUtil.encodeBoolean(out, (Boolean) value);
} else {
throw new IOException("Unsupported VDL bool value (type " + value.getClass()
+ ", value " + value + ")");
}
}
/**
* Writes a VDL byte to output stream and returns true iff the value is non-zero.
*/
private boolean writeVdlByte(EncodingStream out, Object value) throws IOException {
byte byteValue;
if (value instanceof VdlByte) {
byteValue = ((VdlByte) value).getValue();
out.write(byteValue);
} else if (value instanceof Byte) {
byteValue = (Byte) value;
out.write(byteValue);
} else {
throw new IOException("Unsupported VDL byte value (type " + value.getClass()
+ ", value " + value + ")");
}
return byteValue != 0;
}
/**
* Writes a VDL complex to output stream and returns true iff the value is non-zero.
*/
private boolean writeVdlComplex(EncodingStream out, Object value) throws IOException {
if (value instanceof VdlComplex64) {
boolean isNonZero = BinaryUtil.encodeDouble(out, ((VdlComplex64) value).getReal());
isNonZero |= BinaryUtil.encodeDouble(out, ((VdlComplex64) value).getImag());
return isNonZero;
} else if (value instanceof VdlComplex128) {
boolean isNonZero = BinaryUtil.encodeDouble(out, ((VdlComplex128) value).getReal());
isNonZero |= BinaryUtil.encodeDouble(out, ((VdlComplex128) value).getImag());
return isNonZero;
} else {
throw new IOException("Unsupported VDL complex value (type " + value.getClass()
+ ", value " + value + ")");
}
}
/**
* Writes a VDL enum to output stream and returns true iff the value is non-zero.
*/
private boolean writeVdlEnum(EncodingStream out, Object value) throws IOException {
expectClass(Kind.ENUM, value, VdlEnum.class);
int ordinal = ((VdlEnum) value).ordinal();
BinaryUtil.encodeUint(out, ordinal);
return ordinal != 0;
}
/**
* Writes a VDL float to output stream and returns true iff the value is non-zero.
*/
private boolean writeVdlFloat(EncodingStream out, Object value) throws IOException {
if (value instanceof VdlFloat32) {
return BinaryUtil.encodeDouble(out, ((VdlFloat32) value).getValue());
} else if (value instanceof VdlFloat64) {
return BinaryUtil.encodeDouble(out, ((VdlFloat64) value).getValue());
} else if (value instanceof Float) {
return BinaryUtil.encodeDouble(out, (Float) value);
} else if (value instanceof Double){
return BinaryUtil.encodeDouble(out, (Double) value);
} else {
throw new IOException("Unsupported VDL float value (type " + value.getClass()
+ ", value " + value + ")");
}
}
/**
* Writes a VDL int to output stream and returns true iff the value is non-zero.
*/
private boolean writeVdlInt(EncodingStream out, Object value) throws IOException {
if (value instanceof VdlInt16) {
return BinaryUtil.encodeInt(out, ((VdlInt16) value).getValue());
} else if (value instanceof VdlInt32) {
return BinaryUtil.encodeInt(out, ((VdlInt32) value).getValue());
} else if (value instanceof VdlInt64) {
return BinaryUtil.encodeInt(out, ((VdlInt64) value).getValue());
} else if (value instanceof Short){
return BinaryUtil.encodeInt(out, (Short) value);
} else if (value instanceof Integer) {
return BinaryUtil.encodeInt(out, (Integer) value);
} else if (value instanceof Long) {
return BinaryUtil.encodeInt(out, (Long) value);
} else {
throw new IOException("Unsupported VDL int value (type " + value.getClass()
+ ", value " + value + ")");
}
}
/**
* Writes a VDL list to output stream and returns true iff the value is non-zero.
*/
private boolean writeVdlList(EncodingStream out, Object value, VdlType type)
throws IOException {
if (value instanceof List) {
List<?> listValue = (List<?>) value;
BinaryUtil.encodeUint(out, listValue.size());
for (Object elem : listValue) {
writeValue(out, elem, type.getElem());
}
return listValue.size() != 0;
} else if (value.getClass().isArray()) {
Object arrayValue = value;
int len = Array.getLength(arrayValue);
BinaryUtil.encodeUint(out, len);
for (int i = 0; i < len; i++) {
writeValue(out, Array.get(arrayValue, i), type.getElem());
}
return len != 0;
} else {
throw new IOException("Unsupported VDL list value (type " + value.getClass()
+ ", value " + value + ")");
}
}
/**
* Writes a VDL map to output stream and returns true iff the value is non-zero.
*/
private boolean writeVdlMap(EncodingStream out, Object value, VdlType type) throws IOException {
expectClass(Kind.MAP, value, Map.class);
Map<?, ?> mapValue = (Map<?, ?>) value;
BinaryUtil.encodeUint(out, mapValue.size());
for (Map.Entry<?, ?> entry : mapValue.entrySet()) {
writeValue(out, entry.getKey(), type.getKey());
writeValue(out, entry.getValue(), type.getElem());
}
return mapValue.size() != 0;
}
/**
* Writes a VDL union to output stream and returns true iff the value is non-zero.
*/
private boolean writeVdlUnion(EncodingStream out, Object value) throws IOException {
expectClass(Kind.UNION, value, VdlUnion.class);
VdlUnion unionValue = (VdlUnion) value;
Object elem = unionValue.getElem();
int index = unionValue.getIndex();
VdlType elemType = unionValue.vdlType().getFields().get(index).getType();
BinaryUtil.encodeUint(out, index);
boolean isNonZero = index != 0;
isNonZero |= writeValue(out, elem, elemType);
return isNonZero;
}
/**
* Writes a VDL optional to output stream and returns true iff the value is non-zero.
*/
private boolean writeVdlOptional(EncodingStream out, Object value) throws IOException {
expectClass(Kind.OPTIONAL, value, VdlOptional.class);
VdlOptional<?> optionalValue = (VdlOptional<?>) value;
if (optionalValue.isNull()) {
writeVdlByte(out, Constants.WIRE_CTRL_NIL);
return false;
} else {
writeValue(out, optionalValue.getElem(), optionalValue.vdlType().getElem());
return true;
}
}
/**
* Writes a VDL set to output stream and returns true iff the value is non-zero.
*/
private boolean writeVdlSet(EncodingStream out, Object value, VdlType type) throws IOException {
expectClass(Kind.SET, value, Set.class);
Set<?> setValue = (Set<?>) value;
BinaryUtil.encodeUint(out, setValue.size());
for (Object key : setValue) {
writeValue(out, key, type.getKey());
}
return setValue.size() != 0;
}
/**
* Writes a VDL string to output stream and returns true iff the value is non-zero.
*/
private boolean writeVdlString(EncodingStream out, Object value) throws IOException {
String stringValue;
if (value instanceof VdlString) {
stringValue = ((VdlString) value).getValue();
} else if (value instanceof String ){
stringValue = (String) value;
} else {
throw new IOException("Unsupported VDL string value (type " + value.getClass()
+ ", value " + value + ")");
}
BinaryUtil.encodeBytes(out, BinaryUtil.getBytes(stringValue));
return stringValue.length() != 0;
}
/**
* Writes a VDL struct to output stream and returns true iff the value is non-zero.
*/
private boolean writeVdlStruct(EncodingStream out, Object value, VdlType type) throws IOException {
List<VdlField> fields = type.getFields();
boolean hasNonZeroField = false;
for (int i = 0; i < fields.size(); i++) {
VdlField field = fields.get(i);
Object fieldValue = null;
if (value instanceof VdlStruct) {
fieldValue = ((VdlStruct) value).getField(field.getName());
} else {
try {
Field f = value.getClass().getDeclaredField(
BinaryUtil.firstCharToLower(field.getName()));
f.setAccessible(true);
fieldValue = f.get(value);
} catch (Exception e) {
throw new IOException("Unsupported VDL struct value (type " + value.getClass()
+ ", value " + value + ")", e);
}
}
int prevCount = out.getCount();
BinaryUtil.encodeUint(out, i);
if (writeValue(out, fieldValue, field.getType())) {
hasNonZeroField = true;
} else {
// Roll back writing of a zero value.
out.setCount(prevCount);
}
}
writeVdlByte(out, Constants.WIRE_CTRL_END);
return hasNonZeroField;
}
/**
* Writes a VDL uint to output stream and returns true iff the value is non-zero.
*/
private boolean writeVdlUint(EncodingStream out, Object value) throws IOException {
if (value instanceof VdlUint16) {
return BinaryUtil.encodeUint(out, ((VdlUint16) value).getValue());
} else if (value instanceof VdlUint32) {
return BinaryUtil.encodeUint(out, ((VdlUint32) value).getValue());
} else if (value instanceof VdlUint64) {
return BinaryUtil.encodeUint(out, ((VdlUint64) value).getValue());
} else {
throw new IOException("Unsupported VDL uint value (type " + value.getClass()
+ ", value " + value + ")");
}
}
/**
* Writes a VDL typeObject to output stream and returns true iff the value is non-zero.
*/
private boolean writeVdlTypeObject(EncodingStream out, Object object) throws IOException {
expectClass(Kind.TYPEOBJECT, object, VdlTypeObject.class);
VdlTypeObject value = (VdlTypeObject) object;
BinaryUtil.encodeUint(out, getType(value.getTypeObject()).getValue());
return value.getTypeObject() != Types.ANY;
}
private void expectClass(Kind kind, Object value, Class<?> klass) throws IOException {
if (!klass.isAssignableFrom(value.getClass())) {
throw new IOException("Unsupported VDL " + kind + " value (type " + value.getClass()
+ ", value " + value + ")");
}
}
}