| // 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 + ")"); |
| } |
| } |
| } |