package openmods.serializable.cls; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.collect.Sets; 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.Field; import java.util.List; import java.util.Set; import openmods.reflection.FieldAccess; import openmods.reflection.TypeUtils; import openmods.serializable.IObjectSerializer; import openmods.serializable.SerializerRegistry; import openmods.utils.io.IStreamSerializer; import openmods.utils.io.InputBitStream; import openmods.utils.io.OutputBitStream; import openmods.utils.io.StreamUtils; public class ClassSerializerBuilder<T> { private static class SerializableField extends FieldAccess<Object> { private final IStreamSerializer<Object> serializer; private final boolean isNullable; public SerializableField(Class<?> ownerCls, Field field, boolean isNullable) { super(field); this.isNullable = isNullable; final TypeToken<?> fieldType = TypeUtils.resolveFieldType(ownerCls, field); this.serializer = SerializerRegistry.instance.findSerializer(fieldType.getType()); Preconditions.checkNotNull(serializer, "Invalid field %s type", field); } } private static class NonNullableSerializer<T> implements IObjectSerializer<T> { private final List<SerializableField> fields; public NonNullableSerializer(List<SerializableField> fields) { this.fields = ImmutableList.copyOf(fields); } @Override public void readFromStream(T object, DataInput input) throws IOException { for (SerializableField field : fields) { Object value = field.serializer.readFromStream(input); field.set(object, value); } } @Override public void writeToStream(T object, DataOutput output) throws IOException { for (SerializableField field : fields) { Object value = field.get(object); Preconditions.checkNotNull(value, "Non-nullable %s has null value", field.field); field.serializer.writeToStream(value, output); } } } private static class NullableSerializer<T> implements IObjectSerializer<T> { private final List<SerializableField> fields; private final int nullBytesCount; public NullableSerializer(List<SerializableField> fields, int nullBytesCount) { this.fields = ImmutableList.copyOf(fields); this.nullBytesCount = nullBytesCount; } @Override public void readFromStream(T object, DataInput input) throws IOException { final byte[] nullBits = StreamUtils.readBytes(input, nullBytesCount); final InputBitStream nullBitStream = InputBitStream.create(nullBits); for (SerializableField field : fields) { final boolean isNull = field.isNullable && nullBitStream.readBit(); final Object value = isNull? null : field.serializer.readFromStream(input); field.set(object, value); } } @Override public void writeToStream(T object, DataOutput output) throws IOException { final ByteArrayDataOutput payload = ByteStreams.newDataOutput(); final OutputBitStream nullBitsStream = OutputBitStream.create(output); for (SerializableField field : fields) { final Object value = field.get(object); if (field.isNullable) { if (value == null) { nullBitsStream.writeBit(true); } else { nullBitsStream.writeBit(false); field.serializer.writeToStream(value, payload); } } else { field.serializer.writeToStream(value, payload); } } nullBitsStream.flush(); output.write(payload.toByteArray()); } } private final Class<? extends T> ownerClass; private final List<SerializableField> fields = Lists.newArrayList(); private final Set<Field> addedFields = Sets.newHashSet(); private int nullableCount = 0; public ClassSerializerBuilder(Class<? extends T> ownerClass) { this.ownerClass = ownerClass; } public void appendField(Field field) { Preconditions.checkArgument(field.getDeclaringClass().isAssignableFrom(ownerClass), "%s does not belong to %s", field, ownerClass); final boolean newlyAdded = addedFields.add(field); Preconditions.checkState(newlyAdded, "%s already added", field); Serialize annotation = field.getAnnotation(Serialize.class); final boolean isNullable = !field.getType().isPrimitive() && (annotation != null && annotation.nullable()); if (isNullable) nullableCount++; fields.add(new SerializableField(ownerClass, field, isNullable)); } public IObjectSerializer<T> create() { return (nullableCount != 0) ? new NullableSerializer<T>(fields, StreamUtils.bitsToBytes(nullableCount)) : new NonNullableSerializer<T>(fields); } }