AVRO-1684: Add time types to the specific compiler.
This adds a dependency on Joda time for both the compiler and the
compiled classes. When generating Java classes, any conversion that is
registered with the compiler's SpecificData instance will be used.
Closes #86.
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 63fa025..e66726e 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
@@ -74,8 +74,8 @@
}
}
- private <T> Object convert(Schema schema, LogicalType logicalType,
- Conversion<T> conversion, Object datum) {
+ protected <T> Object convert(Schema schema, LogicalType logicalType,
+ Conversion<T> conversion, Object datum) {
if (conversion == null) {
return datum;
}
diff --git a/lang/java/avro/src/main/java/org/apache/avro/specific/SpecificDatumReader.java b/lang/java/avro/src/main/java/org/apache/avro/specific/SpecificDatumReader.java
index 870d16f..774ca09 100644
--- a/lang/java/avro/src/main/java/org/apache/avro/specific/SpecificDatumReader.java
+++ b/lang/java/avro/src/main/java/org/apache/avro/specific/SpecificDatumReader.java
@@ -17,10 +17,13 @@
*/
package org.apache.avro.specific;
+import org.apache.avro.Conversion;
import org.apache.avro.Schema;
import org.apache.avro.AvroRuntimeException;
import org.apache.avro.generic.GenericDatumReader;
+import org.apache.avro.io.ResolvingDecoder;
import org.apache.avro.util.ClassUtils;
+import java.io.IOException;
/** {@link org.apache.avro.io.DatumReader DatumReader} for generated Java classes. */
public class SpecificDatumReader<T> extends GenericDatumReader<T> {
@@ -98,5 +101,26 @@
}
}
+ @Override
+ protected void readField(Object r, Schema.Field f, Object oldDatum,
+ ResolvingDecoder in, Object state)
+ throws IOException {
+ if (r instanceof SpecificRecordBase) {
+ Conversion<?> conversion = ((SpecificRecordBase) r).getConversion(f.pos());
+
+ Object datum;
+ if (conversion != null) {
+ datum = readWithConversion(
+ oldDatum, f.schema(), f.schema().getLogicalType(), conversion, in);
+ } else {
+ datum = readWithoutConversion(oldDatum, f.schema(), in);
+ }
+
+ getData().setField(r, f.name(), f.pos(), datum);
+
+ } else {
+ super.readField(r, f, oldDatum, in, state);
+ }
+ }
}
diff --git a/lang/java/avro/src/main/java/org/apache/avro/specific/SpecificDatumWriter.java b/lang/java/avro/src/main/java/org/apache/avro/specific/SpecificDatumWriter.java
index 128b02e..7bee02a 100644
--- a/lang/java/avro/src/main/java/org/apache/avro/specific/SpecificDatumWriter.java
+++ b/lang/java/avro/src/main/java/org/apache/avro/specific/SpecificDatumWriter.java
@@ -19,6 +19,8 @@
import java.io.IOException;
+import org.apache.avro.Conversion;
+import org.apache.avro.LogicalType;
import org.apache.avro.Schema;
import org.apache.avro.generic.GenericDatumWriter;
import org.apache.avro.io.Encoder;
@@ -69,5 +71,24 @@
writeString(datum, out);
}
+ @Override
+ protected void writeField(Object datum, Schema.Field f, Encoder out,
+ Object state) throws IOException {
+ if (datum instanceof SpecificRecordBase) {
+ Conversion<?> conversion = ((SpecificRecordBase) datum).getConversion(f.pos());
+ Schema fieldSchema = f.schema();
+ LogicalType logicalType = fieldSchema.getLogicalType();
+
+ Object value = getData().getField(datum, f.name(), f.pos());
+ if (conversion != null && logicalType != null) {
+ value = convert(fieldSchema, logicalType, conversion, value);
+ }
+
+ writeWithoutConversion(fieldSchema, value, out);
+
+ } else {
+ super.writeField(datum, f, out, state);
+ }
+ }
}
diff --git a/lang/java/avro/src/main/java/org/apache/avro/specific/SpecificRecordBase.java b/lang/java/avro/src/main/java/org/apache/avro/specific/SpecificRecordBase.java
index 77d0928..baedeb8 100644
--- a/lang/java/avro/src/main/java/org/apache/avro/specific/SpecificRecordBase.java
+++ b/lang/java/avro/src/main/java/org/apache/avro/specific/SpecificRecordBase.java
@@ -22,6 +22,7 @@
import java.io.ObjectInput;
import java.io.IOException;
+import org.apache.avro.Conversion;
import org.apache.avro.Schema;
import org.apache.avro.generic.GenericRecord;
@@ -34,6 +35,11 @@
public abstract Object get(int field);
public abstract void put(int field, Object value);
+ public Conversion<?> getConversion(int field) {
+ // for backward-compatibility. no older specific classes have conversions.
+ return null;
+ }
+
@Override
public void put(String fieldName, Object value) {
put(getSchema().getField(fieldName).pos(), value);
@@ -44,6 +50,10 @@
return get(getSchema().getField(fieldName).pos());
}
+ public Conversion<?> getConverion(String fieldName) {
+ return getConversion(getSchema().getField(fieldName).pos());
+ }
+
@Override
public boolean equals(Object that) {
if (that == this) return true; // identical object
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
new file mode 100644
index 0000000..a01e450
--- /dev/null
+++ b/lang/java/avro/src/test/java/org/apache/avro/specific/TestRecordWithLogicalTypes.java
@@ -0,0 +1,697 @@
+/**
+ * Autogenerated by Avro
+ *
+ * DO NOT EDIT DIRECTLY
+ */
+package org.apache.avro.specific;
+@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 org.apache.avro.Schema getClassSchema() { return SCHEMA$; }
+ @Deprecated public boolean b;
+ @Deprecated public int i32;
+ @Deprecated public long i64;
+ @Deprecated public float f32;
+ @Deprecated public double f64;
+ @Deprecated public java.lang.CharSequence s;
+ @Deprecated public org.joda.time.LocalDate d;
+ @Deprecated public org.joda.time.LocalTime t;
+ @Deprecated public org.joda.time.DateTime ts;
+
+ /**
+ * Default constructor. Note that this does not initialize fields
+ * to their default values from the schema. If that is desired then
+ * one should use <code>newBuilder()</code>.
+ */
+ public TestRecordWithLogicalTypes() {}
+
+ /**
+ * 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) {
+ this.b = b;
+ this.i32 = i32;
+ this.i64 = i64;
+ this.f32 = f32;
+ this.f64 = f64;
+ this.s = s;
+ this.d = d;
+ this.t = t;
+ this.ts = ts;
+ }
+
+ 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 b;
+ case 1: return i32;
+ case 2: return i64;
+ case 3: return f32;
+ case 4: return f64;
+ case 5: return s;
+ case 6: return d;
+ case 7: return t;
+ case 8: return ts;
+ default: throw new org.apache.avro.AvroRuntimeException("Bad index");
+ }
+ }
+ // Used by DatumReader. Applications should not call.
+ @SuppressWarnings(value="unchecked")
+ public void put(int field$, java.lang.Object value$) {
+ switch (field$) {
+ case 0: b = (java.lang.Boolean)value$; break;
+ case 1: i32 = (java.lang.Integer)value$; break;
+ case 2: i64 = (java.lang.Long)value$; break;
+ case 3: f32 = (java.lang.Float)value$; break;
+ case 4: f64 = (java.lang.Double)value$; break;
+ case 5: s = (java.lang.CharSequence)value$; break;
+ 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;
+ default: throw new org.apache.avro.AvroRuntimeException("Bad index");
+ }
+ }
+
+ /**
+ * Gets the value of the 'b' field.
+ */
+ public java.lang.Boolean getB() {
+ return b;
+ }
+
+ /**
+ * Sets the value of the 'b' field.
+ * @param value the value to set.
+ */
+ public void setB(java.lang.Boolean value) {
+ this.b = value;
+ }
+
+ /**
+ * Gets the value of the 'i32' field.
+ */
+ public java.lang.Integer getI32() {
+ return i32;
+ }
+
+ /**
+ * Sets the value of the 'i32' field.
+ * @param value the value to set.
+ */
+ public void setI32(java.lang.Integer value) {
+ this.i32 = value;
+ }
+
+ /**
+ * Gets the value of the 'i64' field.
+ */
+ public java.lang.Long getI64() {
+ return i64;
+ }
+
+ /**
+ * Sets the value of the 'i64' field.
+ * @param value the value to set.
+ */
+ public void setI64(java.lang.Long value) {
+ this.i64 = value;
+ }
+
+ /**
+ * Gets the value of the 'f32' field.
+ */
+ public java.lang.Float getF32() {
+ return f32;
+ }
+
+ /**
+ * Sets the value of the 'f32' field.
+ * @param value the value to set.
+ */
+ public void setF32(java.lang.Float value) {
+ this.f32 = value;
+ }
+
+ /**
+ * Gets the value of the 'f64' field.
+ */
+ public java.lang.Double getF64() {
+ return f64;
+ }
+
+ /**
+ * Sets the value of the 'f64' field.
+ * @param value the value to set.
+ */
+ public void setF64(java.lang.Double value) {
+ this.f64 = value;
+ }
+
+ /**
+ * Gets the value of the 's' field.
+ */
+ public java.lang.CharSequence getS() {
+ return s;
+ }
+
+ /**
+ * Sets the value of the 's' field.
+ * @param value the value to set.
+ */
+ public void setS(java.lang.CharSequence value) {
+ this.s = value;
+ }
+
+ /**
+ * Gets the value of the 'd' field.
+ */
+ public org.joda.time.LocalDate getD() {
+ return d;
+ }
+
+ /**
+ * Sets the value of the 'd' field.
+ * @param value the value to set.
+ */
+ public void setD(org.joda.time.LocalDate value) {
+ this.d = value;
+ }
+
+ /**
+ * Gets the value of the 't' field.
+ */
+ public org.joda.time.LocalTime getT() {
+ return t;
+ }
+
+ /**
+ * Sets the value of the 't' field.
+ * @param value the value to set.
+ */
+ public void setT(org.joda.time.LocalTime value) {
+ this.t = value;
+ }
+
+ /**
+ * Gets the value of the 'ts' field.
+ */
+ public org.joda.time.DateTime getTs() {
+ return ts;
+ }
+
+ /**
+ * Sets the value of the 'ts' field.
+ * @param value the value to set.
+ */
+ public void setTs(org.joda.time.DateTime value) {
+ this.ts = value;
+ }
+
+ 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();
+
+ private final org.apache.avro.Conversion<?>[] conversions =
+ new org.apache.avro.Conversion<?>[] {
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ DATE_CONVERSION,
+ TIME_CONVERSION,
+ TIMESTAMP_CONVERSION,
+ null
+ };
+
+ @Override
+ public org.apache.avro.Conversion<?> getConversion(int field) {
+ return conversions[field];
+ }
+
+ /** Creates a new TestRecordWithLogicalTypes RecordBuilder */
+ public static TestRecordWithLogicalTypes.Builder newBuilder() {
+ return new TestRecordWithLogicalTypes.Builder();
+ }
+
+ /** Creates a new TestRecordWithLogicalTypes RecordBuilder by copying an existing Builder */
+ public static TestRecordWithLogicalTypes.Builder newBuilder(TestRecordWithLogicalTypes.Builder other) {
+ return new TestRecordWithLogicalTypes.Builder(other);
+ }
+
+ /** Creates a new TestRecordWithLogicalTypes RecordBuilder by copying an existing TestRecordWithLogicalTypes instance */
+ public static TestRecordWithLogicalTypes.Builder newBuilder(TestRecordWithLogicalTypes other) {
+ return new TestRecordWithLogicalTypes.Builder(other);
+ }
+
+ /**
+ * RecordBuilder for TestRecordWithLogicalTypes instances.
+ */
+ public static class Builder extends org.apache.avro.specific.SpecificRecordBuilderBase<TestRecordWithLogicalTypes>
+ implements org.apache.avro.data.RecordBuilder<TestRecordWithLogicalTypes> {
+
+ private boolean b;
+ private int i32;
+ private long i64;
+ private float f32;
+ private double f64;
+ private java.lang.CharSequence s;
+ private org.joda.time.LocalDate d;
+ private org.joda.time.LocalTime t;
+ private org.joda.time.DateTime ts;
+
+ /** Creates a new Builder */
+ private Builder() {
+ super(TestRecordWithLogicalTypes.SCHEMA$);
+ }
+
+ /** Creates a Builder by copying an existing Builder */
+ private Builder(TestRecordWithLogicalTypes.Builder other) {
+ super(other);
+ if (isValidValue(fields()[0], other.b)) {
+ this.b = data().deepCopy(fields()[0].schema(), other.b);
+ fieldSetFlags()[0] = true;
+ }
+ if (isValidValue(fields()[1], other.i32)) {
+ this.i32 = data().deepCopy(fields()[1].schema(), other.i32);
+ fieldSetFlags()[1] = true;
+ }
+ if (isValidValue(fields()[2], other.i64)) {
+ this.i64 = data().deepCopy(fields()[2].schema(), other.i64);
+ fieldSetFlags()[2] = true;
+ }
+ if (isValidValue(fields()[3], other.f32)) {
+ this.f32 = data().deepCopy(fields()[3].schema(), other.f32);
+ fieldSetFlags()[3] = true;
+ }
+ if (isValidValue(fields()[4], other.f64)) {
+ this.f64 = data().deepCopy(fields()[4].schema(), other.f64);
+ fieldSetFlags()[4] = true;
+ }
+ if (isValidValue(fields()[5], other.s)) {
+ this.s = data().deepCopy(fields()[5].schema(), other.s);
+ fieldSetFlags()[5] = true;
+ }
+ if (isValidValue(fields()[6], other.d)) {
+ this.d = data().deepCopy(fields()[6].schema(), other.d);
+ fieldSetFlags()[6] = true;
+ }
+ if (isValidValue(fields()[7], other.t)) {
+ this.t = data().deepCopy(fields()[7].schema(), other.t);
+ fieldSetFlags()[7] = true;
+ }
+ if (isValidValue(fields()[8], other.ts)) {
+ this.ts = data().deepCopy(fields()[8].schema(), other.ts);
+ fieldSetFlags()[8] = true;
+ }
+ }
+
+ /** Creates a Builder by copying an existing TestRecordWithLogicalTypes instance */
+ private Builder(TestRecordWithLogicalTypes other) {
+ super(TestRecordWithLogicalTypes.SCHEMA$);
+ if (isValidValue(fields()[0], other.b)) {
+ this.b = data().deepCopy(fields()[0].schema(), other.b);
+ fieldSetFlags()[0] = true;
+ }
+ if (isValidValue(fields()[1], other.i32)) {
+ this.i32 = data().deepCopy(fields()[1].schema(), other.i32);
+ fieldSetFlags()[1] = true;
+ }
+ if (isValidValue(fields()[2], other.i64)) {
+ this.i64 = data().deepCopy(fields()[2].schema(), other.i64);
+ fieldSetFlags()[2] = true;
+ }
+ if (isValidValue(fields()[3], other.f32)) {
+ this.f32 = data().deepCopy(fields()[3].schema(), other.f32);
+ fieldSetFlags()[3] = true;
+ }
+ if (isValidValue(fields()[4], other.f64)) {
+ this.f64 = data().deepCopy(fields()[4].schema(), other.f64);
+ fieldSetFlags()[4] = true;
+ }
+ if (isValidValue(fields()[5], other.s)) {
+ this.s = data().deepCopy(fields()[5].schema(), other.s);
+ fieldSetFlags()[5] = true;
+ }
+ if (isValidValue(fields()[6], other.d)) {
+ this.d = data().deepCopy(fields()[6].schema(), other.d);
+ fieldSetFlags()[6] = true;
+ }
+ if (isValidValue(fields()[7], other.t)) {
+ this.t = data().deepCopy(fields()[7].schema(), other.t);
+ fieldSetFlags()[7] = true;
+ }
+ if (isValidValue(fields()[8], other.ts)) {
+ this.ts = data().deepCopy(fields()[8].schema(), other.ts);
+ fieldSetFlags()[8] = true;
+ }
+ }
+
+ /**
+ * Gets the value of the 'b' field.
+ */
+ public java.lang.Boolean getB() {
+ return b;
+ }
+
+ /**
+ * Sets the value of the 'b' field.
+ * @param value the value to set.
+ */
+ public TestRecordWithLogicalTypes.Builder setB(boolean value) {
+ validate(fields()[0], value);
+ this.b = value;
+ fieldSetFlags()[0] = true;
+ return this;
+ }
+
+ /**
+ * Checks whether the 'b' field has been set.
+ */
+ public boolean hasB() {
+ return fieldSetFlags()[0];
+ }
+
+
+ /**
+ * Clears the value of the 'b' field.
+ */
+ public TestRecordWithLogicalTypes.Builder clearB() {
+ fieldSetFlags()[0] = false;
+ return this;
+ }
+
+ /**
+ * Gets the value of the 'i32' field.
+ */
+ public java.lang.Integer getI32() {
+ return i32;
+ }
+
+ /**
+ * Sets the value of the 'i32' field.
+ * @param value the value to set.
+ */
+ public TestRecordWithLogicalTypes.Builder setI32(int value) {
+ validate(fields()[1], value);
+ this.i32 = value;
+ fieldSetFlags()[1] = true;
+ return this;
+ }
+
+ /**
+ * Checks whether the 'i32' field has been set.
+ */
+ public boolean hasI32() {
+ return fieldSetFlags()[1];
+ }
+
+
+ /**
+ * Clears the value of the 'i32' field.
+ */
+ public TestRecordWithLogicalTypes.Builder clearI32() {
+ fieldSetFlags()[1] = false;
+ return this;
+ }
+
+ /**
+ * Gets the value of the 'i64' field.
+ */
+ public java.lang.Long getI64() {
+ return i64;
+ }
+
+ /**
+ * Sets the value of the 'i64' field.
+ * @param value the value to set.
+ */
+ public TestRecordWithLogicalTypes.Builder setI64(long value) {
+ validate(fields()[2], value);
+ this.i64 = value;
+ fieldSetFlags()[2] = true;
+ return this;
+ }
+
+ /**
+ * Checks whether the 'i64' field has been set.
+ */
+ public boolean hasI64() {
+ return fieldSetFlags()[2];
+ }
+
+
+ /**
+ * Clears the value of the 'i64' field.
+ */
+ public TestRecordWithLogicalTypes.Builder clearI64() {
+ fieldSetFlags()[2] = false;
+ return this;
+ }
+
+ /**
+ * Gets the value of the 'f32' field.
+ */
+ public java.lang.Float getF32() {
+ return f32;
+ }
+
+ /**
+ * Sets the value of the 'f32' field.
+ * @param value the value to set.
+ */
+ public TestRecordWithLogicalTypes.Builder setF32(float value) {
+ validate(fields()[3], value);
+ this.f32 = value;
+ fieldSetFlags()[3] = true;
+ return this;
+ }
+
+ /**
+ * Checks whether the 'f32' field has been set.
+ */
+ public boolean hasF32() {
+ return fieldSetFlags()[3];
+ }
+
+
+ /**
+ * Clears the value of the 'f32' field.
+ */
+ public TestRecordWithLogicalTypes.Builder clearF32() {
+ fieldSetFlags()[3] = false;
+ return this;
+ }
+
+ /**
+ * Gets the value of the 'f64' field.
+ */
+ public java.lang.Double getF64() {
+ return f64;
+ }
+
+ /**
+ * Sets the value of the 'f64' field.
+ * @param value the value to set.
+ */
+ public TestRecordWithLogicalTypes.Builder setF64(double value) {
+ validate(fields()[4], value);
+ this.f64 = value;
+ fieldSetFlags()[4] = true;
+ return this;
+ }
+
+ /**
+ * Checks whether the 'f64' field has been set.
+ */
+ public boolean hasF64() {
+ return fieldSetFlags()[4];
+ }
+
+
+ /**
+ * Clears the value of the 'f64' field.
+ */
+ public TestRecordWithLogicalTypes.Builder clearF64() {
+ fieldSetFlags()[4] = false;
+ return this;
+ }
+
+ /**
+ * Gets the value of the 's' field.
+ */
+ public java.lang.CharSequence getS() {
+ return s;
+ }
+
+ /**
+ * Sets the value of the 's' field.
+ * @param value the value to set.
+ */
+ public TestRecordWithLogicalTypes.Builder setS(java.lang.CharSequence value) {
+ validate(fields()[5], value);
+ this.s = value;
+ fieldSetFlags()[5] = true;
+ return this;
+ }
+
+ /**
+ * Checks whether the 's' field has been set.
+ */
+ public boolean hasS() {
+ return fieldSetFlags()[5];
+ }
+
+
+ /**
+ * Clears the value of the 's' field.
+ */
+ public TestRecordWithLogicalTypes.Builder clearS() {
+ s = null;
+ fieldSetFlags()[5] = false;
+ return this;
+ }
+
+ /**
+ * Gets the value of the 'd' field.
+ */
+ public org.joda.time.LocalDate getD() {
+ return d;
+ }
+
+ /**
+ * Sets the value of the 'd' field.
+ * @param value the value to set.
+ */
+ public TestRecordWithLogicalTypes.Builder setD(org.joda.time.LocalDate value) {
+ validate(fields()[6], value);
+ this.d = value;
+ fieldSetFlags()[6] = true;
+ return this;
+ }
+
+ /**
+ * Checks whether the 'd' field has been set.
+ */
+ public boolean hasD() {
+ return fieldSetFlags()[6];
+ }
+
+
+ /**
+ * Clears the value of the 'd' field.
+ */
+ public TestRecordWithLogicalTypes.Builder clearD() {
+ fieldSetFlags()[6] = false;
+ return this;
+ }
+
+ /**
+ * Gets the value of the 't' field.
+ */
+ public org.joda.time.LocalTime getT() {
+ return t;
+ }
+
+ /**
+ * Sets the value of the 't' field.
+ * @param value the value to set.
+ */
+ public TestRecordWithLogicalTypes.Builder setT(org.joda.time.LocalTime value) {
+ validate(fields()[7], value);
+ this.t = value;
+ fieldSetFlags()[7] = true;
+ return this;
+ }
+
+ /**
+ * Checks whether the 't' field has been set.
+ */
+ public boolean hasT() {
+ return fieldSetFlags()[7];
+ }
+
+
+ /**
+ * Clears the value of the 't' field.
+ */
+ public TestRecordWithLogicalTypes.Builder clearT() {
+ fieldSetFlags()[7] = false;
+ return this;
+ }
+
+ /**
+ * Gets the value of the 'ts' field.
+ */
+ public org.joda.time.DateTime getTs() {
+ return ts;
+ }
+
+ /**
+ * Sets the value of the 'ts' field.
+ * @param value the value to set.
+ */
+ public TestRecordWithLogicalTypes.Builder setTs(org.joda.time.DateTime value) {
+ validate(fields()[8], value);
+ this.ts = value;
+ fieldSetFlags()[8] = true;
+ return this;
+ }
+
+ /**
+ * Checks whether the 'ts' field has been set.
+ */
+ public boolean hasTs() {
+ return fieldSetFlags()[8];
+ }
+
+
+ /**
+ * Clears the value of the 'ts' field.
+ */
+ public TestRecordWithLogicalTypes.Builder clearTs() {
+ fieldSetFlags()[8] = false;
+ return this;
+ }
+
+ @Override
+ public TestRecordWithLogicalTypes build() {
+ try {
+ TestRecordWithLogicalTypes record = new TestRecordWithLogicalTypes();
+ record.b = fieldSetFlags()[0] ? this.b : (java.lang.Boolean) defaultValue(fields()[0]);
+ record.i32 = fieldSetFlags()[1] ? this.i32 : (java.lang.Integer) defaultValue(fields()[1]);
+ record.i64 = fieldSetFlags()[2] ? this.i64 : (java.lang.Long) defaultValue(fields()[2]);
+ record.f32 = fieldSetFlags()[3] ? this.f32 : (java.lang.Float) defaultValue(fields()[3]);
+ record.f64 = fieldSetFlags()[4] ? this.f64 : (java.lang.Double) defaultValue(fields()[4]);
+ record.s = fieldSetFlags()[5] ? this.s : (java.lang.CharSequence) defaultValue(fields()[5]);
+ 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]);
+ return record;
+ } catch (Exception e) {
+ throw new org.apache.avro.AvroRuntimeException(e);
+ }
+ }
+ }
+
+ private static final org.apache.avro.io.DatumWriter
+ WRITER$ = new org.apache.avro.specific.SpecificDatumWriter(SCHEMA$);
+
+ @Override public void writeExternal(java.io.ObjectOutput out)
+ throws java.io.IOException {
+ WRITER$.write(this, org.apache.avro.specific.SpecificData.getEncoder(out));
+ }
+
+ private static final org.apache.avro.io.DatumReader
+ READER$ = new org.apache.avro.specific.SpecificDatumReader(SCHEMA$);
+
+ @Override public void readExternal(java.io.ObjectInput in)
+ throws java.io.IOException {
+ READER$.read(this, org.apache.avro.specific.SpecificData.getDecoder(in));
+ }
+
+}
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
new file mode 100644
index 0000000..afe7d11
--- /dev/null
+++ b/lang/java/avro/src/test/java/org/apache/avro/specific/TestRecordWithoutLogicalTypes.java
@@ -0,0 +1,503 @@
+/**
+ * Autogenerated by Avro
+ *
+ * DO NOT EDIT DIRECTLY
+ */
+package org.apache.avro.specific;
+@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 org.apache.avro.Schema getClassSchema() { return SCHEMA$; }
+ private boolean b;
+ private int i32;
+ private long i64;
+ private float f32;
+ private double f64;
+ private java.lang.String s;
+ private int d;
+ private int t;
+ private long ts;
+
+ /**
+ * Default constructor. Note that this does not initialize fields
+ * to their default values from the schema. If that is desired then
+ * one should use {@link \#newBuilder()}.
+ */
+ public TestRecordWithoutLogicalTypes() {}
+
+ /**
+ * 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) {
+ this.b = b;
+ this.i32 = i32;
+ this.i64 = i64;
+ this.f32 = f32;
+ this.f64 = f64;
+ this.s = s;
+ this.d = d;
+ this.t = t;
+ this.ts = ts;
+ }
+
+ 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 b;
+ case 1: return i32;
+ case 2: return i64;
+ case 3: return f32;
+ case 4: return f64;
+ case 5: return s;
+ case 6: return d;
+ case 7: return t;
+ case 8: return ts;
+ default: throw new org.apache.avro.AvroRuntimeException("Bad index");
+ }
+ }
+ // Used by DatumReader. Applications should not call.
+ @SuppressWarnings(value="unchecked")
+ public void put(int field$, java.lang.Object value$) {
+ switch (field$) {
+ case 0: b = (java.lang.Boolean)value$; break;
+ case 1: i32 = (java.lang.Integer)value$; break;
+ case 2: i64 = (java.lang.Long)value$; break;
+ case 3: f32 = (java.lang.Float)value$; break;
+ case 4: f64 = (java.lang.Double)value$; break;
+ case 5: s = (java.lang.String)value$; break;
+ case 6: d = (java.lang.Integer)value$; break;
+ case 7: t = (java.lang.Integer)value$; break;
+ case 8: ts = (java.lang.Long)value$; break;
+ default: throw new org.apache.avro.AvroRuntimeException("Bad index");
+ }
+ }
+
+ /**
+ * Gets the value of the 'b' field.
+ */
+ public java.lang.Boolean getB() {
+ return b;
+ }
+
+
+ /**
+ * Gets the value of the 'i32' field.
+ */
+ public java.lang.Integer getI32() {
+ return i32;
+ }
+
+
+ /**
+ * Gets the value of the 'i64' field.
+ */
+ public java.lang.Long getI64() {
+ return i64;
+ }
+
+
+ /**
+ * Gets the value of the 'f32' field.
+ */
+ public java.lang.Float getF32() {
+ return f32;
+ }
+
+
+ /**
+ * Gets the value of the 'f64' field.
+ */
+ public java.lang.Double getF64() {
+ return f64;
+ }
+
+
+ /**
+ * Gets the value of the 's' field.
+ */
+ public java.lang.String getS() {
+ return s;
+ }
+
+
+ /**
+ * Gets the value of the 'd' field.
+ */
+ public java.lang.Integer getD() {
+ return d;
+ }
+
+
+ /**
+ * Gets the value of the 't' field.
+ */
+ public java.lang.Integer getT() {
+ return t;
+ }
+
+
+ /**
+ * Gets the value of the 'ts' field.
+ */
+ public java.lang.Long getTs() {
+ return ts;
+ }
+
+
+ /** Creates a new TestRecordWithoutLogicalTypes RecordBuilder */
+ public static TestRecordWithoutLogicalTypes.Builder newBuilder() {
+ return new TestRecordWithoutLogicalTypes.Builder();
+ }
+
+ /** Creates a new TestRecordWithoutLogicalTypes RecordBuilder by copying an existing Builder */
+ public static TestRecordWithoutLogicalTypes.Builder newBuilder(TestRecordWithoutLogicalTypes.Builder other) {
+ return new TestRecordWithoutLogicalTypes.Builder(other);
+ }
+
+ /** Creates a new TestRecordWithoutLogicalTypes RecordBuilder by copying an existing TestRecordWithoutLogicalTypes instance */
+ public static TestRecordWithoutLogicalTypes.Builder newBuilder(TestRecordWithoutLogicalTypes other) {
+ return new TestRecordWithoutLogicalTypes.Builder(other);
+ }
+
+ /**
+ * RecordBuilder for TestRecordWithoutLogicalTypes instances.
+ */
+ public static class Builder extends org.apache.avro.specific.SpecificRecordBuilderBase<TestRecordWithoutLogicalTypes>
+ implements org.apache.avro.data.RecordBuilder<TestRecordWithoutLogicalTypes> {
+
+ private boolean b;
+ private int i32;
+ private long i64;
+ private float f32;
+ private double f64;
+ private java.lang.String s;
+ private int d;
+ private int t;
+ private long ts;
+
+ /** Creates a new Builder */
+ private Builder() {
+ super(TestRecordWithoutLogicalTypes.SCHEMA$);
+ }
+
+ /** Creates a Builder by copying an existing Builder */
+ private Builder(TestRecordWithoutLogicalTypes.Builder other) {
+ super(other);
+ if (isValidValue(fields()[0], other.b)) {
+ this.b = data().deepCopy(fields()[0].schema(), other.b);
+ fieldSetFlags()[0] = true;
+ }
+ if (isValidValue(fields()[1], other.i32)) {
+ this.i32 = data().deepCopy(fields()[1].schema(), other.i32);
+ fieldSetFlags()[1] = true;
+ }
+ if (isValidValue(fields()[2], other.i64)) {
+ this.i64 = data().deepCopy(fields()[2].schema(), other.i64);
+ fieldSetFlags()[2] = true;
+ }
+ if (isValidValue(fields()[3], other.f32)) {
+ this.f32 = data().deepCopy(fields()[3].schema(), other.f32);
+ fieldSetFlags()[3] = true;
+ }
+ if (isValidValue(fields()[4], other.f64)) {
+ this.f64 = data().deepCopy(fields()[4].schema(), other.f64);
+ fieldSetFlags()[4] = true;
+ }
+ if (isValidValue(fields()[5], other.s)) {
+ this.s = data().deepCopy(fields()[5].schema(), other.s);
+ fieldSetFlags()[5] = true;
+ }
+ if (isValidValue(fields()[6], other.d)) {
+ this.d = data().deepCopy(fields()[6].schema(), other.d);
+ fieldSetFlags()[6] = true;
+ }
+ if (isValidValue(fields()[7], other.t)) {
+ this.t = data().deepCopy(fields()[7].schema(), other.t);
+ fieldSetFlags()[7] = true;
+ }
+ if (isValidValue(fields()[8], other.ts)) {
+ this.ts = data().deepCopy(fields()[8].schema(), other.ts);
+ fieldSetFlags()[8] = true;
+ }
+ }
+
+ /** Creates a Builder by copying an existing TestRecordWithoutLogicalTypes instance */
+ private Builder(TestRecordWithoutLogicalTypes other) {
+ super(TestRecordWithoutLogicalTypes.SCHEMA$);
+ if (isValidValue(fields()[0], other.b)) {
+ this.b = data().deepCopy(fields()[0].schema(), other.b);
+ fieldSetFlags()[0] = true;
+ }
+ if (isValidValue(fields()[1], other.i32)) {
+ this.i32 = data().deepCopy(fields()[1].schema(), other.i32);
+ fieldSetFlags()[1] = true;
+ }
+ if (isValidValue(fields()[2], other.i64)) {
+ this.i64 = data().deepCopy(fields()[2].schema(), other.i64);
+ fieldSetFlags()[2] = true;
+ }
+ if (isValidValue(fields()[3], other.f32)) {
+ this.f32 = data().deepCopy(fields()[3].schema(), other.f32);
+ fieldSetFlags()[3] = true;
+ }
+ if (isValidValue(fields()[4], other.f64)) {
+ this.f64 = data().deepCopy(fields()[4].schema(), other.f64);
+ fieldSetFlags()[4] = true;
+ }
+ if (isValidValue(fields()[5], other.s)) {
+ this.s = data().deepCopy(fields()[5].schema(), other.s);
+ fieldSetFlags()[5] = true;
+ }
+ if (isValidValue(fields()[6], other.d)) {
+ this.d = data().deepCopy(fields()[6].schema(), other.d);
+ fieldSetFlags()[6] = true;
+ }
+ if (isValidValue(fields()[7], other.t)) {
+ this.t = data().deepCopy(fields()[7].schema(), other.t);
+ fieldSetFlags()[7] = true;
+ }
+ if (isValidValue(fields()[8], other.ts)) {
+ this.ts = data().deepCopy(fields()[8].schema(), other.ts);
+ fieldSetFlags()[8] = true;
+ }
+ }
+
+ /** Gets the value of the 'b' field */
+ public java.lang.Boolean getB() {
+ return b;
+ }
+
+ /** Sets the value of the 'b' field */
+ public TestRecordWithoutLogicalTypes.Builder setB(boolean value) {
+ validate(fields()[0], value);
+ this.b = value;
+ fieldSetFlags()[0] = true;
+ return this;
+ }
+
+ /** Checks whether the 'b' field has been set */
+ public boolean hasB() {
+ return fieldSetFlags()[0];
+ }
+
+ /** Clears the value of the 'b' field */
+ public TestRecordWithoutLogicalTypes.Builder clearB() {
+ fieldSetFlags()[0] = false;
+ return this;
+ }
+
+ /** Gets the value of the 'i32' field */
+ public java.lang.Integer getI32() {
+ return i32;
+ }
+
+ /** Sets the value of the 'i32' field */
+ public TestRecordWithoutLogicalTypes.Builder setI32(int value) {
+ validate(fields()[1], value);
+ this.i32 = value;
+ fieldSetFlags()[1] = true;
+ return this;
+ }
+
+ /** Checks whether the 'i32' field has been set */
+ public boolean hasI32() {
+ return fieldSetFlags()[1];
+ }
+
+ /** Clears the value of the 'i32' field */
+ public TestRecordWithoutLogicalTypes.Builder clearI32() {
+ fieldSetFlags()[1] = false;
+ return this;
+ }
+
+ /** Gets the value of the 'i64' field */
+ public java.lang.Long getI64() {
+ return i64;
+ }
+
+ /** Sets the value of the 'i64' field */
+ public TestRecordWithoutLogicalTypes.Builder setI64(long value) {
+ validate(fields()[2], value);
+ this.i64 = value;
+ fieldSetFlags()[2] = true;
+ return this;
+ }
+
+ /** Checks whether the 'i64' field has been set */
+ public boolean hasI64() {
+ return fieldSetFlags()[2];
+ }
+
+ /** Clears the value of the 'i64' field */
+ public TestRecordWithoutLogicalTypes.Builder clearI64() {
+ fieldSetFlags()[2] = false;
+ return this;
+ }
+
+ /** Gets the value of the 'f32' field */
+ public java.lang.Float getF32() {
+ return f32;
+ }
+
+ /** Sets the value of the 'f32' field */
+ public TestRecordWithoutLogicalTypes.Builder setF32(float value) {
+ validate(fields()[3], value);
+ this.f32 = value;
+ fieldSetFlags()[3] = true;
+ return this;
+ }
+
+ /** Checks whether the 'f32' field has been set */
+ public boolean hasF32() {
+ return fieldSetFlags()[3];
+ }
+
+ /** Clears the value of the 'f32' field */
+ public TestRecordWithoutLogicalTypes.Builder clearF32() {
+ fieldSetFlags()[3] = false;
+ return this;
+ }
+
+ /** Gets the value of the 'f64' field */
+ public java.lang.Double getF64() {
+ return f64;
+ }
+
+ /** Sets the value of the 'f64' field */
+ public TestRecordWithoutLogicalTypes.Builder setF64(double value) {
+ validate(fields()[4], value);
+ this.f64 = value;
+ fieldSetFlags()[4] = true;
+ return this;
+ }
+
+ /** Checks whether the 'f64' field has been set */
+ public boolean hasF64() {
+ return fieldSetFlags()[4];
+ }
+
+ /** Clears the value of the 'f64' field */
+ public TestRecordWithoutLogicalTypes.Builder clearF64() {
+ fieldSetFlags()[4] = false;
+ return this;
+ }
+
+ /** Gets the value of the 's' field */
+ public java.lang.String getS() {
+ return s;
+ }
+
+ /** Sets the value of the 's' field */
+ public TestRecordWithoutLogicalTypes.Builder setS(java.lang.String value) {
+ validate(fields()[5], value);
+ this.s = value;
+ fieldSetFlags()[5] = true;
+ return this;
+ }
+
+ /** Checks whether the 's' field has been set */
+ public boolean hasS() {
+ return fieldSetFlags()[5];
+ }
+
+ /** Clears the value of the 's' field */
+ public TestRecordWithoutLogicalTypes.Builder clearS() {
+ s = null;
+ fieldSetFlags()[5] = false;
+ return this;
+ }
+
+ /** Gets the value of the 'd' field */
+ public java.lang.Integer getD() {
+ return d;
+ }
+
+ /** Sets the value of the 'd' field */
+ public TestRecordWithoutLogicalTypes.Builder setD(int value) {
+ validate(fields()[6], value);
+ this.d = value;
+ fieldSetFlags()[6] = true;
+ return this;
+ }
+
+ /** Checks whether the 'd' field has been set */
+ public boolean hasD() {
+ return fieldSetFlags()[6];
+ }
+
+ /** Clears the value of the 'd' field */
+ public TestRecordWithoutLogicalTypes.Builder clearD() {
+ fieldSetFlags()[6] = false;
+ return this;
+ }
+
+ /** Gets the value of the 't' field */
+ public java.lang.Integer getT() {
+ return t;
+ }
+
+ /** Sets the value of the 't' field */
+ public TestRecordWithoutLogicalTypes.Builder setT(int value) {
+ validate(fields()[7], value);
+ this.t = value;
+ fieldSetFlags()[7] = true;
+ return this;
+ }
+
+ /** Checks whether the 't' field has been set */
+ public boolean hasT() {
+ return fieldSetFlags()[7];
+ }
+
+ /** Clears the value of the 't' field */
+ public TestRecordWithoutLogicalTypes.Builder clearT() {
+ fieldSetFlags()[7] = false;
+ return this;
+ }
+
+ /** Gets the value of the 'ts' field */
+ public java.lang.Long getTs() {
+ return ts;
+ }
+
+ /** Sets the value of the 'ts' field */
+ public TestRecordWithoutLogicalTypes.Builder setTs(long value) {
+ validate(fields()[8], value);
+ this.ts = value;
+ fieldSetFlags()[8] = true;
+ return this;
+ }
+
+ /** Checks whether the 'ts' field has been set */
+ public boolean hasTs() {
+ return fieldSetFlags()[8];
+ }
+
+ /** Clears the value of the 'ts' field */
+ public TestRecordWithoutLogicalTypes.Builder clearTs() {
+ fieldSetFlags()[8] = false;
+ return this;
+ }
+
+ @Override
+ public TestRecordWithoutLogicalTypes build() {
+ try {
+ TestRecordWithoutLogicalTypes record = new TestRecordWithoutLogicalTypes();
+ record.b = fieldSetFlags()[0] ? this.b : (java.lang.Boolean) defaultValue(fields()[0]);
+ record.i32 = fieldSetFlags()[1] ? this.i32 : (java.lang.Integer) defaultValue(fields()[1]);
+ record.i64 = fieldSetFlags()[2] ? this.i64 : (java.lang.Long) defaultValue(fields()[2]);
+ record.f32 = fieldSetFlags()[3] ? this.f32 : (java.lang.Float) defaultValue(fields()[3]);
+ record.f64 = fieldSetFlags()[4] ? this.f64 : (java.lang.Double) defaultValue(fields()[4]);
+ record.s = fieldSetFlags()[5] ? this.s : (java.lang.String) defaultValue(fields()[5]);
+ 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]);
+ return record;
+ } catch (Exception e) {
+ throw new org.apache.avro.AvroRuntimeException(e);
+ }
+ }
+ }
+}
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
new file mode 100644
index 0000000..c545c5a
--- /dev/null
+++ b/lang/java/avro/src/test/java/org/apache/avro/specific/TestSpecificLogicalTypes.java
@@ -0,0 +1,210 @@
+package org.apache.avro.specific;
+
+import org.apache.avro.Schema;
+import org.apache.avro.data.TimeConversions.DateConversion;
+import org.apache.avro.data.TimeConversions.TimeConversion;
+import org.apache.avro.data.TimeConversions.TimestampConversion;
+import org.apache.avro.file.DataFileReader;
+import org.apache.avro.file.DataFileWriter;
+import org.apache.avro.file.FileReader;
+import org.apache.avro.io.DatumReader;
+import org.apache.avro.io.DatumWriter;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.joda.time.LocalDate;
+import org.joda.time.LocalTime;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This tests compatibility between classes generated before and after
+ * AVRO-1684. TestRecordWithoutLogicalTypes and TestRecordWithLogicalTypes were
+ * generated from the same schema, found in
+ * src/test/resources/record_with_logical_types.avsc, and
+ * TestRecordWithoutLogicalTypes was renamed to avoid the conflict.
+ *
+ * The classes should not be re-generated because they test compatibility of
+ * Avro with existing Avro-generated sources. When using classes generated
+ * before AVRO-1684, logical types should not be applied by the read or write
+ * paths. Those files should behave as they did before.
+ */
+public class TestSpecificLogicalTypes {
+
+ @Rule
+ public final TemporaryFolder temp = new TemporaryFolder();
+
+ @Test
+ public void testRecordWithLogicalTypes() throws IOException {
+ TestRecordWithLogicalTypes record = new TestRecordWithLogicalTypes(
+ true,
+ 34,
+ 35L,
+ 3.14F,
+ 3019.34,
+ null,
+ LocalDate.now(),
+ LocalTime.now(),
+ DateTime.now().withZone(DateTimeZone.UTC)
+ );
+
+ File data = write(TestRecordWithLogicalTypes.getClassSchema(), record);
+ List<TestRecordWithLogicalTypes> actual = read(
+ TestRecordWithLogicalTypes.getClassSchema(), data);
+
+ Assert.assertEquals("Should match written record", record, actual.get(0));
+ }
+
+ @Test
+ public void testRecordWithoutLogicalTypes() throws IOException {
+ // the significance of the record without logical types is that it has the
+ // same schema (besides record name) as the one with logical types,
+ // including the type annotations. this verifies that the type annotations
+ // are only applied if the record was compiled to use those types. this
+ // ensures compatibility with already-compiled code.
+
+ TestRecordWithoutLogicalTypes record = new TestRecordWithoutLogicalTypes(
+ true,
+ 34,
+ 35L,
+ 3.14F,
+ 3019.34,
+ null,
+ new DateConversion().toInt(LocalDate.now(), null, null),
+ new TimeConversion().toInt(LocalTime.now(), null, null),
+ new TimestampConversion().toLong(
+ DateTime.now().withZone(DateTimeZone.UTC), null, null)
+ );
+
+ File data = write(TestRecordWithoutLogicalTypes.getClassSchema(), record);
+ List<TestRecordWithoutLogicalTypes> actual = read(
+ TestRecordWithoutLogicalTypes.getClassSchema(), data);
+
+ Assert.assertEquals("Should match written record", record, actual.get(0));
+ }
+
+ @Test
+ public void testRecordWritePrimitivesReadLogicalTypes() throws IOException {
+ LocalDate date = LocalDate.now();
+ LocalTime time = LocalTime.now();
+ DateTime timestamp = DateTime.now().withZone(DateTimeZone.UTC);
+
+ TestRecordWithoutLogicalTypes record = new TestRecordWithoutLogicalTypes(
+ true,
+ 34,
+ 35L,
+ 3.14F,
+ 3019.34,
+ null,
+ new DateConversion().toInt(date, null, null),
+ new TimeConversion().toInt(time, null, null),
+ new TimestampConversion().toLong(timestamp, null, null)
+ );
+
+ File data = write(TestRecordWithoutLogicalTypes.getClassSchema(), record);
+ // read using the schema with logical types
+ List<TestRecordWithLogicalTypes> actual = read(
+ TestRecordWithLogicalTypes.getClassSchema(), data);
+
+ TestRecordWithLogicalTypes expected = new TestRecordWithLogicalTypes(
+ true,
+ 34,
+ 35L,
+ 3.14F,
+ 3019.34,
+ null,
+ date,
+ time,
+ timestamp
+ );
+
+ Assert.assertEquals("Should match written record", expected, actual.get(0));
+ }
+
+ @Test
+ public void testRecordWriteLogicalTypesReadPrimitives() throws IOException {
+ LocalDate date = LocalDate.now();
+ LocalTime time = LocalTime.now();
+ DateTime timestamp = DateTime.now().withZone(DateTimeZone.UTC);
+
+ TestRecordWithLogicalTypes record = new TestRecordWithLogicalTypes(
+ true,
+ 34,
+ 35L,
+ 3.14F,
+ 3019.34,
+ null,
+ date,
+ time,
+ timestamp
+ );
+
+ File data = write(TestRecordWithLogicalTypes.getClassSchema(), record);
+ // read using the schema with logical types
+ List<TestRecordWithoutLogicalTypes> actual = read(
+ TestRecordWithoutLogicalTypes.getClassSchema(), data);
+
+ TestRecordWithoutLogicalTypes expected = new TestRecordWithoutLogicalTypes(
+ true,
+ 34,
+ 35L,
+ 3.14F,
+ 3019.34,
+ null,
+ new DateConversion().toInt(date, null, null),
+ new TimeConversion().toInt(time, null, null),
+ new TimestampConversion().toLong(timestamp, null, null)
+ );
+
+ Assert.assertEquals("Should match written record", expected, actual.get(0));
+ }
+
+ private <D> List<D> read(Schema schema, File file)
+ throws IOException {
+ DatumReader<D> reader = newReader(schema);
+ List<D> data = new ArrayList<D>();
+ FileReader<D> fileReader = null;
+
+ try {
+ fileReader = new DataFileReader<D>(file, reader);
+ for (D datum : fileReader) {
+ data.add(datum);
+ }
+ } finally {
+ if (fileReader != null) {
+ fileReader.close();
+ }
+ }
+
+ return data;
+ }
+
+ @SuppressWarnings("unchecked")
+ private <D> DatumReader<D> newReader(Schema schema) {
+ return SpecificData.get().createDatumReader(schema);
+ }
+
+ @SuppressWarnings("unchecked")
+ private <D extends SpecificRecord> File write(Schema schema, D... data)
+ throws IOException {
+ File file = temp.newFile();
+ DatumWriter<D> writer = SpecificData.get().createDatumWriter(schema);
+ DataFileWriter<D> fileWriter = new DataFileWriter<D>(writer);
+
+ try {
+ fileWriter.create(schema, file);
+ for (D datum : data) {
+ fileWriter.append(datum);
+ }
+ } finally {
+ fileWriter.close();
+ }
+
+ return file;
+ }
+}
diff --git a/lang/java/avro/src/test/resources/record_with_logical_types.avsc b/lang/java/avro/src/test/resources/record_with_logical_types.avsc
new file mode 100644
index 0000000..9932f95
--- /dev/null
+++ b/lang/java/avro/src/test/resources/record_with_logical_types.avsc
@@ -0,0 +1,45 @@
+{
+ "type" : "record",
+ "name" : "TestRecordWithLogicalTypes",
+ "doc" : "Schema for TestRecordWithLogicalTypes and TestRecordWithoutLogicalTypes, see TestSpecificLogicalTypes"
+ "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"
+ }
+ } ]
+}
+
diff --git a/lang/java/compiler/pom.xml b/lang/java/compiler/pom.xml
index 42e6d27..678ccf8 100644
--- a/lang/java/compiler/pom.xml
+++ b/lang/java/compiler/pom.xml
@@ -129,6 +129,10 @@
<!-- can only be used from within ant -->
<scope>provided</scope>
</dependency>
+ <dependency>
+ <groupId>joda-time</groupId>
+ <artifactId>joda-time</artifactId>
+ </dependency>
</dependencies>
</project>
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 823a2ef..6bf7bd5 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
@@ -34,6 +34,11 @@
import java.util.Map;
import java.util.Set;
+import org.apache.avro.Conversion;
+import org.apache.avro.LogicalTypes;
+import org.apache.avro.data.TimeConversions.DateConversion;
+import org.apache.avro.data.TimeConversions.TimeConversion;
+import org.apache.avro.data.TimeConversions.TimestampConversion;
import org.apache.avro.specific.SpecificData;
import org.codehaus.jackson.JsonNode;
@@ -86,6 +91,13 @@
PUBLIC, PUBLIC_DEPRECATED, PRIVATE
}
+ private static final SpecificData SPECIFIC = new SpecificData();
+ static {
+ SPECIFIC.addLogicalTypeConversion(new DateConversion());
+ SPECIFIC.addLogicalTypeConversion(new TimeConversion());
+ SPECIFIC.addLogicalTypeConversion(new TimestampConversion());
+ }
+
private final Set<Schema> queue = new HashSet<Schema>();
private Protocol protocol;
private VelocityEngine velocityEngine;
@@ -552,6 +564,12 @@
/** 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();
+ }
+
switch (schema.getType()) {
case RECORD:
case ENUM:
@@ -583,6 +601,12 @@
/** 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";
@@ -593,6 +617,26 @@
}
}
+ public boolean hasLogicalTypeField(Schema schema) {
+ for (Schema.Field field : schema.getFields()) {
+ if (field.schema().getLogicalType() != null) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public String conversionInstance(Schema schema) {
+ 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";
+ }
+ return "null";
+ }
+
/** Utility for template use. Returns the java annotations for a schema. */
public String[] javaAnnotations(JsonProperties props) {
JsonNode value = props.getJsonProp("javaAnnotation");
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 8f60b83..775a8cb 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
@@ -69,6 +69,7 @@
import java.net.URL;
import org.apache.avro.Schema;
+import org.apache.avro.LogicalTypes;
import org.apache.avro.Schema.*;
import org.apache.avro.Protocol;
import org.apache.avro.Protocol.*;
@@ -236,6 +237,9 @@
| < TRUE: "true" >
| < UNION: "union" >
| < VOID: "void" >
+| < DATE: "date" >
+| < TIME: "time_ms" >
+| < TIMESTAMP: "timestamp_ms" >
}
/* LITERALS */
@@ -1481,6 +1485,9 @@
| "double" { return Schema.create(Type.DOUBLE); }
| "long" { return Schema.create(Type.LONG); }
| "null" { return Schema.create(Type.NULL); }
+| "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)); }
}
/**
@@ -1545,6 +1552,9 @@
t = <TRUE> |
t = <UNION> |
t = <VOID> |
+ t = <DATE> |
+ t = <TIME> |
+ t = <TIMESTAMP> |
t = <IDENTIFIER>)
{
return t;
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 3e26df5..3bbe1e6 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
@@ -107,6 +107,26 @@
default: throw new org.apache.avro.AvroRuntimeException("Bad index");
}
}
+
+#if ($this.hasLogicalTypeField($schema))
+ 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();
+
+ private final org.apache.avro.Conversion<?>[] conversions =
+ new org.apache.avro.Conversion<?>[] {
+#foreach ($field in $schema.getFields())
+ ${this.conversionInstance($field.schema())},
+#end
+ null
+ };
+
+ @Override
+ public org.apache.avro.Conversion<?> getConversion(int field) {
+ return conversions[field];
+ }
+
+#end
// Used by DatumReader. Applications should not call.
@SuppressWarnings(value="unchecked")
public void put(int field$, java.lang.Object value$) {
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 63568fb..272891a 100644
--- a/lang/java/compiler/src/test/idl/input/mr_events.avdl
+++ b/lang/java/compiler/src/test/idl/input/mr_events.avdl
@@ -42,7 +42,7 @@
record JobFinished {
string jobid;
- long finishTime;
+ timestamp_ms finishTime;
int finishedMaps;
int finishedReduces;
int failedMaps;
@@ -54,7 +54,7 @@
record JobInited {
string jobid;
- long launchTime;
+ timestamp_ms launchTime;
int totalMaps;
int totalReduces;
string jobStatus;
@@ -64,7 +64,7 @@
string jobid;
string jobName;
string userName;
- long submitTime;
+ timestamp_ms submitTime;
string jobConfPath;
}
diff --git a/lang/java/compiler/src/test/idl/input/simple.avdl b/lang/java/compiler/src/test/idl/input/simple.avdl
index db22309..37aed9e 100644
--- a/lang/java/compiler/src/test/idl/input/simple.avdl
+++ b/lang/java/compiler/src/test/idl/input/simple.avdl
@@ -47,6 +47,8 @@
double value = NaN;
float average = -Infinity;
+ date d = 0;
+ time_ms t = 0;
}
error TestError {
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 25da3a8..c4e694f 100644
--- a/lang/java/compiler/src/test/idl/output/mr_events.avpr
+++ b/lang/java/compiler/src/test/idl/output/mr_events.avpr
@@ -52,7 +52,7 @@
"type" : "string"
}, {
"name" : "finishTime",
- "type" : "long"
+ "type" : {"type": "long", "logicalType": "timestamp-millis"}
}, {
"name" : "finishedMaps",
"type" : "int"
@@ -83,7 +83,7 @@
"type" : "string"
}, {
"name" : "launchTime",
- "type" : "long"
+ "type" : {"type": "long", "logicalType": "timestamp-millis"}
}, {
"name" : "totalMaps",
"type" : "int"
@@ -108,7 +108,7 @@
"type" : "string"
}, {
"name" : "submitTime",
- "type" : "long"
+ "type" : {"type": "long", "logicalType": "timestamp-millis"}
}, {
"name" : "jobConfPath",
"type" : "string"
@@ -116,4 +116,4 @@
} ],
"messages" : {
}
-}
\ No newline at end of file
+}
diff --git a/lang/java/compiler/src/test/idl/output/simple.avpr b/lang/java/compiler/src/test/idl/output/simple.avpr
index 40cb58a..c3e18e2 100644
--- a/lang/java/compiler/src/test/idl/output/simple.avpr
+++ b/lang/java/compiler/src/test/idl/output/simple.avpr
@@ -46,6 +46,14 @@
"name" : "average",
"type" : "float",
"default" : "-Infinity"
+ }, {
+ "name": "d",
+ "type": {"type": "int", "logicalType": "date"},
+ "default": 0
+ }, {
+ "name": "t",
+ "type": {"type": "int", "logicalType": "time-millis"},
+ "default": 0
} ],
"my-property" : {
"key" : 3
@@ -109,4 +117,4 @@
"one-way" : true
}
}
-}
\ No newline at end of file
+}
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 7605724..3668d2f 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,11 +35,14 @@
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;
import org.apache.avro.SchemaBuilder;
import org.apache.avro.generic.GenericData.StringType;
import org.junit.After;
+import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -295,4 +298,35 @@
assertThat("Generated files should contain the same characters in the proper encodings",
new String(fileInDefaultEncoding), equalTo(new String(fileInDifferentEncoding, differentEncoding)));
}
+
+ @Test
+ public void testLogicalTypes() throws Exception {
+ SpecificCompiler compiler = createCompiler();
+
+ 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));
+
+ 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",
+ "java.nio.ByteBuffer", compiler.javaType(decimalSchema));
+ }
+
+ @Test
+ public void testLogicalTypesWithMultipleFields() throws Exception {
+ Schema logicalTypesWithMultipleFields = new Schema.Parser().parse(
+ new File("src/test/resources/simple_record.avsc"));
+ assertCompilesWithJavaCompiler(
+ new SpecificCompiler(logicalTypesWithMultipleFields).compile());
+ }
}
diff --git a/lang/java/compiler/src/test/resources/logical_types_with_multiple_fields.avsc b/lang/java/compiler/src/test/resources/logical_types_with_multiple_fields.avsc
new file mode 100644
index 0000000..7a05111
--- /dev/null
+++ b/lang/java/compiler/src/test/resources/logical_types_with_multiple_fields.avsc
@@ -0,0 +1,30 @@
+{
+ "namespace": "schema.common",
+ "type": "record",
+ "name": "Action",
+ "fields": [
+ {
+ "name": "name",
+ "type": "string"
+ },
+ {
+ "name": "uuid",
+ "type": "string"
+ },
+ {
+ "name": "time",
+ "type": {
+ "type": "long",
+ "logicalType": "timestamp-millis"
+ }
+ },
+ {
+ "name": "requestId",
+ "type": [
+ "null",
+ "string"
+ ],
+ "default": null
+ }
+ ]
+}
diff --git a/lang/java/pom.xml b/lang/java/pom.xml
index 4e5ddd8..93cab06 100644
--- a/lang/java/pom.xml
+++ b/lang/java/pom.xml
@@ -473,7 +473,6 @@
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>${joda.version}</version>
- <optional>true</optional>
</dependency>
</dependencies>
</dependencyManagement>