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;


Reply via email to