package openmods.serializable;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.io.ByteArrayDataInput;
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.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import openmods.serializable.cls.SerializableClass;
import openmods.serializable.cls.Serialize;
import openmods.utils.io.IStreamSerializer;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.Matchers;
import org.mockito.Mockito;
public class SerializableTest {
private final SerializerRegistry registry = new SerializerRegistry();
private static void assertFullyRead(ByteArrayDataInput input) {
Assert.assertEquals(0, input.skipBytes(256));
}
public <T> T serializeDeserialize(Class<? extends T> cls, T value) throws IOException {
ByteArrayDataOutput output = ByteStreams.newDataOutput();
registry.writeToStream(output, cls, value);
ByteArrayDataInput input = ByteStreams.newDataInput(output.toByteArray());
final T result = registry.createFromStream(input, cls);
assertFullyRead(input);
return result;
}
public Object genericSerializeDeserialize(Type type, Object value) throws IOException {
ByteArrayDataOutput output = ByteStreams.newDataOutput();
registry.writeToStream(output, type, value);
ByteArrayDataInput input = ByteStreams.newDataInput(output.toByteArray());
final Object result = registry.createFromStream(input, type);
assertFullyRead(input);
return result;
}
public <T> T testValue(T v) throws IOException {
@SuppressWarnings("unchecked")
final Class<? extends T> cls = (Class<? extends T>)v.getClass();
T result = serializeDeserialize(cls, v);
Assert.assertTrue(cls.isInstance(result));
Assert.assertEquals(result, v);
return result;
}
public Object testValueGeneric(Type type, Object value) throws IOException {
Object result = genericSerializeDeserialize(type, value);
TypeToken<?> token = TypeToken.of(type);
Assert.assertTrue(token.getRawType().isInstance(result));
Assert.assertEquals(result, value);
return result;
}
public <T> T[] testArray(T[] v) throws IOException {
@SuppressWarnings("unchecked")
final Class<? extends T[]> cls = (Class<? extends T[]>)v.getClass();
T[] result = serializeDeserialize(cls, v);
Assert.assertTrue(cls.isInstance(result));
Assert.assertTrue(Arrays.deepEquals(v, result));
return result;
}
public int[] testIntArray(int[] v) throws IOException {
int[] result = serializeDeserialize(int[].class, v);
Assert.assertTrue(int[].class.isInstance(result));
Assert.assertArrayEquals(v, result);
return result;
}
private static IStreamSerializer<TestCls> createSerializer() throws IOException {
IStreamSerializer<TestCls> serializer = Mockito.mock(TestSerializer.class);
Mockito.when(serializer.readFromStream(Matchers.any(DataInput.class))).thenReturn(new TestCls());
return serializer;
}
@Test
public void testInteger() throws IOException {
testValue(1);
}
@Test
public void testString() throws IOException {
testValue("hello");
}
public static class TestCls {
@Override
public boolean equals(Object obj) {
return obj instanceof TestCls;
}
@Override
public int hashCode() {
return 0;
}
}
public interface TestSerializer extends IStreamSerializer<TestCls> {
}
@Test
public void testRegister() throws IOException {
TestCls testInstance = new TestCls();
IStreamSerializer<TestCls> serializer = createSerializer();
registry.register(serializer);
testValue(testInstance);
Mockito.verify(serializer).writeToStream(Matchers.eq(testInstance), Matchers.any(DataOutput.class));
Mockito.verify(serializer).readFromStream(Matchers.any(DataInput.class));
}
@Test
public void testAnonymous() throws IOException {
TestCls testInstance = new TestCls();
final IStreamSerializer<TestCls> wrappedSerializer = createSerializer();
registry.register(new IStreamSerializer<TestCls>() {
@Override
public TestCls readFromStream(DataInput input) throws IOException {
return wrappedSerializer.readFromStream(input);
}
@Override
public void writeToStream(TestCls o, DataOutput output) throws IOException {
wrappedSerializer.writeToStream(o, output);
}
});
testValue(testInstance);
Mockito.verify(wrappedSerializer).writeToStream(Matchers.eq(testInstance), Matchers.any(DataOutput.class));
Mockito.verify(wrappedSerializer).readFromStream(Matchers.any(DataInput.class));
}
public class TestSerializable implements IStreamSerializable {
public TestSerializable(IStreamSerializable delegate) {
this.delegate = delegate;
}
public IStreamSerializable delegate;
@Override
public void readFromStream(DataInput input) throws IOException {
delegate.readFromStream(input);
}
@Override
public void writeToStream(DataOutput output) throws IOException {
delegate.writeToStream(output);
}
@Override
public boolean equals(Object obj) {
return obj instanceof TestSerializable;
}
@Override
public int hashCode() {
return 0;
}
}
@Test
public void testClass() throws IOException {
TestSerializable inputInstance = new TestSerializable(Mockito.mock(IStreamSerializable.class));
final TestSerializable outputInstance = new TestSerializable(Mockito.mock(IStreamSerializable.class));
registry.registerSerializable(new IInstanceFactory<TestSerializable>() {
@Override
public TestSerializable create() {
return outputInstance;
}
});
testValue(inputInstance);
Mockito.verify(inputInstance.delegate).writeToStream(Matchers.any(DataOutput.class));
Mockito.verify(outputInstance.delegate).readFromStream(Matchers.any(DataInput.class));
}
public static enum SingleClassEnum {
A,
B,
C
}
public static enum MultipleClassEnum {
A {},
B {},
C {}
}
@Test
public void testEnum() throws IOException {
testValue(SingleClassEnum.A);
testValue(SingleClassEnum.B);
testValue(SingleClassEnum.C);
testValue(MultipleClassEnum.A);
testValue(MultipleClassEnum.B);
testValue(MultipleClassEnum.C);
}
@Test
public void testArrayPrimitive() throws IOException {
testIntArray(new int[] {});
testIntArray(new int[] { 1, 2, 3 });
testIntArray(new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 });
}
@Test
public void testArrayNullable() throws IOException {
testArray(new String[] {});
testArray(new String[] { "aa", "", "ccc" });
testArray(new String[] { null });
testArray(new String[] { "aa", null, "ccc" });
testArray(new String[] { "a", "b", "c", "d", "e", "f", "g", "h" });
}
@Test
public void testMultidimensionalArrayNullable() throws IOException {
testArray(new String[][] {});
testArray(new String[][] { null });
testArray(new String[][] { {} });
testArray(new String[][] { { null } });
testArray(new String[][] { { "a", "b" }, {}, { "c" } });
testArray(new String[][] { { "a", "b" }, null, { "c" } });
testArray(new String[][] { { "a", null }, {}, { "c" } });
}
@Test
public void testMultidimensionalArrayPrimitive() throws IOException {
testArray(new int[][] {});
testArray(new int[][] { null });
testArray(new int[][] { {} });
testArray(new int[][] { { 1, 2 }, null, { 3 } });
testArray(new int[][] { { 1, 2 }, {}, { 3 } });
}
@Test
public void testMultidimensionalArrayEnum() throws IOException {
testArray(new SingleClassEnum[][] {});
testArray(new SingleClassEnum[][] { null });
testArray(new SingleClassEnum[][] { {} });
testArray(new SingleClassEnum[][] { { null } });
testArray(new SingleClassEnum[][] { { SingleClassEnum.A, SingleClassEnum.B }, null, { SingleClassEnum.C } });
testArray(new SingleClassEnum[][] { { SingleClassEnum.A, SingleClassEnum.B }, {}, { SingleClassEnum.C } });
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
private @interface FieldTest {}
protected void testGenericFields(Object obj) {
for (Field f : obj.getClass().getFields()) {
FieldTest ann = f.getAnnotation(FieldTest.class);
final String name = f.getName();
Preconditions.checkNotNull(ann, "Field without annotation: " + name);
try {
testValueGeneric(f.getGenericType(), f.get(obj));
} catch (Exception e) {
throw new RuntimeException("Field " + name, e);
}
}
}
public static class TestGenericList {
@FieldTest
public List<Integer> empty = Lists.newArrayList();
@FieldTest
public List<Integer> nonNull = Lists.newArrayList(1, 2, 3);
@FieldTest
public List<Integer> nulls = Lists.newArrayList(1, null, 2);
@FieldTest
public List<MultipleClassEnum> enums = Lists.newArrayList(null, MultipleClassEnum.A, MultipleClassEnum.C);
@FieldTest
public List<List<Integer>> nested = Lists.newArrayList();
{
nested.add(Lists.newArrayList(1, 2, 3));
nested.add(null);
nested.add(Lists.<Integer> newArrayList());
}
}
@Test
public void testGenericList() throws Exception {
testGenericFields(new TestGenericList());
}
public static class TestGenericSet {
@FieldTest
public Set<Integer> empty = Sets.newHashSet();
@FieldTest
public Set<Boolean> nonNull = Sets.newHashSet(true, false, true);
@FieldTest
public Set<Boolean> nulls = Sets.newHashSet(false, null, true);
@FieldTest
public Set<MultipleClassEnum> enums = Sets.newHashSet(null, MultipleClassEnum.A, MultipleClassEnum.C);
@FieldTest
public Set<Set<Integer>> nested = Sets.newHashSet();
{
nested.add(Sets.newHashSet(1, 3, 3, null));
nested.add(null);
nested.add(Sets.<Integer> newHashSet());
}
}
@Test
public void testGenericSet() throws Exception {
testGenericFields(new TestGenericSet());
}
public static class TestGenericMap {
@FieldTest
public Map<Integer, String> empty = Maps.newHashMap();
@FieldTest
public Map<String, Boolean> nonNull = Maps.newHashMap();
{
for (int i = 0; i < 11; i++)
nonNull.put("aaaa" + i, (i & 2) == 0);
}
@FieldTest
public Map<Float, String> nulls = Maps.newHashMap();
{
nulls.put(null, "aaaa");
nulls.put(5.0f, null);
nulls.put(6.0f, "zzz");
for (int i = 0; i < 11; i++)
nulls.put(99.0f + i, (i & 4) == 0? null : "aaa");
nulls.put(999.0f, "bvbb");
}
@FieldTest
public Map<Float, String> doubleNull = Maps.newHashMap();
{
doubleNull.put(null, null);
}
@FieldTest
public Map<String, MultipleClassEnum> enums = ImmutableMap.of("fff", MultipleClassEnum.A, "bbb", MultipleClassEnum.C);
@FieldTest
public Map<Map<String, Integer>, Map<Float, Boolean>> nested = Maps.newHashMap();
{
nested.put(ImmutableMap.of("a", 3, "f", 5), Maps.<Float, Boolean> newHashMap());
nested.put(null, ImmutableMap.of(4.0f, true, 5.0f, false));
nested.put(ImmutableMap.of("zzz", 2, "ddd", 4), null);
Map<Float, Boolean> valueWithNull = Maps.newHashMap();
valueWithNull.put(null, true);
valueWithNull.put(5.0f, null);
valueWithNull.put(6.0f, false);
Map<String, Integer> keyWithNull = Maps.newHashMap();
keyWithNull.put(null, 4);
keyWithNull.put("zzz", null);
keyWithNull.put("zffd", 9);
nested.put(keyWithNull, valueWithNull);
}
}
@Test
public void testGenericMap() throws Exception {
testGenericFields(new TestGenericMap());
}
public static class TestGenericMixed {
@FieldTest
public List<Set<String>> listOfSets = Lists.newArrayList();
{
listOfSets.add(Sets.newHashSet("A", null, "c"));
listOfSets.add(null);
listOfSets.add(Sets.<String> newHashSet());
}
@FieldTest
public List<Map<String, Integer>> listOfMaps = Lists.newArrayList();
{
listOfMaps.add(Maps.<String, Integer> newHashMap());
Map<String, Integer> k = Maps.newHashMap();
k.put(null, 3);
k.put("zzz", null);
listOfMaps.add(k);
listOfMaps.add(ImmutableMap.of("a", 1, "d", 3));
}
@FieldTest
public Set<List<Float>> setOfLists = Sets.newHashSet();
{
setOfLists.add(Lists.<Float> newArrayList());
setOfLists.add(null);
setOfLists.add(Lists.newArrayList(1.0f, null, 3.0f));
}
@FieldTest
public Set<Map<String, Boolean>> setOfMaps = Sets.newHashSet();
{
setOfMaps.add(ImmutableMap.of("a", true, "d", false));
Map<String, Boolean> k = Maps.newHashMap();
k.put(null, true);
k.put("zzz", null);
setOfMaps.add(k);
setOfMaps.add(Maps.<String, Boolean> newHashMap());
}
@FieldTest
public Map<Set<Integer>, List<Boolean>> mapOfSetsToLists = Maps.newHashMap();
{
mapOfSetsToLists.put(Sets.newHashSet(1, 2, 3), Lists.newArrayList(false, true));
mapOfSetsToLists.put(Sets.newHashSet(5, 9, 7), Lists.newArrayList(false, true));
mapOfSetsToLists.put(Sets.<Integer> newHashSet(), Lists.<Boolean> newArrayList());
mapOfSetsToLists.put(null, Lists.<Boolean> newArrayList(false, true, false));
mapOfSetsToLists.put(Sets.newHashSet(4, 2, 5), null);
}
}
@Test
public void testGenericMixed() throws Exception {
testGenericFields(new TestGenericMixed());
}
private static final int DUMMY_INT = 45;
@SerializableClass
public static class SimpleSerializableClass {
@Serialize
public int intField = 4;
@Serialize(nullable = false)
public String nonNullField = "hello";
@Serialize
public String nullField = "dummy";
public int ignoredField = DUMMY_INT;
}
@Test
public void testSerializableClass() throws IOException {
SimpleSerializableClass target = new SimpleSerializableClass();
target.ignoredField = DUMMY_INT + 444;
target.intField = 6;
target.nonNullField = "gsgfd";
target.nullField = null;
SimpleSerializableClass result = serializeDeserialize(SimpleSerializableClass.class, target);
Assert.assertEquals(DUMMY_INT, result.ignoredField);
Assert.assertEquals(target.intField, result.intField);
Assert.assertNull(result.nullField);
Assert.assertEquals(target.nonNullField, result.nonNullField);
}
@SerializableClass
public static class NestedSerializableClass {
@Serialize
public int intField = 9;
@Serialize
public SimpleSerializableClass nonNullField = new SimpleSerializableClass();
@Serialize
public SimpleSerializableClass nullField = new SimpleSerializableClass();
}
@Test
public void testNestedSerializableClass() throws IOException {
NestedSerializableClass target = new NestedSerializableClass();
target.intField = 534;
target.nonNullField.ignoredField = DUMMY_INT + 444;
target.nonNullField.intField = 6;
target.nonNullField.nonNullField = "gsgfdf";
target.nonNullField.nullField = null;
target.nullField = null;
NestedSerializableClass result = serializeDeserialize(NestedSerializableClass.class, target);
Assert.assertEquals(DUMMY_INT, result.nonNullField.ignoredField);
Assert.assertEquals(target.nonNullField.intField, result.nonNullField.intField);
Assert.assertNull(result.nonNullField.nullField);
Assert.assertEquals(target.nonNullField.nonNullField, result.nonNullField.nonNullField);
Assert.assertEquals(target.intField, result.intField);
Assert.assertNull(result.nullField);
}
}