Author: cutting
Date: Wed Nov 21 22:29:42 2012
New Revision: 1412334

URL: http://svn.apache.org/viewvc?rev=1412334&view=rev
Log:
AVRO-1201. Java: Fix GenericData#toString() to generate valid JSON for enum 
values. Contributed by Sharmarke Aden.

Added:
    avro/trunk/lang/java/avro/src/test/java/org/apache/avro/TypeEnum.java   
(with props)
Modified:
    avro/trunk/CHANGES.txt
    
avro/trunk/lang/java/avro/src/main/java/org/apache/avro/generic/GenericData.java
    
avro/trunk/lang/java/avro/src/test/java/org/apache/avro/FooBarSpecificRecord.java
    
avro/trunk/lang/java/avro/src/test/java/org/apache/avro/specific/TestSpecificData.java
    avro/trunk/lang/java/avro/src/test/resources/FooBarSpecificRecord.avsc

Modified: avro/trunk/CHANGES.txt
URL: 
http://svn.apache.org/viewvc/avro/trunk/CHANGES.txt?rev=1412334&r1=1412333&r2=1412334&view=diff
==============================================================================
--- avro/trunk/CHANGES.txt (original)
+++ avro/trunk/CHANGES.txt Wed Nov 21 22:29:42 2012
@@ -49,6 +49,9 @@ Trunk (not yet released)
     compare against next.  Also improve GenericData#deepCopy() to be
     generic, so that its return type matches its parameter type. (cutting)
 
+    AVRO-1201. Java: Fix GenericData#toString() to generate valid JSON for
+    enum values. (Sharmarke Aden via cutting)
+
 Avro 1.7.2 (20 October 2012)
 
   NEW FEATURES

Modified: 
avro/trunk/lang/java/avro/src/main/java/org/apache/avro/generic/GenericData.java
URL: 
http://svn.apache.org/viewvc/avro/trunk/lang/java/avro/src/main/java/org/apache/avro/generic/GenericData.java?rev=1412334&r1=1412333&r2=1412334&view=diff
==============================================================================
--- 
avro/trunk/lang/java/avro/src/main/java/org/apache/avro/generic/GenericData.java
 (original)
+++ 
avro/trunk/lang/java/avro/src/main/java/org/apache/avro/generic/GenericData.java
 Wed Nov 21 22:29:42 2012
@@ -142,14 +142,18 @@ public class GenericData {
         addAll(c);
       }
     }
+    @Override
     public Schema getSchema() { return schema; }
     @Override public int size() { return size; }
     @Override public void clear() { size = 0; }
     @Override public Iterator<T> iterator() {
       return new Iterator<T>() {
         private int position = 0;
+        @Override
         public boolean hasNext() { return position < size; }
+        @Override
         public T next() { return (T)elements[position++]; }
+        @Override
         public void remove() { throw new UnsupportedOperationException(); }
       };
     }
@@ -196,12 +200,15 @@ public class GenericData {
       elements[size] = null;
       return result;
     }
+    @Override
     public T peek() {
       return (size < elements.length) ? (T)elements[size] : null;
     }
+    @Override
     public int compareTo(GenericArray<T> that) {
       return GenericData.get().compare(this, that, this.getSchema());
     }
+    @Override
     public void reverse() {
       int left = 0;
       int right = elements.length - 1;
@@ -217,7 +224,7 @@ public class GenericData {
     }
     @Override
     public String toString() {
-      StringBuffer buffer = new StringBuffer();
+      StringBuilder buffer = new StringBuilder();
       buffer.append("[");
       int count = 0;
       for (T e : this) {
@@ -253,6 +260,7 @@ public class GenericData {
 
     public void bytes(byte[] bytes) { this.bytes = bytes; }
 
+    @Override
     public byte[] bytes() { return bytes; }
 
     @Override
@@ -268,6 +276,7 @@ public class GenericData {
     @Override
     public String toString() { return Arrays.toString(bytes); }
 
+    @Override
     public int compareTo(Fixed that) {
       return BinaryData.compareBytes(this.bytes, 0, this.bytes.length,
                                      that.bytes, 0, that.bytes.length);
@@ -318,13 +327,13 @@ public class GenericData {
     case ENUM:
       return schema.getEnumSymbols().contains(datum.toString());
     case ARRAY:
-      if (!(datum instanceof Collection)) return false;
+      if (!(isArray(datum))) return false;
       for (Object element : (Collection<?>)datum)
         if (!validate(schema.getElementType(), element))
           return false;
       return true;
     case MAP:
-      if (!(datum instanceof Map)) return false;
+      if (!(isMap(datum))) return false;
       @SuppressWarnings(value="unchecked")
       Map<Object,Object> map = (Map<Object,Object>)datum;
       for (Map.Entry<Object,Object> entry : map.entrySet())
@@ -341,11 +350,11 @@ public class GenericData {
         && ((GenericFixed)datum).bytes().length==schema.getFixedSize();
     case STRING:  return isString(datum);
     case BYTES:   return isBytes(datum);
-    case INT:     return datum instanceof Integer;
-    case LONG:    return datum instanceof Long;
-    case FLOAT:   return datum instanceof Float;
-    case DOUBLE:  return datum instanceof Double;
-    case BOOLEAN: return datum instanceof Boolean;
+    case INT:     return isInteger(datum);
+    case LONG:    return isLong(datum);
+    case FLOAT:   return isFloat(datum);
+    case DOUBLE:  return isDouble(datum);
+    case BOOLEAN: return isBoolean(datum);
     case NULL:    return datum == null;
     default: return false;
     }
@@ -371,7 +380,7 @@ public class GenericData {
           buffer.append(", ");
       }
       buffer.append("}");
-    } else if (datum instanceof Collection) {
+    } else if (isArray(datum)) {
       Collection<?> array = (Collection<?>)datum;
       buffer.append("[");
       long last = array.size()-1;
@@ -382,7 +391,7 @@ public class GenericData {
           buffer.append(", ");
       }        
       buffer.append("]");
-    } else if (datum instanceof Map) {
+    } else if (isMap(datum)) {
       buffer.append("{");
       int count = 0;
       @SuppressWarnings(value="unchecked")
@@ -395,12 +404,11 @@ public class GenericData {
           buffer.append(", ");
       }
       buffer.append("}");
-    } else if (datum instanceof CharSequence
-               || datum instanceof GenericEnumSymbol) {
+    } else if (isString(datum)|| isEnum(datum)) {
       buffer.append("\"");
       writeEscapedString(datum.toString(), buffer);
       buffer.append("\"");
-    } else if (datum instanceof ByteBuffer) {
+    } else if (isBytes(datum)) {
       buffer.append("{\"bytes\": \"");
       ByteBuffer bytes = (ByteBuffer)datum;
       for (int i = bytes.position(); i < bytes.limit(); i++)
@@ -459,7 +467,7 @@ public class GenericData {
   public Schema induce(Object datum) {
     if (isRecord(datum)) {
       return getRecordSchema(datum);
-    } else if (datum instanceof Collection) {
+    } else if (isArray(datum)) {
       Schema elementType = null;
       for (Object element : (Collection<?>)datum) {
         if (elementType == null) {
@@ -473,7 +481,7 @@ public class GenericData {
       }
       return Schema.createArray(elementType);
 
-    } else if (datum instanceof Map) {
+    } else if (isMap(datum)) {
       @SuppressWarnings(value="unchecked")
       Map<Object,Object> map = (Map<Object,Object>)datum;
       Schema value = null;
@@ -492,13 +500,13 @@ public class GenericData {
       return Schema.createFixed(null, null, null,
                                 ((GenericFixed)datum).bytes().length);
     }
-    else if (datum instanceof CharSequence) return Schema.create(Type.STRING);
-    else if (datum instanceof ByteBuffer) return Schema.create(Type.BYTES);
-    else if (datum instanceof Integer)    return Schema.create(Type.INT);
-    else if (datum instanceof Long)       return Schema.create(Type.LONG);
-    else if (datum instanceof Float)      return Schema.create(Type.FLOAT);
-    else if (datum instanceof Double)     return Schema.create(Type.DOUBLE);
-    else if (datum instanceof Boolean)    return Schema.create(Type.BOOLEAN);
+    else if (isString(datum)) return Schema.create(Type.STRING);
+    else if (isBytes(datum)) return Schema.create(Type.BYTES);
+    else if (isInteger(datum))    return Schema.create(Type.INT);
+    else if (isLong(datum))       return Schema.create(Type.LONG);
+    else if (isFloat(datum))      return Schema.create(Type.FLOAT);
+    else if (isDouble(datum))     return Schema.create(Type.DOUBLE);
+    else if (isBoolean(datum))    return Schema.create(Type.BOOLEAN);
     else if (datum == null)               return Schema.create(Type.NULL);
 
     else throw new AvroTypeException("Can't create schema for: "+datum);
@@ -561,15 +569,15 @@ public class GenericData {
       return Type.STRING.getName();
     if (isBytes(datum))
       return Type.BYTES.getName();
-    if (datum instanceof Integer)
+    if (isInteger(datum))
       return Type.INT.getName();
-    if (datum instanceof Long)
+    if (isLong(datum))
       return Type.LONG.getName();
-    if (datum instanceof Float)
+    if (isFloat(datum))
       return Type.FLOAT.getName();
-    if (datum instanceof Double)
+    if (isDouble(datum))
       return Type.DOUBLE.getName();
-    if (datum instanceof Boolean)
+    if (isBoolean(datum))
       return Type.BOOLEAN.getName();
     throw new AvroRuntimeException("Unknown datum type: "+datum);
  }
@@ -593,11 +601,11 @@ public class GenericData {
       return schema.getFullName().equals(getFixedSchema(datum).getFullName());
     case STRING:  return isString(datum);
     case BYTES:   return isBytes(datum);
-    case INT:     return datum instanceof Integer;
-    case LONG:    return datum instanceof Long;
-    case FLOAT:   return datum instanceof Float;
-    case DOUBLE:  return datum instanceof Double;
-    case BOOLEAN: return datum instanceof Boolean;
+    case INT:     return isInteger(datum);
+    case LONG:    return isLong(datum);
+    case FLOAT:   return isFloat(datum);
+    case DOUBLE:  return isDouble(datum);
+    case BOOLEAN: return isBoolean(datum);
     case NULL:    return datum == null;
     default: throw new AvroRuntimeException("Unexpected type: " +schema);
     }
@@ -659,6 +667,42 @@ public class GenericData {
     return datum instanceof ByteBuffer;
   }
 
+   /**
+   * Called by the default implementation of {@link #instanceOf}.
+   */
+  protected boolean isInteger(Object datum) {
+    return datum instanceof Integer;
+  }
+
+  /**
+   * Called by the default implementation of {@link #instanceOf}.
+   */
+  protected boolean isLong(Object datum) {
+    return datum instanceof Long;
+  }
+
+  /**
+   * Called by the default implementation of {@link #instanceOf}.
+   */
+  protected boolean isFloat(Object datum) {
+    return datum instanceof Float;
+  }
+
+  /**
+   * Called by the default implementation of {@link #instanceOf}.
+   */
+  protected boolean isDouble(Object datum) {
+    return datum instanceof Double;
+  }
+
+  /**
+   * Called by the default implementation of {@link #instanceOf}.
+   */
+  protected boolean isBoolean(Object datum) {
+    return datum instanceof Boolean;
+  }
+   
+
   /** Compute a hash code according to a schema, consistent with {@link
    * #compare(Object,Object,Schema)}. */
   public int hashCode(Object o, Schema s) {

Modified: 
avro/trunk/lang/java/avro/src/test/java/org/apache/avro/FooBarSpecificRecord.java
URL: 
http://svn.apache.org/viewvc/avro/trunk/lang/java/avro/src/test/java/org/apache/avro/FooBarSpecificRecord.java?rev=1412334&r1=1412333&r2=1412334&view=diff
==============================================================================
--- 
avro/trunk/lang/java/avro/src/test/java/org/apache/avro/FooBarSpecificRecord.java
 (original)
+++ 
avro/trunk/lang/java/avro/src/test/java/org/apache/avro/FooBarSpecificRecord.java
 Wed Nov 21 22:29:42 2012
@@ -6,15 +6,32 @@
 package org.apache.avro;  
 @SuppressWarnings("all")
 public class FooBarSpecificRecord extends 
org.apache.avro.specific.SpecificRecordBase implements 
org.apache.avro.specific.SpecificRecord {
-  public static final org.apache.avro.Schema SCHEMA$ = new 
org.apache.avro.Schema.Parser().parse("{\"type\":\"record\",\"name\":\"FooBarSpecificRecord\",\"namespace\":\"org.apache.avro\",\"fields\":[{\"name\":\"id\",\"type\":\"int\"},{\"name\":\"relatedids\",\"type\":{\"type\":\"array\",\"items\":\"int\"}}]}");
+  public static final org.apache.avro.Schema SCHEMA$ = new 
org.apache.avro.Schema.Parser().parse("{\"type\":\"record\",\"name\":\"FooBarSpecificRecord\",\"namespace\":\"org.apache.avro\",\"fields\":[{\"name\":\"id\",\"type\":\"int\"},{\"name\":\"relatedids\",\"type\":{\"type\":\"array\",\"items\":\"int\"}},{\"name\":\"typeEnum\",\"type\":[\"null\",{\"type\":\"enum\",\"name\":\"TypeEnum\",\"symbols\":[\"a\",\"b\",\"c\"]}],\"default\":null}]}");
   @Deprecated public int id;
   @Deprecated public java.util.List<java.lang.Integer> relatedids;
+  @Deprecated public org.apache.avro.TypeEnum typeEnum;
+
+  /**
+   * Default constructor.
+   */
+  public FooBarSpecificRecord() {}
+
+  /**
+   * All-args constructor.
+   */
+  public FooBarSpecificRecord(java.lang.Integer id, 
java.util.List<java.lang.Integer> relatedids, org.apache.avro.TypeEnum 
typeEnum) {
+    this.id = id;
+    this.relatedids = relatedids;
+    this.typeEnum = typeEnum;
+  }
+
   public org.apache.avro.Schema getSchema() { return SCHEMA$; }
   // Used by DatumWriter.  Applications should not call. 
   public java.lang.Object get(int field$) {
     switch (field$) {
     case 0: return id;
     case 1: return relatedids;
+    case 2: return typeEnum;
     default: throw new org.apache.avro.AvroRuntimeException("Bad index");
     }
   }
@@ -24,6 +41,7 @@ public class FooBarSpecificRecord extend
     switch (field$) {
     case 0: id = (java.lang.Integer)value$; break;
     case 1: relatedids = (java.util.List<java.lang.Integer>)value$; break;
+    case 2: typeEnum = (org.apache.avro.TypeEnum)value$; break;
     default: throw new org.apache.avro.AvroRuntimeException("Bad index");
     }
   }
@@ -58,6 +76,21 @@ public class FooBarSpecificRecord extend
     this.relatedids = value;
   }
 
+  /**
+   * Gets the value of the 'typeEnum' field.
+   */
+  public org.apache.avro.TypeEnum getTypeEnum() {
+    return typeEnum;
+  }
+
+  /**
+   * Sets the value of the 'typeEnum' field.
+   * @param value the value to set.
+   */
+  public void setTypeEnum(org.apache.avro.TypeEnum value) {
+    this.typeEnum = value;
+  }
+
   /** Creates a new FooBarSpecificRecord RecordBuilder */
   public static org.apache.avro.FooBarSpecificRecord.Builder newBuilder() {
     return new org.apache.avro.FooBarSpecificRecord.Builder();
@@ -81,6 +114,7 @@ public class FooBarSpecificRecord extend
 
     private int id;
     private java.util.List<java.lang.Integer> relatedids;
+    private org.apache.avro.TypeEnum typeEnum;
 
     /** Creates a new Builder */
     private Builder() {
@@ -96,13 +130,17 @@ public class FooBarSpecificRecord extend
     private Builder(org.apache.avro.FooBarSpecificRecord other) {
             super(org.apache.avro.FooBarSpecificRecord.SCHEMA$);
       if (isValidValue(fields()[0], other.id)) {
-        this.id = data().deepCopy(fields()[0].schema(), other.id);
+        this.id = (java.lang.Integer) data().deepCopy(fields()[0].schema(), 
other.id);
         fieldSetFlags()[0] = true;
       }
       if (isValidValue(fields()[1], other.relatedids)) {
-        this.relatedids = data().deepCopy(fields()[1].schema(), 
other.relatedids);
+        this.relatedids = (java.util.List<java.lang.Integer>) 
data().deepCopy(fields()[1].schema(), other.relatedids);
         fieldSetFlags()[1] = true;
       }
+      if (isValidValue(fields()[2], other.typeEnum)) {
+        this.typeEnum = (org.apache.avro.TypeEnum) 
data().deepCopy(fields()[2].schema(), other.typeEnum);
+        fieldSetFlags()[2] = true;
+    }
     }
 
     /** Gets the value of the 'id' field */
@@ -154,12 +192,38 @@ public class FooBarSpecificRecord extend
       return this;
     }
 
+    /** Gets the value of the 'typeEnum' field */
+    public org.apache.avro.TypeEnum getTypeEnum() {
+      return typeEnum;
+    }
+    
+    /** Sets the value of the 'typeEnum' field */
+    public org.apache.avro.FooBarSpecificRecord.Builder 
setTypeEnum(org.apache.avro.TypeEnum value) {
+      validate(fields()[2], value);
+      this.typeEnum = value;
+      fieldSetFlags()[2] = true;
+      return this; 
+    }
+    
+    /** Checks whether the 'typeEnum' field has been set */
+    public boolean hasTypeEnum() {
+      return fieldSetFlags()[2];
+    }
+    
+    /** Clears the value of the 'typeEnum' field */
+    public org.apache.avro.FooBarSpecificRecord.Builder clearTypeEnum() {
+      typeEnum = null;
+      fieldSetFlags()[2] = false;
+      return this;
+    }
+
     @Override
     public FooBarSpecificRecord build() {
       try {
         FooBarSpecificRecord record = new FooBarSpecificRecord();
         record.id = fieldSetFlags()[0] ? this.id : (java.lang.Integer) 
defaultValue(fields()[0]);
         record.relatedids = fieldSetFlags()[1] ? this.relatedids : 
(java.util.List<java.lang.Integer>) defaultValue(fields()[1]);
+        record.typeEnum = fieldSetFlags()[2] ? this.typeEnum : 
(org.apache.avro.TypeEnum) defaultValue(fields()[2]);
         return record;
       } catch (Exception e) {
         throw new org.apache.avro.AvroRuntimeException(e);

Added: avro/trunk/lang/java/avro/src/test/java/org/apache/avro/TypeEnum.java
URL: 
http://svn.apache.org/viewvc/avro/trunk/lang/java/avro/src/test/java/org/apache/avro/TypeEnum.java?rev=1412334&view=auto
==============================================================================
--- avro/trunk/lang/java/avro/src/test/java/org/apache/avro/TypeEnum.java 
(added)
+++ avro/trunk/lang/java/avro/src/test/java/org/apache/avro/TypeEnum.java Wed 
Nov 21 22:29:42 2012
@@ -0,0 +1,11 @@
+/**
+ * Autogenerated by Avro
+ * 
+ * DO NOT EDIT DIRECTLY
+ */
+package org.apache.avro;  
+@SuppressWarnings("all")
+public enum TypeEnum { 
+  a, b, c  ;
+  public static final org.apache.avro.Schema SCHEMA$ = new 
org.apache.avro.Schema.Parser().parse("{\"type\":\"enum\",\"name\":\"TypeEnum\",\"namespace\":\"org.apache.avro\",\"symbols\":[\"a\",\"b\",\"c\"]}");
+}

Propchange: 
avro/trunk/lang/java/avro/src/test/java/org/apache/avro/TypeEnum.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: 
avro/trunk/lang/java/avro/src/test/java/org/apache/avro/specific/TestSpecificData.java
URL: 
http://svn.apache.org/viewvc/avro/trunk/lang/java/avro/src/test/java/org/apache/avro/specific/TestSpecificData.java?rev=1412334&r1=1412333&r2=1412334&view=diff
==============================================================================
--- 
avro/trunk/lang/java/avro/src/test/java/org/apache/avro/specific/TestSpecificData.java
 (original)
+++ 
avro/trunk/lang/java/avro/src/test/java/org/apache/avro/specific/TestSpecificData.java
 Wed Nov 21 22:29:42 2012
@@ -18,14 +18,20 @@
 
 package org.apache.avro.specific;
 
+import java.io.IOException;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
 import java.util.Arrays;
+import org.apache.avro.FooBarSpecificRecord;
 
 import org.apache.avro.Schema;
 import org.apache.avro.Schema.Type;
+import org.apache.avro.TypeEnum;
+import org.codehaus.jackson.JsonFactory;
+import org.codehaus.jackson.JsonParser;
+import org.codehaus.jackson.map.ObjectMapper;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -73,6 +79,23 @@ public class TestSpecificData {
     Reflection.class.getMethod("primitive", integerClass);
   }
 
+  @Test
+  public void testToString() throws IOException {
+   FooBarSpecificRecord foo = FooBarSpecificRecord.newBuilder()
+           .setId(123)
+           .setRelatedids(Arrays.asList(1,2,3))
+           .setTypeEnum(TypeEnum.c)
+           .build();
+    
+    String json = foo.toString();
+    JsonFactory factory = new JsonFactory();
+    JsonParser parser = factory.createJsonParser(json);
+    ObjectMapper mapper = new ObjectMapper();
+    
+    // will throw exception if string is not parsable json
+    mapper.readTree(parser);
+  }
+
   static class Reflection {
     public void primitive(int i) {}
     public void primitiveWrapper(Integer i) {}

Modified: avro/trunk/lang/java/avro/src/test/resources/FooBarSpecificRecord.avsc
URL: 
http://svn.apache.org/viewvc/avro/trunk/lang/java/avro/src/test/resources/FooBarSpecificRecord.avsc?rev=1412334&r1=1412333&r2=1412334&view=diff
==============================================================================
--- avro/trunk/lang/java/avro/src/test/resources/FooBarSpecificRecord.avsc 
(original)
+++ avro/trunk/lang/java/avro/src/test/resources/FooBarSpecificRecord.avsc Wed 
Nov 21 22:29:42 2012
@@ -5,6 +5,15 @@
     "fields": [
         {"name": "id", "type": "int"},
         {"name": "relatedids", "type": 
-            {"type": "array", "items": "int"}}
+            {"type": "array", "items": "int"}},
+        {"name": "typeEnum", "type": 
+            ["null", { 
+                    "type": "enum",
+                    "name": "TypeEnum",
+                    "namespace": "org.apache.avro",
+                    "symbols" : ["a","b", "c"]
+                }],
+            "default": null
+        }
     ]
 }


Reply via email to