avro-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From bus...@apache.org
Subject avro git commit: AVRO-1847 IDL compiler should use BigDecimal to represent decimal logical type.
Date Wed, 22 Jun 2016 22:01:43 GMT
Repository: avro
Updated Branches:
  refs/heads/master 191badb80 -> 89a31b923


AVRO-1847 IDL compiler should use BigDecimal to represent decimal logical type.

Allows opt-in for the specific compiler to use BigDecimal when generating Java
classes for a Schema with decimal types.

Signed-off-by: Sean Busbey <busbey@cloudera.com>


Project: http://git-wip-us.apache.org/repos/asf/avro/repo
Commit: http://git-wip-us.apache.org/repos/asf/avro/commit/89a31b92
Tree: http://git-wip-us.apache.org/repos/asf/avro/tree/89a31b92
Diff: http://git-wip-us.apache.org/repos/asf/avro/diff/89a31b92

Branch: refs/heads/master
Commit: 89a31b9236c92b2036b8f4ddd190183a18c630fc
Parents: 191badb
Author: Yibing Shi <shi.yibing@gmail.com>
Authored: Wed Jun 22 14:31:06 2016 -0500
Committer: Sean Busbey <busbey@cloudera.com>
Committed: Wed Jun 22 14:31:06 2016 -0500

----------------------------------------------------------------------
 .../main/java/org/apache/avro/Conversions.java  | 113 +++++++++++++++++++
 .../main/java/org/apache/avro/LogicalTypes.java |   1 +
 .../org/apache/avro/data/RecordBuilderBase.java |  26 +++++
 .../apache/avro/generic/GenericDatumReader.java |  32 ++----
 .../apache/avro/generic/GenericDatumWriter.java |  49 +++++---
 .../specific/TestRecordWithLogicalTypes.java    |  84 +++++++++++++-
 .../specific/TestRecordWithoutLogicalTypes.java |  54 ++++++++-
 .../avro/specific/TestSpecificLogicalTypes.java |  25 +++-
 .../compiler/specific/SpecificCompiler.java     |  35 ++++--
 .../javacc/org/apache/avro/compiler/idl/idl.jj  |  15 +++
 .../specific/templates/java/classic/record.vm   |   9 ++
 .../compiler/src/test/idl/input/mr_events.avdl  |   1 +
 .../compiler/src/test/idl/output/mr_events.avpr |   3 +
 .../compiler/specific/TestSpecificCompiler.java | 103 ++++++++++++++++-
 .../org/apache/avro/mojo/AbstractAvroMojo.java  |   7 ++
 .../org/apache/avro/mojo/IDLProtocolMojo.java   |   1 +
 .../java/org/apache/avro/mojo/ProtocolMojo.java |   1 +
 .../java/org/apache/avro/mojo/SchemaMojo.java   |   1 +
 .../apache/avro/tool/SpecificCompilerTool.java  |  39 +++++--
 19 files changed, 525 insertions(+), 74 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/avro/blob/89a31b92/lang/java/avro/src/main/java/org/apache/avro/Conversions.java
----------------------------------------------------------------------
diff --git a/lang/java/avro/src/main/java/org/apache/avro/Conversions.java b/lang/java/avro/src/main/java/org/apache/avro/Conversions.java
index 14408d9..bf429ea 100644
--- a/lang/java/avro/src/main/java/org/apache/avro/Conversions.java
+++ b/lang/java/avro/src/main/java/org/apache/avro/Conversions.java
@@ -21,9 +21,14 @@ package org.apache.avro;
 import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Map;
 import java.util.UUID;
 import org.apache.avro.generic.GenericData;
+import org.apache.avro.generic.GenericEnumSymbol;
 import org.apache.avro.generic.GenericFixed;
+import org.apache.avro.generic.IndexedRecord;
 
 public class Conversions {
 
@@ -120,4 +125,112 @@ public class Conversions {
     }
   }
 
+  /**
+   * Convert a underlying representation of a logical type (such as a
+   * ByteBuffer) to a higher level object (such as a BigDecimal).
+   * @param datum The object to be converted.
+   * @param schema The schema of datum. Cannot be null if datum is not null.
+   * @param type The {@link org.apache.avro.LogicalType} of datum. Cannot
+   *             be null if datum is not null.
+   * @param conversion The tool used to finish the conversion. Cannot
+   *                   be null if datum is not null.
+   * @return The result object, which is a high level object of the logical
+   * type. If a null datum is passed in, a null value will be returned.
+   * @throws IllegalArgumentException if a null schema, type or conversion
+   * is passed in while datum is not null.
+   */
+  public static Object convertToLogicalType(Object datum, Schema schema, LogicalType type,
+                                            Conversion<?> conversion) {
+    if (datum == null) {
+      return null;
+    }
+
+    if (schema == null || type == null || conversion == null) {
+      throw new IllegalArgumentException("Parameters cannot be null! Parameter values:" +
+          Arrays.deepToString(new Object[]{datum, schema, type, conversion}));
+    }
+
+    try {
+      switch (schema.getType()) {
+        case RECORD:  return conversion.fromRecord((IndexedRecord) datum, schema, type);
+        case ENUM:    return conversion.fromEnumSymbol((GenericEnumSymbol) datum, schema, type);
+        case ARRAY:   return conversion.fromArray((Collection) datum, schema, type);
+        case MAP:     return conversion.fromMap((Map<?, ?>) datum, schema, type);
+        case FIXED:   return conversion.fromFixed((GenericFixed) datum, schema, type);
+        case STRING:  return conversion.fromCharSequence((CharSequence) datum, schema, type);
+        case BYTES:   return conversion.fromBytes((ByteBuffer) datum, schema, type);
+        case INT:     return conversion.fromInt((Integer) datum, schema, type);
+        case LONG:    return conversion.fromLong((Long) datum, schema, type);
+        case FLOAT:   return conversion.fromFloat((Float) datum, schema, type);
+        case DOUBLE:  return conversion.fromDouble((Double) datum, schema, type);
+        case BOOLEAN: return conversion.fromBoolean((Boolean) datum, schema, type);
+      }
+      return datum;
+    } catch (ClassCastException e) {
+      throw new AvroRuntimeException("Cannot convert " + datum + ":" +
+          datum.getClass().getSimpleName() + ": expected generic type", e);
+    }
+  }
+
+  /**
+   * Convert a high level representation of a logical type (such as a BigDecimal)
+   * to the its underlying representation object (such as a ByteBuffer)
+   * @param datum The object to be converted.
+   * @param schema The schema of datum. Cannot be null if datum is not null.
+   * @param type The {@link org.apache.avro.LogicalType} of datum. Cannot
+   *             be null if datum is not null.
+   * @param conversion The tool used to finish the conversion. Cannot
+   *                   be null if datum is not null.
+   * @return The result object, which is an underlying representation object
+   * of the logical type. If the input param datum is null, a null value will
+   * be returned.
+   * @throws IllegalArgumentException if a null schema, type or conversion
+   * is passed in while datum is not null.
+   */
+  public static <T> Object convertToRawType(Object datum, Schema schema, LogicalType type,
+                                            Conversion<T> conversion) {
+    if (datum == null) {
+      return null;
+    }
+
+    if (schema == null || type == null || conversion == null) {
+      throw new IllegalArgumentException("Parameters cannot be null! Parameter values:" +
+          Arrays.deepToString(new Object[]{datum, schema, type, conversion}));
+    }
+
+    try {
+      Class<T> fromClass = conversion.getConvertedType();
+      switch (schema.getType()) {
+        case RECORD:
+          return conversion.toRecord(fromClass.cast(datum), schema, type);
+        case ENUM:
+          return conversion.toEnumSymbol(fromClass.cast(datum), schema, type);
+        case ARRAY:
+          return conversion.toArray(fromClass.cast(datum), schema, type);
+        case MAP:
+          return conversion.toMap(fromClass.cast(datum), schema, type);
+        case FIXED:
+          return conversion.toFixed(fromClass.cast(datum), schema, type);
+        case STRING:
+          return conversion.toCharSequence(fromClass.cast(datum), schema, type);
+        case BYTES:
+          return conversion.toBytes(fromClass.cast(datum), schema, type);
+        case INT:
+          return conversion.toInt(fromClass.cast(datum), schema, type);
+        case LONG:
+          return conversion.toLong(fromClass.cast(datum), schema, type);
+        case FLOAT:
+          return conversion.toFloat(fromClass.cast(datum), schema, type);
+        case DOUBLE:
+          return conversion.toDouble(fromClass.cast(datum), schema, type);
+        case BOOLEAN:
+          return conversion.toBoolean(fromClass.cast(datum), schema, type);
+      }
+      return datum;
+    } catch (ClassCastException e) {
+      throw new AvroRuntimeException("Cannot convert " + datum + ":" +
+          datum.getClass().getSimpleName() + ": expected logical type", e);
+    }
+  }
+
 }

http://git-wip-us.apache.org/repos/asf/avro/blob/89a31b92/lang/java/avro/src/main/java/org/apache/avro/LogicalTypes.java
----------------------------------------------------------------------
diff --git a/lang/java/avro/src/main/java/org/apache/avro/LogicalTypes.java b/lang/java/avro/src/main/java/org/apache/avro/LogicalTypes.java
index 09fb581..fb8f17b 100644
--- a/lang/java/avro/src/main/java/org/apache/avro/LogicalTypes.java
+++ b/lang/java/avro/src/main/java/org/apache/avro/LogicalTypes.java
@@ -348,4 +348,5 @@ public class LogicalTypes {
       }
     }
   }
+
 }

http://git-wip-us.apache.org/repos/asf/avro/blob/89a31b92/lang/java/avro/src/main/java/org/apache/avro/data/RecordBuilderBase.java
----------------------------------------------------------------------
diff --git a/lang/java/avro/src/main/java/org/apache/avro/data/RecordBuilderBase.java b/lang/java/avro/src/main/java/org/apache/avro/data/RecordBuilderBase.java
index 8e34a36..22a30f6 100644
--- a/lang/java/avro/src/main/java/org/apache/avro/data/RecordBuilderBase.java
+++ b/lang/java/avro/src/main/java/org/apache/avro/data/RecordBuilderBase.java
@@ -21,6 +21,9 @@ import java.io.IOException;
 import java.util.Arrays;
 
 import org.apache.avro.AvroRuntimeException;
+import org.apache.avro.Conversion;
+import org.apache.avro.Conversions;
+import org.apache.avro.LogicalType;
 import org.apache.avro.Schema;
 import org.apache.avro.Schema.Field;
 import org.apache.avro.Schema.Type;
@@ -135,6 +138,29 @@ public abstract class RecordBuilderBase<T extends IndexedRecord>
     return data.deepCopy(field.schema(), data.getDefaultValue(field));
   }
 
+  /**
+   * Gets the default value of the given field, if any. Pass in a conversion
+   * to convert data to logical type class. Please make sure the schema does
+   * have a logical type, otherwise an exception would be thrown out.
+   * @param field the field whose default value should be retrieved.
+   * @param conversion the tool to convert data to logical type class
+   * @return the default value associated with the given field,
+   * or null if none is specified in the schema.
+   * @throws IOException
+   */
+  @SuppressWarnings({ "rawtypes", "unchecked" })
+  protected Object defaultValue(Field field, Conversion<?> conversion) throws IOException {
+    Schema schema = field.schema();
+    LogicalType logicalType = schema.getLogicalType();
+    Object rawDefaultValue = data.deepCopy(schema, data.getDefaultValue(field));
+    if (conversion == null || logicalType == null) {
+      return rawDefaultValue;
+    } else {
+      return Conversions.convertToLogicalType(rawDefaultValue, schema,
+          logicalType, conversion);
+    }
+  }
+
   @Override
   public int hashCode() {
     final int prime = 31;

http://git-wip-us.apache.org/repos/asf/avro/blob/89a31b92/lang/java/avro/src/main/java/org/apache/avro/generic/GenericDatumReader.java
----------------------------------------------------------------------
diff --git a/lang/java/avro/src/main/java/org/apache/avro/generic/GenericDatumReader.java b/lang/java/avro/src/main/java/org/apache/avro/generic/GenericDatumReader.java
index 6133d6b..7cde13f 100644
--- a/lang/java/avro/src/main/java/org/apache/avro/generic/GenericDatumReader.java
+++ b/lang/java/avro/src/main/java/org/apache/avro/generic/GenericDatumReader.java
@@ -28,6 +28,7 @@ import java.lang.reflect.InvocationTargetException;
 
 import org.apache.avro.AvroRuntimeException;
 import org.apache.avro.Conversion;
+import org.apache.avro.Conversions;
 import org.apache.avro.LogicalType;
 import org.apache.avro.Schema;
 import org.apache.avro.Schema.Field;
@@ -189,28 +190,19 @@ public class GenericDatumReader<D> implements DatumReader<D> {
     }
   }
 
+  /**
+   * Convert a underlying representation of a logical type (such as a
+   * ByteBuffer) to a higher level object (such as a BigDecimal).
+   * @throws IllegalArgumentException if a null schema or logicalType is passed
+   * in while datum and conversion are not null. Please be noticed that
+   * the exception type has changed. With version 1.8.0 and earlier, in above
+   * circumstance, the exception thrown out depends on the implementation
+   * of conversion (most likely a NullPointerException). Now, an
+   * IllegalArgumentException will be thrown out instead.
+   */
   protected Object convert(Object datum, Schema schema, LogicalType type,
                            Conversion<?> conversion) {
-    try {
-      switch (schema.getType()) {
-      case RECORD:  return conversion.fromRecord((IndexedRecord) datum, schema, type);
-      case ENUM:    return conversion.fromEnumSymbol((GenericEnumSymbol) datum, schema, type);
-      case ARRAY:   return conversion.fromArray(getData().getArrayAsCollection(datum), schema, type);
-      case MAP:     return conversion.fromMap((Map<?, ?>) datum, schema, type);
-      case FIXED:   return conversion.fromFixed((GenericFixed) datum, schema, type);
-      case STRING:  return conversion.fromCharSequence((CharSequence) datum, schema, type);
-      case BYTES:   return conversion.fromBytes((ByteBuffer) datum, schema, type);
-      case INT:     return conversion.fromInt((Integer) datum, schema, type);
-      case LONG:    return conversion.fromLong((Long) datum, schema, type);
-      case FLOAT:   return conversion.fromFloat((Float) datum, schema, type);
-      case DOUBLE:  return conversion.fromDouble((Double) datum, schema, type);
-      case BOOLEAN: return conversion.fromBoolean((Boolean) datum, schema, type);
-      }
-      return datum;
-    } catch (ClassCastException e) {
-      throw new AvroRuntimeException("Cannot convert " + datum + ":" +
-          datum.getClass().getSimpleName() + ": expected generic type", e);
-    }
+    return Conversions.convertToLogicalType(datum, schema, type, conversion);
   }
 
   /** Called to read a record instance. May be overridden for alternate record

http://git-wip-us.apache.org/repos/asf/avro/blob/89a31b92/lang/java/avro/src/main/java/org/apache/avro/generic/GenericDatumWriter.java
----------------------------------------------------------------------
diff --git a/lang/java/avro/src/main/java/org/apache/avro/generic/GenericDatumWriter.java b/lang/java/avro/src/main/java/org/apache/avro/generic/GenericDatumWriter.java
index e66726e..d6a36ab 100644
--- a/lang/java/avro/src/main/java/org/apache/avro/generic/GenericDatumWriter.java
+++ b/lang/java/avro/src/main/java/org/apache/avro/generic/GenericDatumWriter.java
@@ -24,8 +24,10 @@ import java.util.Iterator;
 import java.util.Map;
 import java.util.Collection;
 
+import org.apache.avro.AvroRuntimeException;
 import org.apache.avro.AvroTypeException;
 import org.apache.avro.Conversion;
+import org.apache.avro.Conversions;
 import org.apache.avro.LogicalType;
 import org.apache.avro.Schema;
 import org.apache.avro.Schema.Field;
@@ -74,27 +76,38 @@ public class GenericDatumWriter<D> implements DatumWriter<D> {
     }
   }
 
+  /**
+   * Convert a high level representation of a logical type (such as a BigDecimal)
+   * to the its underlying representation object (such as a ByteBuffer).
+   * @throws IllegalArgumentException if a null schema or logicalType is passed
+   * in while datum and conversion are not null. Please be noticed that
+   * the exception type has changed. With version 1.8.0 and earlier, in above
+   * circumstance, the exception thrown out depends on the implementation
+   * of conversion (most likely a NullPointerException). Now, an
+   * IllegalArgumentException will be thrown out instead.
+   */
   protected <T> Object convert(Schema schema, LogicalType logicalType,
                                Conversion<T> conversion, Object datum) {
-    if (conversion == null) {
-      return datum;
-    }
-    Class<T> fromClass = conversion.getConvertedType();
-    switch (schema.getType()) {
-    case RECORD:  return conversion.toRecord(fromClass.cast(datum), schema, logicalType);
-    case ENUM:    return conversion.toEnumSymbol(fromClass.cast(datum), schema, logicalType);
-    case ARRAY:   return conversion.toArray(fromClass.cast(datum), schema, logicalType);
-    case MAP:     return conversion.toMap(fromClass.cast(datum), schema, logicalType);
-    case FIXED:   return conversion.toFixed(fromClass.cast(datum), schema, logicalType);
-    case STRING:  return conversion.toCharSequence(fromClass.cast(datum), schema, logicalType);
-    case BYTES:   return conversion.toBytes(fromClass.cast(datum), schema, logicalType);
-    case INT:     return conversion.toInt(fromClass.cast(datum), schema, logicalType);
-    case LONG:    return conversion.toLong(fromClass.cast(datum), schema, logicalType);
-    case FLOAT:   return conversion.toFloat(fromClass.cast(datum), schema, logicalType);
-    case DOUBLE:  return conversion.toDouble(fromClass.cast(datum), schema, logicalType);
-    case BOOLEAN: return conversion.toBoolean(fromClass.cast(datum), schema, logicalType);
+    try {
+      if (conversion == null) {
+        return datum;
+      } else {
+        return Conversions.convertToRawType(datum, schema, logicalType, conversion);
+      }
+    } catch (AvroRuntimeException e) {
+      Throwable cause = e.getCause();
+      if (cause != null && cause.getClass() == ClassCastException.class) {
+        // This is to keep backwards compatibility. The convert function here used to
+        // throw CCE. After being moved to Conversions, it throws AvroRuntimeException
+        // instead. To keep as much same behaviour as before, this function checks if
+        // the cause is a CCE. If yes, rethrow it in case any child class checks it. This
+        // behaviour can be changed later in future versions to make it consistent with
+        // reading path, which throws AvroRuntimeException
+        throw (ClassCastException)cause;
+      } else {
+        throw e;
+      }
     }
-    return datum;
   }
 
   /** Called to write data.*/

http://git-wip-us.apache.org/repos/asf/avro/blob/89a31b92/lang/java/avro/src/test/java/org/apache/avro/specific/TestRecordWithLogicalTypes.java
----------------------------------------------------------------------
diff --git a/lang/java/avro/src/test/java/org/apache/avro/specific/TestRecordWithLogicalTypes.java b/lang/java/avro/src/test/java/org/apache/avro/specific/TestRecordWithLogicalTypes.java
index a01e450..c1b98c7 100644
--- a/lang/java/avro/src/test/java/org/apache/avro/specific/TestRecordWithLogicalTypes.java
+++ b/lang/java/avro/src/test/java/org/apache/avro/specific/TestRecordWithLogicalTypes.java
@@ -4,11 +4,16 @@
  * DO NOT EDIT DIRECTLY
  */
 package org.apache.avro.specific;
+
+import org.apache.avro.Conversions;
+
+import java.math.BigDecimal;
+
 @SuppressWarnings("all")
 @org.apache.avro.specific.AvroGenerated
 public class TestRecordWithLogicalTypes extends org.apache.avro.specific.SpecificRecordBase implements org.apache.avro.specific.SpecificRecord {
   private static final long serialVersionUID = -4211233492739285532L;
-  public static final org.apache.avro.Schema SCHEMA$ = new org.apache.avro.Schema.Parser().parse("{\"type\":\"record\",\"name\":\"TestRecordWithLogicalTypes\",\"namespace\":\"org.apache.avro.specific\",\"fields\":[{\"name\":\"b\",\"type\":\"boolean\"},{\"name\":\"i32\",\"type\":\"int\"},{\"name\":\"i64\",\"type\":\"long\"},{\"name\":\"f32\",\"type\":\"float\"},{\"name\":\"f64\",\"type\":\"double\"},{\"name\":\"s\",\"type\":[\"null\",\"string\"],\"default\":null},{\"name\":\"d\",\"type\":{\"type\":\"int\",\"logicalType\":\"date\"}},{\"name\":\"t\",\"type\":{\"type\":\"int\",\"logicalType\":\"time-millis\"}},{\"name\":\"ts\",\"type\":{\"type\":\"long\",\"logicalType\":\"timestamp-millis\"}}]}");
+  public static final org.apache.avro.Schema SCHEMA$ = new org.apache.avro.Schema.Parser().parse("{\"type\":\"record\",\"name\":\"TestRecordWithLogicalTypes\",\"namespace\":\"org.apache.avro.specific\",\"fields\":[{\"name\":\"b\",\"type\":\"boolean\"},{\"name\":\"i32\",\"type\":\"int\"},{\"name\":\"i64\",\"type\":\"long\"},{\"name\":\"f32\",\"type\":\"float\"},{\"name\":\"f64\",\"type\":\"double\"},{\"name\":\"s\",\"type\":[\"null\",\"string\"],\"default\":null},{\"name\":\"d\",\"type\":{\"type\":\"int\",\"logicalType\":\"date\"}},{\"name\":\"t\",\"type\":{\"type\":\"int\",\"logicalType\":\"time-millis\"}},{\"name\":\"ts\",\"type\":{\"type\":\"long\",\"logicalType\":\"timestamp-millis\"}},{\"name\":\"dec\",\"type\":{\"type\":\"bytes\",\"logicalType\":\"decimal\",\"precision\":9,\"scale\":2}}]}");
   public static org.apache.avro.Schema getClassSchema() { return SCHEMA$; }
   @Deprecated public boolean b;
   @Deprecated public int i32;
@@ -19,6 +24,7 @@ public class TestRecordWithLogicalTypes extends org.apache.avro.specific.Specifi
   @Deprecated public org.joda.time.LocalDate d;
   @Deprecated public org.joda.time.LocalTime t;
   @Deprecated public org.joda.time.DateTime ts;
+  @Deprecated public BigDecimal dec;
 
   /**
    * Default constructor.  Note that this does not initialize fields
@@ -30,7 +36,16 @@ public class TestRecordWithLogicalTypes extends org.apache.avro.specific.Specifi
   /**
    * All-args constructor.
    */
-  public TestRecordWithLogicalTypes(java.lang.Boolean b, java.lang.Integer i32, java.lang.Long i64, java.lang.Float f32, java.lang.Double f64, java.lang.CharSequence s, org.joda.time.LocalDate d, org.joda.time.LocalTime t, org.joda.time.DateTime ts) {
+  public TestRecordWithLogicalTypes(java.lang.Boolean b,
+                                    java.lang.Integer i32,
+                                    java.lang.Long i64,
+                                    java.lang.Float f32,
+                                    java.lang.Double f64,
+                                    java.lang.CharSequence s,
+                                    org.joda.time.LocalDate d,
+                                    org.joda.time.LocalTime t,
+                                    org.joda.time.DateTime ts,
+                                    BigDecimal dec) {
     this.b = b;
     this.i32 = i32;
     this.i64 = i64;
@@ -40,6 +55,7 @@ public class TestRecordWithLogicalTypes extends org.apache.avro.specific.Specifi
     this.d = d;
     this.t = t;
     this.ts = ts;
+    this.dec = dec;
   }
 
   public org.apache.avro.Schema getSchema() { return SCHEMA$; }
@@ -55,6 +71,7 @@ public class TestRecordWithLogicalTypes extends org.apache.avro.specific.Specifi
     case 6: return d;
     case 7: return t;
     case 8: return ts;
+    case 9: return dec;
     default: throw new org.apache.avro.AvroRuntimeException("Bad index");
     }
   }
@@ -71,6 +88,7 @@ public class TestRecordWithLogicalTypes extends org.apache.avro.specific.Specifi
     case 6: d = (org.joda.time.LocalDate)value$; break;
     case 7: t = (org.joda.time.LocalTime)value$; break;
     case 8: ts = (org.joda.time.DateTime)value$; break;
+    case 9: dec = (BigDecimal) value$; break;
     default: throw new org.apache.avro.AvroRuntimeException("Bad index");
     }
   }
@@ -203,6 +221,21 @@ public class TestRecordWithLogicalTypes extends org.apache.avro.specific.Specifi
   }
 
   /**
+   * Sets the value of the 'dec' field.
+   * @param value the value to set.
+   */
+  public void setDec(BigDecimal value) {
+    this.dec = value;
+  }
+
+  /**
+   * Gets the value of the 'ts' field.
+   */
+  public BigDecimal getDec() {
+    return dec;
+  }
+
+  /**
    * Sets the value of the 'ts' field.
    * @param value the value to set.
    */
@@ -213,7 +246,7 @@ public class TestRecordWithLogicalTypes extends org.apache.avro.specific.Specifi
   protected static final org.apache.avro.data.TimeConversions.DateConversion DATE_CONVERSION = new org.apache.avro.data.TimeConversions.DateConversion();
   protected static final org.apache.avro.data.TimeConversions.TimeConversion TIME_CONVERSION = new org.apache.avro.data.TimeConversions.TimeConversion();
   protected static final org.apache.avro.data.TimeConversions.TimestampConversion TIMESTAMP_CONVERSION = new org.apache.avro.data.TimeConversions.TimestampConversion();
-
+  protected static final org.apache.avro.Conversions.DecimalConversion DECIMAL_CONVERSION = new org.apache.avro.Conversions.DecimalConversion();
   private final org.apache.avro.Conversion<?>[] conversions =
       new org.apache.avro.Conversion<?>[] {
       null,
@@ -225,6 +258,7 @@ public class TestRecordWithLogicalTypes extends org.apache.avro.specific.Specifi
       DATE_CONVERSION,
       TIME_CONVERSION,
       TIMESTAMP_CONVERSION,
+      DECIMAL_CONVERSION,
       null
   };
 
@@ -263,6 +297,7 @@ public class TestRecordWithLogicalTypes extends org.apache.avro.specific.Specifi
     private org.joda.time.LocalDate d;
     private org.joda.time.LocalTime t;
     private org.joda.time.DateTime ts;
+    private BigDecimal dec;
 
     /** Creates a new Builder */
     private Builder() {
@@ -308,6 +343,10 @@ public class TestRecordWithLogicalTypes extends org.apache.avro.specific.Specifi
         this.ts = data().deepCopy(fields()[8].schema(), other.ts);
         fieldSetFlags()[8] = true;
       }
+      if (isValidValue(fields()[9], other.dec)) {
+        this.dec = data().deepCopy(fields()[9].schema(), other.dec);
+        fieldSetFlags()[9] = true;
+      }
     }
     
     /** Creates a Builder by copying an existing TestRecordWithLogicalTypes instance */
@@ -349,6 +388,10 @@ public class TestRecordWithLogicalTypes extends org.apache.avro.specific.Specifi
         this.ts = data().deepCopy(fields()[8].schema(), other.ts);
         fieldSetFlags()[8] = true;
       }
+      if (isValidValue(fields()[9], other.dec)) {
+        this.dec = data().deepCopy(fields()[9].schema(), other.dec);
+        fieldSetFlags()[9] = true;
+      }
     }
 
     /**
@@ -658,6 +701,40 @@ public class TestRecordWithLogicalTypes extends org.apache.avro.specific.Specifi
       return this;
     }
 
+    /**
+     * Gedec the value of the 'dec' field.
+     */
+    public BigDecimal getDec() {
+      return dec;
+    }
+
+    /**
+     * Sedec the value of the 'dec' field.
+     * @param value the value to set.
+     */
+    public TestRecordWithLogicalTypes.Builder setDec(BigDecimal value) {
+      validate(fields()[8], value);
+      this.dec = value;
+      fieldSetFlags()[8] = true;
+      return this;
+    }
+
+    /**
+     * Checks whether the 'dec' field has been set.
+     */
+    public boolean hasDec() {
+      return fieldSetFlags()[8];
+    }
+
+
+    /**
+     * Clears the value of the 'dec' field.
+     */
+    public TestRecordWithLogicalTypes.Builder clearDec() {
+      fieldSetFlags()[8] = false;
+      return this;
+    }
+
     @Override
     public TestRecordWithLogicalTypes build() {
       try {
@@ -671,6 +748,7 @@ public class TestRecordWithLogicalTypes extends org.apache.avro.specific.Specifi
         record.d = fieldSetFlags()[6] ? this.d : (org.joda.time.LocalDate) defaultValue(fields()[6]);
         record.t = fieldSetFlags()[7] ? this.t : (org.joda.time.LocalTime) defaultValue(fields()[7]);
         record.ts = fieldSetFlags()[8] ? this.ts : (org.joda.time.DateTime) defaultValue(fields()[8]);
+        record.dec = fieldSetFlags()[9] ? this.dec : (BigDecimal) defaultValue(fields()[9]);
         return record;
       } catch (Exception e) {
         throw new org.apache.avro.AvroRuntimeException(e);

http://git-wip-us.apache.org/repos/asf/avro/blob/89a31b92/lang/java/avro/src/test/java/org/apache/avro/specific/TestRecordWithoutLogicalTypes.java
----------------------------------------------------------------------
diff --git a/lang/java/avro/src/test/java/org/apache/avro/specific/TestRecordWithoutLogicalTypes.java b/lang/java/avro/src/test/java/org/apache/avro/specific/TestRecordWithoutLogicalTypes.java
index afe7d11..86fb8f0 100644
--- a/lang/java/avro/src/test/java/org/apache/avro/specific/TestRecordWithoutLogicalTypes.java
+++ b/lang/java/avro/src/test/java/org/apache/avro/specific/TestRecordWithoutLogicalTypes.java
@@ -3,11 +3,14 @@
  * 
  * DO NOT EDIT DIRECTLY
  */
-package org.apache.avro.specific;  
+package org.apache.avro.specific;
+
+import java.nio.ByteBuffer;
+
 @SuppressWarnings("all")
 @org.apache.avro.specific.AvroGenerated
 public class TestRecordWithoutLogicalTypes 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\":\"TestRecordWithoutLogicalTypes\",\"namespace\":\"org.apache.avro.specific\",\"fields\":[{\"name\":\"b\",\"type\":\"boolean\"},{\"name\":\"i32\",\"type\":\"int\"},{\"name\":\"i64\",\"type\":\"long\"},{\"name\":\"f32\",\"type\":\"float\"},{\"name\":\"f64\",\"type\":\"double\"},{\"name\":\"s\",\"type\":[\"null\",\"string\"],\"default\":null},{\"name\":\"d\",\"type\":{\"type\":\"int\",\"logicalType\":\"date\"}},{\"name\":\"t\",\"type\":{\"type\":\"int\",\"logicalType\":\"time-millis\"}},{\"name\":\"ts\",\"type\":{\"type\":\"long\",\"logicalType\":\"timestamp-millis\"}}]}");
+  public static final org.apache.avro.Schema SCHEMA$ = new org.apache.avro.Schema.Parser().parse("{\"type\":\"record\",\"name\":\"TestRecordWithoutLogicalTypes\",\"namespace\":\"org.apache.avro.specific\",\"fields\":[{\"name\":\"b\",\"type\":\"boolean\"},{\"name\":\"i32\",\"type\":\"int\"},{\"name\":\"i64\",\"type\":\"long\"},{\"name\":\"f32\",\"type\":\"float\"},{\"name\":\"f64\",\"type\":\"double\"},{\"name\":\"s\",\"type\":[\"null\",\"string\"],\"default\":null},{\"name\":\"d\",\"type\":{\"type\":\"int\",\"logicalType\":\"date\"}},{\"name\":\"t\",\"type\":{\"type\":\"int\",\"logicalType\":\"time-millis\"}},{\"name\":\"ts\",\"type\":{\"type\":\"long\",\"logicalType\":\"timestamp-millis\"}},{\"name\":\"dec\",\"type\":{\"type\":\"bytes\",\"logicalType\":\"decimal\",\"precision\":9,\"scale\":2}}]}");
   public static org.apache.avro.Schema getClassSchema() { return SCHEMA$; }
    private boolean b;
    private int i32;
@@ -18,6 +21,7 @@ public class TestRecordWithoutLogicalTypes extends org.apache.avro.specific.Spec
    private int d;
    private int t;
    private long ts;
+   private ByteBuffer dec;
 
   /**
    * Default constructor.  Note that this does not initialize fields
@@ -29,7 +33,7 @@ public class TestRecordWithoutLogicalTypes extends org.apache.avro.specific.Spec
   /**
    * All-args constructor.
    */
-  public TestRecordWithoutLogicalTypes(java.lang.Boolean b, java.lang.Integer i32, java.lang.Long i64, java.lang.Float f32, java.lang.Double f64, java.lang.String s, java.lang.Integer d, java.lang.Integer t, java.lang.Long ts) {
+  public TestRecordWithoutLogicalTypes(java.lang.Boolean b, java.lang.Integer i32, java.lang.Long i64, java.lang.Float f32, java.lang.Double f64, java.lang.String s, java.lang.Integer d, java.lang.Integer t, java.lang.Long ts, java.nio.ByteBuffer dec) {
     this.b = b;
     this.i32 = i32;
     this.i64 = i64;
@@ -39,6 +43,7 @@ public class TestRecordWithoutLogicalTypes extends org.apache.avro.specific.Spec
     this.d = d;
     this.t = t;
     this.ts = ts;
+    this.dec = dec;
   }
 
   public org.apache.avro.Schema getSchema() { return SCHEMA$; }
@@ -54,6 +59,7 @@ public class TestRecordWithoutLogicalTypes extends org.apache.avro.specific.Spec
     case 6: return d;
     case 7: return t;
     case 8: return ts;
+    case 9: return dec;
     default: throw new org.apache.avro.AvroRuntimeException("Bad index");
     }
   }
@@ -70,6 +76,7 @@ public class TestRecordWithoutLogicalTypes extends org.apache.avro.specific.Spec
     case 6: d = (java.lang.Integer)value$; break;
     case 7: t = (java.lang.Integer)value$; break;
     case 8: ts = (java.lang.Long)value$; break;
+    case 9: dec = (java.nio.ByteBuffer) value$; break;
     default: throw new org.apache.avro.AvroRuntimeException("Bad index");
     }
   }
@@ -146,6 +153,12 @@ public class TestRecordWithoutLogicalTypes extends org.apache.avro.specific.Spec
   }
 
 
+  /**
+   * Gets the value of the 'ts' field.
+   */
+  public java.nio.ByteBuffer getDec()  { return dec; }
+
+
   /** Creates a new TestRecordWithoutLogicalTypes RecordBuilder */
   public static TestRecordWithoutLogicalTypes.Builder newBuilder() {
     return new TestRecordWithoutLogicalTypes.Builder();
@@ -176,6 +189,7 @@ public class TestRecordWithoutLogicalTypes extends org.apache.avro.specific.Spec
     private int d;
     private int t;
     private long ts;
+    private ByteBuffer dec;
 
     /** Creates a new Builder */
     private Builder() {
@@ -221,6 +235,10 @@ public class TestRecordWithoutLogicalTypes extends org.apache.avro.specific.Spec
         this.ts = data().deepCopy(fields()[8].schema(), other.ts);
         fieldSetFlags()[8] = true;
       }
+      if (isValidValue(fields()[9], other.dec)) {
+        this.dec = data().deepCopy(fields()[9].schema(), other.dec);
+        fieldSetFlags()[9] = true;
+      }
     }
     
     /** Creates a Builder by copying an existing TestRecordWithoutLogicalTypes instance */
@@ -262,6 +280,10 @@ public class TestRecordWithoutLogicalTypes extends org.apache.avro.specific.Spec
         this.ts = data().deepCopy(fields()[8].schema(), other.ts);
         fieldSetFlags()[8] = true;
       }
+      if (isValidValue(fields()[9], other.ts)) {
+        this.dec = data().deepCopy(fields()[9].schema(), other.dec);
+        fieldSetFlags()[9] = true;
+      }
     }
 
     /** Gets the value of the 'b' field */
@@ -481,6 +503,31 @@ public class TestRecordWithoutLogicalTypes extends org.apache.avro.specific.Spec
       return this;
     }
 
+    /** Gedec the value of the 'dec' field */
+    public java.nio.ByteBuffer getDec() {
+      return dec;
+    }
+
+    /** Sedec the value of the 'dec' field */
+    public TestRecordWithoutLogicalTypes.Builder setDec(java.nio.ByteBuffer value) {
+      validate(fields()[9], value);
+      this.dec = value;
+      fieldSetFlags()[9] = true;
+      return this;
+    }
+
+    /** Checks whether the 'dec' field has been set */
+    public boolean hasDec() {
+      return fieldSetFlags()[9];
+    }
+
+    /** Clears the value of the 'dec' field */
+    public TestRecordWithoutLogicalTypes.Builder clearDec() {
+      fieldSetFlags()[9] = false;
+      return this;
+    }
+
+
     @Override
     public TestRecordWithoutLogicalTypes build() {
       try {
@@ -494,6 +541,7 @@ public class TestRecordWithoutLogicalTypes extends org.apache.avro.specific.Spec
         record.d = fieldSetFlags()[6] ? this.d : (java.lang.Integer) defaultValue(fields()[6]);
         record.t = fieldSetFlags()[7] ? this.t : (java.lang.Integer) defaultValue(fields()[7]);
         record.ts = fieldSetFlags()[8] ? this.ts : (java.lang.Long) defaultValue(fields()[8]);
+        record.dec = fieldSetFlags()[9] ? this.dec : (java.nio.ByteBuffer) defaultValue(fields()[9]);
         return record;
       } catch (Exception e) {
         throw new org.apache.avro.AvroRuntimeException(e);

http://git-wip-us.apache.org/repos/asf/avro/blob/89a31b92/lang/java/avro/src/test/java/org/apache/avro/specific/TestSpecificLogicalTypes.java
----------------------------------------------------------------------
diff --git a/lang/java/avro/src/test/java/org/apache/avro/specific/TestSpecificLogicalTypes.java b/lang/java/avro/src/test/java/org/apache/avro/specific/TestSpecificLogicalTypes.java
index c545c5a..2b93717 100644
--- a/lang/java/avro/src/test/java/org/apache/avro/specific/TestSpecificLogicalTypes.java
+++ b/lang/java/avro/src/test/java/org/apache/avro/specific/TestSpecificLogicalTypes.java
@@ -1,5 +1,7 @@
 package org.apache.avro.specific;
 
+import org.apache.avro.Conversions;
+import org.apache.avro.LogicalTypes;
 import org.apache.avro.Schema;
 import org.apache.avro.data.TimeConversions.DateConversion;
 import org.apache.avro.data.TimeConversions.TimeConversion;
@@ -19,6 +21,7 @@ import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
 import java.io.File;
 import java.io.IOException;
+import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -50,7 +53,8 @@ public class TestSpecificLogicalTypes {
         null,
         LocalDate.now(),
         LocalTime.now(),
-        DateTime.now().withZone(DateTimeZone.UTC)
+        DateTime.now().withZone(DateTimeZone.UTC),
+        new BigDecimal(123.45f).setScale(2, BigDecimal.ROUND_HALF_DOWN)
     );
 
     File data = write(TestRecordWithLogicalTypes.getClassSchema(), record);
@@ -78,7 +82,10 @@ public class TestSpecificLogicalTypes {
         new DateConversion().toInt(LocalDate.now(), null, null),
         new TimeConversion().toInt(LocalTime.now(), null, null),
         new TimestampConversion().toLong(
-            DateTime.now().withZone(DateTimeZone.UTC), null, null)
+            DateTime.now().withZone(DateTimeZone.UTC), null, null),
+        new Conversions.DecimalConversion().toBytes(
+            new BigDecimal(123.45f).setScale(2, BigDecimal.ROUND_HALF_DOWN), null,
+            LogicalTypes.decimal(9, 2))
     );
 
     File data = write(TestRecordWithoutLogicalTypes.getClassSchema(), record);
@@ -93,6 +100,7 @@ public class TestSpecificLogicalTypes {
     LocalDate date = LocalDate.now();
     LocalTime time = LocalTime.now();
     DateTime timestamp = DateTime.now().withZone(DateTimeZone.UTC);
+    BigDecimal decimal = new BigDecimal(123.45f).setScale(2, BigDecimal.ROUND_HALF_DOWN);
 
     TestRecordWithoutLogicalTypes record = new TestRecordWithoutLogicalTypes(
         true,
@@ -103,7 +111,8 @@ public class TestSpecificLogicalTypes {
         null,
         new DateConversion().toInt(date, null, null),
         new TimeConversion().toInt(time, null, null),
-        new TimestampConversion().toLong(timestamp, null, null)
+        new TimestampConversion().toLong(timestamp, null, null),
+        new Conversions.DecimalConversion().toBytes(decimal, null, LogicalTypes.decimal(9, 2))
     );
 
     File data = write(TestRecordWithoutLogicalTypes.getClassSchema(), record);
@@ -120,7 +129,8 @@ public class TestSpecificLogicalTypes {
         null,
         date,
         time,
-        timestamp
+        timestamp,
+        decimal
     );
 
     Assert.assertEquals("Should match written record", expected, actual.get(0));
@@ -131,6 +141,7 @@ public class TestSpecificLogicalTypes {
     LocalDate date = LocalDate.now();
     LocalTime time = LocalTime.now();
     DateTime timestamp = DateTime.now().withZone(DateTimeZone.UTC);
+    BigDecimal decimal = new BigDecimal(123.45f).setScale(2, BigDecimal.ROUND_HALF_DOWN);
 
     TestRecordWithLogicalTypes record = new TestRecordWithLogicalTypes(
         true,
@@ -141,7 +152,8 @@ public class TestSpecificLogicalTypes {
         null,
         date,
         time,
-        timestamp
+        timestamp,
+        decimal
     );
 
     File data = write(TestRecordWithLogicalTypes.getClassSchema(), record);
@@ -158,7 +170,8 @@ public class TestSpecificLogicalTypes {
         null,
         new DateConversion().toInt(date, null, null),
         new TimeConversion().toInt(time, null, null),
-        new TimestampConversion().toLong(timestamp, null, null)
+        new TimestampConversion().toLong(timestamp, null, null),
+        new Conversions.DecimalConversion().toBytes(decimal, null, LogicalTypes.decimal(9, 2))
     );
 
     Assert.assertEquals("Should match written record", expected, actual.get(0));

http://git-wip-us.apache.org/repos/asf/avro/blob/89a31b92/lang/java/compiler/src/main/java/org/apache/avro/compiler/specific/SpecificCompiler.java
----------------------------------------------------------------------
diff --git a/lang/java/compiler/src/main/java/org/apache/avro/compiler/specific/SpecificCompiler.java b/lang/java/compiler/src/main/java/org/apache/avro/compiler/specific/SpecificCompiler.java
index 6bf7bd5..9ba36d3 100644
--- a/lang/java/compiler/src/main/java/org/apache/avro/compiler/specific/SpecificCompiler.java
+++ b/lang/java/compiler/src/main/java/org/apache/avro/compiler/specific/SpecificCompiler.java
@@ -35,6 +35,7 @@ import java.util.Map;
 import java.util.Set;
 
 import org.apache.avro.Conversion;
+import org.apache.avro.Conversions;
 import org.apache.avro.LogicalTypes;
 import org.apache.avro.data.TimeConversions.DateConversion;
 import org.apache.avro.data.TimeConversions.TimeConversion;
@@ -96,6 +97,7 @@ public class SpecificCompiler {
     SPECIFIC.addLogicalTypeConversion(new DateConversion());
     SPECIFIC.addLogicalTypeConversion(new TimeConversion());
     SPECIFIC.addLogicalTypeConversion(new TimestampConversion());
+    SPECIFIC.addLogicalTypeConversion(new Conversions.DecimalConversion());
   }
 
   private final Set<Schema> queue = new HashSet<Schema>();
@@ -106,6 +108,7 @@ public class SpecificCompiler {
   private boolean createSetters = true;
   private boolean createAllArgsConstructor = true;
   private String outputCharacterEncoding;
+  private boolean enableDecimalLogicalType = false;
 
   /*
    * Used in the record.vm template.
@@ -208,6 +211,14 @@ public class SpecificCompiler {
     this.createSetters = createSetters;
   }
 
+  /**
+   * Set to true to use {@link java.math.BigDecimal} instead of
+   * {@link java.nio.ByteBuffer} for logical type "decimal"
+   */
+  public void setEnableDecimalLogicalType(boolean enableDecimalLogicalType) {
+    this.enableDecimalLogicalType = enableDecimalLogicalType;
+  }
+
   private static String logChuteName = null;
 
   private void initializeVelocity() {
@@ -564,10 +575,13 @@ public class SpecificCompiler {
 
   /** Utility for template use.  Returns the java type for a Schema. */
   public String javaType(Schema schema) {
-    Conversion<?> conversion = SPECIFIC
-        .getConversionFor(schema.getLogicalType());
-    if (conversion != null) {
-      return conversion.getConvertedType().getName();
+    if (enableDecimalLogicalType
+        || !(schema.getLogicalType() instanceof LogicalTypes.Decimal)) {
+      Conversion<?> conversion = SPECIFIC
+          .getConversionFor(schema.getLogicalType());
+      if (conversion != null) {
+        return conversion.getConvertedType().getName();
+      }
     }
 
     switch (schema.getType()) {
@@ -601,12 +615,6 @@ public class SpecificCompiler {
 
   /** Utility for template use.  Returns the unboxed java type for a Schema. */
   public String javaUnbox(Schema schema) {
-    Conversion<?> conversion = SPECIFIC
-        .getConversionFor(schema.getLogicalType());
-    if (conversion != null) {
-      return conversion.getConvertedType().getName();
-    }
-
     switch (schema.getType()) {
     case INT:     return "int";
     case LONG:    return "long";
@@ -627,13 +635,20 @@ public class SpecificCompiler {
   }
 
   public String conversionInstance(Schema schema) {
+    if (schema == null || schema.getLogicalType() == null) {
+      return "null";
+    }
+
     if (LogicalTypes.date().equals(schema.getLogicalType())) {
       return "DATE_CONVERSION";
     } else if (LogicalTypes.timeMillis().equals(schema.getLogicalType())) {
       return "TIME_CONVERSION";
     } else if (LogicalTypes.timestampMillis().equals(schema.getLogicalType())) {
       return "TIMESTAMP_CONVERSION";
+    } else if (LogicalTypes.Decimal.class.equals(schema.getLogicalType().getClass())) {
+      return enableDecimalLogicalType ? "DECIMAL_CONVERSION" : "null";
     }
+
     return "null";
   }
 

http://git-wip-us.apache.org/repos/asf/avro/blob/89a31b92/lang/java/compiler/src/main/javacc/org/apache/avro/compiler/idl/idl.jj
----------------------------------------------------------------------
diff --git a/lang/java/compiler/src/main/javacc/org/apache/avro/compiler/idl/idl.jj b/lang/java/compiler/src/main/javacc/org/apache/avro/compiler/idl/idl.jj
index 775a8cb..6f59413 100644
--- a/lang/java/compiler/src/main/javacc/org/apache/avro/compiler/idl/idl.jj
+++ b/lang/java/compiler/src/main/javacc/org/apache/avro/compiler/idl/idl.jj
@@ -240,6 +240,7 @@ TOKEN :
 | < DATE: "date" >
 | < TIME: "time_ms" >
 | < TIMESTAMP: "timestamp_ms" >
+| < DECIMAL: "decimal" >
 }
 
 /* LITERALS */
@@ -1488,6 +1489,19 @@ Schema PrimitiveType():
 | "date" { return LogicalTypes.date().addToSchema(Schema.create(Type.INT)); }
 | "time_ms" { return LogicalTypes.timeMillis().addToSchema(Schema.create(Type.INT)); }
 | "timestamp_ms" { return LogicalTypes.timestampMillis().addToSchema(Schema.create(Type.LONG)); }
+| "decimal" { return DecimalTypeProperties(); }
+}
+
+Schema DecimalTypeProperties():
+{
+  int precision;
+  int scale;
+}
+{
+  "(" {precision = Json().asInt();} "," {scale = Json().asInt();} ")"
+  {
+    return LogicalTypes.decimal(precision, scale).addToSchema(Schema.create(Type.BYTES)); 
+  }
 }
 
 /**
@@ -1555,6 +1569,7 @@ Token AnyIdentifier():
    t = <DATE> |
    t = <TIME> |
    t = <TIMESTAMP> |
+   t = <DECIMAL> |
    t = <IDENTIFIER>)
   {
     return t;

http://git-wip-us.apache.org/repos/asf/avro/blob/89a31b92/lang/java/compiler/src/main/velocity/org/apache/avro/compiler/specific/templates/java/classic/record.vm
----------------------------------------------------------------------
diff --git a/lang/java/compiler/src/main/velocity/org/apache/avro/compiler/specific/templates/java/classic/record.vm b/lang/java/compiler/src/main/velocity/org/apache/avro/compiler/specific/templates/java/classic/record.vm
index 3bbe1e6..dc81aba 100644
--- a/lang/java/compiler/src/main/velocity/org/apache/avro/compiler/specific/templates/java/classic/record.vm
+++ b/lang/java/compiler/src/main/velocity/org/apache/avro/compiler/specific/templates/java/classic/record.vm
@@ -112,6 +112,7 @@ public class ${this.mangle($schema.getName())}#if ($schema.isError()) extends or
   protected static final org.apache.avro.data.TimeConversions.DateConversion DATE_CONVERSION = new org.apache.avro.data.TimeConversions.DateConversion();
   protected static final org.apache.avro.data.TimeConversions.TimeConversion TIME_CONVERSION = new org.apache.avro.data.TimeConversions.TimeConversion();
   protected static final org.apache.avro.data.TimeConversions.TimestampConversion TIMESTAMP_CONVERSION = new org.apache.avro.data.TimeConversions.TimestampConversion();
+  protected static final org.apache.avro.Conversions.DecimalConversion DECIMAL_CONVERSION = new org.apache.avro.Conversions.DecimalConversion();
 
   private final org.apache.avro.Conversion<?>[] conversions =
       new org.apache.avro.Conversion<?>[] {
@@ -382,12 +383,20 @@ public class ${this.mangle($schema.getName())}#if ($schema.isError()) extends or
         if (${this.mangle($field.name(), $schema.isError())}Builder != null) {
           record.${this.mangle($field.name(), $schema.isError())} = this.${this.mangle($field.name(), $schema.isError())}Builder.build();
         } else {
+#if ($this.hasLogicalTypeField($schema))
+          record.${this.mangle($field.name(), $schema.isError())} = fieldSetFlags()[$field.pos()] ? this.${this.mangle($field.name(), $schema.isError())} : (${this.javaType($field.schema())}) defaultValue(fields()[$field.pos()], record.getConversion($field.pos()));
+#else
           record.${this.mangle($field.name(), $schema.isError())} = fieldSetFlags()[$field.pos()] ? this.${this.mangle($field.name(), $schema.isError())} : (${this.javaType($field.schema())}) defaultValue(fields()[$field.pos()]);
+#end
         }
 #else
+#if ($this.hasLogicalTypeField($schema))
+        record.${this.mangle($field.name(), $schema.isError())} = fieldSetFlags()[$field.pos()] ? this.${this.mangle($field.name(), $schema.isError())} : (${this.javaType($field.schema())}) defaultValue(fields()[$field.pos()], record.getConversion($field.pos()));
+#else
         record.${this.mangle($field.name(), $schema.isError())} = fieldSetFlags()[$field.pos()] ? this.${this.mangle($field.name(), $schema.isError())} : (${this.javaType($field.schema())}) defaultValue(fields()[$field.pos()]);
 #end
 #end
+#end
         return record;
       } catch (Exception e) {
         throw new org.apache.avro.AvroRuntimeException(e);

http://git-wip-us.apache.org/repos/asf/avro/blob/89a31b92/lang/java/compiler/src/test/idl/input/mr_events.avdl
----------------------------------------------------------------------
diff --git a/lang/java/compiler/src/test/idl/input/mr_events.avdl b/lang/java/compiler/src/test/idl/input/mr_events.avdl
index 272891a..89c68fd 100644
--- a/lang/java/compiler/src/test/idl/input/mr_events.avdl
+++ b/lang/java/compiler/src/test/idl/input/mr_events.avdl
@@ -43,6 +43,7 @@ protocol Events {
   record JobFinished {
     string jobid;
     timestamp_ms finishTime;
+    decimal(9,2) finishRatio;
     int finishedMaps;
     int finishedReduces;
     int failedMaps;

http://git-wip-us.apache.org/repos/asf/avro/blob/89a31b92/lang/java/compiler/src/test/idl/output/mr_events.avpr
----------------------------------------------------------------------
diff --git a/lang/java/compiler/src/test/idl/output/mr_events.avpr b/lang/java/compiler/src/test/idl/output/mr_events.avpr
index c4e694f..58fd991 100644
--- a/lang/java/compiler/src/test/idl/output/mr_events.avpr
+++ b/lang/java/compiler/src/test/idl/output/mr_events.avpr
@@ -54,6 +54,9 @@
       "name" : "finishTime",
       "type" : {"type": "long", "logicalType": "timestamp-millis"}
     }, {
+      "name" : "finishRatio",
+      "type" : {"type": "bytes", "logicalType": "decimal", "precision": 9, "scale": 2}
+    }, {
       "name" : "finishedMaps",
       "type" : "int"
     }, {

http://git-wip-us.apache.org/repos/asf/avro/blob/89a31b92/lang/java/compiler/src/test/java/org/apache/avro/compiler/specific/TestSpecificCompiler.java
----------------------------------------------------------------------
diff --git a/lang/java/compiler/src/test/java/org/apache/avro/compiler/specific/TestSpecificCompiler.java b/lang/java/compiler/src/test/java/org/apache/avro/compiler/specific/TestSpecificCompiler.java
index 3668d2f..58d2af4 100644
--- a/lang/java/compiler/src/test/java/org/apache/avro/compiler/specific/TestSpecificCompiler.java
+++ b/lang/java/compiler/src/test/java/org/apache/avro/compiler/specific/TestSpecificCompiler.java
@@ -35,7 +35,6 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 
-import com.google.common.io.Resources;
 import org.apache.avro.AvroTestUtil;
 import org.apache.avro.LogicalTypes;
 import org.apache.avro.Schema;
@@ -300,8 +299,9 @@ public class TestSpecificCompiler {
   }
 
   @Test
-  public void testLogicalTypes() throws Exception {
+  public void testJavaTypeWithDecimalLogicalTypeEnabled() throws Exception {
     SpecificCompiler compiler = createCompiler();
+    compiler.setEnableDecimalLogicalType(true);
 
     Schema dateSchema = LogicalTypes.date()
         .addToSchema(Schema.create(Schema.Type.INT));
@@ -311,15 +311,55 @@ public class TestSpecificCompiler {
         .addToSchema(Schema.create(Schema.Type.LONG));
     Schema decimalSchema = LogicalTypes.decimal(9,2)
         .addToSchema(Schema.create(Schema.Type.BYTES));
+    Schema uuidSchema = LogicalTypes.uuid()
+        .addToSchema(Schema.create(Schema.Type.STRING));
 
+    // Date/time types should always use upper level java classes
+    // Decimal type target class depends on configuration
+    // UUID should always be CharSequence since we haven't added its
+    // support in SpecificRecord
     Assert.assertEquals("Should use Joda LocalDate for date type",
         "org.joda.time.LocalDate", compiler.javaType(dateSchema));
     Assert.assertEquals("Should use Joda LocalTime for time-millis type",
         "org.joda.time.LocalTime", compiler.javaType(timeSchema));
     Assert.assertEquals("Should use Joda DateTime for timestamp-millis type",
         "org.joda.time.DateTime", compiler.javaType(timestampSchema));
-    Assert.assertEquals("Should use underlying type when missing conversion",
+    Assert.assertEquals("Should use Java BigDecimal type",
+        "java.math.BigDecimal", compiler.javaType(decimalSchema));
+    Assert.assertEquals("Should use Java CharSequence type",
+        "java.lang.CharSequence", compiler.javaType(uuidSchema));
+  }
+
+  @Test
+  public void testJavaTypeWithDecimalLogicalTypeDisabled() throws Exception {
+    SpecificCompiler compiler = createCompiler();
+    compiler.setEnableDecimalLogicalType(false);
+
+    Schema dateSchema = LogicalTypes.date()
+        .addToSchema(Schema.create(Schema.Type.INT));
+    Schema timeSchema = LogicalTypes.timeMillis()
+        .addToSchema(Schema.create(Schema.Type.INT));
+    Schema timestampSchema = LogicalTypes.timestampMillis()
+        .addToSchema(Schema.create(Schema.Type.LONG));
+    Schema decimalSchema = LogicalTypes.decimal(9,2)
+        .addToSchema(Schema.create(Schema.Type.BYTES));
+    Schema uuidSchema = LogicalTypes.uuid()
+        .addToSchema(Schema.create(Schema.Type.STRING));
+
+    // Date/time types should always use upper level java classes
+    // Decimal type target class depends on configuration
+    // UUID should always be CharSequence since we haven't added its
+    // support in SpecificRecord
+    Assert.assertEquals("Should use Joda LocalDate for date type",
+        "org.joda.time.LocalDate", compiler.javaType(dateSchema));
+    Assert.assertEquals("Should use Joda LocalTime for time-millis type",
+        "org.joda.time.LocalTime", compiler.javaType(timeSchema));
+    Assert.assertEquals("Should use Joda DateTime for timestamp-millis type",
+        "org.joda.time.DateTime", compiler.javaType(timestampSchema));
+    Assert.assertEquals("Should use ByteBuffer type",
         "java.nio.ByteBuffer", compiler.javaType(decimalSchema));
+    Assert.assertEquals("Should use Java CharSequence type",
+        "java.lang.CharSequence", compiler.javaType(uuidSchema));
   }
 
   @Test
@@ -329,4 +369,61 @@ public class TestSpecificCompiler {
     assertCompilesWithJavaCompiler(
         new SpecificCompiler(logicalTypesWithMultipleFields).compile());
   }
+
+  @Test
+  public void testConversionInstanceWithDecimalLogicalTypeDisabled() throws Exception {
+    SpecificCompiler compiler = createCompiler();
+    compiler.setEnableDecimalLogicalType(false);
+
+    Schema dateSchema = LogicalTypes.date()
+        .addToSchema(Schema.create(Schema.Type.INT));
+    Schema timeSchema = LogicalTypes.timeMillis()
+        .addToSchema(Schema.create(Schema.Type.INT));
+    Schema timestampSchema = LogicalTypes.timestampMillis()
+        .addToSchema(Schema.create(Schema.Type.LONG));
+    Schema decimalSchema = LogicalTypes.decimal(9,2)
+        .addToSchema(Schema.create(Schema.Type.BYTES));
+    Schema uuidSchema = LogicalTypes.uuid()
+        .addToSchema(Schema.create(Schema.Type.STRING));
+
+    Assert.assertEquals("Should use DATE_CONVERSION for date type",
+        "DATE_CONVERSION", compiler.conversionInstance(dateSchema));
+    Assert.assertEquals("Should use TIME_CONVERSION for time type",
+        "TIME_CONVERSION", compiler.conversionInstance(timeSchema));
+    Assert.assertEquals("Should use TIMESTAMP_CONVERSION for date type",
+        "TIMESTAMP_CONVERSION", compiler.conversionInstance(timestampSchema));
+    Assert.assertEquals("Should use null for decimal if the flag is off",
+        "null", compiler.conversionInstance(decimalSchema));
+    Assert.assertEquals("Should use null for decimal if the flag is off",
+        "null", compiler.conversionInstance(uuidSchema));
+  }
+
+
+  @Test
+  public void testConversionInstanceWithDecimalLogicalTypeEnabled() throws Exception {
+    SpecificCompiler compiler = createCompiler();
+    compiler.setEnableDecimalLogicalType(true);
+
+    Schema dateSchema = LogicalTypes.date()
+        .addToSchema(Schema.create(Schema.Type.INT));
+    Schema timeSchema = LogicalTypes.timeMillis()
+        .addToSchema(Schema.create(Schema.Type.INT));
+    Schema timestampSchema = LogicalTypes.timestampMillis()
+        .addToSchema(Schema.create(Schema.Type.LONG));
+    Schema decimalSchema = LogicalTypes.decimal(9,2)
+        .addToSchema(Schema.create(Schema.Type.BYTES));
+    Schema uuidSchema = LogicalTypes.uuid()
+        .addToSchema(Schema.create(Schema.Type.STRING));
+
+    Assert.assertEquals("Should use DATE_CONVERSION for date type",
+        "DATE_CONVERSION", compiler.conversionInstance(dateSchema));
+    Assert.assertEquals("Should use TIME_CONVERSION for time type",
+        "TIME_CONVERSION", compiler.conversionInstance(timeSchema));
+    Assert.assertEquals("Should use TIMESTAMP_CONVERSION for date type",
+        "TIMESTAMP_CONVERSION", compiler.conversionInstance(timestampSchema));
+    Assert.assertEquals("Should use null for decimal if the flag is off",
+        "DECIMAL_CONVERSION", compiler.conversionInstance(decimalSchema));
+    Assert.assertEquals("Should use null for decimal if the flag is off",
+        "null", compiler.conversionInstance(uuidSchema));
+  }
 }

http://git-wip-us.apache.org/repos/asf/avro/blob/89a31b92/lang/java/maven-plugin/src/main/java/org/apache/avro/mojo/AbstractAvroMojo.java
----------------------------------------------------------------------
diff --git a/lang/java/maven-plugin/src/main/java/org/apache/avro/mojo/AbstractAvroMojo.java b/lang/java/maven-plugin/src/main/java/org/apache/avro/mojo/AbstractAvroMojo.java
index 83db1c0..9d78ede 100644
--- a/lang/java/maven-plugin/src/main/java/org/apache/avro/mojo/AbstractAvroMojo.java
+++ b/lang/java/maven-plugin/src/main/java/org/apache/avro/mojo/AbstractAvroMojo.java
@@ -123,6 +123,13 @@ public abstract class AbstractAvroMojo extends AbstractMojo {
   protected boolean createSetters;
 
   /**
+   * Determines whether or not to use Java classes for decimal types
+   *
+   * @parameter default-value="false"
+   */
+  protected boolean enableDecimalLogicalType;
+
+  /**
    * The current Maven project.
    *
    * @parameter default-value="${project}"

http://git-wip-us.apache.org/repos/asf/avro/blob/89a31b92/lang/java/maven-plugin/src/main/java/org/apache/avro/mojo/IDLProtocolMojo.java
----------------------------------------------------------------------
diff --git a/lang/java/maven-plugin/src/main/java/org/apache/avro/mojo/IDLProtocolMojo.java b/lang/java/maven-plugin/src/main/java/org/apache/avro/mojo/IDLProtocolMojo.java
index 48f9050..3f61ac2 100644
--- a/lang/java/maven-plugin/src/main/java/org/apache/avro/mojo/IDLProtocolMojo.java
+++ b/lang/java/maven-plugin/src/main/java/org/apache/avro/mojo/IDLProtocolMojo.java
@@ -93,6 +93,7 @@ public class IDLProtocolMojo extends AbstractAvroMojo {
       compiler.setTemplateDir(templateDirectory);
       compiler.setFieldVisibility(getFieldVisibility());
       compiler.setCreateSetters(createSetters);
+      compiler.setEnableDecimalLogicalType(enableDecimalLogicalType);
       compiler.compileToDestination(null, outputDirectory);
     } catch (ParseException e) {
       throw new IOException(e);

http://git-wip-us.apache.org/repos/asf/avro/blob/89a31b92/lang/java/maven-plugin/src/main/java/org/apache/avro/mojo/ProtocolMojo.java
----------------------------------------------------------------------
diff --git a/lang/java/maven-plugin/src/main/java/org/apache/avro/mojo/ProtocolMojo.java b/lang/java/maven-plugin/src/main/java/org/apache/avro/mojo/ProtocolMojo.java
index d49ec8c..a30a082 100644
--- a/lang/java/maven-plugin/src/main/java/org/apache/avro/mojo/ProtocolMojo.java
+++ b/lang/java/maven-plugin/src/main/java/org/apache/avro/mojo/ProtocolMojo.java
@@ -61,6 +61,7 @@ public class ProtocolMojo extends AbstractAvroMojo {
     compiler.setStringType(StringType.valueOf(stringType));
     compiler.setFieldVisibility(getFieldVisibility());
     compiler.setCreateSetters(createSetters);
+    compiler.setEnableDecimalLogicalType(enableDecimalLogicalType);
     compiler.compileToDestination(src, outputDirectory);
   }
 

http://git-wip-us.apache.org/repos/asf/avro/blob/89a31b92/lang/java/maven-plugin/src/main/java/org/apache/avro/mojo/SchemaMojo.java
----------------------------------------------------------------------
diff --git a/lang/java/maven-plugin/src/main/java/org/apache/avro/mojo/SchemaMojo.java b/lang/java/maven-plugin/src/main/java/org/apache/avro/mojo/SchemaMojo.java
index 6fc3d8d..9a84920 100644
--- a/lang/java/maven-plugin/src/main/java/org/apache/avro/mojo/SchemaMojo.java
+++ b/lang/java/maven-plugin/src/main/java/org/apache/avro/mojo/SchemaMojo.java
@@ -78,6 +78,7 @@ public class SchemaMojo extends AbstractAvroMojo {
     compiler.setStringType(StringType.valueOf(stringType));
     compiler.setFieldVisibility(getFieldVisibility());
     compiler.setCreateSetters(createSetters);
+    compiler.setEnableDecimalLogicalType(enableDecimalLogicalType);
     compiler.setOutputCharacterEncoding(project.getProperties().getProperty("project.build.sourceEncoding"));
     compiler.compileToDestination(src, outputDirectory);
   }

http://git-wip-us.apache.org/repos/asf/avro/blob/89a31b92/lang/java/tools/src/main/java/org/apache/avro/tool/SpecificCompilerTool.java
----------------------------------------------------------------------
diff --git a/lang/java/tools/src/main/java/org/apache/avro/tool/SpecificCompilerTool.java b/lang/java/tools/src/main/java/org/apache/avro/tool/SpecificCompilerTool.java
index b038b4e..2ba6616 100644
--- a/lang/java/tools/src/main/java/org/apache/avro/tool/SpecificCompilerTool.java
+++ b/lang/java/tools/src/main/java/org/apache/avro/tool/SpecificCompilerTool.java
@@ -19,6 +19,7 @@ package org.apache.avro.tool;
 
 import java.io.File;
 import java.io.FilenameFilter;
+import java.io.IOException;
 import java.io.InputStream;
 import java.io.PrintStream;
 import java.util.ArrayList;
@@ -42,16 +43,21 @@ public class SpecificCompilerTool implements Tool {
       List<String> args) throws Exception {
     if (args.size() < 3) {
       System.err
-          .println("Usage: [-encoding <outputencoding>] [-string] (schema|protocol) input... outputdir");
+          .println("Usage: [-encoding <outputencoding>] [-string] [-bigDecimal] (schema|protocol) input... outputdir");
       System.err
           .println(" input - input files or directories");
       System.err
           .println(" outputdir - directory to write generated java");
+      System.err.println(" -encoding <outputencoding> - set the encoding of " +
+          "output file(s)");
       System.err.println(" -string - use java.lang.String instead of Utf8");
+      System.err.println(" -bigDecimal - use java.math.BigDecimal for " +
+          "decimal type instead of java.nio.ByteBuffer");
       return 1;
     }
 
     StringType stringType = StringType.CharSequence;
+    boolean useLogicalDecimal = false;
 
     int arg = 0;
 
@@ -67,6 +73,11 @@ public class SpecificCompilerTool implements Tool {
       arg++;
     }
 
+    if ("-bigDecimal".equalsIgnoreCase(args.get(arg))) {
+      useLogicalDecimal = true;
+      arg++;
+    }
+
     String method = args.get(arg);
     List<File> inputs = new ArrayList<File>();
     File output = new File(args.get(args.size() - 1));
@@ -80,21 +91,13 @@ public class SpecificCompilerTool implements Tool {
       for (File src : determineInputs(inputs, SCHEMA_FILTER)) {
         Schema schema = parser.parse(src);
         SpecificCompiler compiler = new SpecificCompiler(schema);
-        compiler.setStringType(stringType);
-        if (encoding != null) {
-          compiler.setOutputCharacterEncoding(encoding);
-        }
-        compiler.compileToDestination(src, output);
+        executeCompiler(compiler, encoding, stringType, useLogicalDecimal, src, output);
       }
     } else if ("protocol".equals(method)) {
       for (File src : determineInputs(inputs, PROTOCOL_FILTER)) {
         Protocol protocol = Protocol.parse(src);
         SpecificCompiler compiler = new SpecificCompiler(protocol);
-        compiler.setStringType(stringType);
-        if (encoding != null) {
-          compiler.setOutputCharacterEncoding(encoding);
-        }
-        compiler.compileToDestination(src, output);
+        executeCompiler(compiler, encoding, stringType, useLogicalDecimal, src, output);
       }
     } else {
       System.err.println("Expected \"schema\" or \"protocol\".");
@@ -103,6 +106,20 @@ public class SpecificCompilerTool implements Tool {
     return 0;
   }
 
+  private void executeCompiler(SpecificCompiler compiler,
+                               String encoding,
+                               StringType stringType,
+                               boolean enableDecimalLogicalType,
+                               File src,
+                               File output) throws IOException {
+    compiler.setStringType(stringType);
+    compiler.setEnableDecimalLogicalType(enableDecimalLogicalType);
+    if (encoding != null) {
+      compiler.setOutputCharacterEncoding(encoding);
+    }
+    compiler.compileToDestination(src, output);
+  }
+
   @Override
   public String getName() {
     return "compile";


Mime
View raw message