diff --git a/lang/java/avro/src/main/java/org/apache/avro/data/Jsr310TimeConversions.java b/lang/java/avro/src/main/java/org/apache/avro/data/Jsr310TimeConversions.java new file mode 100644 index 00000000000..a717bbc9fd7 --- /dev/null +++ b/lang/java/avro/src/main/java/org/apache/avro/data/Jsr310TimeConversions.java @@ -0,0 +1,184 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.avro.data; + +import org.apache.avro.Conversion; +import org.apache.avro.LogicalType; +import org.apache.avro.LogicalTypes; +import org.apache.avro.Schema; + +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.concurrent.TimeUnit; + +public class Jsr310TimeConversions { + public static class DateConversion extends Conversion { + + @Override + public Class getConvertedType() { + return LocalDate.class; + } + + @Override + public String getLogicalTypeName() { + return "date"; + } + + @Override + public LocalDate fromInt(Integer daysFromEpoch, Schema schema, LogicalType type) { + return LocalDate.ofEpochDay(daysFromEpoch); + } + + @Override + public Integer toInt(LocalDate date, Schema schema, LogicalType type) { + long epochDays = date.toEpochDay(); + + return (int) epochDays; + } + + @Override + public Schema getRecommendedSchema() { + return LogicalTypes.date().addToSchema(Schema.create(Schema.Type.INT)); + } + } + + public static class TimeMillisConversion extends Conversion { + @Override + public Class getConvertedType() { + return LocalTime.class; + } + + @Override + public String getLogicalTypeName() { + return "time-millis"; + } + + @Override + public LocalTime fromInt(Integer millisFromMidnight, Schema schema, LogicalType type) { + return LocalTime.ofNanoOfDay(TimeUnit.MILLISECONDS.toNanos(millisFromMidnight)); + } + + @Override + public Integer toInt(LocalTime time, Schema schema, LogicalType type) { + return (int) TimeUnit.NANOSECONDS.toMillis(time.toNanoOfDay()); + } + + @Override + public Schema getRecommendedSchema() { + return LogicalTypes.timeMillis().addToSchema(Schema.create(Schema.Type.INT)); + } + } + + public static class TimeMicrosConversion extends Conversion { + @Override + public Class getConvertedType() { + return LocalTime.class; + } + + @Override + public String getLogicalTypeName() { + return "time-micros"; + } + + @Override + public LocalTime fromLong(Long microsFromMidnight, Schema schema, LogicalType type) { + return LocalTime.ofNanoOfDay(TimeUnit.MICROSECONDS.toNanos(microsFromMidnight)); + } + + @Override + public Long toLong(LocalTime time, Schema schema, LogicalType type) { + return TimeUnit.NANOSECONDS.toMicros(time.toNanoOfDay()); + } + + @Override + public Schema getRecommendedSchema() { + return LogicalTypes.timeMicros().addToSchema(Schema.create(Schema.Type.LONG)); + } + } + + public static class TimestampMillisConversion extends Conversion { + @Override + public Class getConvertedType() { + return Instant.class; + } + + @Override + public String getLogicalTypeName() { + return "timestamp-millis"; + } + + @Override + public Instant fromLong(Long millisFromEpoch, Schema schema, LogicalType type) { + return Instant.ofEpochMilli(millisFromEpoch); + } + + @Override + public Long toLong(Instant timestamp, Schema schema, LogicalType type) { + return timestamp.toEpochMilli(); + } + + @Override + public Schema getRecommendedSchema() { + return LogicalTypes.timestampMillis().addToSchema(Schema.create(Schema.Type.LONG)); + } + } + + public static class TimestampMicrosConversion extends Conversion { + @Override + public Class getConvertedType() { + return Instant.class; + } + + @Override + public String getLogicalTypeName() { + return "timestamp-micros"; + } + + @Override + public Instant fromLong(Long microsFromEpoch, Schema schema, LogicalType type) { + long epochSeconds = microsFromEpoch / (1_000_000); + long nanoAdjustment = (microsFromEpoch % (1_000_000)) * 1_000; + + return Instant.ofEpochSecond(epochSeconds, nanoAdjustment); + } + + @Override + public Long toLong(Instant instant, Schema schema, LogicalType type) { + long seconds = instant.getEpochSecond(); + int nanos = instant.getNano(); + + if (seconds < 0 && nanos > 0) { + long micros = Math.multiplyExact(seconds + 1, 1_000_000); + long adjustment = (nanos / 1_000L) - 1_000_000; + + return Math.addExact(micros, adjustment); + } else { + long micros = Math.multiplyExact(seconds, 1_000_000); + + return Math.addExact(micros, nanos / 1_000); + } + } + + @Override + public Schema getRecommendedSchema() { + return LogicalTypes.timestampMicros().addToSchema(Schema.create(Schema.Type.LONG)); + } + } +} diff --git a/lang/java/avro/src/test/java/org/apache/avro/data/TestJsr310TimeConversions.java b/lang/java/avro/src/test/java/org/apache/avro/data/TestJsr310TimeConversions.java new file mode 100644 index 00000000000..caf699e7d41 --- /dev/null +++ b/lang/java/avro/src/test/java/org/apache/avro/data/TestJsr310TimeConversions.java @@ -0,0 +1,241 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.avro.data; + +import org.apache.avro.LogicalTypes; +import org.apache.avro.Schema; +import org.apache.avro.data.Jsr310TimeConversions.DateConversion; +import org.apache.avro.data.Jsr310TimeConversions.TimeMillisConversion; +import org.apache.avro.data.Jsr310TimeConversions.TimeMicrosConversion; +import org.apache.avro.data.Jsr310TimeConversions.TimestampMicrosConversion; +import org.apache.avro.data.Jsr310TimeConversions.TimestampMillisConversion; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.ZonedDateTime; +import java.time.ZoneOffset; + +public class TestJsr310TimeConversions { + + public static Schema DATE_SCHEMA; + public static Schema TIME_MILLIS_SCHEMA; + public static Schema TIME_MICROS_SCHEMA; + public static Schema TIMESTAMP_MILLIS_SCHEMA; + public static Schema TIMESTAMP_MICROS_SCHEMA; + + @BeforeClass + public static void createSchemas() { + TestJsr310TimeConversions.DATE_SCHEMA = LogicalTypes.date() + .addToSchema(Schema.create(Schema.Type.INT)); + TestJsr310TimeConversions.TIME_MILLIS_SCHEMA = LogicalTypes.timeMillis() + .addToSchema(Schema.create(Schema.Type.INT)); + TestJsr310TimeConversions.TIME_MICROS_SCHEMA = LogicalTypes.timeMicros() + .addToSchema(Schema.create(Schema.Type.LONG)); + TestJsr310TimeConversions.TIMESTAMP_MILLIS_SCHEMA = LogicalTypes.timestampMillis() + .addToSchema(Schema.create(Schema.Type.LONG)); + TestJsr310TimeConversions.TIMESTAMP_MICROS_SCHEMA = LogicalTypes.timestampMicros() + .addToSchema(Schema.create(Schema.Type.LONG)); + } + + @Test + public void testDateConversion() throws Exception { + DateConversion conversion = new DateConversion(); + LocalDate Jan_6_1970 = LocalDate.of(1970, 1, 6); // 5 + LocalDate Jan_1_1970 = LocalDate.of(1970, 1, 1); // 0 + LocalDate Dec_27_1969 = LocalDate.of(1969, 12, 27); // -5 + + Assert.assertEquals("6 Jan 1970 should be 5", 5, + (int) conversion.toInt(Jan_6_1970, DATE_SCHEMA, LogicalTypes.date())); + Assert.assertEquals("1 Jan 1970 should be 0", 0, + (int) conversion.toInt(Jan_1_1970, DATE_SCHEMA, LogicalTypes.date())); + Assert.assertEquals("27 Dec 1969 should be -5", -5, + (int) conversion.toInt(Dec_27_1969, DATE_SCHEMA, LogicalTypes.date())); + + Assert.assertEquals("6 Jan 1970 should be 5", + conversion.fromInt(5, DATE_SCHEMA, LogicalTypes.date()), Jan_6_1970); + Assert.assertEquals("1 Jan 1970 should be 0", + conversion.fromInt(0, DATE_SCHEMA, LogicalTypes.date()), Jan_1_1970); + Assert.assertEquals("27 Dec 1969 should be -5", + conversion.fromInt(-5, DATE_SCHEMA, LogicalTypes.date()), Dec_27_1969); + } + + @Test + public void testTimeMillisConversion() throws Exception { + TimeMillisConversion conversion = new TimeMillisConversion(); + LocalTime oneAM = LocalTime.of(1, 0); + LocalTime afternoon = LocalTime.of(15, 14, 15, 926_000_000); + int afternoonMillis = ((15 * 60 + 14) * 60 + 15) * 1000 + 926; + + Assert.assertEquals("Midnight should be 0", 0, + (int) conversion.toInt( + LocalTime.MIDNIGHT, TIME_MILLIS_SCHEMA, LogicalTypes.timeMillis())); + Assert.assertEquals("01:00 should be 3,600,000", 3_600_000, + (int) conversion.toInt( + oneAM, TIME_MILLIS_SCHEMA, LogicalTypes.timeMillis())); + Assert.assertEquals("15:14:15.926 should be " + afternoonMillis, + afternoonMillis, + (int) conversion.toInt( + afternoon, TIME_MILLIS_SCHEMA, LogicalTypes.timeMillis())); + + Assert.assertEquals("Midnight should be 0", + LocalTime.MIDNIGHT, + conversion.fromInt(0, TIME_MILLIS_SCHEMA, LogicalTypes.timeMillis())); + Assert.assertEquals("01:00 should be 3,600,000", + oneAM, + conversion.fromInt( + 3600000, TIME_MILLIS_SCHEMA, LogicalTypes.timeMillis())); + Assert.assertEquals("15:14:15.926 should be " + afternoonMillis, + afternoon, + conversion.fromInt( + afternoonMillis, TIME_MILLIS_SCHEMA, LogicalTypes.timeMillis())); + } + + @Test + public void testTimeMicrosConversion() throws Exception { + TimeMicrosConversion conversion = new TimeMicrosConversion(); + LocalTime oneAM = LocalTime.of(1, 0); + LocalTime afternoon = LocalTime.of(15, 14, 15, 926_551_000); + long afternoonMicros = ((long) (15 * 60 + 14) * 60 + 15) * 1_000_000 + 926_551; + + Assert.assertEquals("Midnight should be 0", + LocalTime.MIDNIGHT, + conversion.fromLong(0L, TIME_MICROS_SCHEMA, LogicalTypes.timeMicros())); + Assert.assertEquals("01:00 should be 3,600,000,000", + oneAM, + conversion.fromLong( + 3_600_000_000L, TIME_MICROS_SCHEMA, LogicalTypes.timeMicros())); + Assert.assertEquals("15:14:15.926551 should be " + afternoonMicros, + afternoon, + conversion.fromLong( + afternoonMicros, TIME_MICROS_SCHEMA, LogicalTypes.timeMicros())); + + Assert.assertEquals("Midnight should be 0", 0, + (long) conversion.toLong( + LocalTime.MIDNIGHT, TIME_MICROS_SCHEMA, LogicalTypes.timeMicros())); + Assert.assertEquals("01:00 should be 3,600,000,000", 3_600_000_000L, + (long) conversion.toLong( + oneAM, TIME_MICROS_SCHEMA, LogicalTypes.timeMicros())); + Assert.assertEquals("15:14:15.926551 should be " + afternoonMicros, + afternoonMicros, + (long) conversion.toLong( + afternoon, TIME_MICROS_SCHEMA, LogicalTypes.timeMicros())); + } + + @Test + public void testTimestampMillisConversion() throws Exception { + TimestampMillisConversion conversion = new TimestampMillisConversion(); + long nowInstant = Instant.now().toEpochMilli(); // ms precision + + // round trip + Instant now = conversion.fromLong( + nowInstant, TIMESTAMP_MILLIS_SCHEMA, LogicalTypes.timestampMillis()); + long roundTrip = conversion.toLong( + now, TIMESTAMP_MILLIS_SCHEMA, LogicalTypes.timestampMillis()); + Assert.assertEquals("Round-trip conversion should work", + nowInstant, roundTrip); + + long May_28_2015_21_46_53_221_instant = 1432849613221L; + Instant May_28_2015_21_46_53_221 = + ZonedDateTime.of(2015, 5, 28, 21, 46, 53, 221_000_000, ZoneOffset.UTC).toInstant(); + + // known dates from https://www.epochconverter.com/ + // > Epoch + Assert.assertEquals("Known date should be correct", + May_28_2015_21_46_53_221, + conversion.fromLong(May_28_2015_21_46_53_221_instant, + TIMESTAMP_MILLIS_SCHEMA, LogicalTypes.timestampMillis())); + Assert.assertEquals("Known date should be correct", + May_28_2015_21_46_53_221_instant, + (long) conversion.toLong(May_28_2015_21_46_53_221, + TIMESTAMP_MILLIS_SCHEMA, LogicalTypes.timestampMillis())); + + // Epoch + Assert.assertEquals("1970-01-01 should be 0", + Instant.EPOCH, + conversion.fromLong(0L, + TIMESTAMP_MILLIS_SCHEMA, LogicalTypes.timestampMillis())); + Assert.assertEquals("1970-01-01 should be 0", + 0L, + (long) conversion.toLong(ZonedDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC).toInstant(), + TIMESTAMP_MILLIS_SCHEMA, LogicalTypes.timestampMillis())); + + // < Epoch + long Jul_01_1969_12_00_00_123_instant = -15854400000L + 123; + Instant Jul_01_1969_12_00_00_123 = + ZonedDateTime.of(1969, 7, 1, 12, 0, 0, 123_000_000, ZoneOffset.UTC).toInstant(); + + Assert.assertEquals("Pre 1970 date should be correct", + Jul_01_1969_12_00_00_123, + conversion.fromLong(Jul_01_1969_12_00_00_123_instant, + TIMESTAMP_MILLIS_SCHEMA, LogicalTypes.timestampMillis())); + Assert.assertEquals("Pre 1970 date should be correct", + Jul_01_1969_12_00_00_123_instant, + (long) conversion.toLong(Jul_01_1969_12_00_00_123, + TIMESTAMP_MILLIS_SCHEMA, LogicalTypes.timestampMillis())); + } + + @Test + public void testTimestampMicrosConversion() throws Exception { + TimestampMicrosConversion conversion = new TimestampMicrosConversion(); + + // known dates from https://www.epochconverter.com/ + // > Epoch + long May_28_2015_21_46_53_221_843_instant = 1432849613221L * 1000 + 843; + Instant May_28_2015_21_46_53_221_843 = + ZonedDateTime.of(2015, 5, 28, 21, 46, 53, 221_843_000, ZoneOffset.UTC).toInstant(); + + Assert.assertEquals("Known date should be correct", + May_28_2015_21_46_53_221_843, + conversion.fromLong(May_28_2015_21_46_53_221_843_instant, + TIMESTAMP_MICROS_SCHEMA, LogicalTypes.timestampMicros())); + + Assert.assertEquals("Known date should be correct", + May_28_2015_21_46_53_221_843_instant, + (long) conversion.toLong(May_28_2015_21_46_53_221_843, + TIMESTAMP_MICROS_SCHEMA, LogicalTypes.timestampMillis())); + + // Epoch + Assert.assertEquals("1970-01-01 should be 0", + Instant.EPOCH, + conversion.fromLong(0L, + TIMESTAMP_MILLIS_SCHEMA, LogicalTypes.timestampMillis())); + Assert.assertEquals("1970-01-01 should be 0", + 0L, + (long) conversion.toLong(ZonedDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC).toInstant(), + TIMESTAMP_MILLIS_SCHEMA, LogicalTypes.timestampMillis())); + + // < Epoch + long Jul_01_1969_12_00_00_000_123_instant = -15854400000L * 1000 + 123; + Instant Jul_01_1969_12_00_00_000_123 = + ZonedDateTime.of(1969, 7, 1, 12, 0, 0, 123_000, ZoneOffset.UTC).toInstant(); + + Assert.assertEquals("Pre 1970 date should be correct", + Jul_01_1969_12_00_00_000_123, + conversion.fromLong(Jul_01_1969_12_00_00_000_123_instant, + TIMESTAMP_MILLIS_SCHEMA, LogicalTypes.timestampMillis())); + Assert.assertEquals("Pre 1970 date should be correct", + Jul_01_1969_12_00_00_000_123_instant, + (long) conversion.toLong(Jul_01_1969_12_00_00_000_123, + TIMESTAMP_MILLIS_SCHEMA, LogicalTypes.timestampMillis())); + } +} diff --git a/lang/java/avro/src/test/java/org/apache/avro/specific/TestRecordWithJsr310LogicalTypes.java b/lang/java/avro/src/test/java/org/apache/avro/specific/TestRecordWithJsr310LogicalTypes.java new file mode 100644 index 00000000000..c42b3ca0d5c --- /dev/null +++ b/lang/java/avro/src/test/java/org/apache/avro/specific/TestRecordWithJsr310LogicalTypes.java @@ -0,0 +1,892 @@ +/** + * Autogenerated by Avro + * + * DO NOT EDIT DIRECTLY + */ +package org.apache.avro.specific; + +import org.apache.avro.message.BinaryMessageEncoder; +import org.apache.avro.message.BinaryMessageDecoder; +import org.apache.avro.message.SchemaStore; + +@SuppressWarnings("all") +@org.apache.avro.specific.AvroGenerated +public class TestRecordWithJsr310LogicalTypes extends org.apache.avro.specific.SpecificRecordBase implements org.apache.avro.specific.SpecificRecord { + private static final long serialVersionUID = 3313339903648295220L; + public static final org.apache.avro.Schema SCHEMA$ = new org.apache.avro.Schema.Parser().parse("{\"type\":\"record\",\"name\":\"TestRecordWithJsr310LogicalTypes\",\"namespace\":\"org.apache.avro.specific\",\"fields\":[{\"name\":\"b\",\"type\":\"boolean\"},{\"name\":\"i32\",\"type\":\"int\"},{\"name\":\"i64\",\"type\":\"long\"},{\"name\":\"f32\",\"type\":\"float\"},{\"name\":\"f64\",\"type\":\"double\"},{\"name\":\"s\",\"type\":[\"null\",\"string\"],\"default\":null},{\"name\":\"d\",\"type\":{\"type\":\"int\",\"logicalType\":\"date\"}},{\"name\":\"t\",\"type\":{\"type\":\"int\",\"logicalType\":\"time-millis\"}},{\"name\":\"ts\",\"type\":{\"type\":\"long\",\"logicalType\":\"timestamp-millis\"}},{\"name\":\"dec\",\"type\":{\"type\":\"bytes\",\"logicalType\":\"decimal\",\"precision\":9,\"scale\":2}}]}"); + public static org.apache.avro.Schema getClassSchema() { return SCHEMA$; } + + private static SpecificData MODEL$ = new SpecificData(); + + private static final BinaryMessageEncoder ENCODER = + new BinaryMessageEncoder(MODEL$, SCHEMA$); + + private static final BinaryMessageDecoder DECODER = + new BinaryMessageDecoder(MODEL$, SCHEMA$); + + /** + * Return the BinaryMessageDecoder instance used by this class. + */ + public static BinaryMessageDecoder getDecoder() { + return DECODER; + } + + /** + * Create a new BinaryMessageDecoder instance for this class that uses the specified {@link SchemaStore}. + * @param resolver a {@link SchemaStore} used to find schemas by fingerprint + */ + public static BinaryMessageDecoder createDecoder(SchemaStore resolver) { + return new BinaryMessageDecoder(MODEL$, SCHEMA$, resolver); + } + + /** Serializes this TestRecordWithJsr310LogicalTypes to a ByteBuffer. */ + public java.nio.ByteBuffer toByteBuffer() throws java.io.IOException { + return ENCODER.encode(this); + } + + /** Deserializes a TestRecordWithJsr310LogicalTypes from a ByteBuffer. */ + public static TestRecordWithJsr310LogicalTypes fromByteBuffer( + java.nio.ByteBuffer b) throws java.io.IOException { + return DECODER.decode(b); + } + + @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 java.time.LocalDate d; + @Deprecated public java.time.LocalTime t; + @Deprecated public java.time.Instant ts; + @Deprecated public java.math.BigDecimal dec; + + /** + * 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 TestRecordWithJsr310LogicalTypes() {} + + /** + * All-args constructor. + * @param b The new value for b + * @param i32 The new value for i32 + * @param i64 The new value for i64 + * @param f32 The new value for f32 + * @param f64 The new value for f64 + * @param s The new value for s + * @param d The new value for d + * @param t The new value for t + * @param ts The new value for ts + * @param dec The new value for dec + */ + public TestRecordWithJsr310LogicalTypes(java.lang.Boolean b, java.lang.Integer i32, java.lang.Long i64, java.lang.Float f32, java.lang.Double f64, java.lang.CharSequence s, java.time.LocalDate d, java.time.LocalTime t, java.time.Instant ts, java.math.BigDecimal dec) { + 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; + this.dec = dec; + } + + 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; + case 9: return dec; + default: throw new org.apache.avro.AvroRuntimeException("Bad index"); + } + } + + protected static final org.apache.avro.Conversions.DecimalConversion DECIMAL_CONVERSION = new org.apache.avro.Conversions.DecimalConversion(); + protected static final org.apache.avro.data.Jsr310TimeConversions.DateConversion DATE_CONVERSION = new org.apache.avro.data.Jsr310TimeConversions.DateConversion(); + protected static final org.apache.avro.data.Jsr310TimeConversions.TimeMillisConversion TIME_CONVERSION = new org.apache.avro.data.Jsr310TimeConversions.TimeMillisConversion(); + protected static final org.apache.avro.data.Jsr310TimeConversions.TimestampMillisConversion TIMESTAMP_CONVERSION = new org.apache.avro.data.Jsr310TimeConversions.TimestampMillisConversion(); + + private static final org.apache.avro.Conversion[] conversions = + new org.apache.avro.Conversion[] { + null, + null, + null, + null, + null, + null, + DATE_CONVERSION, + TIME_CONVERSION, + TIMESTAMP_CONVERSION, + DECIMAL_CONVERSION, + null + }; + + @Override + public org.apache.avro.Conversion getConversion(int field) { + return conversions[field]; + } + + // 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 = (java.time.LocalDate)value$; break; + case 7: t = (java.time.LocalTime)value$; break; + case 8: ts = (java.time.Instant)value$; break; + case 9: dec = (java.math.BigDecimal)value$; break; + default: throw new org.apache.avro.AvroRuntimeException("Bad index"); + } + } + + /** + * Gets the value of the 'b' field. + * @return 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. + * @return 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. + * @return 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. + * @return 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. + * @return 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. + * @return 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. + * @return The value of the 'd' field. + */ + public java.time.LocalDate getD() { + return d; + } + + /** + * Sets the value of the 'd' field. + * @param value the value to set. + */ + public void setD(java.time.LocalDate value) { + this.d = value; + } + + /** + * Gets the value of the 't' field. + * @return The value of the 't' field. + */ + public java.time.LocalTime getT() { + return t; + } + + /** + * Sets the value of the 't' field. + * @param value the value to set. + */ + public void setT(java.time.LocalTime value) { + this.t = value; + } + + /** + * Gets the value of the 'ts' field. + * @return The value of the 'ts' field. + */ + public java.time.Instant getTs() { + return ts; + } + + /** + * Sets the value of the 'ts' field. + * @param value the value to set. + */ + public void setTs(java.time.Instant value) { + this.ts = value; + } + + /** + * Gets the value of the 'dec' field. + * @return The value of the 'dec' field. + */ + public java.math.BigDecimal getDec() { + return dec; + } + + /** + * Sets the value of the 'dec' field. + * @param value the value to set. + */ + public void setDec(java.math.BigDecimal value) { + this.dec = value; + } + + /** + * Creates a new TestRecordWithJsr310LogicalTypes RecordBuilder. + * @return A new TestRecordWithJsr310LogicalTypes RecordBuilder + */ + public static TestRecordWithJsr310LogicalTypes.Builder newBuilder() { + return new TestRecordWithJsr310LogicalTypes.Builder(); + } + + /** + * Creates a new TestRecordWithJsr310LogicalTypes RecordBuilder by copying an existing Builder. + * @param other The existing builder to copy. + * @return A new TestRecordWithJsr310LogicalTypes RecordBuilder + */ + public static TestRecordWithJsr310LogicalTypes.Builder newBuilder(TestRecordWithJsr310LogicalTypes.Builder other) { + if (other == null) { + return new TestRecordWithJsr310LogicalTypes.Builder(); + } else { + return new TestRecordWithJsr310LogicalTypes.Builder(other); + } + } + + /** + * Creates a new TestRecordWithJsr310LogicalTypes RecordBuilder by copying an existing TestRecordWithJsr310LogicalTypes instance. + * @param other The existing instance to copy. + * @return A new TestRecordWithJsr310LogicalTypes RecordBuilder + */ + public static TestRecordWithJsr310LogicalTypes.Builder newBuilder(TestRecordWithJsr310LogicalTypes other) { + if (other == null) { + return new TestRecordWithJsr310LogicalTypes.Builder(); + } else { + return new TestRecordWithJsr310LogicalTypes.Builder(other); + } + } + + /** + * RecordBuilder for TestRecordWithJsr310LogicalTypes 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 java.time.LocalDate d; + private java.time.LocalTime t; + private java.time.Instant ts; + private java.math.BigDecimal dec; + + /** Creates a new Builder */ + private Builder() { + super(SCHEMA$); + } + + /** + * Creates a Builder by copying an existing Builder. + * @param other The existing Builder to copy. + */ + private Builder(TestRecordWithJsr310LogicalTypes.Builder other) { + super(other); + if (isValidValue(fields()[0], other.b)) { + this.b = data().deepCopy(fields()[0].schema(), other.b); + fieldSetFlags()[0] = other.fieldSetFlags()[0]; + } + if (isValidValue(fields()[1], other.i32)) { + this.i32 = data().deepCopy(fields()[1].schema(), other.i32); + fieldSetFlags()[1] = other.fieldSetFlags()[1]; + } + if (isValidValue(fields()[2], other.i64)) { + this.i64 = data().deepCopy(fields()[2].schema(), other.i64); + fieldSetFlags()[2] = other.fieldSetFlags()[2]; + } + if (isValidValue(fields()[3], other.f32)) { + this.f32 = data().deepCopy(fields()[3].schema(), other.f32); + fieldSetFlags()[3] = other.fieldSetFlags()[3]; + } + if (isValidValue(fields()[4], other.f64)) { + this.f64 = data().deepCopy(fields()[4].schema(), other.f64); + fieldSetFlags()[4] = other.fieldSetFlags()[4]; + } + if (isValidValue(fields()[5], other.s)) { + this.s = data().deepCopy(fields()[5].schema(), other.s); + fieldSetFlags()[5] = other.fieldSetFlags()[5]; + } + if (isValidValue(fields()[6], other.d)) { + this.d = data().deepCopy(fields()[6].schema(), other.d); + fieldSetFlags()[6] = other.fieldSetFlags()[6]; + } + if (isValidValue(fields()[7], other.t)) { + this.t = data().deepCopy(fields()[7].schema(), other.t); + fieldSetFlags()[7] = other.fieldSetFlags()[7]; + } + if (isValidValue(fields()[8], other.ts)) { + this.ts = data().deepCopy(fields()[8].schema(), other.ts); + fieldSetFlags()[8] = other.fieldSetFlags()[8]; + } + if (isValidValue(fields()[9], other.dec)) { + this.dec = data().deepCopy(fields()[9].schema(), other.dec); + fieldSetFlags()[9] = other.fieldSetFlags()[9]; + } + } + + /** + * Creates a Builder by copying an existing TestRecordWithJsr310LogicalTypes instance + * @param other The existing instance to copy. + */ + private Builder(TestRecordWithJsr310LogicalTypes other) { + super(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; + } + if (isValidValue(fields()[9], other.dec)) { + this.dec = data().deepCopy(fields()[9].schema(), other.dec); + fieldSetFlags()[9] = true; + } + } + + /** + * Gets the value of the 'b' field. + * @return The value. + */ + public java.lang.Boolean getB() { + return b; + } + + /** + * Sets the value of the 'b' field. + * @param value The value of 'b'. + * @return This builder. + */ + public TestRecordWithJsr310LogicalTypes.Builder setB(boolean value) { + validate(fields()[0], value); + this.b = value; + fieldSetFlags()[0] = true; + return this; + } + + /** + * Checks whether the 'b' field has been set. + * @return True if the 'b' field has been set, false otherwise. + */ + public boolean hasB() { + return fieldSetFlags()[0]; + } + + + /** + * Clears the value of the 'b' field. + * @return This builder. + */ + public TestRecordWithJsr310LogicalTypes.Builder clearB() { + fieldSetFlags()[0] = false; + return this; + } + + /** + * Gets the value of the 'i32' field. + * @return The value. + */ + public java.lang.Integer getI32() { + return i32; + } + + /** + * Sets the value of the 'i32' field. + * @param value The value of 'i32'. + * @return This builder. + */ + public TestRecordWithJsr310LogicalTypes.Builder setI32(int value) { + validate(fields()[1], value); + this.i32 = value; + fieldSetFlags()[1] = true; + return this; + } + + /** + * Checks whether the 'i32' field has been set. + * @return True if the 'i32' field has been set, false otherwise. + */ + public boolean hasI32() { + return fieldSetFlags()[1]; + } + + + /** + * Clears the value of the 'i32' field. + * @return This builder. + */ + public TestRecordWithJsr310LogicalTypes.Builder clearI32() { + fieldSetFlags()[1] = false; + return this; + } + + /** + * Gets the value of the 'i64' field. + * @return The value. + */ + public java.lang.Long getI64() { + return i64; + } + + /** + * Sets the value of the 'i64' field. + * @param value The value of 'i64'. + * @return This builder. + */ + public TestRecordWithJsr310LogicalTypes.Builder setI64(long value) { + validate(fields()[2], value); + this.i64 = value; + fieldSetFlags()[2] = true; + return this; + } + + /** + * Checks whether the 'i64' field has been set. + * @return True if the 'i64' field has been set, false otherwise. + */ + public boolean hasI64() { + return fieldSetFlags()[2]; + } + + + /** + * Clears the value of the 'i64' field. + * @return This builder. + */ + public TestRecordWithJsr310LogicalTypes.Builder clearI64() { + fieldSetFlags()[2] = false; + return this; + } + + /** + * Gets the value of the 'f32' field. + * @return The value. + */ + public java.lang.Float getF32() { + return f32; + } + + /** + * Sets the value of the 'f32' field. + * @param value The value of 'f32'. + * @return This builder. + */ + public TestRecordWithJsr310LogicalTypes.Builder setF32(float value) { + validate(fields()[3], value); + this.f32 = value; + fieldSetFlags()[3] = true; + return this; + } + + /** + * Checks whether the 'f32' field has been set. + * @return True if the 'f32' field has been set, false otherwise. + */ + public boolean hasF32() { + return fieldSetFlags()[3]; + } + + + /** + * Clears the value of the 'f32' field. + * @return This builder. + */ + public TestRecordWithJsr310LogicalTypes.Builder clearF32() { + fieldSetFlags()[3] = false; + return this; + } + + /** + * Gets the value of the 'f64' field. + * @return The value. + */ + public java.lang.Double getF64() { + return f64; + } + + /** + * Sets the value of the 'f64' field. + * @param value The value of 'f64'. + * @return This builder. + */ + public TestRecordWithJsr310LogicalTypes.Builder setF64(double value) { + validate(fields()[4], value); + this.f64 = value; + fieldSetFlags()[4] = true; + return this; + } + + /** + * Checks whether the 'f64' field has been set. + * @return True if the 'f64' field has been set, false otherwise. + */ + public boolean hasF64() { + return fieldSetFlags()[4]; + } + + + /** + * Clears the value of the 'f64' field. + * @return This builder. + */ + public TestRecordWithJsr310LogicalTypes.Builder clearF64() { + fieldSetFlags()[4] = false; + return this; + } + + /** + * Gets the value of the 's' field. + * @return The value. + */ + public java.lang.CharSequence getS() { + return s; + } + + /** + * Sets the value of the 's' field. + * @param value The value of 's'. + * @return This builder. + */ + public TestRecordWithJsr310LogicalTypes.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. + * @return True if the 's' field has been set, false otherwise. + */ + public boolean hasS() { + return fieldSetFlags()[5]; + } + + + /** + * Clears the value of the 's' field. + * @return This builder. + */ + public TestRecordWithJsr310LogicalTypes.Builder clearS() { + s = null; + fieldSetFlags()[5] = false; + return this; + } + + /** + * Gets the value of the 'd' field. + * @return The value. + */ + public java.time.LocalDate getD() { + return d; + } + + /** + * Sets the value of the 'd' field. + * @param value The value of 'd'. + * @return This builder. + */ + public TestRecordWithJsr310LogicalTypes.Builder setD(java.time.LocalDate value) { + validate(fields()[6], value); + this.d = value; + fieldSetFlags()[6] = true; + return this; + } + + /** + * Checks whether the 'd' field has been set. + * @return True if the 'd' field has been set, false otherwise. + */ + public boolean hasD() { + return fieldSetFlags()[6]; + } + + + /** + * Clears the value of the 'd' field. + * @return This builder. + */ + public TestRecordWithJsr310LogicalTypes.Builder clearD() { + fieldSetFlags()[6] = false; + return this; + } + + /** + * Gets the value of the 't' field. + * @return The value. + */ + public java.time.LocalTime getT() { + return t; + } + + /** + * Sets the value of the 't' field. + * @param value The value of 't'. + * @return This builder. + */ + public TestRecordWithJsr310LogicalTypes.Builder setT(java.time.LocalTime value) { + validate(fields()[7], value); + this.t = value; + fieldSetFlags()[7] = true; + return this; + } + + /** + * Checks whether the 't' field has been set. + * @return True if the 't' field has been set, false otherwise. + */ + public boolean hasT() { + return fieldSetFlags()[7]; + } + + + /** + * Clears the value of the 't' field. + * @return This builder. + */ + public TestRecordWithJsr310LogicalTypes.Builder clearT() { + fieldSetFlags()[7] = false; + return this; + } + + /** + * Gets the value of the 'ts' field. + * @return The value. + */ + public java.time.Instant getTs() { + return ts; + } + + /** + * Sets the value of the 'ts' field. + * @param value The value of 'ts'. + * @return This builder. + */ + public TestRecordWithJsr310LogicalTypes.Builder setTs(java.time.Instant value) { + validate(fields()[8], value); + this.ts = value; + fieldSetFlags()[8] = true; + return this; + } + + /** + * Checks whether the 'ts' field has been set. + * @return True if the 'ts' field has been set, false otherwise. + */ + public boolean hasTs() { + return fieldSetFlags()[8]; + } + + + /** + * Clears the value of the 'ts' field. + * @return This builder. + */ + public TestRecordWithJsr310LogicalTypes.Builder clearTs() { + fieldSetFlags()[8] = false; + return this; + } + + /** + * Gets the value of the 'dec' field. + * @return The value. + */ + public java.math.BigDecimal getDec() { + return dec; + } + + /** + * Sets the value of the 'dec' field. + * @param value The value of 'dec'. + * @return This builder. + */ + public TestRecordWithJsr310LogicalTypes.Builder setDec(java.math.BigDecimal value) { + validate(fields()[9], value); + this.dec = value; + fieldSetFlags()[9] = true; + return this; + } + + /** + * Checks whether the 'dec' field has been set. + * @return True if the 'dec' field has been set, false otherwise. + */ + public boolean hasDec() { + return fieldSetFlags()[9]; + } + + + /** + * Clears the value of the 'dec' field. + * @return This builder. + */ + public TestRecordWithJsr310LogicalTypes.Builder clearDec() { + dec = null; + fieldSetFlags()[9] = false; + return this; + } + + @Override + @SuppressWarnings("unchecked") + public TestRecordWithJsr310LogicalTypes build() { + try { + TestRecordWithJsr310LogicalTypes record = new TestRecordWithJsr310LogicalTypes(); + record.b = fieldSetFlags()[0] ? this.b : (java.lang.Boolean) defaultValue(fields()[0], record.getConversion(0)); + record.i32 = fieldSetFlags()[1] ? this.i32 : (java.lang.Integer) defaultValue(fields()[1], record.getConversion(1)); + record.i64 = fieldSetFlags()[2] ? this.i64 : (java.lang.Long) defaultValue(fields()[2], record.getConversion(2)); + record.f32 = fieldSetFlags()[3] ? this.f32 : (java.lang.Float) defaultValue(fields()[3], record.getConversion(3)); + record.f64 = fieldSetFlags()[4] ? this.f64 : (java.lang.Double) defaultValue(fields()[4], record.getConversion(4)); + record.s = fieldSetFlags()[5] ? this.s : (java.lang.CharSequence) defaultValue(fields()[5], record.getConversion(5)); + record.d = fieldSetFlags()[6] ? this.d : (java.time.LocalDate) defaultValue(fields()[6], record.getConversion(6)); + record.t = fieldSetFlags()[7] ? this.t : (java.time.LocalTime) defaultValue(fields()[7], record.getConversion(7)); + record.ts = fieldSetFlags()[8] ? this.ts : (java.time.Instant) defaultValue(fields()[8], record.getConversion(8)); + record.dec = fieldSetFlags()[9] ? this.dec : (java.math.BigDecimal) defaultValue(fields()[9], record.getConversion(9)); + return record; + } catch (java.lang.Exception e) { + throw new org.apache.avro.AvroRuntimeException(e); + } + } + } + + @SuppressWarnings("unchecked") + private static final org.apache.avro.io.DatumWriter + WRITER$ = (org.apache.avro.io.DatumWriter)MODEL$.createDatumWriter(SCHEMA$); + + @Override public void writeExternal(java.io.ObjectOutput out) + throws java.io.IOException { + WRITER$.write(this, SpecificData.getEncoder(out)); + } + + @SuppressWarnings("unchecked") + private static final org.apache.avro.io.DatumReader + READER$ = (org.apache.avro.io.DatumReader)MODEL$.createDatumReader(SCHEMA$); + + @Override public void readExternal(java.io.ObjectInput in) + throws java.io.IOException { + READER$.read(this, SpecificData.getDecoder(in)); + } + +} diff --git a/lang/java/avro/src/test/java/org/apache/avro/specific/TestRecordWithLogicalTypes.java b/lang/java/avro/src/test/java/org/apache/avro/specific/TestRecordWithLogicalTypes.java index 493a8e67c9e..17e74ae2f91 100644 --- a/lang/java/avro/src/test/java/org/apache/avro/specific/TestRecordWithLogicalTypes.java +++ b/lang/java/avro/src/test/java/org/apache/avro/specific/TestRecordWithLogicalTypes.java @@ -5,6 +5,7 @@ */ package org.apache.avro.specific; +import org.apache.avro.data.TimeConversions; import org.apache.avro.message.BinaryMessageDecoder; import org.apache.avro.message.BinaryMessageEncoder; @@ -264,9 +265,9 @@ 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(); + protected static final TimeConversions.DateConversion DATE_CONVERSION = new TimeConversions.DateConversion(); + protected static final TimeConversions.TimeConversion TIME_CONVERSION = new TimeConversions.TimeConversion(); + protected static final TimeConversions.TimestampConversion TIMESTAMP_CONVERSION = new TimeConversions.TimestampConversion(); protected static final org.apache.avro.Conversions.DecimalConversion DECIMAL_CONVERSION = new org.apache.avro.Conversions.DecimalConversion(); private final org.apache.avro.Conversion[] conversions = new org.apache.avro.Conversion[] { diff --git a/lang/java/avro/src/test/java/org/apache/avro/specific/TestSpecificLogicalTypes.java b/lang/java/avro/src/test/java/org/apache/avro/specific/TestSpecificLogicalTypes.java index 933cd81891d..a07dec5c85c 100644 --- a/lang/java/avro/src/test/java/org/apache/avro/specific/TestSpecificLogicalTypes.java +++ b/lang/java/avro/src/test/java/org/apache/avro/specific/TestSpecificLogicalTypes.java @@ -32,6 +32,7 @@ import org.joda.time.DateTimeZone; import org.joda.time.LocalDate; import org.joda.time.LocalTime; +import org.joda.time.format.ISODateTimeFormat; import org.junit.Assert; import org.junit.Rule; import org.junit.Test; @@ -42,6 +43,14 @@ import java.util.ArrayList; import java.util.List; +import static java.time.format.DateTimeFormatter.ISO_INSTANT; +import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE; +import static java.time.format.DateTimeFormatter.ISO_LOCAL_TIME; +import static org.hamcrest.Matchers.comparesEqualTo; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; + /** * This tests compatibility between classes generated before and after * AVRO-1684. TestRecordWithoutLogicalTypes and TestRecordWithLogicalTypes were @@ -53,6 +62,10 @@ * 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. + * + * For AVRO-2079 {@link TestRecordWithJsr310LogicalTypes} was generated from + * the same schema and tests were added to test compatibility between the + * two versions. */ public class TestSpecificLogicalTypes { @@ -80,6 +93,97 @@ public void testRecordWithLogicalTypes() throws IOException { Assert.assertEquals("Should match written record", record, actual.get(0)); } + @Test + public void testRecordWithJsr310LogicalTypes() throws IOException { + TestRecordWithJsr310LogicalTypes record = new TestRecordWithJsr310LogicalTypes( + true, + 34, + 35L, + 3.14F, + 3019.34, + null, + java.time.LocalDate.now(), + java.time.LocalTime.now(), + java.time.Instant.now(), + new BigDecimal(123.45f).setScale(2, BigDecimal.ROUND_HALF_DOWN) + ); + + File data = write(TestRecordWithJsr310LogicalTypes.getClassSchema(), record); + List actual = read( + TestRecordWithJsr310LogicalTypes.getClassSchema(), data); + + Assert.assertEquals("Should match written record", record, actual.get(0)); + } + + @Test + public void testAbilityToReadJsr310RecordWrittenAsJodaRecord() throws IOException { + TestRecordWithLogicalTypes withJoda = new TestRecordWithLogicalTypes( + true, + 34, + 35L, + 3.14F, + 3019.34, + null, + LocalDate.now(), + LocalTime.now(), + DateTime.now().withZone(DateTimeZone.UTC), + new BigDecimal(123.45f).setScale(2, BigDecimal.ROUND_HALF_DOWN) + ); + + File data = write(TestRecordWithLogicalTypes.getClassSchema(), withJoda); + List actual = read( + TestRecordWithJsr310LogicalTypes.getClassSchema(), data); + + Assert.assertThat(actual, is(not(empty()))); + TestRecordWithJsr310LogicalTypes withJsr310 = actual.get(0); + + Assert.assertThat(withJsr310.getB(), is(withJoda.getB())); + Assert.assertThat(withJsr310.getI32(), is(withJoda.getI32())); + Assert.assertThat(withJsr310.getI64(), is(withJoda.getI64())); + Assert.assertThat(withJsr310.getF32(), is(withJoda.getF32())); + Assert.assertThat(withJsr310.getF64(), is(withJoda.getF64())); + Assert.assertThat(withJsr310.getS(), is(withJoda.getS())); + + Assert.assertThat(ISO_LOCAL_DATE.format(withJsr310.getD()), is(ISODateTimeFormat.date().print(withJoda.getD()))); + Assert.assertThat(ISO_LOCAL_TIME.format(withJsr310.getT()), is(ISODateTimeFormat.time().print(withJoda.getT()))); + Assert.assertThat(ISO_INSTANT.format(withJsr310.getTs()), is(ISODateTimeFormat.dateTime().print(withJoda.getTs()))); + Assert.assertThat(withJsr310.getDec(), comparesEqualTo(withJoda.getDec())); + } + + @Test + public void testAbilityToReadJodaRecordWrittenAsJsr310Record() throws IOException { + TestRecordWithJsr310LogicalTypes withJsr310 = new TestRecordWithJsr310LogicalTypes( + true, + 34, + 35L, + 3.14F, + 3019.34, + null, + java.time.LocalDate.now(), + java.time.LocalTime.now(), + java.time.Instant.now(), + new BigDecimal(123.45f).setScale(2, BigDecimal.ROUND_HALF_DOWN) + ); + + File data = write(TestRecordWithJsr310LogicalTypes.getClassSchema(), withJsr310); + List actual = read( + TestRecordWithLogicalTypes.getClassSchema(), data); + + Assert.assertThat(actual, is(not(empty()))); + TestRecordWithLogicalTypes withJoda = actual.get(0); + + Assert.assertThat(withJoda.getB(), is(withJsr310.getB())); + Assert.assertThat(withJoda.getI32(), is(withJsr310.getI32())); + Assert.assertThat(withJoda.getI64(), is(withJsr310.getI64())); + Assert.assertThat(withJoda.getF32(), is(withJsr310.getF32())); + Assert.assertThat(withJoda.getF64(), is(withJsr310.getF64())); + Assert.assertThat(withJoda.getS(), is(withJsr310.getS())); + // all of these print in the ISO-8601 format + Assert.assertThat(withJoda.getD().toString(), is(withJsr310.getD().toString())); + Assert.assertThat(withJoda.getT().toString(), is(withJsr310.getT().toString())); + Assert.assertThat(withJoda.getTs().toString(), is(withJsr310.getTs().toString())); + Assert.assertThat(withJoda.getDec(), comparesEqualTo(withJsr310.getDec())); + } @Test public void testRecordWithoutLogicalTypes() throws IOException { @@ -198,17 +302,11 @@ 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); + try (FileReader fileReader = new DataFileReader<>(file, reader)) { for (D datum : fileReader) { data.add(datum); } - } finally { - if (fileReader != null) { - fileReader.close(); - } } return data; @@ -224,15 +322,12 @@ 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 { + try (DataFileWriter fileWriter = new DataFileWriter<>(writer)) { 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 index 9932f95056f..f5d212917f4 100644 --- a/lang/java/avro/src/test/resources/record_with_logical_types.avsc +++ b/lang/java/avro/src/test/resources/record_with_logical_types.avsc @@ -1,7 +1,7 @@ { "type" : "record", "name" : "TestRecordWithLogicalTypes", - "doc" : "Schema for TestRecordWithLogicalTypes and TestRecordWithoutLogicalTypes, see TestSpecificLogicalTypes" + "doc" : "Schema for TestRecordWithLogicalTypes and TestRecordWithoutLogicalTypes, see TestSpecificLogicalTypes", "namespace" : "org.apache.avro.specific", "fields" : [ { "name" : "b", diff --git a/lang/java/compiler/src/main/java/org/apache/avro/compiler/specific/ProtocolTask.java b/lang/java/compiler/src/main/java/org/apache/avro/compiler/specific/ProtocolTask.java index b7f94383822..02a1af083a1 100644 --- a/lang/java/compiler/src/main/java/org/apache/avro/compiler/specific/ProtocolTask.java +++ b/lang/java/compiler/src/main/java/org/apache/avro/compiler/specific/ProtocolTask.java @@ -23,6 +23,7 @@ import org.apache.avro.AvroRuntimeException; import org.apache.avro.Protocol; +import org.apache.avro.compiler.specific.SpecificCompiler.DateTimeLogicalTypeImplementation; import org.apache.avro.generic.GenericData.StringType; import org.apache.tools.ant.BuildException; @@ -36,6 +37,7 @@ public class ProtocolTask extends Task { private File src; private File dest = new File("."); private StringType stringType = StringType.CharSequence; + private DateTimeLogicalTypeImplementation dateTimeLogicalTypeImplementation = DateTimeLogicalTypeImplementation.JODA; private final ArrayList filesets = new ArrayList<>(); @@ -51,6 +53,16 @@ public class ProtocolTask extends Task { /** Get the string type. */ public StringType getStringType() { return this.stringType; } + /** Sets the date/time logical type type (either JODA or JSR310) */ + public void setDateTimeLogicalTypeImplementation(DateTimeLogicalTypeImplementation dateTimeLogicalTypeImplementation) { + this.dateTimeLogicalTypeImplementation = dateTimeLogicalTypeImplementation; + } + + /** Get the date/time logical type type (either JODA or JSR310) */ + public DateTimeLogicalTypeImplementation getDateTimeLogicalTypeImplementation() { + return dateTimeLogicalTypeImplementation; + } + /** Add a fileset. */ public void addFileset(FileSet set) { filesets.add(set); } @@ -77,7 +89,7 @@ public void execute() { protected void doCompile(File src, File dir) throws IOException { Protocol protocol = Protocol.parse(src); - SpecificCompiler compiler = new SpecificCompiler(protocol); + SpecificCompiler compiler = new SpecificCompiler(protocol, getDateTimeLogicalTypeImplementation()); compiler.setStringType(getStringType()); compiler.compileToDestination(src, dest); } diff --git a/lang/java/compiler/src/main/java/org/apache/avro/compiler/specific/SchemaTask.java b/lang/java/compiler/src/main/java/org/apache/avro/compiler/specific/SchemaTask.java index 306aff00ed6..9d2c244d375 100644 --- a/lang/java/compiler/src/main/java/org/apache/avro/compiler/specific/SchemaTask.java +++ b/lang/java/compiler/src/main/java/org/apache/avro/compiler/specific/SchemaTask.java @@ -28,7 +28,7 @@ public class SchemaTask extends ProtocolTask { protected void doCompile(File src, File dest) throws IOException { Schema.Parser parser = new Schema.Parser(); Schema schema = parser.parse(src); - SpecificCompiler compiler = new SpecificCompiler(schema); + SpecificCompiler compiler = new SpecificCompiler(schema, getDateTimeLogicalTypeImplementation()); compiler.setStringType(getStringType()); compiler.compileToDestination(src, dest); } 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 d2fe6cc4647..575462e7366 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 @@ -37,9 +37,8 @@ import org.apache.avro.Conversion; import org.apache.avro.Conversions; import org.apache.avro.LogicalTypes; -import org.apache.avro.data.TimeConversions.DateConversion; -import org.apache.avro.data.TimeConversions.TimeConversion; -import org.apache.avro.data.TimeConversions.TimestampConversion; +import org.apache.avro.data.Jsr310TimeConversions; +import org.apache.avro.data.TimeConversions; import org.apache.avro.specific.SpecificData; import org.codehaus.jackson.JsonNode; @@ -88,18 +87,35 @@ public class SpecificCompiler { */ protected static final int MAX_FIELD_PARAMETER_UNIT_COUNT = JVM_METHOD_ARG_LIMIT - 1; - public static enum FieldVisibility { + public 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()); - SPECIFIC.addLogicalTypeConversion(new Conversions.DecimalConversion()); + public enum DateTimeLogicalTypeImplementation { + JODA { + @Override + void addLogicalTypeConversions(SpecificData specificData) { + specificData.addLogicalTypeConversion(new TimeConversions.DateConversion()); + specificData.addLogicalTypeConversion(new TimeConversions.TimeConversion()); + specificData.addLogicalTypeConversion(new TimeConversions.TimestampConversion()); + } + }, + JSR310 { + @Override + void addLogicalTypeConversions(SpecificData specificData) { + specificData.addLogicalTypeConversion(new Jsr310TimeConversions.DateConversion()); + specificData.addLogicalTypeConversion(new Jsr310TimeConversions.TimeMillisConversion()); + specificData.addLogicalTypeConversion(new Jsr310TimeConversions.TimestampMillisConversion()); + } + }; + + public static final DateTimeLogicalTypeImplementation DEFAULT = JODA; + + abstract void addLogicalTypeConversions(SpecificData specificData); } + private final SpecificData specificData = new SpecificData(); + private final Set queue = new HashSet<>(); private Protocol protocol; private VelocityEngine velocityEngine; @@ -109,6 +125,7 @@ public static enum FieldVisibility { private boolean createAllArgsConstructor = true; private String outputCharacterEncoding; private boolean enableDecimalLogicalType = false; + private final DateTimeLogicalTypeImplementation dateTimeLogicalTypeImplementation; private String suffix = ".java"; /* @@ -144,7 +161,11 @@ public boolean isCreateAllArgsConstructor() { " */\n"; public SpecificCompiler(Protocol protocol) { - this(); + this(protocol, DateTimeLogicalTypeImplementation.JODA); + } + + public SpecificCompiler(Protocol protocol, DateTimeLogicalTypeImplementation dateTimeLogicalTypeImplementation) { + this(dateTimeLogicalTypeImplementation); // enqueue all types for (Schema s : protocol.getTypes()) { enqueue(s); @@ -153,16 +174,38 @@ public SpecificCompiler(Protocol protocol) { } public SpecificCompiler(Schema schema) { - this(); + this(schema, DateTimeLogicalTypeImplementation.JODA); + } + + public SpecificCompiler(Schema schema, DateTimeLogicalTypeImplementation dateTimeLogicalTypeImplementation) { + this(dateTimeLogicalTypeImplementation); enqueue(schema); this.protocol = null; } + /** + * Creates a specific compiler with the default (Joda) type for date/time related logical types. + * + * @see #SpecificCompiler(DateTimeLogicalTypeImplementation) + */ SpecificCompiler() { + this(DateTimeLogicalTypeImplementation.JODA); + } + + /** + * Creates a specific compiler with the given type to use for date/time related logical types. + * Use {@link DateTimeLogicalTypeImplementation#JODA} to generate Joda Time classes, use {@link DateTimeLogicalTypeImplementation#JSR310} + * to generate {@code java.time.*} classes for the date/time local types. + * + * @param dateTimeLogicalTypeImplementation the types used for date/time related logical types + */ + SpecificCompiler(DateTimeLogicalTypeImplementation dateTimeLogicalTypeImplementation) { + this.dateTimeLogicalTypeImplementation = dateTimeLogicalTypeImplementation; this.templateDir = System.getProperty("org.apache.avro.specific.templates", "/org/apache/avro/compiler/specific/templates/java/classic/"); initializeVelocity(); + initializeSpecificData(); } /** Set the resource directory where templates reside. First, the compiler checks @@ -225,6 +268,10 @@ public void setEnableDecimalLogicalType(boolean enableDecimalLogicalType) { this.enableDecimalLogicalType = enableDecimalLogicalType; } + public DateTimeLogicalTypeImplementation getDateTimeLogicalTypeImplementation() { + return dateTimeLogicalTypeImplementation; + } + private static String logChuteName = null; private void initializeVelocity() { @@ -253,6 +300,11 @@ private void initializeVelocity() { velocityEngine.setProperty("runtime.log.logsystem.class", logChuteName); } + private void initializeSpecificData() { + dateTimeLogicalTypeImplementation.addLogicalTypeConversions(specificData); + specificData.addLogicalTypeConversion(new Conversions.DecimalConversion()); + } + /** * Captures output file path and contents. */ @@ -324,7 +376,7 @@ public static void compileSchema(File[] srcFiles, File dest) throws IOException for (File src : srcFiles) { Schema schema = parser.parse(src); - SpecificCompiler compiler = new SpecificCompiler(schema); + SpecificCompiler compiler = new SpecificCompiler(schema, DateTimeLogicalTypeImplementation.JODA); compiler.compileToDestination(src, dest); } } @@ -630,7 +682,7 @@ private String javaType(Schema schema, boolean checkConvertedLogicalType) { private String getConvertedLogicalType(Schema schema) { if (enableDecimalLogicalType || !(schema.getLogicalType() instanceof LogicalTypes.Decimal)) { - Conversion conversion = SPECIFIC + Conversion conversion = specificData .getConversionFor(schema.getLogicalType()); if (conversion != null) { return conversion.getConvertedType().getName(); 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 de7fd1c339a..045c7e1ef3b 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 @@ -161,10 +161,16 @@ public class ${this.mangle($schema.getName())}#if ($schema.isError()) extends or } #if ($this.hasLogicalTypeField($schema)) + protected static final org.apache.avro.Conversions.DecimalConversion DECIMAL_CONVERSION = new org.apache.avro.Conversions.DecimalConversion(); +#if ($this.getDateTimeLogicalTypeImplementation().name() == "JODA") protected static final org.apache.avro.data.TimeConversions.DateConversion DATE_CONVERSION = new org.apache.avro.data.TimeConversions.DateConversion(); protected static final org.apache.avro.data.TimeConversions.TimeConversion TIME_CONVERSION = new org.apache.avro.data.TimeConversions.TimeConversion(); protected static final org.apache.avro.data.TimeConversions.TimestampConversion TIMESTAMP_CONVERSION = new org.apache.avro.data.TimeConversions.TimestampConversion(); - protected static final org.apache.avro.Conversions.DecimalConversion DECIMAL_CONVERSION = new org.apache.avro.Conversions.DecimalConversion(); +#elseif ($this.getDateTimeLogicalTypeImplementation().name() == "JSR310") + protected static final org.apache.avro.data.Jsr310TimeConversions.DateConversion DATE_CONVERSION = new org.apache.avro.data.Jsr310TimeConversions.DateConversion(); + protected static final org.apache.avro.data.Jsr310TimeConversions.TimeMillisConversion TIME_CONVERSION = new org.apache.avro.data.Jsr310TimeConversions.TimeMillisConversion(); + protected static final org.apache.avro.data.Jsr310TimeConversions.TimestampMillisConversion TIMESTAMP_CONVERSION = new org.apache.avro.data.Jsr310TimeConversions.TimestampMillisConversion(); +#end private static final org.apache.avro.Conversion[] conversions = new org.apache.avro.Conversion[] { 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 3b4e3588055..b5b3a0ab29d 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 @@ -17,6 +17,8 @@ */ package org.apache.avro.compiler.specific; +import static org.apache.avro.compiler.specific.SpecificCompiler.DateTimeLogicalTypeImplementation.JODA; +import static org.apache.avro.compiler.specific.SpecificCompiler.DateTimeLogicalTypeImplementation.JSR310; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -142,9 +144,13 @@ private static Schema createSampleRecordSchema(int numStringFields, int numDoubl } private SpecificCompiler createCompiler() throws IOException { + return createCompiler(JODA); + } + + private SpecificCompiler createCompiler(SpecificCompiler.DateTimeLogicalTypeImplementation dateTimeLogicalTypeImplementation) throws IOException { Schema.Parser parser = new Schema.Parser(); Schema schema = parser.parse(this.src); - SpecificCompiler compiler = new SpecificCompiler(schema); + SpecificCompiler compiler = new SpecificCompiler(schema, dateTimeLogicalTypeImplementation); compiler.setTemplateDir(this.velocityTemplateDir); compiler.setStringType(StringType.CharSequence); return compiler; @@ -407,6 +413,26 @@ public void testJavaTypeWithDecimalLogicalTypeDisabled() throws Exception { "java.lang.CharSequence", compiler.javaType(uuidSchema)); } + @Test + public void testJavaTypeWithJsr310DateTimeTypes() throws Exception { + SpecificCompiler compiler = createCompiler(JSR310); + + 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)); + + // Date/time types should always use upper level java classes + Assert.assertEquals("Should use java.time.LocalDate for date type", + "java.time.LocalDate", compiler.javaType(dateSchema)); + Assert.assertEquals("Should use java.time.LocalTime for time-millis type", + "java.time.LocalTime", compiler.javaType(timeSchema)); + Assert.assertEquals("Should use java.time.Instant for timestamp-millis type", + "java.time.Instant", compiler.javaType(timestampSchema)); + } + @Test public void testJavaUnbox() throws Exception { SpecificCompiler compiler = createCompiler(); @@ -442,7 +468,26 @@ public void testJavaUnbox() throws Exception { "org.joda.time.LocalTime", compiler.javaUnbox(timeSchema)); Assert.assertEquals("Should use Joda DateTime for timestamp-millis type", "org.joda.time.DateTime", compiler.javaUnbox(timestampSchema)); + } + @Test + public void testJavaUnboxJsr310DateTime() throws Exception { + SpecificCompiler compiler = createCompiler(JSR310); + + 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)); + // Date/time types should always use upper level java classes, even though + // their underlying representations are primitive types + Assert.assertEquals("Should use java.time.LocalDate for date type", + "java.time.LocalDate", compiler.javaUnbox(dateSchema)); + Assert.assertEquals("Should use java.time.LocalTime for time-millis type", + "java.time.LocalTime", compiler.javaUnbox(timeSchema)); + Assert.assertEquals("Should use java.time.Instant for timestamp-millis type", + "java.time.Instant", compiler.javaUnbox(timestampSchema)); } @Test @@ -513,6 +558,14 @@ public void testUnionAndFixedFields() throws Exception { new SpecificCompiler(unionTypesWithMultipleFields).compile()); } + @Test + public void testLogicalTypesWithMultipleFieldsJsr310DateTime() throws Exception { + Schema logicalTypesWithMultipleFields = new Schema.Parser().parse( + new File("src/test/resources/logical_types_with_multiple_fields.avsc")); + assertCompilesWithJavaCompiler( + new SpecificCompiler(logicalTypesWithMultipleFields, JSR310).compile()); + } + @Test public void testConversionInstanceWithDecimalLogicalTypeDisabled() throws Exception { SpecificCompiler compiler = createCompiler(); diff --git a/lang/java/maven-plugin/src/main/java/org/apache/avro/mojo/AbstractAvroMojo.java b/lang/java/maven-plugin/src/main/java/org/apache/avro/mojo/AbstractAvroMojo.java index eb51f76753c..3ddb0629b76 100644 --- a/lang/java/maven-plugin/src/main/java/org/apache/avro/mojo/AbstractAvroMojo.java +++ b/lang/java/maven-plugin/src/main/java/org/apache/avro/mojo/AbstractAvroMojo.java @@ -23,7 +23,7 @@ import java.util.Arrays; import org.apache.avro.compiler.specific.SpecificCompiler; - +import org.apache.avro.compiler.specific.SpecificCompiler.DateTimeLogicalTypeImplementation; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.project.MavenProject; @@ -129,6 +129,14 @@ public abstract class AbstractAvroMojo extends AbstractMojo { */ protected boolean enableDecimalLogicalType; + /** + * Determines which type of classes to generate for date/time related logical types. Either 'joda' or 'jsr310'. + * Defaults to joda for backwards compatibility reasons. + * + * @parameter default-value="joda" + */ + protected String dateTimeLogicalTypeImplementation = DateTimeLogicalTypeImplementation.JODA.name().toLowerCase(); + /** * The current Maven project. * @@ -237,6 +245,22 @@ protected SpecificCompiler.FieldVisibility getFieldVisibility() { } } + protected DateTimeLogicalTypeImplementation getDateTimeLogicalTypeImplementation() { + try { + if (this.dateTimeLogicalTypeImplementation == null || this.dateTimeLogicalTypeImplementation.isEmpty()) { + return DateTimeLogicalTypeImplementation.DEFAULT; + } else { + String upper = String.valueOf(this.dateTimeLogicalTypeImplementation).trim().toUpperCase(); + return DateTimeLogicalTypeImplementation.valueOf(upper); + } + } catch (IllegalArgumentException e) { + getLog().warn("Unknown value '" + this.dateTimeLogicalTypeImplementation + + "' for property dateTimeLogicalTypeImplementation; using '" + + DateTimeLogicalTypeImplementation.DEFAULT.name().toLowerCase() + "' instead"); + return DateTimeLogicalTypeImplementation.DEFAULT; + } + } + protected abstract void doCompile(String filename, File sourceDirectory, File outputDirectory) throws IOException; protected abstract String[] getIncludes(); diff --git a/lang/java/maven-plugin/src/main/java/org/apache/avro/mojo/IDLProtocolMojo.java b/lang/java/maven-plugin/src/main/java/org/apache/avro/mojo/IDLProtocolMojo.java index 3c9542e3a4c..3ee7fcb0028 100644 --- a/lang/java/maven-plugin/src/main/java/org/apache/avro/mojo/IDLProtocolMojo.java +++ b/lang/java/maven-plugin/src/main/java/org/apache/avro/mojo/IDLProtocolMojo.java @@ -88,7 +88,7 @@ protected void doCompile(String filename, File sourceDirectory, File outputDirec Protocol p = parser.CompilationUnit(); String json = p.toString(true); Protocol protocol = Protocol.parse(json); - SpecificCompiler compiler = new SpecificCompiler(protocol); + SpecificCompiler compiler = new SpecificCompiler(protocol, getDateTimeLogicalTypeImplementation()); compiler.setStringType(GenericData.StringType.valueOf(stringType)); compiler.setTemplateDir(templateDirectory); compiler.setFieldVisibility(getFieldVisibility()); diff --git a/lang/java/maven-plugin/src/main/java/org/apache/avro/mojo/ProtocolMojo.java b/lang/java/maven-plugin/src/main/java/org/apache/avro/mojo/ProtocolMojo.java index a4dbe65af10..25ee56ba16f 100644 --- a/lang/java/maven-plugin/src/main/java/org/apache/avro/mojo/ProtocolMojo.java +++ b/lang/java/maven-plugin/src/main/java/org/apache/avro/mojo/ProtocolMojo.java @@ -56,7 +56,7 @@ public class ProtocolMojo extends AbstractAvroMojo { protected void doCompile(String filename, File sourceDirectory, File outputDirectory) throws IOException { File src = new File(sourceDirectory, filename); Protocol protocol = Protocol.parse(src); - SpecificCompiler compiler = new SpecificCompiler(protocol); + SpecificCompiler compiler = new SpecificCompiler(protocol, getDateTimeLogicalTypeImplementation()); compiler.setTemplateDir(templateDirectory); compiler.setStringType(StringType.valueOf(stringType)); compiler.setFieldVisibility(getFieldVisibility()); diff --git a/lang/java/maven-plugin/src/main/java/org/apache/avro/mojo/SchemaMojo.java b/lang/java/maven-plugin/src/main/java/org/apache/avro/mojo/SchemaMojo.java index 2d6671f6b22..ab9d0eb29ef 100644 --- a/lang/java/maven-plugin/src/main/java/org/apache/avro/mojo/SchemaMojo.java +++ b/lang/java/maven-plugin/src/main/java/org/apache/avro/mojo/SchemaMojo.java @@ -73,7 +73,7 @@ protected void doCompile(String filename, File sourceDirectory, File outputDirec schema = schemaParser.parse(src); } - SpecificCompiler compiler = new SpecificCompiler(schema); + SpecificCompiler compiler = new SpecificCompiler(schema, getDateTimeLogicalTypeImplementation()); compiler.setTemplateDir(templateDirectory); compiler.setStringType(StringType.valueOf(stringType)); compiler.setFieldVisibility(getFieldVisibility()); diff --git a/lang/java/maven-plugin/src/test/avro/User.avdl b/lang/java/maven-plugin/src/test/avro/User.avdl index 4d4fa5a9a1f..cb63ee52848 100644 --- a/lang/java/maven-plugin/src/test/avro/User.avdl +++ b/lang/java/maven-plugin/src/test/avro/User.avdl @@ -25,6 +25,7 @@ protocol IdlTest { record IdlUser { union { null, string } id; union { null, long } createdOn; + timestamp_ms modifiedOn; union { null, IdlPrivacy } privacy; } diff --git a/lang/java/maven-plugin/src/test/avro/User.avpr b/lang/java/maven-plugin/src/test/avro/User.avpr index 9d8a10958d0..6dd8b9b8900 100644 --- a/lang/java/maven-plugin/src/test/avro/User.avpr +++ b/lang/java/maven-plugin/src/test/avro/User.avpr @@ -27,6 +27,13 @@ "name": "privacy", "type": ["null", "ProtocolPrivacy"], "default": null + }, + { + "name": "modifiedOn", + "type": { + "type": "long", + "logicalType": "timestamp-millis" + } } ] } diff --git a/lang/java/maven-plugin/src/test/avro/User.avsc b/lang/java/maven-plugin/src/test/avro/User.avsc index 0eb5e096ee6..a93e0d13f21 100644 --- a/lang/java/maven-plugin/src/test/avro/User.avsc +++ b/lang/java/maven-plugin/src/test/avro/User.avsc @@ -33,6 +33,13 @@ "name": "privacyDirectImport", "type": ["null", "test.PrivacyDirectImport"], "default": null + }, + { + "name": "time", + "type": { + "type": "long", + "logicalType": "timestamp-millis" + } } ] } diff --git a/lang/java/maven-plugin/src/test/java/org/apache/avro/mojo/AbstractAvroMojoTest.java b/lang/java/maven-plugin/src/test/java/org/apache/avro/mojo/AbstractAvroMojoTest.java index 94a58703efd..3a75a68d983 100644 --- a/lang/java/maven-plugin/src/test/java/org/apache/avro/mojo/AbstractAvroMojoTest.java +++ b/lang/java/maven-plugin/src/test/java/org/apache/avro/mojo/AbstractAvroMojoTest.java @@ -20,6 +20,7 @@ import java.io.File; import java.util.Arrays; import java.util.List; + import org.apache.maven.plugin.testing.AbstractMojoTestCase; /** @@ -27,7 +28,7 @@ * * @author saden */ -public class AbstractAvroMojoTest extends AbstractMojoTestCase { +public abstract class AbstractAvroMojoTest extends AbstractMojoTestCase { @Override protected void setUp() throws Exception { diff --git a/lang/java/maven-plugin/src/test/java/org/apache/avro/mojo/TestIDLProtocolMojo.java b/lang/java/maven-plugin/src/test/java/org/apache/avro/mojo/TestIDLProtocolMojo.java index bdd41384b6f..e686fda63e6 100644 --- a/lang/java/maven-plugin/src/test/java/org/apache/avro/mojo/TestIDLProtocolMojo.java +++ b/lang/java/maven-plugin/src/test/java/org/apache/avro/mojo/TestIDLProtocolMojo.java @@ -17,6 +17,8 @@ */ package org.apache.avro.mojo; +import org.codehaus.plexus.util.FileUtils; + import java.io.File; /** @@ -26,19 +28,36 @@ */ public class TestIDLProtocolMojo extends AbstractAvroMojoTest { - protected File testPom = new File(getBasedir(), - "src/test/resources/unit/idl/pom.xml"); + protected File jodaTestPom = new File(getBasedir(), + "src/test/resources/unit/idl/pom-joda.xml"); + protected File jsr310TestPom = new File(getBasedir(), + "src/test/resources/unit/idl/pom-jsr310.xml"); + + public void testIdlProtocolMojoJoda() throws Exception { + IDLProtocolMojo mojo = (IDLProtocolMojo) lookupMojo("idl-protocol", jodaTestPom); + + assertNotNull(mojo); + mojo.execute(); + + File outputDir = new File(getBasedir(), "target/test-harness/idl-joda/test"); + String[] generatedFileNames = new String[]{"IdlPrivacy.java", + "IdlTest.java", "IdlUser.java", "IdlUserWrapper.java"}; + + String idlUserContent = FileUtils.fileRead(new File(outputDir, "IdlUser.java")); + assertTrue(idlUserContent.contains("org.joda.time.DateTime")); + } - public void testIdlProtocolMojo() throws Exception { - IDLProtocolMojo mojo = (IDLProtocolMojo) lookupMojo("idl-protocol", testPom); + public void testIdlProtocolMojoJsr310() throws Exception { + IDLProtocolMojo mojo = (IDLProtocolMojo) lookupMojo("idl-protocol", jsr310TestPom); assertNotNull(mojo); mojo.execute(); - File outputDir = new File(getBasedir(), "target/test-harness/idl/test"); - String[] generatedFiles = new String[]{"IdlPrivacy.java", + File outputDir = new File(getBasedir(), "target/test-harness/idl-jsr310/test"); + String[] generatedFileNames = new String[]{"IdlPrivacy.java", "IdlTest.java", "IdlUser.java", "IdlUserWrapper.java"}; - assertFilesExist(outputDir, generatedFiles); + String idlUserContent = FileUtils.fileRead(new File(outputDir, "IdlUser.java")); + assertTrue(idlUserContent.contains("java.time.Instant")); } } diff --git a/lang/java/maven-plugin/src/test/java/org/apache/avro/mojo/TestProtocolMojo.java b/lang/java/maven-plugin/src/test/java/org/apache/avro/mojo/TestProtocolMojo.java index 9618a9d41bf..be7fcd20e26 100644 --- a/lang/java/maven-plugin/src/test/java/org/apache/avro/mojo/TestProtocolMojo.java +++ b/lang/java/maven-plugin/src/test/java/org/apache/avro/mojo/TestProtocolMojo.java @@ -17,6 +17,8 @@ */ package org.apache.avro.mojo; +import org.codehaus.plexus.util.FileUtils; + import java.io.File; /** @@ -26,19 +28,40 @@ */ public class TestProtocolMojo extends AbstractAvroMojoTest { - protected File testPom = new File(getBasedir(), - "src/test/resources/unit/protocol/pom.xml"); + protected File jodaTestPom = new File(getBasedir(), + "src/test/resources/unit/protocol/pom-joda.xml"); + protected File jsr310TestPom = new File(getBasedir(), + "src/test/resources/unit/protocol/pom-jsr310.xml"); - public void testProtocolMojo() throws Exception { - ProtocolMojo mojo = (ProtocolMojo) lookupMojo("protocol", testPom); + public void testProtocolMojoJoda() throws Exception { + ProtocolMojo mojo = (ProtocolMojo) lookupMojo("protocol", jodaTestPom); assertNotNull(mojo); mojo.execute(); - File outputDir = new File(getBasedir(), "target/test-harness/protocol/test"); + File outputDir = new File(getBasedir(), "target/test-harness/protocol-joda/test"); String[] generatedFiles = new String[]{"ProtocolPrivacy.java", "ProtocolTest.java", "ProtocolUser.java"}; assertFilesExist(outputDir, generatedFiles); + + String protocolUserContent = FileUtils.fileRead(new File(outputDir, "ProtocolUser.java")); + assertTrue(protocolUserContent.contains("org.joda.time.DateTime")); + } + + public void testProtocolMojoJsr310() throws Exception { + ProtocolMojo mojo = (ProtocolMojo) lookupMojo("protocol", jsr310TestPom); + + assertNotNull(mojo); + mojo.execute(); + + File outputDir = new File(getBasedir(), "target/test-harness/protocol-jsr310/test"); + String[] generatedFiles = new String[]{"ProtocolPrivacy.java", + "ProtocolTest.java", "ProtocolUser.java"}; + + assertFilesExist(outputDir, generatedFiles); + + String protocolUserContent = FileUtils.fileRead(new File(outputDir, "ProtocolUser.java")); + assertTrue(protocolUserContent.contains("java.time.Instant")); } } diff --git a/lang/java/maven-plugin/src/test/java/org/apache/avro/mojo/TestSchemaMojo.java b/lang/java/maven-plugin/src/test/java/org/apache/avro/mojo/TestSchemaMojo.java index 08d58715d73..40ed5f69d7b 100644 --- a/lang/java/maven-plugin/src/test/java/org/apache/avro/mojo/TestSchemaMojo.java +++ b/lang/java/maven-plugin/src/test/java/org/apache/avro/mojo/TestSchemaMojo.java @@ -17,6 +17,8 @@ */ package org.apache.avro.mojo; +import org.codehaus.plexus.util.FileUtils; + import java.io.File; /** @@ -26,19 +28,40 @@ */ public class TestSchemaMojo extends AbstractAvroMojoTest { - protected File testPom = new File(getBasedir(), - "src/test/resources/unit/schema/pom.xml"); + protected File jodaTestPom = new File(getBasedir(), + "src/test/resources/unit/schema/pom-joda.xml"); + protected File jsr310TestPom = new File(getBasedir(), + "src/test/resources/unit/schema/pom-jsr310.xml"); - public void testSchemaMojo() throws Exception { - SchemaMojo mojo = (SchemaMojo) lookupMojo("schema", testPom); + public void testSchemaMojoJoda() throws Exception { + SchemaMojo mojo = (SchemaMojo) lookupMojo("schema", jodaTestPom); assertNotNull(mojo); mojo.execute(); - File outputDir = new File(getBasedir(), "target/test-harness/schema/test"); + File outputDir = new File(getBasedir(), "target/test-harness/schema-joda/test"); String[] generatedFiles = new String[]{"PrivacyDirectImport.java", "PrivacyImport.java", "SchemaPrivacy.java", "SchemaUser.java"}; assertFilesExist(outputDir, generatedFiles); + + String schemaUserContent = FileUtils.fileRead(new File(outputDir, "SchemaUser.java")); + assertTrue(schemaUserContent.contains("org.joda.time.DateTime")); + } + + public void testSchemaMojoJsr310() throws Exception { + SchemaMojo mojo = (SchemaMojo) lookupMojo("schema", jsr310TestPom); + + assertNotNull(mojo); + mojo.execute(); + + File outputDir = new File(getBasedir(), "target/test-harness/schema-jsr310/test"); + String[] generatedFiles = new String[]{"PrivacyDirectImport.java", + "PrivacyImport.java", "SchemaPrivacy.java", "SchemaUser.java"}; + + assertFilesExist(outputDir, generatedFiles); + + String schemaUserContent = FileUtils.fileRead(new File(outputDir, "SchemaUser.java")); + assertTrue(schemaUserContent.contains("java.time.Instant")); } } diff --git a/lang/java/maven-plugin/src/test/resources/unit/idl/pom.xml b/lang/java/maven-plugin/src/test/resources/unit/idl/pom-joda.xml similarity index 85% rename from lang/java/maven-plugin/src/test/resources/unit/idl/pom.xml rename to lang/java/maven-plugin/src/test/resources/unit/idl/pom-joda.xml index 69c504bb35e..92c761d2f9e 100644 --- a/lang/java/maven-plugin/src/test/resources/unit/idl/pom.xml +++ b/lang/java/maven-plugin/src/test/resources/unit/idl/pom-joda.xml @@ -18,12 +18,14 @@ 4.0.0 + avro-parent org.apache.avro - 1.7.3-SNAPSHOT - ../../../../../../../../../ + 1.9.0-SNAPSHOT + ../../../../../../../../../pom.xml + avro-maven-plugin-test jar @@ -43,7 +45,9 @@ ${basedir}/src/test - ${basedir}/target/test-harness/idl + ${basedir}/target/test-harness/idl-joda + String + joda @@ -53,7 +57,7 @@ org.apache.avro avro - 1.7.3-SNAPSHOT + ${parent.version} org.codehaus.jackson diff --git a/lang/java/maven-plugin/src/test/resources/unit/idl/pom-jsr310.xml b/lang/java/maven-plugin/src/test/resources/unit/idl/pom-jsr310.xml new file mode 100644 index 00000000000..5709945d33c --- /dev/null +++ b/lang/java/maven-plugin/src/test/resources/unit/idl/pom-jsr310.xml @@ -0,0 +1,69 @@ + + + + 4.0.0 + + + avro-parent + org.apache.avro + 1.9.0-SNAPSHOT + ../../../../../../../../../pom.xml + + + avro-maven-plugin-test + jar + + testproject + + + + + avro-maven-plugin + + + idl + + idl-protocol + + + + + ${basedir}/src/test + ${basedir}/target/test-harness/idl-jsr310 + String + jsr310 + + + + + + + + org.apache.avro + avro + ${parent.version} + + + org.codehaus.jackson + jackson-mapper-asl + 1.9.10 + + + + diff --git a/lang/java/maven-plugin/src/test/resources/unit/protocol/pom.xml b/lang/java/maven-plugin/src/test/resources/unit/protocol/pom-joda.xml similarity index 92% rename from lang/java/maven-plugin/src/test/resources/unit/protocol/pom.xml rename to lang/java/maven-plugin/src/test/resources/unit/protocol/pom-joda.xml index b484e3dd47a..68ddcdcd2cd 100644 --- a/lang/java/maven-plugin/src/test/resources/unit/protocol/pom.xml +++ b/lang/java/maven-plugin/src/test/resources/unit/protocol/pom-joda.xml @@ -18,12 +18,14 @@ 4.0.0 + avro-parent org.apache.avro - 1.7.3-SNAPSHOT - ../../../../../../../../../ + 1.9.0-SNAPSHOT + ../../../../../../../../../pom.xml + avro-maven-plugin-test jar @@ -43,7 +45,7 @@ ${basedir}/src/test/avro - ${basedir}/target/test-harness/protocol + ${basedir}/target/test-harness/protocol-joda @@ -53,7 +55,7 @@ org.apache.avro avro - 1.7.3-SNAPSHOT + ${parent.version} org.codehaus.jackson diff --git a/lang/java/maven-plugin/src/test/resources/unit/protocol/pom-jsr310.xml b/lang/java/maven-plugin/src/test/resources/unit/protocol/pom-jsr310.xml new file mode 100644 index 00000000000..5ad5bba9f2c --- /dev/null +++ b/lang/java/maven-plugin/src/test/resources/unit/protocol/pom-jsr310.xml @@ -0,0 +1,68 @@ + + + + 4.0.0 + + + avro-parent + org.apache.avro + 1.9.0-SNAPSHOT + ../../../../../../../../../pom.xml + + + avro-maven-plugin-test + jar + + testproject + + + + + avro-maven-plugin + + + protocol + + protocol + + + + + ${basedir}/src/test/avro + ${basedir}/target/test-harness/protocol-jsr310 + jsr310 + + + + + + + + org.apache.avro + avro + ${parent.version} + + + org.codehaus.jackson + jackson-mapper-asl + 1.9.10 + + + + diff --git a/lang/java/maven-plugin/src/test/resources/unit/schema/pom.xml b/lang/java/maven-plugin/src/test/resources/unit/schema/pom-joda.xml similarity index 89% rename from lang/java/maven-plugin/src/test/resources/unit/schema/pom.xml rename to lang/java/maven-plugin/src/test/resources/unit/schema/pom-joda.xml index cc000df05ec..f31692b0d69 100644 --- a/lang/java/maven-plugin/src/test/resources/unit/schema/pom.xml +++ b/lang/java/maven-plugin/src/test/resources/unit/schema/pom-joda.xml @@ -19,6 +19,13 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 + + avro-parent + org.apache.avro + 1.9.0-SNAPSHOT + ../../../../../../../../../pom.xml + + avro-maven-plugin-test jar @@ -38,7 +45,7 @@ ${basedir}/src/test/avro - ${basedir}/target/test-harness/schema + ${basedir}/target/test-harness/schema-joda ${basedir}/src/test/avro/imports ${basedir}/src/test/avro/directImport/PrivacyDirectImport.avsc @@ -52,7 +59,7 @@ org.apache.avro avro - 1.7.3-SNAPSHOT + ${parent.version} org.codehaus.jackson diff --git a/lang/java/maven-plugin/src/test/resources/unit/schema/pom-jsr310.xml b/lang/java/maven-plugin/src/test/resources/unit/schema/pom-jsr310.xml new file mode 100644 index 00000000000..a439363170c --- /dev/null +++ b/lang/java/maven-plugin/src/test/resources/unit/schema/pom-jsr310.xml @@ -0,0 +1,72 @@ + + + + 4.0.0 + + + avro-parent + org.apache.avro + 1.9.0-SNAPSHOT + ../../../../../../../../../pom.xml + + + avro-maven-plugin-test + jar + + testproject + + + + + avro-maven-plugin + + + schema + + schema + + + + + ${basedir}/src/test/avro + ${basedir}/target/test-harness/schema-jsr310 + jsr310 + + ${basedir}/src/test/avro/imports + ${basedir}/src/test/avro/directImport/PrivacyDirectImport.avsc + + + + + + + + + org.apache.avro + avro + ${parent.version} + + + org.codehaus.jackson + jackson-mapper-asl + 1.9.10 + + + + diff --git a/lang/java/tools/src/main/java/org/apache/avro/tool/SpecificCompilerTool.java b/lang/java/tools/src/main/java/org/apache/avro/tool/SpecificCompilerTool.java index fc7df846d84..b6403885677 100644 --- a/lang/java/tools/src/main/java/org/apache/avro/tool/SpecificCompilerTool.java +++ b/lang/java/tools/src/main/java/org/apache/avro/tool/SpecificCompilerTool.java @@ -23,12 +23,16 @@ import java.io.InputStream; import java.io.PrintStream; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Optional; import java.util.Set; import java.util.LinkedHashSet; import java.util.List; import org.apache.avro.Protocol; import org.apache.avro.Schema; +import org.apache.avro.compiler.specific.SpecificCompiler.DateTimeLogicalTypeImplementation; import org.apache.avro.generic.GenericData.StringType; import org.apache.avro.compiler.specific.SpecificCompiler; @@ -43,7 +47,7 @@ public int run(InputStream in, PrintStream out, PrintStream err, List args) throws Exception { if (args.size() < 3) { System.err - .println("Usage: [-encoding ] [-string] [-bigDecimal] (schema|protocol) input... outputdir"); + .println("Usage: [-encoding ] [-string] [-bigDecimal] [-dateTimeLogicalTypeImpl ] (schema|protocol) input... outputdir"); System.err .println(" input - input files or directories"); System.err @@ -53,18 +57,21 @@ public int run(InputStream in, PrintStream out, PrintStream err, System.err.println(" -string - use java.lang.String instead of Utf8"); System.err.println(" -bigDecimal - use java.math.BigDecimal for " + "decimal type instead of java.nio.ByteBuffer"); + System.err.println(" -dateTimeLogicalTypeImpl [joda|jsr310] - use either " + + "Joda time classes (default) or Java 8 native date/time classes (JSR 310)"); return 1; } StringType stringType = StringType.CharSequence; boolean useLogicalDecimal = false; + Optional dateTimeLogicalTypeImplementation = Optional.empty(); + Optional encoding = Optional.empty(); int arg = 0; - String encoding = null; if ("-encoding".equals(args.get(arg))) { arg++; - encoding = args.get(arg); + encoding = Optional.of(args.get(arg)); arg++; } @@ -78,6 +85,17 @@ public int run(InputStream in, PrintStream out, PrintStream err, arg++; } + if ("-dateTimeLogicalTypeImpl".equalsIgnoreCase(args.get(arg))) { + arg++; + try { + dateTimeLogicalTypeImplementation = Optional.of(DateTimeLogicalTypeImplementation.valueOf(args.get(arg).toUpperCase())); + } catch (IllegalArgumentException | IndexOutOfBoundsException e) { + System.err.println("Expected one of " + Arrays.toString(DateTimeLogicalTypeImplementation.values())); + return 1; + } + arg++; + } + String method = args.get(arg); List inputs = new ArrayList<>(); File output = new File(args.get(args.size() - 1)); @@ -90,13 +108,15 @@ public int run(InputStream in, PrintStream out, PrintStream err, Schema.Parser parser = new Schema.Parser(); for (File src : determineInputs(inputs, SCHEMA_FILTER)) { Schema schema = parser.parse(src); - SpecificCompiler compiler = new SpecificCompiler(schema); + SpecificCompiler compiler = new SpecificCompiler(schema, + dateTimeLogicalTypeImplementation.orElse(DateTimeLogicalTypeImplementation.JODA)); executeCompiler(compiler, encoding, stringType, useLogicalDecimal, src, output); } } else if ("protocol".equals(method)) { for (File src : determineInputs(inputs, PROTOCOL_FILTER)) { Protocol protocol = Protocol.parse(src); - SpecificCompiler compiler = new SpecificCompiler(protocol); + SpecificCompiler compiler = new SpecificCompiler(protocol, + dateTimeLogicalTypeImplementation.orElse(DateTimeLogicalTypeImplementation.JODA)); executeCompiler(compiler, encoding, stringType, useLogicalDecimal, src, output); } } else { @@ -107,16 +127,14 @@ public int run(InputStream in, PrintStream out, PrintStream err, } private void executeCompiler(SpecificCompiler compiler, - String encoding, + Optional encoding, StringType stringType, boolean enableDecimalLogicalType, File src, File output) throws IOException { compiler.setStringType(stringType); compiler.setEnableDecimalLogicalType(enableDecimalLogicalType); - if (encoding != null) { - compiler.setOutputCharacterEncoding(encoding); - } + encoding.ifPresent(compiler::setOutputCharacterEncoding); compiler.compileToDestination(src, output); } @@ -144,9 +162,8 @@ private static File[] determineInputs(List inputs, FilenameFilter filter) for (File file : inputs) { // if directory, look at contents to see what files match extension if (file.isDirectory()) { - for (File f : file.listFiles(filter)) { - fileSet.add(f); - } + File[] files = file.listFiles(filter); + Collections.addAll(fileSet, files != null ? files : new File[0]); } // otherwise, just add the file. else { @@ -164,7 +181,7 @@ private static File[] determineInputs(List inputs, FilenameFilter filter) System.err.println("No input files found."); } - return fileSet.toArray((new File[fileSet.size()])); + return fileSet.toArray(new File[0]); } private static final FileExtensionFilter SCHEMA_FILTER = diff --git a/pom.xml b/pom.xml index f497ba424e7..23d06c5bad8 100644 --- a/pom.xml +++ b/pom.xml @@ -250,6 +250,7 @@ lang/ruby/.gem/** lang/ruby/pkg/** lang/ruby/.bundle/** + lang/java/avro/src/test/java/org/apache/avro/specific/TestRecordWithJsr310LogicalTypes.java lang/java/avro/src/test/java/org/apache/avro/specific/TestRecordWithLogicalTypes.java lang/java/avro/src/test/java/org/apache/avro/specific/TestRecordWithoutLogicalTypes.java lang/java/tools/src/test/compiler/output-string/avro/examples/baseball/Player.java