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 63fa025d0aa..e66726e6c63 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 @@ protected void write(Schema schema, Object datum, Encoder out) } } - private Object convert(Schema schema, LogicalType logicalType, - Conversion conversion, Object datum) { + protected Object convert(Schema schema, LogicalType logicalType, + Conversion 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 870d16f548c..774ca094451 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 extends GenericDatumReader { @@ -98,5 +101,26 @@ private Class getPropAsClass(Schema schema, String prop) { } } + @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 128b02e9237..7bee02a653c 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 @@ && getSpecificData().isStringable(datum.getClass())) { 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 77d0928cfbb..baedeb872f7 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 class SpecificRecordBase 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 @@ public Object get(String fieldName) { 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 00000000000..a01e450e01e --- /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 newBuilder(). + */ + 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 + implements org.apache.avro.data.RecordBuilder { + + 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 00000000000..afe7d11747c --- /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 + implements org.apache.avro.data.RecordBuilder { + + 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 00000000000..c545c5a0045 --- /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 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 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 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 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 List read(Schema schema, File file) + throws IOException { + DatumReader reader = newReader(schema); + List data = new ArrayList(); + FileReader fileReader = null; + + try { + fileReader = new DataFileReader(file, reader); + for (D datum : fileReader) { + data.add(datum); + } + } finally { + if (fileReader != null) { + fileReader.close(); + } + } + + return data; + } + + @SuppressWarnings("unchecked") + private DatumReader newReader(Schema schema) { + return SpecificData.get().createDatumReader(schema); + } + + @SuppressWarnings("unchecked") + private File write(Schema schema, D... data) + throws IOException { + File file = temp.newFile(); + DatumWriter writer = SpecificData.get().createDatumWriter(schema); + DataFileWriter fileWriter = new DataFileWriter(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 00000000000..9932f95056f --- /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 42e6d27c8ff..678ccf8c210 100644 --- a/lang/java/compiler/pom.xml +++ b/lang/java/compiler/pom.xml @@ -129,6 +129,10 @@ provided + + joda-time + joda-time + 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 823a2ef298d..6bf7bd567e3 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 static enum FieldVisibility { 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 queue = new HashSet(); private Protocol protocol; private VelocityEngine velocityEngine; @@ -552,6 +564,12 @@ private String getStringType(JsonNode overrideClassProperty) { /** 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 @@ public String javaType(Schema schema) { /** 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 String javaUnbox(Schema schema) { } } + 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 8f60b83b90b..775a8cb27c5 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.util.Map; 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 @@ TOKEN : | < TRUE: "true" > | < UNION: "union" > | < VOID: "void" > +| < DATE: "date" > +| < TIME: "time_ms" > +| < TIMESTAMP: "timestamp_ms" > } /* LITERALS */ @@ -1481,6 +1485,9 @@ Schema PrimitiveType(): | "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 @@ Token AnyIdentifier(): t = | t = | t = | + t = | + t =