/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.avro.reflect; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.lang.reflect.Array; import java.lang.reflect.Type; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; import org.apache.avro.AvroRuntimeException; import org.apache.avro.AvroTypeException; import org.apache.avro.Protocol; import org.apache.avro.Schema; import org.apache.avro.Schema.Field; import org.apache.avro.generic.GenericData; import org.apache.avro.io.Decoder; import org.apache.avro.io.DecoderFactory; import org.apache.avro.io.Encoder; import org.apache.avro.io.EncoderFactory; import org.apache.avro.reflect.TestReflect.SampleRecord.AnotherSampleRecord; import org.codehaus.jackson.node.NullNode; import org.junit.Test; public class TestReflect { EncoderFactory factory = new EncoderFactory(); // test primitive type inference @Test public void testVoid() { check(Void.TYPE, "\"null\""); check(Void.class, "\"null\""); } @Test public void testBoolean() { check(Boolean.TYPE, "\"boolean\""); check(Boolean.class, "\"boolean\""); } @Test public void testInt() { check(Integer.TYPE, "\"int\""); check(Integer.class, "\"int\""); } @Test public void testByte() { check(Byte.TYPE, "{\"type\":\"int\",\"java-class\":\"java.lang.Byte\"}"); check(Byte.class, "{\"type\":\"int\",\"java-class\":\"java.lang.Byte\"}"); } @Test public void testShort() { check(Short.TYPE, "{\"type\":\"int\",\"java-class\":\"java.lang.Short\"}"); check(Short.class, "{\"type\":\"int\",\"java-class\":\"java.lang.Short\"}"); } @Test public void testChar() { check(Character.TYPE, "{\"type\":\"int\",\"java-class\":\"java.lang.Character\"}"); check(Character.class, "{\"type\":\"int\",\"java-class\":\"java.lang.Character\"}"); } @Test public void testLong() { check(Long.TYPE, "\"long\""); check(Long.class, "\"long\""); } @Test public void testFloat() { check(Float.TYPE, "\"float\""); check(Float.class, "\"float\""); } @Test public void testDouble() { check(Double.TYPE, "\"double\""); check(Double.class, "\"double\""); } @Test public void testString() { check("Foo", "\"string\""); } @Test public void testBytes() { check(ByteBuffer.allocate(0), "\"bytes\""); check(new byte[0], "{\"type\":\"bytes\",\"java-class\":\"[B\"}"); } @Test public void testUnionWithCollection() { Schema s = new Schema.Parser().parse ("[\"null\", {\"type\":\"array\",\"items\":\"float\"}]"); GenericData data = ReflectData.get(); assertEquals(1, data.resolveUnion(s, new ArrayList<Float>())); } @Test public void testUnionWithMap() { Schema s = new Schema.Parser().parse ("[\"null\", {\"type\":\"map\",\"values\":\"float\"}]"); GenericData data = ReflectData.get(); assertEquals(1, data.resolveUnion(s, new HashMap<String,Float>())); } @Test public void testUnionWithFixed() { Schema s = new Schema.Parser().parse ("[\"null\", {\"type\":\"fixed\",\"name\":\"f\",\"size\":1}]"); Schema f = new Schema.Parser().parse("{\"type\":\"fixed\",\"name\":\"f\",\"size\":1}"); GenericData data = ReflectData.get(); assertEquals(1, data.resolveUnion(s, new GenericData.Fixed(f))); } @Test public void testUnionWithEnum() { Schema s = new Schema.Parser().parse ("[\"null\", {\"type\":\"enum\",\"name\":\"E\",\"namespace\":" + "\"org.apache.avro.reflect.TestReflect$\",\"symbols\":[\"A\",\"B\"]}]"); GenericData data = ReflectData.get(); assertEquals(1, data.resolveUnion(s, E.A)); } @Test public void testUnionWithBytes() { Schema s = new Schema.Parser().parse ("[\"null\", \"bytes\"]"); GenericData data = ReflectData.get(); assertEquals(1, data.resolveUnion(s, ByteBuffer.wrap(new byte[]{1}))); } // test map, array and list type inference public static class R1 { private Map<String,String> mapField = new HashMap<String,String>(); private String[] arrayField = new String[] { "foo" }; private List<String> listField = new ArrayList<String>(); { mapField.put("foo", "bar"); listField.add("foo"); } @Override public boolean equals(Object o) { if (!(o instanceof R1)) return false; R1 that = (R1)o; return mapField.equals(that.mapField) && Arrays.equals(this.arrayField, that.arrayField) && listField.equals(that.listField); } } @Test public void testMap() throws Exception { check(R1.class.getDeclaredField("mapField").getGenericType(), "{\"type\":\"map\",\"values\":\"string\"}"); } @Test public void testArray() throws Exception { check(R1.class.getDeclaredField("arrayField").getGenericType(), "{\"type\":\"array\",\"items\":\"string\",\"java-class\":\"[Ljava.lang.String;\"}"); } @Test public void testList() throws Exception { check(R1.class.getDeclaredField("listField").getGenericType(), "{\"type\":\"array\",\"items\":\"string\"" +",\"java-class\":\"java.util.List\"}"); } @Test public void testR1() throws Exception { checkReadWrite(new R1()); } // test record, array and list i/o public static class R2 { private String[] arrayField; private Collection<String> collectionField; @Override public boolean equals(Object o) { if (!(o instanceof R2)) return false; R2 that = (R2)o; return Arrays.equals(this.arrayField, that.arrayField) && collectionField.equals(that.collectionField); } } @Test public void testR2() throws Exception { R2 r2 = new R2(); r2.arrayField = new String[] {"foo"}; r2.collectionField = new ArrayList<String>(); r2.collectionField.add("foo"); checkReadWrite(r2); } // test array i/o of unboxed type public static class R3 { private int[] intArray; @Override public boolean equals(Object o) { if (!(o instanceof R3)) return false; R3 that = (R3)o; return Arrays.equals(this.intArray, that.intArray); } } @Test public void testR3() throws Exception { R3 r3 = new R3(); r3.intArray = new int[] {1}; checkReadWrite(r3); } // test inherited fields & short datatype public static class R4 { public short value; public short[] shorts; public byte b; public char c; @Override public boolean equals(Object o) { if (!(o instanceof R4)) return false; R4 that = (R4)o; return this.value == that.value && Arrays.equals(this.shorts, that.shorts) && this.b == that.b && this.c == that.c; } } public static class R5 extends R4 {} @Test public void testR5() throws Exception { R5 r5 = new R5(); r5.value = 1; r5.shorts = new short[] {3,255,256,Short.MAX_VALUE,Short.MIN_VALUE}; r5.b = 99; r5.c = 'a'; checkReadWrite(r5); } // test union annotation on a class @Union({R7.class, R8.class}) public static class R6 {} public static class R7 extends R6 { public int value; @Override public boolean equals(Object o) { if (!(o instanceof R7)) return false; return this.value == ((R7)o).value; } } public static class R8 extends R6 { public float value; @Override public boolean equals(Object o) { if (!(o instanceof R8)) return false; return this.value == ((R8)o).value; } } // test arrays of union annotated class public static class R9 { public R6[] r6s; @Override public boolean equals(Object o) { if (!(o instanceof R9)) return false; return Arrays.equals(this.r6s, ((R9)o).r6s); } } @Test public void testR6() throws Exception { R7 r7 = new R7(); r7.value = 1; checkReadWrite(r7, ReflectData.get().getSchema(R6.class)); R8 r8 = new R8(); r8.value = 1; checkReadWrite(r8, ReflectData.get().getSchema(R6.class)); R9 r9 = new R9(); r9.r6s = new R6[] {r7, r8}; checkReadWrite(r9, ReflectData.get().getSchema(R9.class)); } // test union annotation on methods and parameters public static interface P0 { @Union({Void.class,String.class}) String foo(@Union({Void.class,String.class}) String s); } @Test public void testP0() throws Exception { Protocol p0 = ReflectData.get().getProtocol(P0.class); Protocol.Message message = p0.getMessages().get("foo"); // check response schema is union Schema response = message.getResponse(); assertEquals(Schema.Type.UNION, response.getType()); assertEquals(Schema.Type.NULL, response.getTypes().get(0).getType()); assertEquals(Schema.Type.STRING, response.getTypes().get(1).getType()); // check request schema is union Schema request = message.getRequest(); Field field = request.getField("s"); assertNotNull("field 's' should not be null", field); Schema param = field.schema(); assertEquals(Schema.Type.UNION, param.getType()); assertEquals(Schema.Type.NULL, param.getTypes().get(0).getType()); assertEquals(Schema.Type.STRING, param.getTypes().get(1).getType()); // check union erasure assertEquals(String.class, ReflectData.get().getClass(response)); assertEquals(String.class, ReflectData.get().getClass(param)); } // test Stringable annotation @Stringable public static class R10 { private String text; public R10(String text) { this.text = text; } @Override public String toString() { return text; } @Override public boolean equals(Object o) { if (!(o instanceof R10)) return false; return this.text.equals(((R10)o).text); } } @Test public void testR10() throws Exception { Schema r10Schema = ReflectData.get().getSchema(R10.class); assertEquals(Schema.Type.STRING, r10Schema.getType()); assertEquals(R10.class.getName(), r10Schema.getProp("java-class")); checkReadWrite(new R10("foo"), r10Schema); } // test Nullable annotation on field public static class R11 { @Nullable private String text; @Override public boolean equals(Object o) { if (!(o instanceof R11)) return false; R11 that = (R11)o; if (this.text == null) return that.text == null; return this.text.equals(that.text); } } @Test public void testR11() throws Exception { Schema r11Record = ReflectData.get().getSchema(R11.class); assertEquals(Schema.Type.RECORD, r11Record.getType()); Field r11Field = r11Record.getField("text"); assertEquals(NullNode.getInstance(), r11Field.defaultValue()); Schema r11FieldSchema = r11Field.schema(); assertEquals(Schema.Type.UNION, r11FieldSchema.getType()); assertEquals(Schema.Type.NULL, r11FieldSchema.getTypes().get(0).getType()); Schema r11String = r11FieldSchema.getTypes().get(1); assertEquals(Schema.Type.STRING, r11String.getType()); R11 r11 = new R11(); checkReadWrite(r11, r11Record); r11.text = "foo"; checkReadWrite(r11, r11Record); } // test nullable annotation on methods and parameters public static interface P1 { @Nullable String foo(@Nullable String s); } @Test public void testP1() throws Exception { Protocol p1 = ReflectData.get().getProtocol(P1.class); Protocol.Message message = p1.getMessages().get("foo"); // check response schema is union Schema response = message.getResponse(); assertEquals(Schema.Type.UNION, response.getType()); assertEquals(Schema.Type.NULL, response.getTypes().get(0).getType()); assertEquals(Schema.Type.STRING, response.getTypes().get(1).getType()); // check request schema is union Schema request = message.getRequest(); Field field = request.getField("s"); assertNotNull("field 's' should not be null", field); Schema param = field.schema(); assertEquals(Schema.Type.UNION, param.getType()); assertEquals(Schema.Type.NULL, param.getTypes().get(0).getType()); assertEquals(Schema.Type.STRING, param.getTypes().get(1).getType()); // check union erasure assertEquals(String.class, ReflectData.get().getClass(response)); assertEquals(String.class, ReflectData.get().getClass(param)); } // test AvroSchema annotation public static class R12 { // fields @AvroSchema("\"int\"") Object x; @AvroSchema("{\"type\":\"array\",\"items\":[\"null\",\"string\"]}") List<String> strings; } @Test public void testR12() throws Exception { Schema s = ReflectData.get().getSchema(R12.class); assertEquals(Schema.Type.INT, s.getField("x").schema().getType()); assertEquals(Schema.parse ("{\"type\":\"array\",\"items\":[\"null\",\"string\"]}"), s.getField("strings").schema()); } @AvroSchema("\"null\"") // record public class R13 {} @Test public void testR13() throws Exception { Schema s = ReflectData.get().getSchema(R13.class); assertEquals(Schema.Type.NULL, s.getType()); } public interface P4 { @AvroSchema("\"int\"") // message value Object foo(@AvroSchema("\"int\"")Object x); // message param } @Test public void testP4() throws Exception { Protocol p = ReflectData.get().getProtocol(P4.class); Protocol.Message message = p.getMessages().get("foo"); assertEquals(Schema.Type.INT, message.getResponse().getType()); Field field = message.getRequest().getField("x"); assertEquals(Schema.Type.INT, field.schema().getType()); } // test error @SuppressWarnings("serial") public static class E1 extends Exception {} public static interface P2 { void error() throws E1; } @Test public void testP2() throws Exception { Schema e1 = ReflectData.get().getSchema(E1.class); assertEquals(Schema.Type.RECORD, e1.getType()); assertTrue(e1.isError()); Field message = e1.getField("detailMessage"); assertNotNull("field 'detailMessage' should not be null", message); Schema messageSchema = message.schema(); assertEquals(Schema.Type.UNION, messageSchema.getType()); assertEquals(Schema.Type.NULL, messageSchema.getTypes().get(0).getType()); assertEquals(Schema.Type.STRING, messageSchema.getTypes().get(1).getType()); Protocol p2 = ReflectData.get().getProtocol(P2.class); Protocol.Message m = p2.getMessages().get("error"); // check error schema is union Schema response = m.getErrors(); assertEquals(Schema.Type.UNION, response.getType()); assertEquals(Schema.Type.STRING, response.getTypes().get(0).getType()); assertEquals(e1, response.getTypes().get(1)); } @Test public void testNoPackage() throws Exception { Class<?> noPackage = Class.forName("NoPackage"); Schema s = ReflectData.get().getSchema(noPackage); assertEquals(noPackage.getName(), ReflectData.getClassName(s)); } void checkReadWrite(Object object) throws Exception { checkReadWrite(object, ReflectData.get().getSchema(object.getClass())); } void checkReadWrite(Object object, Schema s) throws Exception { ReflectDatumWriter<Object> writer = new ReflectDatumWriter<Object>(s); ByteArrayOutputStream out = new ByteArrayOutputStream(); writer.write(object, factory.directBinaryEncoder(out, null)); ReflectDatumReader<Object> reader = new ReflectDatumReader<Object>(s); Object after = reader.read(null, DecoderFactory.get().binaryDecoder( out.toByteArray(), null)); assertEquals(object, after); // check reflective setField works for records if (s.getType().equals(Schema.Type.RECORD)) { Object copy = object.getClass().newInstance(); for (Field f : s.getFields()) { Object val = ReflectData.get().getField(object, f.name(), f.pos()); ReflectData.get().setField(copy, f.name(), f.pos(), val); } assertEquals("setField", object, copy); } } public static enum E { A, B }; @Test public void testEnum() throws Exception { check(E.class, "{\"type\":\"enum\",\"name\":\"E\",\"namespace\":" +"\"org.apache.avro.reflect.TestReflect$\",\"symbols\":[\"A\",\"B\"]}"); } public static class R { int a; long b; } @Test public void testRecord() throws Exception { check(R.class, "{\"type\":\"record\",\"name\":\"R\",\"namespace\":" +"\"org.apache.avro.reflect.TestReflect$\",\"fields\":[" +"{\"name\":\"a\",\"type\":\"int\"}," +"{\"name\":\"b\",\"type\":\"long\"}]}"); } public static class RAvroIgnore { @AvroIgnore int a; } @Test public void testAnnotationAvroIgnore() throws Exception { check(RAvroIgnore.class, "{\"type\":\"record\",\"name\":\"RAvroIgnore\",\"namespace\":" +"\"org.apache.avro.reflect.TestReflect$\",\"fields\":[]}"); } public static class RAvroMeta { @AvroMeta(key="K", value="V") int a; } @Test public void testAnnotationAvroMeta() throws Exception { check(RAvroMeta.class, "{\"type\":\"record\",\"name\":\"RAvroMeta\",\"namespace\":" +"\"org.apache.avro.reflect.TestReflect$\",\"fields\":[" +"{\"name\":\"a\",\"type\":\"int\",\"K\":\"V\"}]}"); } public static class RAvroName { @AvroName("b") int a; } @Test public void testAnnotationAvroName() throws Exception { check(RAvroName.class, "{\"type\":\"record\",\"name\":\"RAvroName\",\"namespace\":" +"\"org.apache.avro.reflect.TestReflect$\",\"fields\":[" +"{\"name\":\"b\",\"type\":\"int\"}]}"); } public static class RAvroNameCollide { @AvroName("b") int a; int b; } @Test(expected=Exception.class) public void testAnnotationAvroNameCollide() throws Exception { check(RAvroNameCollide.class, "{\"type\":\"record\",\"name\":\"RAvroNameCollide\",\"namespace\":" +"\"org.apache.avro.reflect.TestReflect$\",\"fields\":[" +"{\"name\":\"b\",\"type\":\"int\"}," +"{\"name\":\"b\",\"type\":\"int\"}]}"); } public static class RAvroStringableField { @Stringable int a; } public void testAnnotationAvroStringableFields() throws Exception { check(RAvroStringableField.class, "{\"type\":\"record\",\"name\":\"RAvroNameCollide\",\"namespace\":" +"\"org.apache.avro.reflect.TestReflect$\",\"fields\":[" +"{\"name\":\"a\",\"type\":\"String\"}]}"); } private void check(Object o, String schemaJson) { check(o.getClass(), schemaJson); } private void check(Type type, String schemaJson) { assertEquals(schemaJson, ReflectData.get().getSchema(type).toString()); } @Test public void testRecordIO() throws IOException { Schema schm = ReflectData.get().getSchema(SampleRecord.class); ReflectDatumWriter<SampleRecord> writer = new ReflectDatumWriter<SampleRecord>(schm); ByteArrayOutputStream out = new ByteArrayOutputStream(); SampleRecord record = new SampleRecord(); record.x = 5; record.y = 10; writer.write(record, factory.directBinaryEncoder(out, null)); ReflectDatumReader<SampleRecord> reader = new ReflectDatumReader<SampleRecord>(schm); SampleRecord decoded = reader.read(null, DecoderFactory.get().binaryDecoder( out.toByteArray(), null)); assertEquals(record, decoded); } public static class AvroEncRecord { @AvroEncode(using=DateAsLongEncoding.class) java.util.Date date; @Override public boolean equals(Object o) { if (!(o instanceof AvroEncRecord)) return false; return date.equals(((AvroEncRecord)o).date); } } public static class multipleAnnotationRecord { @AvroIgnore @Stringable Integer i1; @AvroIgnore @Nullable Integer i2; @AvroIgnore @AvroName("j") Integer i3; @AvroIgnore @AvroEncode(using=DateAsLongEncoding.class) java.util.Date i4; @Stringable @Nullable Integer i5; @Stringable @AvroName("j6") Integer i6 = 6; @Stringable @AvroEncode(using=DateAsLongEncoding.class) java.util.Date i7 = new java.util.Date(7L); @Nullable @AvroName("j8") Integer i8; @Nullable @AvroEncode(using=DateAsLongEncoding.class) java.util.Date i9; @AvroName("j10") @AvroEncode(using=DateAsLongEncoding.class) java.util.Date i10 = new java.util.Date(10L); @Stringable @Nullable @AvroName("j11") @AvroEncode(using=DateAsLongEncoding.class) java.util.Date i11; } @Test public void testMultipleAnnotations() throws IOException { Schema schm = ReflectData.get().getSchema(multipleAnnotationRecord.class); ReflectDatumWriter<multipleAnnotationRecord> writer = new ReflectDatumWriter<multipleAnnotationRecord>(schm); ByteArrayOutputStream out = new ByteArrayOutputStream(); multipleAnnotationRecord record = new multipleAnnotationRecord(); record.i1 = 1; record.i2 = 2; record.i3 = 3; record.i4 = new java.util.Date(4L); record.i5 = 5; record.i6 = 6; record.i7 = new java.util.Date(7L); record.i8 = 8; record.i9 = new java.util.Date(9L); record.i10 = new java.util.Date(10L); record.i11 = new java.util.Date(11L); writer.write(record, factory.directBinaryEncoder(out, null)); ReflectDatumReader<multipleAnnotationRecord> reader = new ReflectDatumReader<multipleAnnotationRecord>(schm); multipleAnnotationRecord decoded = reader.read(new multipleAnnotationRecord(), DecoderFactory.get().binaryDecoder( out.toByteArray(), null)); assertTrue(decoded.i1 == null); assertTrue(decoded.i2 == null); assertTrue(decoded.i3 == null); assertTrue(decoded.i4 == null); assertTrue(decoded.i5 == 5); assertTrue(decoded.i6 == 6); assertTrue(decoded.i7.getTime() == 7); assertTrue(decoded.i8 == 8); assertTrue(decoded.i9.getTime() == 9); assertTrue(decoded.i10.getTime() == 10); assertTrue(decoded.i11.getTime() == 11); } @Test public void testAvroEncodeInducing() throws IOException { Schema schm = ReflectData.get().getSchema(AvroEncRecord.class); assertEquals(schm.toString(), "{\"type\":\"record\",\"name\":\"AvroEncRecord\",\"namespace" + "\":\"org.apache.avro.reflect.TestReflect$\",\"fields\":[{\"name\":\"date\"," + "\"type\":{\"type\":\"long\",\"CustomEncoding\":\"DateAsLongEncoding\"}}]}"); } @Test public void testAvroEncodeIO() throws IOException { Schema schm = ReflectData.get().getSchema(AvroEncRecord.class); ReflectDatumWriter<AvroEncRecord> writer = new ReflectDatumWriter<AvroEncRecord>(schm); ByteArrayOutputStream out = new ByteArrayOutputStream(); AvroEncRecord record = new AvroEncRecord(); record.date = new java.util.Date(948833323L); writer.write(record, factory.directBinaryEncoder(out, null)); ReflectDatumReader<AvroEncRecord> reader = new ReflectDatumReader<AvroEncRecord>(schm); AvroEncRecord decoded = reader.read(new AvroEncRecord(), DecoderFactory.get().binaryDecoder( out.toByteArray(), null)); assertEquals(record, decoded); } @Test public void testRecordWithNullIO() throws IOException { ReflectData reflectData = ReflectData.AllowNull.get(); Schema schm = reflectData.getSchema(AnotherSampleRecord.class); ReflectDatumWriter<AnotherSampleRecord> writer = new ReflectDatumWriter<AnotherSampleRecord>(schm); ByteArrayOutputStream out = new ByteArrayOutputStream(); // keep record.a null and see if that works Encoder e = factory.directBinaryEncoder(out, null); AnotherSampleRecord a = new AnotherSampleRecord(); writer.write(a, e); AnotherSampleRecord b = new AnotherSampleRecord(10); writer.write(b, e); e.flush(); ReflectDatumReader<AnotherSampleRecord> reader = new ReflectDatumReader<AnotherSampleRecord>(schm); ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); Decoder d = DecoderFactory.get().binaryDecoder(in, null); AnotherSampleRecord decoded = reader.read(null, d); assertEquals(a, decoded); decoded = reader.read(null, d); assertEquals(b, decoded); } @Test public void testDisableUnsafe() throws Exception { String saved = System.getProperty("avro.disable.unsafe"); try { System.setProperty("avro.disable.unsafe", "true"); ReflectData.ACCESSOR_CACHE.clear(); ReflectionUtil.resetFieldAccess(); testMultipleAnnotations(); testRecordWithNullIO(); } finally { if (saved == null) System.clearProperty("avro.disable.unsafe"); else System.setProperty("avro.disable.unsafe", saved); ReflectData.ACCESSOR_CACHE.clear(); ReflectionUtil.resetFieldAccess(); } } public static class SampleRecord { public int x = 1; private int y = 2; @Override public int hashCode() { return x + y; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; final SampleRecord other = (SampleRecord)obj; if (x != other.x) return false; if (y != other.y) return false; return true; } public static class AnotherSampleRecord { private Integer a = null; private SampleRecord s = null; public AnotherSampleRecord() { } AnotherSampleRecord(Integer a) { this.a = a; this.s = new SampleRecord(); } @Override public int hashCode() { int hash = (a != null ? a.hashCode() : 0); hash += (s != null ? s.hashCode() : 0); return hash; } @Override public boolean equals(Object other) { if (other instanceof AnotherSampleRecord) { AnotherSampleRecord o = (AnotherSampleRecord) other; if ( (this.a == null && o.a != null) || (this.a != null && !this.a.equals(o.a)) || (this.s == null && o.s != null) || (this.s != null && !this.s.equals(o.s)) ) { return false; } return true; } else { return false; } } } } public static class X { int i; } public static class B1 { X x; } public static class B2 { X x; } public static class A { B1 b1; B2 b2; } public static interface C { void foo(A a); } @Test public void testForwardReference() { ReflectData data = ReflectData.get(); Protocol reflected = data.getProtocol(C.class); Protocol reparsed = Protocol.parse(reflected.toString()); assertEquals(reflected, reparsed); assert(reparsed.getTypes().contains(data.getSchema(A.class))); assert(reparsed.getTypes().contains(data.getSchema(B1.class))); assert(reparsed.getTypes().contains(data.getSchema(B2.class))); assert(reparsed.getTypes().contains(data.getSchema(X.class))); } public static interface P3 { void m1(); void m1(int x); } @Test(expected=AvroTypeException.class) public void testOverloadedMethod() { ReflectData.get().getProtocol(P3.class); } @Test public void testNoPackageSchema() throws Exception { ReflectData.get().getSchema(Class.forName("NoPackage")); } @Test public void testNoPackageProtocol() throws Exception { ReflectData.get().getProtocol(Class.forName("NoPackage")); } public static class Y { int i; } @Test /** Test nesting of reflect data within generic. */ public void testReflectWithinGeneric() throws Exception { ReflectData data = ReflectData.get(); // define a record with a field that's a specific Y Schema schema = Schema.createRecord("Foo", "", "x.y.z", false); List<Schema.Field> fields = new ArrayList<Schema.Field>(); fields.add(new Schema.Field("f", data.getSchema(Y.class), "", null)); schema.setFields(fields); // create a generic instance of this record Y y = new Y(); y.i = 1; GenericData.Record record = new GenericData.Record(schema); record.put("f", y); // test that this instance can be written & re-read checkBinary(schema, record); } @Test public void testPrimitiveArray() throws Exception { testPrimitiveArrays(false); } @Test public void testPrimitiveArrayBlocking() throws Exception { testPrimitiveArrays(true); } private void testPrimitiveArrays(boolean blocking) throws Exception { testPrimitiveArray(boolean.class, blocking); testPrimitiveArray(byte.class, blocking); testPrimitiveArray(short.class, blocking); testPrimitiveArray(char.class, blocking); testPrimitiveArray(int.class, blocking); testPrimitiveArray(long.class, blocking); testPrimitiveArray(float.class, blocking); testPrimitiveArray(double.class, blocking); } private void testPrimitiveArray(Class<?> c, boolean blocking) throws Exception { ReflectData data = new ReflectData(); Random r = new Random(); int size = 200; Object array = Array.newInstance(c, size); Schema s = data.getSchema(array.getClass()); for(int i = 0; i < size; i++) { Array.set(array, i, randomFor(c, r)); } checkBinary(data, s, array, false, blocking); } private Object randomFor(Class<?> c, Random r) { if (c == boolean.class) return r.nextBoolean(); if (c == int.class) return r.nextInt(); if (c == long.class) return r.nextLong(); if (c == byte.class) return (byte)r.nextInt(); if (c == float.class) return r.nextFloat(); if (c == double.class) return r.nextDouble(); if (c == char.class) return (char)r.nextInt(); if (c == short.class) return (short)r.nextInt(); return null; } /** Test union of null and an array. */ @Test public void testNullArray() throws Exception { String json = "[{\"type\":\"array\", \"items\": \"long\"}, \"null\"]"; Schema schema = new Schema.Parser().parse(json); checkBinary(schema, null); } /** Test stringable classes. */ @Test public void testStringables() throws Exception { checkStringable(java.math.BigDecimal.class, "10"); checkStringable(java.math.BigInteger.class, "20"); checkStringable(java.net.URI.class, "foo://bar:9000/baz"); checkStringable(java.net.URL.class, "http://bar:9000/baz"); checkStringable(java.io.File.class, "foo.bar"); } @SuppressWarnings({ "unchecked", "rawtypes" }) public void checkStringable(Class c, String value) throws Exception { ReflectData data = new ReflectData(); Schema schema = data.getSchema(c); assertEquals ("{\"type\":\"string\",\"java-class\":\""+c.getName()+"\"}", schema.toString()); checkBinary(schema, c.getConstructor(String.class).newInstance(value)); } public static class M1 { Map<Integer, String> integerKeyMap; Map<java.math.BigInteger, String> bigIntegerKeyMap; Map<java.math.BigDecimal, String> bigDecimalKeyMap; Map<java.io.File, String> fileKeyMap; } /** Test Map with stringable key classes. */ @Test public void testStringableMapKeys() throws Exception { M1 record = new M1(); record.integerKeyMap = new HashMap<Integer, String>(1); record.integerKeyMap.put(10, "foo"); record.bigIntegerKeyMap = new HashMap<java.math.BigInteger, String>(1); record.bigIntegerKeyMap.put(java.math.BigInteger.TEN, "bar"); record.bigDecimalKeyMap = new HashMap<java.math.BigDecimal, String>(1); record.bigDecimalKeyMap.put(java.math.BigDecimal.ONE, "bigDecimal"); record.fileKeyMap = new HashMap<java.io.File, String>(1); record.fileKeyMap.put(new java.io.File("foo.bar"), "file"); ReflectData data = new ReflectData().addStringable(Integer.class); checkBinary(data, data.getSchema(M1.class), record, true); } public static class NullableStringable { java.math.BigDecimal number; } @Test public void testNullableStringableField() throws Exception { NullableStringable datum = new NullableStringable(); datum.number = java.math.BigDecimal.TEN; Schema schema = ReflectData.AllowNull.get().getSchema(NullableStringable.class); checkBinary(schema, datum); } public static void checkBinary(ReflectData reflectData, Schema schema, Object datum, boolean equals) throws IOException { checkBinary(reflectData, schema, datum, equals, false); } private static void checkBinary(ReflectData reflectData, Schema schema, Object datum, boolean equals, boolean blocking) throws IOException { ReflectDatumWriter<Object> writer = new ReflectDatumWriter<Object>(schema); ByteArrayOutputStream out = new ByteArrayOutputStream(); if (!blocking) { writer.write(datum, EncoderFactory.get().directBinaryEncoder(out, null)); } else { writer.write(datum, new EncoderFactory().configureBlockSize(64) .blockingBinaryEncoder(out, null)); } writer.write(datum, EncoderFactory.get().directBinaryEncoder(out, null)); byte[] data = out.toByteArray(); ReflectDatumReader<Object> reader = new ReflectDatumReader<Object>(schema); Object decoded = reader.read(null, DecoderFactory.get().binaryDecoder(data, null)); assertEquals(0, reflectData.compare(datum, decoded, schema, equals)); } public static void checkBinary(Schema schema, Object datum) throws IOException { checkBinary(ReflectData.get(), schema, datum, false); } /** Test that the error message contains the name of the class. */ @Test public void testReflectFieldError() throws Exception { Object datum = ""; try { ReflectData.get().getField(datum, "notAFieldOfString", 0); } catch (AvroRuntimeException e) { assertTrue(e.getMessage().contains(datum.getClass().getName())); } } @AvroAlias(alias="a", space="b") private static class AliasA { } @AvroAlias(alias="a", space="") private static class AliasB { } @AvroAlias(alias="a") private static class AliasC { } @Test public void testAvroAlias() { check(AliasA.class, "{\"type\":\"record\",\"name\":\"AliasA\",\"namespace\":\"org.apache.avro.reflect.TestReflect$\",\"fields\":[],\"aliases\":[\"b.a\"]}"); check(AliasB.class, "{\"type\":\"record\",\"name\":\"AliasB\",\"namespace\":\"org.apache.avro.reflect.TestReflect$\",\"fields\":[],\"aliases\":[\"a\"]}"); check(AliasC.class, "{\"type\":\"record\",\"name\":\"AliasC\",\"namespace\":\"org.apache.avro.reflect.TestReflect$\",\"fields\":[],\"aliases\":[\"a\"]}"); } private static class DefaultTest { @AvroDefault("1") int foo; } @Test public void testAvroDefault() { check(DefaultTest.class, "{\"type\":\"record\",\"name\":\"DefaultTest\"," +"\"namespace\":\"org.apache.avro.reflect.TestReflect$\",\"fields\":[" +"{\"name\":\"foo\",\"type\":\"int\",\"default\":1}]}"); } }