package com.ctriposs.baiji.specific; import com.ctriposs.baiji.exception.BaijiRuntimeException; import com.ctriposs.baiji.generic.DatumReader; import com.ctriposs.baiji.generic.DatumWriter; import com.ctriposs.baiji.io.BinaryDecoder; import com.ctriposs.baiji.io.BinaryEncoder; import com.ctriposs.baiji.io.Decoder; import com.ctriposs.baiji.io.Encoder; import com.ctriposs.baiji.schema.RecordSchema; import com.ctriposs.baiji.schema.Schema; import com.google.common.base.Objects; import org.junit.Test; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.*; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; public class SpecificTests { @Test public void testEnumResolution() throws Exception { EnumRecord testRecord = new EnumRecord(); testRecord.enumType = EnumType.SECOND; // Serialize byte[] data = serialize(testRecord, EnumRecord.class, EnumRecord.SCHEMA); // Deserialize EnumRecord rec2 = deserialize(data, EnumRecord.class, EnumRecord.SCHEMA); assertEquals(testRecord.enumType, rec2.enumType); } @Test public void testRecordNullFields() throws Exception { TestRecord testRecord = new TestRecord(); // Serialize byte[] data = serialize(testRecord, TestRecord.class, TestRecord.SCHEMA); // Deserialize TestRecord rec2 = deserialize(data, TestRecord.class, TestRecord.SCHEMA); assertEquals(testRecord, rec2); } @Test public void testRecordValidFields() throws Exception { List<Integer> list = new ArrayList<Integer>(); list.add(Integer.MIN_VALUE); list.add(-1); list.add(0); list.add(1); list.add(2); list.add(3); list.add(5); list.add(8); list.add(13); list.add(Integer.MAX_VALUE); Map<String, String> map = new HashMap<String, String>(); map.put("Name", "Value"); map.put("Beijing", "China"); map.put("San Francisco", "USA"); InnerRecord innerRecord = new InnerRecord(777, "Baiji Test"); byte[] binary = new byte[]{1, 2, 3, 4, 5, -100}; TestRecord testRecord = new TestRecord(true, 123456, 9876543211234567L, 12341981.1234234f, 1273891231.787, innerRecord, new TestRecord(), list, map, binary); // Serialize byte[] data = serialize(testRecord, TestRecord.class, TestRecord.SCHEMA); // Deserialize TestRecord rec2 = deserialize(data, TestRecord.class, TestRecord.SCHEMA); assertEquals(testRecord, rec2); } private static <S extends SpecificRecord> S deserialize(byte[] data, Class<S> clazz, Schema schema) throws IOException { ByteArrayInputStream is = new ByteArrayInputStream(data); DatumReader<S> r = new SpecificDatumReader<S>(schema); Decoder d = new BinaryDecoder(is); S output = r.read(null, d); assertEquals(0, is.available()); // Ensure we have read everything. checkAlternateDeserializers(output, data, clazz, schema); return output; } private static <S extends SpecificRecord> void checkAlternateDeserializers(S expected, byte[] data, Class<S> clazz, Schema schema) throws IOException { ByteArrayInputStream is = new ByteArrayInputStream(data); DatumReader<S> r = new SpecificDatumReader<S>(schema); Decoder d = new BinaryDecoder(is); S output = r.read(null, d); assertEquals(0, is.available()); // Ensure we have read everything. assertSpecificRecordEqual(expected, output); } private static void assertSpecificRecordEqual(SpecificRecord rec1, SpecificRecord rec2) { RecordSchema recordSchema = (RecordSchema) rec1.getSchema(); for (int i = 0; i < recordSchema.size(); i++) { Object rec1Val = rec1.get(i); Object rec2Val = rec2.get(i); if (rec1Val instanceof SpecificRecord) { assertSpecificRecordEqual((SpecificRecord) rec1Val, (SpecificRecord) rec2Val); } else if (rec1Val instanceof List) { List rec1List = (List) rec1Val; if (!rec1List.isEmpty() && rec1List.get(0) instanceof SpecificRecord) { List rec2List = (List) rec2Val; assertEquals(rec1List.size(), rec2List.size()); for (int j = 0; j < rec1List.size(); j++) { assertSpecificRecordEqual((SpecificRecord) rec1List.get(j), (SpecificRecord) rec2List.get(j)); } } else { assertEquals(rec1Val, rec2Val); } } else if (rec1Val instanceof Map) { Map rec1Dict = (Map) rec1Val; Map rec2Dict = (Map) rec2Val; assertEquals(rec2Dict.size(), rec2Dict.size()); for (Object key : rec1Dict.keySet()) { Object val1 = rec1Dict.get(key); Object val2 = rec2Dict.get(key); if (val1 instanceof SpecificRecord) { assertSpecificRecordEqual((SpecificRecord) val1, (SpecificRecord) val2); } else { assertEquals(val1, val2); } } } else if (rec1Val instanceof byte[]){ assertArrayEquals((byte[])rec1Val, (byte[])rec2Val); } else { assertEquals(rec1Val, rec2Val); } } } private static <T> byte[] serialize(T actual, Class<?> clazz, Schema schema) throws IOException { ByteArrayOutputStream os = new ByteArrayOutputStream(); Encoder e = new BinaryEncoder(os); DatumWriter<T> w = new SpecificDatumWriter<T>(schema); w.write(actual, e); byte[] data = os.toByteArray(); checkAlternateSerializers(data, actual, schema); return data; } private static void checkAlternateSerializers(byte[] expected, Object value, Schema schema) throws IOException { ByteArrayOutputStream os = new ByteArrayOutputStream(); Encoder e = new BinaryEncoder(os); DatumWriter w = new SpecificDatumWriter(schema); w.write(value, e); byte[] output = os.toByteArray(); assertEquals(expected.length, output.length); assertArrayEquals(expected, output); } } enum EnumType { THIRD(0), FIRST(1), SECOND(2); private final int _value; EnumType(int value) { _value = value; } public int getValue() { return _value; } public static EnumType findByValue(int value) { switch (value) { case 0: return THIRD; case 1: return FIRST; case 2: return SECOND; default: return null; } } } class EnumRecord extends SpecificRecordBase { public static final com.ctriposs.baiji.schema.Schema SCHEMA = com.ctriposs.baiji.schema.Schema.parse( "{\"type\":\"record\",\"name\":\"EnumRecord\",\"namespace\":\"com.ctriposs.baiji.specific\"," + "\"fields\":[{\"name\":\"enumType\",\"type\": { \"type\": \"enum\", \"name\":" + " \"EnumType\", \"symbols\": [\"THIRD\", \"FIRST\", \"SECOND\"]} }]}"); public EnumType enumType; public EnumRecord() { } public com.ctriposs.baiji.schema.Schema getSchema() { return SCHEMA; } public EnumType getEnumType() { return enumType; } public void setEnumType(EnumType enumType) { this.enumType = enumType; } @Override public Object get(int fieldPos) { switch (fieldPos) { case 0: return enumType; default: throw new BaijiRuntimeException("Bad index " + fieldPos + " in Get()"); } } @Override public void put(int fieldPos, Object fieldValue) { switch (fieldPos) { case 0: enumType = (EnumType) fieldValue; break; default: throw new BaijiRuntimeException("Bad index " + fieldPos + " in Put()"); } } } @SuppressWarnings("all") class InnerRecord extends SpecificRecordBase implements SpecificRecord { private static final long serialVersionUID = 1L; public static final Schema SCHEMA = Schema.parse("{\"type\":\"record\",\"name\":\"InnerRecord\",\"namespace\":\"com.ctriposs.baiji.specific\",\"doc\":null,\"fields\":[{\"name\":\"id\",\"type\":[\"int\",\"null\"]},{\"name\":\"name\",\"type\":[\"string\",\"null\"]}]}"); @Override public Schema getSchema() { return SCHEMA; } public InnerRecord( Integer id, String name ) { this.id = id; this.name = name; } public InnerRecord() { } public Integer id; public String name; public Integer getId() { return id; } public void setId(final Integer id) { this.id = id; } public String getName() { return name; } public void setName(final String name) { this.name = name; } // Used by DatumWriter. Applications should not call. public java.lang.Object get(int fieldPos) { switch (fieldPos) { case 0: return this.id; case 1: return this.name; default: throw new BaijiRuntimeException("Bad index " + fieldPos + " in get()"); } } // Used by DatumReader. Applications should not call. @SuppressWarnings(value = "unchecked") public void put(int fieldPos, java.lang.Object fieldValue) { switch (fieldPos) { case 0: this.id = (Integer) fieldValue; break; case 1: this.name = (String) fieldValue; break; default: throw new BaijiRuntimeException("Bad index " + fieldPos + " in put()"); } } @Override public boolean equals(Object obj) { if (obj == null) return false; if (getClass() != obj.getClass()) return false; final InnerRecord other = (InnerRecord) obj; return Objects.equal(this.id, other.id) && Objects.equal(this.name, other.name); } @Override public int hashCode() { int result = 1; result = 31 * result + (this.id == null ? 0 : this.id.hashCode()); result = 31 * result + (this.name == null ? 0 : this.name.hashCode()); return result; } } @SuppressWarnings("all") class TestRecord extends SpecificRecordBase implements SpecificRecord { private static final long serialVersionUID = 1L; public static final Schema SCHEMA = Schema.parse("{\"type\":\"record\",\"name\":\"TestRecord\",\"namespace\":\"com.ctriposs.baiji.specific\",\"doc\":null,\"fields\":[{\"name\":\"flag\",\"type\":[\"boolean\",\"null\"]},{\"name\":\"num1\",\"type\":[\"int\",\"null\"]},{\"name\":\"num2\",\"type\":[\"long\",\"null\"]},{\"name\":\"realNum1\",\"type\":[\"float\",\"null\"]},{\"name\":\"realNum2\",\"type\":[\"double\",\"null\"]},{\"name\":\"record\",\"type\":[{\"type\":\"record\",\"name\":\"InnerRecord\",\"namespace\":\"com.ctriposs.baiji.specific\",\"doc\":null,\"fields\":[{\"name\":\"id\",\"type\":[\"int\",\"null\"]},{\"name\":\"name\",\"type\":[\"string\",\"null\"]}]},\"null\"]},{\"name\":\"parent\",\"type\":[\"TestRecord\",\"null\"]},{\"name\":\"nums\",\"type\":[{\"type\":\"array\",\"items\":\"int\"},\"null\"]},{\"name\":\"names\",\"type\":[{\"type\":\"map\",\"values\":\"string\"},\"null\"]},{\"name\":\"data\",\"type\":[\"bytes\",\"null\"]}]}"); @Override public Schema getSchema() { return SCHEMA; } public TestRecord( Boolean flag, Integer num1, Long num2, Float realNum1, Double realNum2, InnerRecord record, TestRecord parent, List<Integer> nums, Map<String, String> names, byte[] data ) { this.flag = flag; this.num1 = num1; this.num2 = num2; this.realNum1 = realNum1; this.realNum2 = realNum2; this.record = record; this.parent = parent; this.nums = nums; this.names = names; this.data = data; } public TestRecord() { } public Boolean flag; public Integer num1; public Long num2; public Float realNum1; public Double realNum2; public InnerRecord record; public TestRecord parent; public List<Integer> nums; public Map<String, String> names; private byte[] data; public Boolean isFlag() { return flag; } public void setFlag(final Boolean flag) { this.flag = flag; } public Integer getNum1() { return num1; } public void setNum1(final Integer num1) { this.num1 = num1; } public Long getNum2() { return num2; } public void setNum2(final Long num2) { this.num2 = num2; } public Float getRealNum1() { return realNum1; } public void setRealNum2(final Float realNum1) { this.realNum1 = realNum1; } public Double getRealNum2() { return realNum2; } public void setRealNum2(final Double realNum2) { this.realNum2 = realNum2; } public InnerRecord getRecord() { return record; } public void setRecord(final InnerRecord record) { this.record = record; } public TestRecord getParent() { return parent; } public void setParent(final TestRecord parent) { this.parent = parent; } public List<Integer> getNums() { return nums; } public void setNums(final List<Integer> nums) { this.nums = nums; } public Map<String, String> getNames() { return names; } public void setNames(final Map<String, String> names) { this.names = names; } public byte[] getData() { return data; } public void setData(final byte[] data) { this.data = data; } // Used by DatumWriter. Applications should not call. public java.lang.Object get(int fieldPos) { switch (fieldPos) { case 0: return this.flag; case 1: return this.num1; case 2: return this.num2; case 3: return this.realNum1; case 4: return this.realNum2; case 5: return this.record; case 6: return this.parent; case 7: return this.nums; case 8: return this.names; case 9: return this.data; default: throw new BaijiRuntimeException("Bad index " + fieldPos + " in get()"); } } // Used by DatumReader. Applications should not call. @SuppressWarnings(value = "unchecked") public void put(int fieldPos, java.lang.Object fieldValue) { switch (fieldPos) { case 0: this.flag = (Boolean) fieldValue; break; case 1: this.num1 = (Integer) fieldValue; break; case 2: this.num2 = (Long) fieldValue; break; case 3: this.realNum1 = (Float) fieldValue; break; case 4: this.realNum2 = (Double) fieldValue; break; case 5: this.record = (InnerRecord) fieldValue; break; case 6: this.parent = (TestRecord) fieldValue; break; case 7: this.nums = (List<Integer>) fieldValue; break; case 8: this.names = (Map<String, String>) fieldValue; break; case 9: this.data = (byte[]) fieldValue; break; default: throw new BaijiRuntimeException("Bad index " + fieldPos + " in put()"); } } @Override public boolean equals(Object obj) { if (obj == null) return false; if (getClass() != obj.getClass()) return false; final TestRecord other = (TestRecord) obj; return Objects.equal(this.flag, other.flag) && Objects.equal(this.num1, other.num1) && Objects.equal(this.num2, other.num2) && Objects.equal(this.realNum1, other.realNum1) && Objects.equal(this.realNum2, other.realNum2) && Objects.equal(this.record, other.record) && Objects.equal(this.parent, other.parent) && Objects.equal(this.nums, other.nums) && Objects.equal(this.names, other.names) && Arrays.equals(this.data, other.data); } @Override public int hashCode() { int result = 1; result = 31 * result + (this.flag == null ? 0 : this.flag.hashCode()); result = 31 * result + (this.num1 == null ? 0 : this.num1.hashCode()); result = 31 * result + (this.num2 == null ? 0 : this.num2.hashCode()); result = 31 * result + (this.realNum1 == null ? 0 : this.realNum1.hashCode()); result = 31 * result + (this.realNum2 == null ? 0 : this.realNum2.hashCode()); result = 31 * result + (this.record == null ? 0 : this.record.hashCode()); result = 31 * result + (this.parent == null ? 0 : this.parent.hashCode()); result = 31 * result + (this.nums == null ? 0 : this.nums.hashCode()); result = 31 * result + (this.names == null ? 0 : this.names.hashCode()); result = 31 * result + (this.data == null ? 0 : Arrays.hashCode(data)); return result; } }