package openmods.serializable.providers; import com.google.common.base.Preconditions; import com.google.common.io.ByteArrayDataOutput; import com.google.common.io.ByteStreams; import com.google.common.reflect.TypeToken; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import java.lang.reflect.Type; import openmods.serializable.SerializerRegistry; import openmods.utils.ByteUtils; import openmods.utils.io.IStreamSerializer; import openmods.utils.io.InputBitStream; import openmods.utils.io.OutputBitStream; import openmods.utils.io.StreamUtils; public abstract class NullableCollectionSerializer<T> implements IStreamSerializer<T> { public static IStreamSerializer<Object[]> createObjectArraySerializer(final TypeToken<?> componentType) { return new NullableCollectionSerializer<Object[]>(componentType) { @Override protected Object[] createCollection(TypeToken<?> componentCls, int length) { return new Object[length]; } @Override protected int getLength(Object[] collection) { return collection.length; } @Override protected Object getElement(Object[] collection, int index) { return collection[index]; } @Override protected void setElement(Object[] collection, int index, Object value) { collection[index] = value; } }; } private final IStreamSerializer<Object> componentSerializer; private final TypeToken<?> componentType; public NullableCollectionSerializer(TypeToken<?> componentType) { final Type type = componentType.getType(); this.componentSerializer = SerializerRegistry.instance.findSerializer(type); Preconditions.checkNotNull(componentSerializer, "Can't find serializer for %s", type); this.componentType = componentType; } @Override public T readFromStream(DataInput input) throws IOException { final int length = ByteUtils.readVLI(input); T result = createCollection(componentType, length); if (length > 0) { final int nullBitsSize = StreamUtils.bitsToBytes(length); final byte[] nullBits = StreamUtils.readBytes(input, nullBitsSize); final InputBitStream nullBitStream = InputBitStream.create(nullBits); for (int i = 0; i < length; i++) { if (nullBitStream.readBit()) { final Object value = componentSerializer.readFromStream(input); setElement(result, i, value); } } } return result; } @Override public void writeToStream(T o, DataOutput output) throws IOException { final int length = getLength(o); ByteUtils.writeVLI(output, length); if (length > 0) { final ByteArrayDataOutput nullBits = ByteStreams.newDataOutput(); final OutputBitStream nullBitsStream = OutputBitStream.create(nullBits); for (int i = 0; i < length; i++) { Object value = getElement(o, i); nullBitsStream.writeBit(value != null); } nullBitsStream.flush(); output.write(nullBits.toByteArray()); for (int i = 0; i < length; i++) { Object value = getElement(o, i); if (value != null) componentSerializer.writeToStream(value, output); } } } protected abstract T createCollection(TypeToken<?> componentCls, int length); protected abstract int getLength(T collection); protected abstract Object getElement(T collection, int index); protected abstract void setElement(T collection, int index, final Object value); }