package org.yamcs.yarch; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.yamcs.YConfiguration; import org.yamcs.yarch.DataType._type; import com.google.common.collect.BiMap; import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.MessageLite; import com.google.protobuf.MessageLite.Builder; public class ColumnSerializerFactory { static YConfiguration config; static int maxBinaryLength=1048576; static Logger log=LoggerFactory.getLogger(ColumnSerializer.class.getName()); static final BooleanColumnSerializer BOOLEAN_CS = new BooleanColumnSerializer(); static final ByteColumnSerializer BYTE_CS = new ByteColumnSerializer(); static final ShortColumnSerializer SHORT_CS = new ShortColumnSerializer(); static final IntegerColumnSerializer INT_CS = new IntegerColumnSerializer(); static final LongColumnSerializer LONG_CS = new LongColumnSerializer(); static final DoubleColumnSerializer DOUBLE_CS = new DoubleColumnSerializer(); static final StringColumnSerializer STRING_CS = new StringColumnSerializer(); static final BinaryColumnSerializer BINARY_CS = new BinaryColumnSerializer(); static final ParameterValueColumnSerializer PARAMETER_VALUE_CS = new ParameterValueColumnSerializer(); static Map<String, ProtobufColumnSerializer> protoSerialziers = new HashMap<>(); static { config=YConfiguration.getConfiguration("yamcs"); if(config.containsKey("maxBinaryLength")) { maxBinaryLength=config.getInt("maxBinaryLength"); } } public static ColumnSerializer<?> getColumnSerializer(TableDefinition tblDef, ColumnDefinition cd) { DataType type = cd.getType(); if(type.val==_type.ENUM) { return new EnumColumnSerializer(tblDef, cd); } else if(type.val==_type.PROTOBUF) { return getProtobufSerializer(cd); } else { return getBasicColumnSerializer(cd.getType()); } } /** * returns a column serializer for basic types * @param type * @return */ @SuppressWarnings("incomplete-switch") public static ColumnSerializer<?> getBasicColumnSerializer(DataType type) { switch(type.val) { case BOOLEAN: return BOOLEAN_CS; case BYTE: return BYTE_CS; case SHORT: return SHORT_CS; case INT: return INT_CS; case DOUBLE: return DOUBLE_CS; case TIMESTAMP: return LONG_CS; case STRING: return STRING_CS; case BINARY: return BINARY_CS; case PARAMETER_VALUE: return PARAMETER_VALUE_CS; case LIST: case TUPLE: //TODO throw new UnsupportedOperationException("List and Tuple not implemented"); } throw new IllegalArgumentException("' "+type+" is not a basic type"); } static public ColumnSerializer<?> getProtobufSerializer(ColumnDefinition cd) { String className = ((ProtobufDataType)cd.getType()).getClassName(); synchronized(protoSerialziers) { ProtobufColumnSerializer pcs = protoSerialziers.get(className); if(pcs!=null) { return pcs; } Class<?> c; try { c = Class.forName(className); Method newBuilderMethod = c.getMethod("newBuilder"); pcs = new ProtobufColumnSerializer(newBuilderMethod); protoSerialziers.put(className, pcs); return pcs; } catch (ClassNotFoundException e) { throw new IllegalArgumentException("Cannot find class '"+className+"' required to deserialize column '"+cd.getName()+"'", e); } catch (NoSuchMethodException e) { throw new IllegalArgumentException("Class '"+className+"' required to deserialize column '"+cd.getName()+"' does not have a method newBuilder", e); } } } static abstract class AbstractColumnSerializer<T> implements ColumnSerializer<T> { int size; public AbstractColumnSerializer(int size) { this.size = size; } @Override public T fromByteArray(byte[] b, ColumnDefinition cd) throws IOException { DataInputStream dos = new DataInputStream(new ByteArrayInputStream(b)); return deserialize(dos, cd); } public byte[] toByteArray(T v) { try (ByteArrayOutputStream baos = new ByteArrayOutputStream(size)){ DataOutputStream dos = new DataOutputStream(baos); serialize(dos, v); return baos.toByteArray(); } catch (IOException e) { throw new RuntimeException("cannot serialize in memory?", e); } } } static class BooleanColumnSerializer implements ColumnSerializer<Boolean> { @Override public Boolean deserialize(DataInputStream stream, ColumnDefinition cd) throws IOException { return stream.readBoolean(); } @Override public void serialize(DataOutputStream stream, Boolean v) throws IOException { stream.writeBoolean((Boolean)v); } @Override public byte[] toByteArray(Boolean v) { boolean b = (Boolean)v; return new byte[]{(byte)(b?1:0)}; } @Override public Boolean fromByteArray(byte[] b, ColumnDefinition cd) throws IOException { return b[0]==1; } } static class ByteColumnSerializer implements ColumnSerializer<Byte> { @Override public Byte deserialize(DataInputStream stream, ColumnDefinition cd) throws IOException { return stream.readByte(); } @Override public void serialize(DataOutputStream stream, Byte v) throws IOException { stream.writeByte((Byte)v); } @Override public byte[] toByteArray(Byte v) { return new byte[]{v}; } @Override public Byte fromByteArray(byte[] b, ColumnDefinition cd) throws IOException { return b[0]; } } static class ShortColumnSerializer implements ColumnSerializer<Short> { @Override public Short deserialize(DataInputStream stream, ColumnDefinition cd) throws IOException { return stream.readShort(); } @Override public void serialize(DataOutputStream stream, Short v) throws IOException { stream.writeShort((Short)v); } @Override public byte[] toByteArray(Short v) { short s = v; return new byte[] { (byte)((s>>8)&0xFF), (byte) (s&0xFF)}; } @Override public Short fromByteArray(byte[] b, ColumnDefinition cd) throws IOException { return (short)(((b[0]&0xFF)<<8) + (b[1]&0xFF)); } } static class IntegerColumnSerializer implements ColumnSerializer<Integer> { @Override public Integer deserialize(DataInputStream stream, ColumnDefinition cd) throws IOException { return stream.readInt(); } @Override public void serialize(DataOutputStream stream, Integer v) throws IOException { stream.writeInt((Integer)v); } @Override public byte[] toByteArray(Integer v) { int x = v; return new byte[] { (byte)((x>>24)&0xFF), (byte)((x>>16)&0xFF), (byte)((x>>8)&0xFF), (byte) (x&0xFF)}; } @Override public Integer fromByteArray(byte[] b, ColumnDefinition cd) throws IOException { return (b[0]<<24) + (b[1]<<16) + (b[2]<<8) +b[3]; } } static class DoubleColumnSerializer extends AbstractColumnSerializer<Double> { public DoubleColumnSerializer() { super(8); } @Override public Double deserialize(DataInputStream stream, ColumnDefinition cd) throws IOException { return stream.readDouble(); } @Override public void serialize(DataOutputStream stream, Double v) throws IOException { stream.writeDouble(v); } } static class LongColumnSerializer extends AbstractColumnSerializer<Long> { public LongColumnSerializer() { super(8); } @Override public Long deserialize(DataInputStream stream, ColumnDefinition cd) throws IOException { return stream.readLong(); } @Override public void serialize(DataOutputStream stream, Long v) throws IOException { stream.writeLong(v); } } static class StringColumnSerializer extends AbstractColumnSerializer<String> { public StringColumnSerializer() { super(32); } @Override public String deserialize(DataInputStream stream, ColumnDefinition cd) throws IOException { return stream.readUTF(); } @Override public void serialize(DataOutputStream stream, String v) throws IOException { stream.writeUTF(v); } } static class BinaryColumnSerializer implements ColumnSerializer<byte[]> { @Override public byte[] deserialize(DataInputStream stream, ColumnDefinition cd) throws IOException { int length=stream.readInt(); if(length>maxBinaryLength) { log.warn("binary length greater than maxBinaryLenght (is the endianess wrong?): ?>?", length, maxBinaryLength); return null; } byte[] bp = new byte[length]; stream.readFully(bp); return bp; } @Override public void serialize(DataOutputStream stream, byte[] v) throws IOException { byte[]va=(byte[])v; stream.writeInt(va.length); stream.write(va); } @Override public byte[] toByteArray(byte[] v) { return v; } @Override public byte[] fromByteArray(byte[] b, ColumnDefinition cd) throws IOException { return b; } } static class ProtobufColumnSerializer extends AbstractColumnSerializer<MessageLite> { //for columns of type PROTOBUF private final Method newBuilderMethod; public ProtobufColumnSerializer(Method newBuilderMethod) { super(32); this.newBuilderMethod = newBuilderMethod; } @Override public MessageLite deserialize(DataInputStream stream, ColumnDefinition cd) throws IOException { int length = stream.readInt(); if(length>maxBinaryLength) { log.warn("binary length greater than maxBinaryLenght (is the endianess wrong?): ?>?", length, maxBinaryLength); throw new IOException("binary length greater than maxBinaryLength"); } byte[] bp = new byte[length]; stream.readFully(bp); return readProtobufMessage(bp); } @Override public void serialize(DataOutputStream stream, MessageLite v) throws IOException { byte[] b = v.toByteArray(); stream.writeInt(b.length); stream.write(b); } private MessageLite readProtobufMessage(byte[] bp) throws InvalidProtocolBufferException { try { Builder b = (Builder) newBuilderMethod.invoke(null); b.mergeFrom(bp); return b.build(); } catch (IllegalAccessException | InvocationTargetException e) { throw new IllegalStateException(e); } } } static class EnumColumnSerializer extends AbstractColumnSerializer<String> { private final TableDefinition tblDef; //for columns of type ENUM private volatile BiMap<String,Short> enumValues; private final String columnName; public EnumColumnSerializer(TableDefinition tblDef, ColumnDefinition cd) { super(2); this.tblDef = tblDef; this.columnName = cd.getName(); } @Override public String deserialize(DataInputStream stream, ColumnDefinition cd) throws IOException { short x=stream.readShort(); return enumValues.inverse().get(x); } @Override public void serialize(DataOutputStream stream, String v) throws IOException { Short v1; if((enumValues==null) || (v1=enumValues.get(v))==null) { tblDef.addEnumValue(this, v); serialize(stream, v); return; } stream.writeShort(v1); } void setEnumValues(BiMap<String,Short> enumValues) { this.enumValues=enumValues; } public String getColumnName() { return columnName; } } }