Author: cutting
Date: Thu Dec 10 22:07:10 2009
New Revision: 889442
URL: http://svn.apache.org/viewvc?rev=889442&view=rev
Log:
AVRO-250. Make reflect's Union annotation applicable to message paramters and
return types.
Modified:
hadoop/avro/trunk/CHANGES.txt
hadoop/avro/trunk/src/java/org/apache/avro/reflect/ReflectData.java
hadoop/avro/trunk/src/java/org/apache/avro/reflect/ReflectDatumWriter.java
hadoop/avro/trunk/src/java/org/apache/avro/reflect/Union.java
hadoop/avro/trunk/src/java/org/apache/avro/specific/SpecificData.java
hadoop/avro/trunk/src/test/java/org/apache/avro/TestReflect.java
Modified: hadoop/avro/trunk/CHANGES.txt
URL:
http://svn.apache.org/viewvc/hadoop/avro/trunk/CHANGES.txt?rev=889442&r1=889441&r2=889442&view=diff
==============================================================================
--- hadoop/avro/trunk/CHANGES.txt (original)
+++ hadoop/avro/trunk/CHANGES.txt Thu Dec 10 22:07:10 2009
@@ -129,6 +129,9 @@
AVRO-246 Java schema parser should take schema from InputStream
in addition to file. (thiru)
+ AVRO-250. Make reflect's Union annotation applicable to message
+ parameters and return types too. (cutting)
+
OPTIMIZATIONS
AVRO-172. More efficient schema processing (massie)
Modified: hadoop/avro/trunk/src/java/org/apache/avro/reflect/ReflectData.java
URL:
http://svn.apache.org/viewvc/hadoop/avro/trunk/src/java/org/apache/avro/reflect/ReflectData.java?rev=889442&r1=889441&r2=889442&view=diff
==============================================================================
--- hadoop/avro/trunk/src/java/org/apache/avro/reflect/ReflectData.java
(original)
+++ hadoop/avro/trunk/src/java/org/apache/avro/reflect/ReflectData.java Thu Dec
10 22:07:10 2009
@@ -23,6 +23,7 @@
import java.lang.reflect.Type;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.GenericArrayType;
+import java.lang.annotation.Annotation;
import java.util.Collection;
import java.util.List;
import java.util.ArrayList;
@@ -256,10 +257,7 @@
space = c.getEnclosingClass().getName() + "$";
Union union = (Union)c.getAnnotation(Union.class);
if (union != null) { // union annotated
- List<Schema> branches = new ArrayList<Schema>();
- for (Class branch : union.value())
- branches.add(createSchema(branch, names));
- return Schema.createUnion(branches);
+ return getAnnotatedUnion(union, names);
} else if (c.isAnnotationPresent(Stringable.class)){ // Stringable
Schema result = Schema.create(Schema.Type.STRING);
result.setProp(CLASS_PROP, c.getName());
@@ -304,6 +302,14 @@
schema.setProp(ELEMENT_PROP, c.getName());
}
+ // construct a schema from a union annotation
+ private Schema getAnnotatedUnion(Union union, Map<String,Schema> names) {
+ List<Schema> branches = new ArrayList<Schema>();
+ for (Class branch : union.value())
+ branches.add(createSchema(branch, names));
+ return Schema.createUnion(branches);
+ }
+
// Return of this class and its superclasses to serialize.
// Not cached, since this is only used to create schemas, which are cached.
private Collection<Field> getFields(Class recordClass) {
@@ -358,8 +364,14 @@
new LinkedHashMap<String,Schema.Field>();
String[] paramNames = paranamer.lookupParameterNames(method);
Type[] paramTypes = method.getGenericParameterTypes();
+ Annotation[][] annotations = method.getParameterAnnotations();
for (int i = 0; i < paramTypes.length; i++) {
- Schema paramSchema = getSchema(paramTypes[i], names);
+ Schema paramSchema = null;
+ for (int j = 0; j < annotations[i].length; j++)
+ if (annotations[i][j] instanceof Union)
+ paramSchema = getAnnotatedUnion(((Union)annotations[i][j]), names);
+ if (paramSchema == null)
+ paramSchema = getSchema(paramTypes[i], names);
String paramName = paramNames.length == paramTypes.length
? paramNames[i]
: paramSchema.getName()+i;
@@ -367,7 +379,10 @@
}
Schema request = Schema.createRecord(fields);
- Schema response = getSchema(method.getGenericReturnType(), names);
+ Union union = (Union)method.getAnnotation(Union.class);
+ Schema response = union == null
+ ? getSchema(method.getGenericReturnType(), names)
+ : getAnnotatedUnion(union, names);
List<Schema> errs = new ArrayList<Schema>();
errs.add(Protocol.SYSTEM_ERROR); // every method can throw
Modified:
hadoop/avro/trunk/src/java/org/apache/avro/reflect/ReflectDatumWriter.java
URL:
http://svn.apache.org/viewvc/hadoop/avro/trunk/src/java/org/apache/avro/reflect/ReflectDatumWriter.java?rev=889442&r1=889441&r2=889442&view=diff
==============================================================================
--- hadoop/avro/trunk/src/java/org/apache/avro/reflect/ReflectDatumWriter.java
(original)
+++ hadoop/avro/trunk/src/java/org/apache/avro/reflect/ReflectDatumWriter.java
Thu Dec 10 22:07:10 2009
@@ -112,7 +112,14 @@
throws IOException {
if (datum instanceof Short)
datum = ((Short)datum).intValue();
- super.write(schema, datum, out);
+ try {
+ super.write(schema, datum, out);
+ } catch (NullPointerException e) { // improve error message
+ NullPointerException result =
+ new NullPointerException("in "+schema.getName()+" "+e.getMessage());
+ result.initCause(e);
+ throw result;
+ }
}
}
Modified: hadoop/avro/trunk/src/java/org/apache/avro/reflect/Union.java
URL:
http://svn.apache.org/viewvc/hadoop/avro/trunk/src/java/org/apache/avro/reflect/Union.java?rev=889442&r1=889441&r2=889442&view=diff
==============================================================================
--- hadoop/avro/trunk/src/java/org/apache/avro/reflect/Union.java (original)
+++ hadoop/avro/trunk/src/java/org/apache/avro/reflect/Union.java Thu Dec 10
22:07:10 2009
@@ -24,12 +24,14 @@
import java.lang.annotation.Target;
/**
- * Declares that a class should be represented by a union type. Use for base
- * classes or interfaces whose instantiable subclasses should be listed in the
- * parameters to the @Union annotation.
+ * Declares that a Java type should be represented by an Avro union schema.
+ * May be used for base classes or interfaces whose instantiable subclasses can
+ * be listed in the parameters to the @Union annotation. If applied to method
+ * parameters this determines the reflected message parameter type. If applied
+ * to a method, this determines its return type.
*/
@Retention(RetentionPolicy.RUNTIME)
-...@target({ElementType.TYPE})
+...@target({ElementType.TYPE, ElementType.PARAMETER, ElementType.METHOD})
@Documented
public @interface Union {
/** The instantiable classes that compose this union. */
Modified: hadoop/avro/trunk/src/java/org/apache/avro/specific/SpecificData.java
URL:
http://svn.apache.org/viewvc/hadoop/avro/trunk/src/java/org/apache/avro/specific/SpecificData.java?rev=889442&r1=889441&r2=889442&view=diff
==============================================================================
--- hadoop/avro/trunk/src/java/org/apache/avro/specific/SpecificData.java
(original)
+++ hadoop/avro/trunk/src/java/org/apache/avro/specific/SpecificData.java Thu
Dec 10 22:07:10 2009
@@ -19,6 +19,7 @@
import java.util.Iterator;
import java.util.Map;
+import java.util.List;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.LinkedHashMap;
@@ -82,6 +83,8 @@
private Map<String,Class> classCache = new ConcurrentHashMap<String,Class>();
+ private static final Schema NULL_SCHEMA = Schema.create(Schema.Type.NULL);
+
/** Return the class that implements a schema. */
public Class getClass(Schema schema) {
switch (schema.getType()) {
@@ -101,7 +104,11 @@
return c;
case ARRAY: return GenericArray.class;
case MAP: return Map.class;
- case UNION: return Object.class;
+ case UNION:
+ List<Schema> types = schema.getTypes(); // elide unions with null
+ if ((types.size() == 2) && types.contains(NULL_SCHEMA))
+ return getClass(types.get(types.get(0).equals(NULL_SCHEMA) ? 1 : 0));
+ return Object.class;
case STRING: return Utf8.class;
case BYTES: return ByteBuffer.class;
case INT: return Integer.TYPE;
Modified: hadoop/avro/trunk/src/test/java/org/apache/avro/TestReflect.java
URL:
http://svn.apache.org/viewvc/hadoop/avro/trunk/src/test/java/org/apache/avro/TestReflect.java?rev=889442&r1=889441&r2=889442&view=diff
==============================================================================
--- hadoop/avro/trunk/src/test/java/org/apache/avro/TestReflect.java (original)
+++ hadoop/avro/trunk/src/test/java/org/apache/avro/TestReflect.java Thu Dec 10
22:07:10 2009
@@ -181,7 +181,7 @@
checkReadWrite(r5);
}
- // test union annotation
+ // test union annotation on a class
@Union({R7.class, R8.class})
public static class R6 {}
@@ -200,7 +200,7 @@
}
}
- // test arrays with union annotation
+ // test arrays of union annotated class
public static class R9 {
public R6[] r6s;
public boolean equals(Object o) {
@@ -221,6 +221,31 @@
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();
+ Schema param = request.getFields().get("s").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;