Skip to content

Commit 574cf48

Browse files
committed
Cleaning up and adding some tests
1 parent acd4b65 commit 574cf48

File tree

7 files changed

+491
-15
lines changed

7 files changed

+491
-15
lines changed

lang/java/avro/src/main/java/org/apache/avro/reflect/CustomEncoding.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,4 +57,8 @@ protected Schema getSchema() {
5757
return schema;
5858
}
5959

60+
public CustomEncoding<T> setSchema(Schema schema) {
61+
return this;
62+
}
63+
6064
}

lang/java/avro/src/main/java/org/apache/avro/reflect/ReflectData.java

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -690,13 +690,10 @@ protected Schema createSchema(Type type, Map<String, Schema> names) {
690690
AvroSchema explicit = c.getAnnotation(AvroSchema.class);
691691
if (explicit != null) // explicit schema
692692
return new Schema.Parser().parse(explicit.value());
693-
AvroEncode custom = getAvroEncode(c);
694-
if (custom != null) {
695-
try {
696-
return custom.using().getDeclaredConstructor().newInstance().getSchema();
697-
} catch (ReflectiveOperationException e) {
698-
throw new AvroRuntimeException(e);
699-
}
693+
CustomEncoding<?> custom = getCustomEncoding(c);
694+
// if custom encoding does not specify the schema use the default schema
695+
if (custom != null && custom.getSchema() != null) {
696+
return custom.getSchema();
700697
}
701698
if (CharSequence.class.isAssignableFrom(c)) // String
702699
return Schema.create(Schema.Type.STRING);

lang/java/avro/src/main/java/org/apache/avro/reflect/ReflectRecordEncoding.java

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,23 @@
3434
import org.apache.avro.io.Encoder;
3535
import org.apache.avro.io.ResolvingDecoder;
3636

37-
public class ReflectRecordEncoding extends CustomEncoding {
37+
public class ReflectRecordEncoding extends CustomEncoding<Object> {
3838

39+
private final Class<?> type;
3940
private final List<FieldWriter> writer;
4041
private final Constructor<?> constructor;
4142
private List<FieldReader> reader;
4243

43-
public ReflectRecordEncoding(Schema schema, Class<?> type) {
44-
this.schema = schema;
44+
public ReflectRecordEncoding(Class<?> type) {
45+
this.type = type;
46+
this.writer = null;
47+
this.constructor = null;
48+
49+
}
4550

51+
public ReflectRecordEncoding(Class<?> type, Schema schema) {
52+
this.type = type;
53+
this.schema = schema;
4654
this.writer = schema.getFields().stream().map(field -> {
4755
try {
4856
Field classField = type.getDeclaredField(field.name());
@@ -94,6 +102,11 @@ public ReflectRecordEncoding(Schema schema, Class<?> type) {
94102
}).collect(Collectors.toList());
95103
}
96104

105+
@Override
106+
public CustomEncoding<Object> setSchema(Schema schema) {
107+
return new ReflectRecordEncoding(type, schema);
108+
}
109+
97110
@Override
98111
protected void write(Object datum, Encoder out) throws IOException {
99112
throw new UnsupportedOperationException("No writer specified");

lang/java/avro/src/main/java/org/apache/avro/specific/SpecificData.java

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -301,12 +301,18 @@ public Class getClass(Schema schema) {
301301
}
302302
}
303303

304-
protected static AvroEncode getAvroEncode(Class<?> c) {
304+
private static AvroEncode getAvroEncode(Class<?> c) {
305305
while (c != null && !c.equals(Object.class)) {
306306
AvroEncode avroEncode = c.getAnnotation(AvroEncode.class);
307307
if (avroEncode != null) {
308308
return avroEncode;
309309
}
310+
if (c.getSuperclass() != null) {
311+
avroEncode = getAvroEncode(c.getSuperclass());
312+
}
313+
if (avroEncode != null) {
314+
return avroEncode;
315+
}
310316
for (Class<?> inter : c.getInterfaces()) {
311317
avroEncode = getAvroEncode(inter);
312318
if (avroEncode != null) {
@@ -320,13 +326,32 @@ protected static AvroEncode getAvroEncode(Class<?> c) {
320326

321327
}
322328

329+
protected static CustomEncoding<?> getCustomEncoding(Class<?> c) {
330+
try {
331+
AvroEncode avroEncode = getAvroEncode(c);
332+
if (avroEncode != null) {
333+
// first see if constructor that takes the class as an argument exists
334+
try {
335+
return avroEncode.using().getDeclaredConstructor(Class.class).newInstance(c);
336+
} catch (NoSuchMethodException e) {
337+
// zero argument constructor
338+
return avroEncode.using().getDeclaredConstructor().newInstance();
339+
}
340+
} else {
341+
return null;
342+
}
343+
} catch (ReflectiveOperationException e) {
344+
throw new AvroRuntimeException(e);
345+
}
346+
}
347+
323348
private static CustomEncoding<?> extractCustomEncoder(Schema schema, Class<?> c) {
324-
AvroEncode customEncode = getAvroEncode(c);
349+
CustomEncoding<?> customEncoding = getCustomEncoding(c);
325350
try {
326-
if (customEncode != null) {
327-
return customEncode.using().getDeclaredConstructor().newInstance();
351+
if (customEncoding != null) {
352+
return customEncoding.setSchema(schema);
328353
} else if (IS_RECORD_METHOD != null && IS_RECORD_METHOD.invoke(c).equals(true)) {
329-
return new ReflectRecordEncoding(schema, c);
354+
return new ReflectRecordEncoding(c).setSchema(schema);
330355
}
331356
} catch (ReflectiveOperationException e) {
332357
throw new AvroRuntimeException(e);
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* https://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
package org.apache.avro.reflect;
20+
21+
import static org.junit.Assert.assertEquals;
22+
import static org.junit.Assert.assertNotNull;
23+
24+
import java.io.ByteArrayInputStream;
25+
import java.io.ByteArrayOutputStream;
26+
import java.io.IOException;
27+
import java.io.UncheckedIOException;
28+
import java.util.Arrays;
29+
30+
import org.apache.avro.Schema;
31+
import org.apache.avro.file.DataFileStream;
32+
import org.apache.avro.file.DataFileWriter;
33+
import org.apache.avro.io.DatumReader;
34+
import org.apache.avro.io.Decoder;
35+
import org.apache.avro.io.Encoder;
36+
import org.junit.Test;
37+
38+
public class TestCustomEncodingClassAnnotation {
39+
40+
@Test
41+
public void testEncoderOnTopLevelField() throws IOException {
42+
Custom in = new Custom("hello world");
43+
byte[] encoded = write(in);
44+
Custom decoded = read(encoded);
45+
46+
assertNotNull(decoded);
47+
assertEquals("hello world", decoded.getField());
48+
}
49+
50+
@Test
51+
public void testWhenWrapped() throws IOException {
52+
CustomWrapper in = new CustomWrapper(new Custom("hello world"));
53+
byte[] encoded = write(in);
54+
CustomWrapper decoded = read(encoded);
55+
56+
assertNotNull(decoded);
57+
assertEquals("hello world", decoded.getField().getField());
58+
}
59+
60+
@Test
61+
public void testEncoderSpecifiedInWrapperWins() throws IOException {
62+
CustomWrapperWithEncoder in = new CustomWrapperWithEncoder(new Custom("hello world"));
63+
byte[] encoded = write(in);
64+
CustomWrapperWithEncoder decoded = read(encoded);
65+
66+
assertNotNull(decoded);
67+
assertEquals("Override", decoded.getField().getField());
68+
}
69+
70+
private <T> T read(byte[] toDecode) throws IOException {
71+
DatumReader<T> datumReader = new ReflectDatumReader<>();
72+
try (DataFileStream<T> dataFileReader = new DataFileStream<>(new ByteArrayInputStream(toDecode, 0, toDecode.length),
73+
datumReader);) {
74+
dataFileReader.hasNext();
75+
return dataFileReader.next();
76+
}
77+
}
78+
79+
private <T> byte[] write(T custom) {
80+
Schema schema = ReflectData.get().getSchema(custom.getClass());
81+
ReflectDatumWriter<T> datumWriter = new ReflectDatumWriter<>();
82+
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
83+
DataFileWriter<T> writer = new DataFileWriter<>(datumWriter)) {
84+
writer.create(schema, baos);
85+
writer.append(custom);
86+
writer.flush();
87+
return baos.toByteArray();
88+
} catch (IOException e) {
89+
throw new UncheckedIOException(e);
90+
}
91+
}
92+
93+
@AvroEncode(using = CustomEncoder.class)
94+
public static class Custom {
95+
96+
private final String field;
97+
98+
public Custom(String field) {
99+
this.field = field;
100+
}
101+
102+
public String getField() {
103+
return field;
104+
}
105+
}
106+
107+
public static class CustomWrapper {
108+
109+
private Custom field;
110+
111+
public CustomWrapper() {
112+
}
113+
114+
public CustomWrapper(Custom field) {
115+
super();
116+
this.field = field;
117+
}
118+
119+
public Custom getField() {
120+
return field;
121+
}
122+
123+
}
124+
125+
public static class CustomWrapperWithEncoder {
126+
127+
@AvroEncode(using = CustomEncoder2.class)
128+
private Custom field;
129+
130+
public CustomWrapperWithEncoder() {
131+
}
132+
133+
public CustomWrapperWithEncoder(Custom field) {
134+
super();
135+
this.field = field;
136+
}
137+
138+
public Custom getField() {
139+
return field;
140+
}
141+
142+
}
143+
144+
public static class CustomEncoder extends CustomEncoding<Custom> {
145+
146+
{
147+
schema = Schema.createRecord("Custom", null, "org.apache.avro.reflect.TestCustomEncodingClassAnnotation", false,
148+
Arrays.asList(new Schema.Field("field", Schema.create(Schema.Type.STRING), null, null)));
149+
}
150+
151+
@Override
152+
protected void write(Object datum, Encoder out) throws IOException {
153+
Custom c = (Custom) datum;
154+
out.writeString(c.getField());
155+
156+
}
157+
158+
@Override
159+
protected Custom read(Object reuse, Decoder in) throws IOException {
160+
return new Custom(in.readString());
161+
}
162+
}
163+
164+
public static class CustomEncoder2 extends CustomEncoding<Custom> {
165+
166+
{
167+
schema = Schema.createRecord("Custom", null, "org.apache.avro.reflect.TestCustomEncodingClassAnnotation", false,
168+
Arrays.asList(new Schema.Field("field", Schema.create(Schema.Type.STRING), null, null)));
169+
}
170+
171+
@Override
172+
protected void write(Object datum, Encoder out) throws IOException {
173+
Custom c = (Custom) datum;
174+
out.writeString(c.getField());
175+
176+
}
177+
178+
@Override
179+
protected Custom read(Object reuse, Decoder in) throws IOException {
180+
in.readString();
181+
return new Custom("Override");
182+
}
183+
}
184+
185+
}

0 commit comments

Comments
 (0)