This is an automated email from the ASF dual-hosted git repository.
fokko pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/avro.git
The following commit(s) were added to refs/heads/master by this push:
new de48a0a AVRO-2357: Allow generic types in reflect protos (#490)
de48a0a is described below
commit de48a0a8a0343b61fdb255011fde38c619761046
Author: ivangreene <[email protected]>
AuthorDate: Sun Mar 31 11:53:25 2019 -0500
AVRO-2357: Allow generic types in reflect protos (#490)
Adds support for generic types in ReflectData for
Protocols.
---
.../java/org/apache/avro/reflect/ReflectData.java | 38 ++++++++-----------
.../org/apache/avro/reflect/ReflectionUtil.java | 37 ++++++++++++++++++
.../org/apache/avro/reflect/TestReflectData.java | 44 ++++++++++++++++++++++
3 files changed, 97 insertions(+), 22 deletions(-)
diff --git
a/lang/java/avro/src/main/java/org/apache/avro/reflect/ReflectData.java
b/lang/java/avro/src/main/java/org/apache/avro/reflect/ReflectData.java
index af1da1d..5d0d0a6 100644
--- a/lang/java/avro/src/main/java/org/apache/avro/reflect/ReflectData.java
+++ b/lang/java/avro/src/main/java/org/apache/avro/reflect/ReflectData.java
@@ -26,6 +26,7 @@ import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
@@ -769,13 +770,15 @@ public class ReflectData extends SpecificData {
iface.getPackage() == null ? "" : iface.getPackage().getName());
Map<String, Schema> names = new LinkedHashMap<>();
Map<String, Message> messages = protocol.getMessages();
- for (Method method : iface.getMethods())
+ Map<TypeVariable<?>, Type> genericTypeVariableMap =
ReflectionUtil.resolveTypeVariables(iface);
+ for (Method method : iface.getMethods()) {
if ((method.getModifiers() & Modifier.STATIC) == 0) {
String name = method.getName();
if (messages.containsKey(name))
throw new AvroTypeException("Two methods with same name: " + name);
- messages.put(name, getMessage(method, protocol, names));
+ messages.put(name, getMessage(method, protocol, names,
genericTypeVariableMap));
}
+ }
// reverse types, since they were defined in reference order
List<Schema> types = new ArrayList<>(names.values());
@@ -785,24 +788,13 @@ public class ReflectData extends SpecificData {
return protocol;
}
- private String[] getParameterNames(Method m) {
- Parameter[] parameters = m.getParameters();
- String[] paramNames = new String[parameters.length];
- for (int i = 0; i < parameters.length; i++) {
- paramNames[i] = parameters[i].getName();
- }
- return paramNames;
- }
-
- private Message getMessage(Method method, Protocol protocol, Map<String,
Schema> names) {
+ private Message getMessage(Method method, Protocol protocol, Map<String,
Schema> names,
+ Map<? extends Type, Type> genericTypeMap) {
List<Schema.Field> fields = new ArrayList<>();
- String[] paramNames = getParameterNames(method);
- Type[] paramTypes = method.getGenericParameterTypes();
- Annotation[][] annotations = method.getParameterAnnotations();
- for (int i = 0; i < paramTypes.length; i++) {
- Schema paramSchema = getSchema(paramTypes[i], names);
- for (int j = 0; j < annotations[i].length; j++) {
- Annotation annotation = annotations[i][j];
+ for (Parameter parameter : method.getParameters()) {
+ Schema paramSchema =
getSchema(genericTypeMap.getOrDefault(parameter.getParameterizedType(),
parameter.getType()),
+ names);
+ for (Annotation annotation : parameter.getAnnotations()) {
if (annotation instanceof AvroSchema) // explicit schema
paramSchema = new Schema.Parser().parse(((AvroSchema)
annotation).value());
else if (annotation instanceof Union) // union
@@ -810,13 +802,15 @@ public class ReflectData extends SpecificData {
else if (annotation instanceof Nullable) // nullable
paramSchema = makeNullable(paramSchema);
}
- String paramName = paramNames.length == paramTypes.length ?
paramNames[i] : paramSchema.getName() + i;
- fields.add(new Schema.Field(paramName, paramSchema, null /* doc */,
null));
+ fields.add(new Schema.Field(parameter.getName(), paramSchema, null /*
doc */, null));
}
+
Schema request = Schema.createRecord(fields);
+ Type genericReturnType = method.getGenericReturnType();
+ Type returnType = genericTypeMap.getOrDefault(genericReturnType,
genericReturnType);
Union union = method.getAnnotation(Union.class);
- Schema response = union == null ? getSchema(method.getGenericReturnType(),
names) : getAnnotatedUnion(union, names);
+ Schema response = union == null ? getSchema(returnType, names) :
getAnnotatedUnion(union, names);
if (method.isAnnotationPresent(Nullable.class)) // nullable
response = makeNullable(response);
diff --git
a/lang/java/avro/src/main/java/org/apache/avro/reflect/ReflectionUtil.java
b/lang/java/avro/src/main/java/org/apache/avro/reflect/ReflectionUtil.java
index 2a859f1..1138c3b 100644
--- a/lang/java/avro/src/main/java/org/apache/avro/reflect/ReflectionUtil.java
+++ b/lang/java/avro/src/main/java/org/apache/avro/reflect/ReflectionUtil.java
@@ -19,6 +19,12 @@ package org.apache.avro.reflect;
import org.apache.avro.AvroRuntimeException;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.util.IdentityHashMap;
+import java.util.Map;
+
/**
* A few utility methods for using @link{java.misc.Unsafe}, mostly for private
* use.
@@ -119,4 +125,35 @@ class ReflectionUtil {
}
}
+ /**
+ * For an interface, get a map of any {@link TypeVariable}s to their actual
+ * types.
+ *
+ * @param iface interface to resolve types for.
+ * @return a map of {@link TypeVariable}s to actual types.
+ */
+ protected static Map<TypeVariable<?>, Type> resolveTypeVariables(Class<?>
iface) {
+ return resolveTypeVariables(iface, new IdentityHashMap<>());
+ }
+
+ private static Map<TypeVariable<?>, Type> resolveTypeVariables(Class<?>
iface, Map<TypeVariable<?>, Type> reuse) {
+
+ for (Type type : iface.getGenericInterfaces()) {
+ if (type instanceof ParameterizedType) {
+ ParameterizedType parameterizedType = (ParameterizedType) type;
+ Type rawType = parameterizedType.getRawType();
+ if (rawType instanceof Class<?>) {
+ Class<?> classType = (Class<?>) rawType;
+ TypeVariable<? extends Class<?>>[] typeParameters =
classType.getTypeParameters();
+ Type[] actualTypeArguments =
parameterizedType.getActualTypeArguments();
+ for (int i = 0; i < typeParameters.length; i++) {
+ reuse.putIfAbsent(typeParameters[i],
reuse.getOrDefault(actualTypeArguments[i], actualTypeArguments[i]));
+ }
+ resolveTypeVariables(classType, reuse);
+ }
+ }
+ }
+ return reuse;
+ }
+
}
diff --git
a/lang/java/avro/src/test/java/org/apache/avro/reflect/TestReflectData.java
b/lang/java/avro/src/test/java/org/apache/avro/reflect/TestReflectData.java
index 5aa8134..024d842 100644
--- a/lang/java/avro/src/test/java/org/apache/avro/reflect/TestReflectData.java
+++ b/lang/java/avro/src/test/java/org/apache/avro/reflect/TestReflectData.java
@@ -18,11 +18,15 @@
package org.apache.avro.reflect;
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.lessThan;
import static org.junit.Assert.assertThat;
import java.util.Collections;
+import org.apache.avro.Protocol;
import org.apache.avro.Schema;
import org.junit.Test;
@@ -46,4 +50,44 @@ public class TestReflectData {
assertThat("ReflectData cache should release references",
classData.bySchema.size(), lessThan(numSchemas));
}
+
+ @Test
+ public void testGenericProtocol() {
+ Protocol protocol = ReflectData.get().getProtocol(FooBarProtocol.class);
+ Schema recordSchema =
ReflectData.get().getSchema(FooBarReflectiveRecord.class);
+
+ assertThat(protocol.getTypes(), contains(recordSchema));
+
+ assertThat(protocol.getMessages().keySet(), containsInAnyOrder("store",
"findById", "exists"));
+
+ Schema.Field storeArgument =
protocol.getMessages().get("store").getRequest().getFields().get(0);
+ assertThat(storeArgument.schema(), equalTo(recordSchema));
+
+ Schema.Field findByIdArgument =
protocol.getMessages().get("findById").getRequest().getFields().get(0);
+ assertThat(findByIdArgument.schema(),
equalTo(Schema.create(Schema.Type.STRING)));
+
+ Schema findByIdResponse =
protocol.getMessages().get("findById").getResponse();
+ assertThat(findByIdResponse, equalTo(recordSchema));
+
+ Schema.Field existsArgument =
protocol.getMessages().get("exists").getRequest().getFields().get(0);
+ assertThat(existsArgument.schema(),
equalTo(Schema.create(Schema.Type.STRING)));
+ }
+
+ private interface CrudProtocol<R, I> extends OtherProtocol<I> {
+ void store(R record);
+
+ R findById(I id);
+ }
+
+ private interface OtherProtocol<G> {
+ boolean exists(G id);
+ }
+
+ private interface FooBarProtocol extends OtherProtocol<String>,
CrudProtocol<FooBarReflectiveRecord, String> {
+ }
+
+ private static class FooBarReflectiveRecord {
+ private String bar;
+ private int baz;
+ }
}