/* Copyright (c) 2014 Boundless and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Distribution License v1.0 * which accompanies this distribution, and is available at * https://www.eclipse.org/org/documents/edl-v10.html * * Contributors: * Gabriel Roldan (Boundless) - initial implementation */ package org.locationtech.geogig.storage.datastream; import static org.locationtech.geogig.storage.datastream.Varint.readSignedVarInt; import static org.locationtech.geogig.storage.datastream.Varint.readSignedVarLong; import static org.locationtech.geogig.storage.datastream.Varint.readUnsignedVarInt; import static org.locationtech.geogig.storage.datastream.Varint.writeSignedVarInt; import static org.locationtech.geogig.storage.datastream.Varint.writeSignedVarLong; import static org.locationtech.geogig.storage.datastream.Varint.writeUnsignedVarInt; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Date; import java.util.EnumMap; import java.util.Map; import org.locationtech.geogig.storage.FieldType; import com.google.common.base.Optional; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.io.ParseException; import com.vividsolutions.jts.io.WKBReader; import com.vividsolutions.jts.io.WKBWriter; /** * A class to serializer/deserialize attribute values to/from a data stream * */ class DataStreamValueSerializerV2 { public static interface ValueSerializer { public Object read(DataInput in) throws IOException; public void write(Object obj, DataOutput out) throws IOException; } static final Map<FieldType, ValueSerializer> serializers = new EnumMap<>(FieldType.class); static { serializers.put(FieldType.NULL, new ValueSerializer() { @Override public Object read(DataInput in) throws IOException { return null; } @Override public void write(Object obj, DataOutput out) { // NO-OP: There is no body for a NULL field } }); serializers.put(FieldType.BOOLEAN, new ValueSerializer() { @Override public Object read(DataInput in) throws IOException { return in.readBoolean(); } @Override public void write(Object field, DataOutput data) throws IOException { data.writeBoolean((Boolean) field); } }); serializers.put(FieldType.BYTE, new ValueSerializer() { @Override public Object read(DataInput in) throws IOException { return in.readByte(); } @Override public void write(Object field, DataOutput data) throws IOException { data.writeByte((Byte) field); } }); serializers.put(FieldType.SHORT, new ValueSerializer() { @Override public Object read(DataInput in) throws IOException { return Short.valueOf((short) readSignedVarInt(in)); } @Override public void write(Object field, DataOutput data) throws IOException { writeSignedVarInt(((Number) field).intValue(), data); } }); serializers.put(FieldType.INTEGER, new ValueSerializer() { @Override public Object read(DataInput in) throws IOException { return Integer.valueOf(readSignedVarInt(in)); } @Override public void write(Object field, DataOutput data) throws IOException { writeSignedVarInt(((Number) field).intValue(), data); } }); serializers.put(FieldType.LONG, new ValueSerializer() { @Override public Object read(DataInput in) throws IOException { return Long.valueOf(readSignedVarLong(in)); } @Override public void write(Object field, DataOutput data) throws IOException { writeSignedVarLong(((Number) field).longValue(), data); } }); serializers.put(FieldType.FLOAT, new ValueSerializer() { @Override public Object read(DataInput in) throws IOException { return in.readFloat(); } @Override public void write(Object field, DataOutput data) throws IOException { data.writeFloat((Float) field); } }); serializers.put(FieldType.DOUBLE, new ValueSerializer() { @Override public Object read(DataInput in) throws IOException { return in.readDouble(); } @Override public void write(Object field, DataOutput data) throws IOException { data.writeDouble((Double) field); } }); serializers.put(FieldType.STRING, new ValueSerializer() { @Override public Object read(DataInput in) throws IOException { return in.readUTF(); } @Override public void write(Object field, DataOutput data) throws IOException { data.writeUTF((String) field); } }); serializers.put(FieldType.BOOLEAN_ARRAY, new ValueSerializer() { @Override public Object read(DataInput in) throws IOException { final int len = readUnsignedVarInt(in); byte[] packed = new byte[(len + 7) / 8]; // we want to round up as long as i % 8 != // 0 boolean[] bits = new boolean[len]; int offset = 0; int remainingBits = len; while (remainingBits > 8) { byte chunk = packed[offset / 8]; for (int i = 0; i < 8; i++) { bits[offset + i] = (chunk & (128 >> i)) != 0; } offset += 8; remainingBits -= 8; } if (remainingBits > 0) { byte chunk = packed[packed.length - 1]; int bitN = 0; while (remainingBits > 0) { bits[offset + bitN] = (chunk & (128 >> bitN)) != 0; remainingBits -= 1; bitN += 1; } } return bits; } @Override public void write(Object field, DataOutput data) throws IOException { boolean[] bools = (boolean[]) field; byte[] bytes = new byte[(bools.length + 7) / 8]; int index = 0; while (index < bytes.length) { int bIndex = index * 8; int chunk = 0; int bitsInChunk = Math.min(bools.length - bIndex, 8); for (int i = 0; i < bitsInChunk; i++) { chunk |= (bools[bIndex + i] ? 0 : 1) << (7 - i); } bytes[index] = (byte) chunk; } writeUnsignedVarInt(bools.length, data); data.write(bytes); } }); final ValueSerializer byteArray = new ValueSerializer() { @Override public Object read(DataInput in) throws IOException { final int len = readUnsignedVarInt(in); byte[] bytes = new byte[len]; in.readFully(bytes); return bytes; } @Override public void write(Object field, DataOutput data) throws IOException { writeUnsignedVarInt(((byte[]) field).length, data); data.write((byte[]) field); } }; serializers.put(FieldType.BYTE_ARRAY, byteArray); serializers.put(FieldType.SHORT_ARRAY, new ValueSerializer() { @Override public Object read(DataInput in) throws IOException { final int len = readUnsignedVarInt(in); short[] shorts = new short[len]; for (int i = 0; i < len; i++) { shorts[i] = in.readShort(); } return shorts; } @Override public void write(Object field, DataOutput data) throws IOException { writeUnsignedVarInt(((short[]) field).length, data); for (short s : (short[]) field) data.writeShort(s); } }); serializers.put(FieldType.INTEGER_ARRAY, new ValueSerializer() { @Override public Object read(DataInput in) throws IOException { final int len = readUnsignedVarInt(in); int[] ints = new int[len]; for (int i = 0; i < len; i++) { ints[i] = in.readInt(); } return ints; } @Override public void write(Object field, DataOutput data) throws IOException { writeUnsignedVarInt(((int[]) field).length, data); for (int i : (int[]) field) data.writeInt(i); } }); serializers.put(FieldType.LONG_ARRAY, new ValueSerializer() { @Override public Object read(DataInput in) throws IOException { final int len = readUnsignedVarInt(in); long[] longs = new long[len]; for (int i = 0; i < len; i++) { longs[i] = in.readLong(); } return longs; } @Override public void write(Object field, DataOutput data) throws IOException { writeUnsignedVarInt(((long[]) field).length, data); for (long l : (long[]) field) data.writeLong(l); } }); serializers.put(FieldType.FLOAT_ARRAY, new ValueSerializer() { @Override public Object read(DataInput in) throws IOException { final int len = readUnsignedVarInt(in); float[] floats = new float[len]; for (int i = 0; i < len; i++) { floats[i] = in.readFloat(); } return floats; } @Override public void write(Object field, DataOutput data) throws IOException { writeUnsignedVarInt(((float[]) field).length, data); for (float f : (float[]) field) data.writeFloat(f); } }); serializers.put(FieldType.DOUBLE_ARRAY, new ValueSerializer() { @Override public Object read(DataInput in) throws IOException { final int len = readUnsignedVarInt(in); double[] doubles = new double[len]; for (int i = 0; i < len; i++) { doubles[i] = in.readDouble(); } return doubles; } @Override public void write(Object field, DataOutput data) throws IOException { writeUnsignedVarInt(((double[]) field).length, data); for (double d : (double[]) field) data.writeDouble(d); } }); serializers.put(FieldType.STRING_ARRAY, new ValueSerializer() { @Override public Object read(DataInput in) throws IOException { final int len = readUnsignedVarInt(in); String[] strings = new String[len]; for (int i = 0; i < len; i++) { strings[i] = in.readUTF(); } return strings; } @Override public void write(Object field, DataOutput data) throws IOException { writeUnsignedVarInt(((String[]) field).length, data); for (String s : (String[]) field) data.writeUTF(s); } }); ValueSerializer geometry = new ValueSerializer() { @Override public Object read(DataInput in) throws IOException { byte[] bytes = (byte[]) byteArray.read(in); WKBReader wkbReader = new WKBReader(); try { return wkbReader.read(bytes); } catch (ParseException e) { throw new RuntimeException(e); } } @Override public void write(Object field, DataOutput data) throws IOException { WKBWriter wkbWriter = new WKBWriter(); byte[] bytes = wkbWriter.write((Geometry) field); byteArray.write(bytes, data); } }; // ValueSerializer geometry = new GeometrySerializer(); serializers.put(FieldType.GEOMETRY, geometry); serializers.put(FieldType.POINT, geometry); serializers.put(FieldType.LINESTRING, geometry); serializers.put(FieldType.POLYGON, geometry); serializers.put(FieldType.MULTIPOINT, geometry); serializers.put(FieldType.MULTILINESTRING, geometry); serializers.put(FieldType.MULTIPOLYGON, geometry); serializers.put(FieldType.GEOMETRYCOLLECTION, geometry); serializers.put(FieldType.UUID, new ValueSerializer() { @Override public Object read(DataInput in) throws IOException { long upper = readSignedVarLong(in); long lower = readSignedVarLong(in); return new java.util.UUID(upper, lower); } @Override public void write(Object field, DataOutput data) throws IOException { writeSignedVarLong(((java.util.UUID) field).getMostSignificantBits(), data); writeSignedVarLong(((java.util.UUID) field).getLeastSignificantBits(), data); } }); final ValueSerializer bigInteger = new ValueSerializer() { @Override public Object read(DataInput in) throws IOException { byte[] bytes = (byte[]) byteArray.read(in); return new BigInteger(bytes); } @Override public void write(Object field, DataOutput data) throws IOException { byte[] bytes = ((BigInteger) field).toByteArray(); byteArray.write(bytes, data); } }; serializers.put(FieldType.BIG_INTEGER, bigInteger); serializers.put(FieldType.BIG_DECIMAL, new ValueSerializer() { @Override public Object read(DataInput in) throws IOException { int scale = in.readInt(); BigInteger intValue = (BigInteger) bigInteger.read(in); BigDecimal decValue = new BigDecimal(intValue, scale); return decValue; } @Override public void write(Object field, DataOutput data) throws IOException { BigDecimal d = (BigDecimal) field; int scale = d.scale(); BigInteger i = d.unscaledValue(); data.writeInt(scale); bigInteger.write(i, data); } }); serializers.put(FieldType.DATETIME, new ValueSerializer() { @Override public Object read(DataInput in) throws IOException { long time = in.readLong(); return new Date(time); } @Override public void write(Object field, DataOutput data) throws IOException { Date date = (Date) field; data.writeLong(date.getTime()); } }); serializers.put(FieldType.DATE, new ValueSerializer() { @Override public Object read(DataInput in) throws IOException { long time = in.readLong(); return new java.sql.Date(time); } @Override public void write(Object field, DataOutput data) throws IOException { java.sql.Date date = (java.sql.Date) field; data.writeLong(date.getTime()); } }); serializers.put(FieldType.TIME, new ValueSerializer() { @Override public Object read(DataInput in) throws IOException { long time = in.readLong(); return new java.sql.Time(time); } @Override public void write(Object field, DataOutput data) throws IOException { java.sql.Time time = (java.sql.Time) field; data.writeLong(time.getTime()); } }); serializers.put(FieldType.TIMESTAMP, new ValueSerializer() { @Override public Object read(DataInput in) throws IOException { long time = in.readLong(); int nanos = in.readInt(); java.sql.Timestamp timestamp = new java.sql.Timestamp(time); timestamp.setNanos(nanos); return timestamp; } @Override public void write(Object field, DataOutput data) throws IOException { java.sql.Timestamp timestamp = (java.sql.Timestamp) field; data.writeLong(timestamp.getTime()); data.writeInt(timestamp.getNanos()); } }); } /** * Writes the passed attribute value in the specified data stream * * @param opt * @param data */ public static void write(Optional<Object> opt, DataOutput data) throws IOException { FieldType type = FieldType.forValue(opt); if (serializers.containsKey(type)) { serializers.get(type).write(opt.orNull(), data); } else { throw new IllegalArgumentException("The specified type (" + type + ") is not supported"); } } /** * Reads an object of the specified type from the provided data stream * * @param type * @param in * @return */ public static Object read(FieldType type, DataInput in) throws IOException { if (serializers.containsKey(type)) { return serializers.get(type).read(in); } else { throw new IllegalArgumentException("The specified type is not supported"); } } }