This is an automated email from the ASF dual-hosted git repository.

cutting pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/avro.git


The following commit(s) were added to refs/heads/master by this push:
     new 238714d  AVRO-1340. Java: Add enum defaults, including builder & IDL 
support.
238714d is described below

commit 238714d324d1623f144ca09c0dfb10330a2ba2c4
Author: Adam Bellemare <[email protected]>
AuthorDate: Wed May 2 12:50:54 2018 -0400

    AVRO-1340. Java: Add enum defaults, including builder & IDL support.
    
    * AVRO-1340: Added Enum Defaults and unit tests.
    
    * AVRO-1340: Added support for enum defaults to SchemaBuilder
    
    * AVRO-1340: Added ResolvingVisitor enum default, introduces an attempt at 
the idl.jj for enum default but tests not passing for simple.avdl
    
    * AVRO-1340: Added override annotation to getEnumDefault() implementation
    
    * AVRO-1340: Added Doug Cutting\'s fix to the idl.jj for Avro enum parsing. 
Added test case to simple.avdl and simple.avpr
    
    * AVRO-1340: Updated docs, updated the reserved keywords for enums so that 
the @default annotation no longer works on enum avdl definitions. Updated the 
way that SchemaBuilder allows an enum to be built sith a default by separating 
out the default setter.
    
    * AVRO-1340: Updated the docs and verified that the generated doc is 
readable and well-formatted.
    
    * AVRO-1340: Updated the enum type check to a reasonable, sane check.
    
    * AVRO-1340: Added the missing  semicolon to enum doc
---
 doc/src/content/xdocs/idl.xml                      |  22 +++-
 .../avro/src/main/java/org/apache/avro/Schema.java |  46 ++++++-
 .../main/java/org/apache/avro/SchemaBuilder.java   |  14 +-
 .../java/org/apache/avro/SchemaCompatibility.java  |  16 ++-
 .../avro/io/parsing/ResolvingGrammarGenerator.java |  10 +-
 .../java/org/apache/avro/TestSchemaBuilder.java    |  11 ++
 .../org/apache/avro/TestSchemaCompatibility.java   |  20 ++-
 .../avro/TestSchemaCompatibilityEnumDefaults.java  | 146 +++++++++++++++++++++
 .../src/test/java/org/apache/avro/TestSchemas.java |  19 +++
 .../apache/avro/compiler/idl/ResolvingVisitor.java |   2 +-
 .../javacc/org/apache/avro/compiler/idl/idl.jj     |   7 +-
 lang/java/compiler/src/test/idl/input/simple.avdl  |   9 ++
 lang/java/compiler/src/test/idl/output/simple.avpr |  10 ++
 13 files changed, 308 insertions(+), 24 deletions(-)

diff --git a/doc/src/content/xdocs/idl.xml b/doc/src/content/xdocs/idl.xml
index c9a809f..08c4585 100644
--- a/doc/src/content/xdocs/idl.xml
+++ b/doc/src/content/xdocs/idl.xml
@@ -152,15 +152,29 @@ protocol MyProtocol {
     <section id="format_enums">
       <title>Defining an Enumeration</title>
       <p>
-        Enums are defined in Avro IDL using a syntax similar to C or Java:
+        Enums are defined in Avro IDL using a syntax similar to C or Java. An 
Avro Enum supports optional default values.
+        In the case that a reader schema is unable to recognize a symbol 
written by the writer, the reader will fall back to using the defined default 
value.
+        This default is only used when an incompatible symbol is read. It is 
not used if the enum field is missing.
+      </p>
+      <p>
+        Example Writer Enum Definition
       </p>
       <source>
-enum Suit {
-  SPADES, DIAMONDS, CLUBS, HEARTS
+enum Shapes {
+  SQUARE, TRIANGLE, CIRCLE, OVAL
 }
       </source>
       <p>
-        Note that, unlike the JSON format, anonymous enums cannot be defined.
+        Example Reader Enum Definition
+      </p>
+      <source>
+enum Shapes {
+  SQUARE, TRIANGLE, CIRCLE
+} = CIRCLE;
+      </source>
+      <p>
+        In the above example, the reader will use the default value of CIRCLE 
whenever reading data written with the OVAL symbol of the writer.
+        Also note that, unlike the JSON format, anonymous enums cannot be 
defined.
       </p>
     </section>
     <section id="format_fixed">
diff --git a/lang/java/avro/src/main/java/org/apache/avro/Schema.java 
b/lang/java/avro/src/main/java/org/apache/avro/Schema.java
index 9333b79..9fcbfd3 100644
--- a/lang/java/avro/src/main/java/org/apache/avro/Schema.java
+++ b/lang/java/avro/src/main/java/org/apache/avro/Schema.java
@@ -102,7 +102,7 @@ public abstract class Schema extends JsonProperties {
   private LogicalType logicalType = null;
 
   Schema(Type type) {
-    super(SCHEMA_RESERVED);
+    super(type == Type.ENUM ? ENUM_RESERVED : SCHEMA_RESERVED);
     this.type = type;
   }
 
@@ -127,6 +127,11 @@ public abstract class Schema extends JsonProperties {
                        "doc", "fields", "items", "name", "namespace",
                        "size", "symbols", "values", "type", "aliases");
   }
+  private static final Set<String> ENUM_RESERVED = new HashSet<>();
+  static {
+    ENUM_RESERVED.add("default");
+    ENUM_RESERVED.addAll(SCHEMA_RESERVED);
+  }
 
   int hashCode = NO_HASHCODE;
 
@@ -171,7 +176,14 @@ public abstract class Schema extends JsonProperties {
   public static Schema createEnum(String name, String doc, String namespace,
                                   List<String> values) {
     return new EnumSchema(new Name(name, namespace), doc,
-        new LockableArrayList<>(values));
+      new LockableArrayList<>(values), null);
+  }
+
+  /** Create an enum schema. */
+  public static Schema createEnum(String name, String doc, String namespace,
+                                  List<String> values, String enumDefault) {
+    return new EnumSchema(new Name(name, namespace), doc,
+        new LockableArrayList<>(values), enumDefault);
   }
 
   /** Create an array schema. */
@@ -233,6 +245,11 @@ public abstract class Schema extends JsonProperties {
     throw new AvroRuntimeException("Not an enum: "+this);
   }
 
+  /** If this is an enum, return its default value. */
+  public String getEnumDefault() {
+    throw new AvroRuntimeException("Not an enum: "+this);
+  }
+
   /** If this is an enum, return a symbol's ordinal value. */
   public int getEnumOrdinal(String symbol) {
     throw new AvroRuntimeException("Not an enum: "+this);
@@ -748,15 +765,19 @@ public abstract class Schema extends JsonProperties {
   private static class EnumSchema extends NamedSchema {
     private final List<String> symbols;
     private final Map<String,Integer> ordinals;
+    private final String enumDefault;
     public EnumSchema(Name name, String doc,
-        LockableArrayList<String> symbols) {
+        LockableArrayList<String> symbols, String enumDefault) {
       super(Type.ENUM, name, doc);
       this.symbols = symbols.lock();
       this.ordinals = new HashMap<>();
+      this.enumDefault = enumDefault;
       int i = 0;
       for (String symbol : symbols)
         if (ordinals.put(validateName(symbol), i++) != null)
           throw new SchemaParseException("Duplicate enum symbol: "+symbol);
+      if (enumDefault != null && !symbols.contains(enumDefault))
+        throw new SchemaParseException("The Enum Default: " + enumDefault + " 
is not in the enum symbol set: " + symbols);
     }
     public List<String> getEnumSymbols() { return symbols; }
     public boolean hasEnumSymbol(String symbol) {
@@ -771,6 +792,8 @@ public abstract class Schema extends JsonProperties {
         && symbols.equals(that.symbols)
         && props.equals(that.props);
     }
+    @Override
+    public String getEnumDefault() { return enumDefault; }
     @Override int computeHash() { return super.computeHash() + 
symbols.hashCode(); }
     void toJson(Names names, JsonGenerator gen) throws IOException {
       if (writeNameRef(names, gen)) return;
@@ -783,6 +806,8 @@ public abstract class Schema extends JsonProperties {
       for (String symbol : symbols)
         gen.writeString(symbol);
       gen.writeEndArray();
+      if (getEnumDefault() != null)
+        gen.writeStringField("default", getEnumDefault());
       writeProps(gen);
       aliasesToJson(gen);
       gen.writeEndObject();
@@ -1309,7 +1334,11 @@ public abstract class Schema extends JsonProperties {
         LockableArrayList<String> symbols = new 
LockableArrayList<>(symbolsNode.size());
         for (JsonNode n : symbolsNode)
           symbols.add(n.getTextValue());
-        result = new EnumSchema(name, doc, symbols);
+        JsonNode enumDefault = schema.get("default");
+        String defaultSymbol = null;
+        if (enumDefault != null)
+          defaultSymbol = enumDefault.getTextValue();
+        result = new EnumSchema(name, doc, symbols, defaultSymbol);
         if (name != null) names.add(result);
       } else if (type.equals("array")) {          // array
         JsonNode itemsNode = schema.get("items");
@@ -1330,9 +1359,14 @@ public abstract class Schema extends JsonProperties {
       } else
         throw new SchemaParseException("Type not supported: "+type);
       Iterator<String> i = schema.getFieldNames();
+
+      Set reserved = SCHEMA_RESERVED;
+      if (type.equals("enum")) {
+        reserved = ENUM_RESERVED;
+      }
       while (i.hasNext()) {                       // add properties
         String prop = i.next();
-        if (!SCHEMA_RESERVED.contains(prop))      // ignore reserved
+        if (!reserved.contains(prop))      // ignore reserved
           result.addProp(prop, schema.get(prop));
       }
       // parse logical type if present
@@ -1456,7 +1490,7 @@ public abstract class Schema extends JsonProperties {
     case ENUM:
       if (aliases.containsKey(name))
         result = Schema.createEnum(aliases.get(name).full, s.getDoc(), null,
-                                   s.getEnumSymbols());
+                                   s.getEnumSymbols(), s.getEnumDefault());
       break;
     case ARRAY:
       Schema e = applyAliases(s.getElementType(), seen, aliases, fieldAliases);
diff --git a/lang/java/avro/src/main/java/org/apache/avro/SchemaBuilder.java 
b/lang/java/avro/src/main/java/org/apache/avro/SchemaBuilder.java
index 9c768f2..579ecd0 100644
--- a/lang/java/avro/src/main/java/org/apache/avro/SchemaBuilder.java
+++ b/lang/java/avro/src/main/java/org/apache/avro/SchemaBuilder.java
@@ -767,6 +767,7 @@ public class SchemaBuilder {
     private EnumBuilder(Completion<R> context, NameContext names, String name) 
{
       super(context, names, name);
     }
+    private String enumDefault = null;
 
     private static <R> EnumBuilder<R> create(Completion<R> context,
         NameContext names, String name) {
@@ -778,14 +779,19 @@ public class SchemaBuilder {
       return this;
     }
 
-    /** Configure this enum type's symbols, and end its configuration. **/
+    /** Configure this enum type's symbols, and end its configuration. 
Populates the default if it was set.**/
     public R symbols(String... symbols) {
       Schema schema = Schema.createEnum(name(), doc(), space(),
-          Arrays.asList(symbols));
+          Arrays.asList(symbols), this.enumDefault);
       completeSchema(schema);
       return context().complete(schema);
     }
 
+    /** Set the default value of the enum. */
+    public EnumBuilder<R> defaultSymbol(String enumDefault) {
+      this.enumDefault = enumDefault;
+      return self();
+    }
   }
 
   /**
@@ -1200,14 +1206,14 @@ public class SchemaBuilder {
 
     /** Build an Avro enum type. Example usage:
      * <pre>
-     * enumeration("Suits").namespace("org.cards").doc("card suit names")
+     * enumeration("Suits").namespace("org.cards").doc("card suit 
names").defaultSymbol("HEART")
      *   .symbols("HEART", "SPADE", "DIAMOND", "CLUB")
      * </pre>
      * Equivalent to Avro JSON Schema:
      * <pre>
      * {"type":"enum", "name":"Suits", "namespace":"org.cards",
      *  "doc":"card suit names", "symbols":[
-     *    "HEART", "SPADE", "DIAMOND", "CLUB"]}
+     *    "HEART", "SPADE", "DIAMOND", "CLUB"], "default":"HEART"}
      * </pre>
      **/
     public final EnumBuilder<R> enumeration(String name) {
diff --git 
a/lang/java/avro/src/main/java/org/apache/avro/SchemaCompatibility.java 
b/lang/java/avro/src/main/java/org/apache/avro/SchemaCompatibility.java
index 5ec0737..b1a499e 100644
--- a/lang/java/avro/src/main/java/org/apache/avro/SchemaCompatibility.java
+++ b/lang/java/avro/src/main/java/org/apache/avro/SchemaCompatibility.java
@@ -418,10 +418,15 @@ public class SchemaCompatibility {
           // Reader field does not correspond to any field in the writer 
record schema, so the
           // reader field must have a default value.
           if (readerField.defaultValue() == null) {
-            // reader field has no default value
-            result = result.mergedWith(SchemaCompatibilityResult.incompatible(
+            // reader field has no default value. Check for the enum default 
value
+            if (readerField.schema().getType() == Type.ENUM && 
readerField.schema().getEnumDefault() != null) {
+              result = result.mergedWith(getCompatibility("type", 
readerField.schema(),
+                writerField.schema(), location));
+            } else {
+              result = 
result.mergedWith(SchemaCompatibilityResult.incompatible(
                 SchemaIncompatibilityType.READER_FIELD_MISSING_DEFAULT_VALUE, 
reader, writer,
                 readerField.name(), asList(location)));
+            }
           }
         } else {
           result = result.mergedWith(getCompatibility("type", 
readerField.schema(),
@@ -443,9 +448,14 @@ public class SchemaCompatibility {
       final Set<String> symbols = new TreeSet<>(writer.getEnumSymbols());
       symbols.removeAll(reader.getEnumSymbols());
       if (!symbols.isEmpty()) {
-        result = SchemaCompatibilityResult.incompatible(
+        if(reader.getEnumDefault() != null && 
reader.getEnumSymbols().contains(reader.getEnumDefault())) {
+          symbols.clear();
+          result = SchemaCompatibilityResult.compatible();
+        } else {
+          result = SchemaCompatibilityResult.incompatible(
             SchemaIncompatibilityType.MISSING_ENUM_SYMBOLS, reader, writer,
             symbols.toString(), asList(location));
+        }
       }
       // POP "symbols" literal
       location.removeFirst();
diff --git 
a/lang/java/avro/src/main/java/org/apache/avro/io/parsing/ResolvingGrammarGenerator.java
 
b/lang/java/avro/src/main/java/org/apache/avro/io/parsing/ResolvingGrammarGenerator.java
index 7c5da11..51d9bc9 100644
--- 
a/lang/java/avro/src/main/java/org/apache/avro/io/parsing/ResolvingGrammarGenerator.java
+++ 
b/lang/java/avro/src/main/java/org/apache/avro/io/parsing/ResolvingGrammarGenerator.java
@@ -99,7 +99,7 @@ public class ResolvingGrammarGenerator extends 
ValidatingGrammarGenerator {
         if (writer.getFullName() == null
                 || writer.getFullName().equals(reader.getFullName())) {
           return Symbol.seq(mkEnumAdjust(writer.getEnumSymbols(),
-                  reader.getEnumSymbols()), Symbol.ENUM);
+                  reader.getEnumSymbols(), reader.getEnumDefault()), 
Symbol.ENUM);
         }
         break;
 
@@ -416,11 +416,15 @@ public class ResolvingGrammarGenerator extends 
ValidatingGrammarGenerator {
     }
   }
 
-  private static Symbol mkEnumAdjust(List<String> wsymbols,
-      List<String> rsymbols){
+  private static Symbol mkEnumAdjust(List<String> wsymbols, List<String> 
rsymbols, Object rEnumDefault){
     Object[] adjustments = new Object[wsymbols.size()];
     for (int i = 0; i < adjustments.length; i++) {
       int j = rsymbols.indexOf(wsymbols.get(i));
+      if (j == -1) {
+        if (rEnumDefault instanceof String) {
+          j = rsymbols.indexOf(rEnumDefault);
+        }
+      }
       adjustments[i] = (j == -1 ? "No match for " + wsymbols.get(i)
                                 : new Integer(j));
     }
diff --git 
a/lang/java/avro/src/test/java/org/apache/avro/TestSchemaBuilder.java 
b/lang/java/avro/src/test/java/org/apache/avro/TestSchemaBuilder.java
index e250ffe..af15c38 100644
--- a/lang/java/avro/src/test/java/org/apache/avro/TestSchemaBuilder.java
+++ b/lang/java/avro/src/test/java/org/apache/avro/TestSchemaBuilder.java
@@ -562,6 +562,17 @@ public class TestSchemaBuilder {
   }
 
   @Test
+  public void testEnumWithDefault() {
+    List<String> symbols = Arrays.asList("a", "b");
+    String enumDefault = "a";
+    Schema expected = Schema.createEnum("myenum", null, null, symbols, 
enumDefault);
+    expected.addProp("p", "v");
+    Schema schema = SchemaBuilder.enumeration("myenum")
+      .prop("p", "v").defaultSymbol(enumDefault).symbols("a", "b");
+    Assert.assertEquals(expected, schema);
+  }
+
+  @Test
   public void testFixed() {
     Schema expected = Schema.createFixed("myfixed", null, null, 16);
     expected.addAlias("myOldFixed");
diff --git 
a/lang/java/avro/src/test/java/org/apache/avro/TestSchemaCompatibility.java 
b/lang/java/avro/src/test/java/org/apache/avro/TestSchemaCompatibility.java
index d46bfd3..05726e9 100644
--- a/lang/java/avro/src/test/java/org/apache/avro/TestSchemaCompatibility.java
+++ b/lang/java/avro/src/test/java/org/apache/avro/TestSchemaCompatibility.java
@@ -35,6 +35,12 @@ import static org.apache.avro.TestSchemas.EMPTY_UNION_SCHEMA;
 import static org.apache.avro.TestSchemas.ENUM1_ABC_SCHEMA;
 import static org.apache.avro.TestSchemas.ENUM1_AB_SCHEMA;
 import static org.apache.avro.TestSchemas.ENUM1_BC_SCHEMA;
+import static 
org.apache.avro.TestSchemas.ENUM_AB_FIELD_DEFAULT_A_ENUM_DEFAULT_B_RECORD;
+import static 
org.apache.avro.TestSchemas.ENUM_ABC_FIELD_DEFAULT_B_ENUM_DEFAULT_A_RECORD;
+import static org.apache.avro.TestSchemas.ENUM_AB_ENUM_DEFAULT_A_RECORD;
+import static org.apache.avro.TestSchemas.ENUM_ABC_ENUM_DEFAULT_A_RECORD;
+import static org.apache.avro.TestSchemas.ENUM_AB_ENUM_DEFAULT_A_SCHEMA;
+import static org.apache.avro.TestSchemas.ENUM_ABC_ENUM_DEFAULT_A_SCHEMA;
 import static org.apache.avro.TestSchemas.FIXED_4_BYTES;
 import static org.apache.avro.TestSchemas.FLOAT_SCHEMA;
 import static org.apache.avro.TestSchemas.FLOAT_UNION_SCHEMA;
@@ -74,9 +80,11 @@ import 
org.apache.avro.SchemaCompatibility.SchemaCompatibilityType;
 import org.apache.avro.SchemaCompatibility.SchemaIncompatibilityType;
 import org.apache.avro.SchemaCompatibility.SchemaPairCompatibility;
 import org.apache.avro.TestSchemas.ReaderWriter;
+import org.apache.avro.generic.GenericData;
 import org.apache.avro.generic.GenericData.EnumSymbol;
 import org.apache.avro.generic.GenericDatumReader;
 import org.apache.avro.generic.GenericDatumWriter;
+import org.apache.avro.generic.GenericRecord;
 import org.apache.avro.io.DatumReader;
 import org.apache.avro.io.DatumWriter;
 import org.apache.avro.io.Decoder;
@@ -97,6 +105,9 @@ public class TestSchemaCompatibility {
       new Schema.Field("oldfield1", INT_SCHEMA, null, null),
       new Schema.Field("oldfield2", STRING_SCHEMA, null, null)));
 
+
+
+
   @Test
   public void testValidateSchemaPairMissingField() throws Exception {
     final List<Schema.Field> readerFields = list(
@@ -358,7 +369,10 @@ public class TestSchemaCompatibility {
       new ReaderWriter(LONG_LIST_RECORD, LONG_LIST_RECORD),
       new ReaderWriter(LONG_LIST_RECORD, INT_LIST_RECORD),
 
-      new ReaderWriter(NULL_SCHEMA, NULL_SCHEMA)
+      new ReaderWriter(NULL_SCHEMA, NULL_SCHEMA),
+      new ReaderWriter(ENUM_AB_ENUM_DEFAULT_A_RECORD, 
ENUM_ABC_ENUM_DEFAULT_A_RECORD),
+      new ReaderWriter(ENUM_AB_FIELD_DEFAULT_A_ENUM_DEFAULT_B_RECORD, 
ENUM_ABC_FIELD_DEFAULT_B_ENUM_DEFAULT_A_RECORD)
+
   );
 
   // 
-----------------------------------------------------------------------------------------------
@@ -503,6 +517,10 @@ public class TestSchemaCompatibility {
           ENUM1_BC_SCHEMA, new EnumSymbol(ENUM1_BC_SCHEMA, "B")),
 
       new DecodingTestCase(
+          ENUM_ABC_ENUM_DEFAULT_A_SCHEMA, new 
EnumSymbol(ENUM_ABC_ENUM_DEFAULT_A_SCHEMA, "C"),
+          ENUM_AB_ENUM_DEFAULT_A_SCHEMA, new 
EnumSymbol(ENUM_AB_ENUM_DEFAULT_A_SCHEMA, "A")),
+
+      new DecodingTestCase(
           INT_STRING_UNION_SCHEMA, "the string",
           STRING_SCHEMA, new Utf8("the string")),
 
diff --git 
a/lang/java/avro/src/test/java/org/apache/avro/TestSchemaCompatibilityEnumDefaults.java
 
b/lang/java/avro/src/test/java/org/apache/avro/TestSchemaCompatibilityEnumDefaults.java
new file mode 100644
index 0000000..159733a
--- /dev/null
+++ 
b/lang/java/avro/src/test/java/org/apache/avro/TestSchemaCompatibilityEnumDefaults.java
@@ -0,0 +1,146 @@
+/*
+ * 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;
+
+import org.apache.avro.generic.GenericData;
+import org.apache.avro.generic.GenericDatumReader;
+import org.apache.avro.generic.GenericDatumWriter;
+import org.apache.avro.generic.GenericRecord;
+import org.apache.avro.io.DatumReader;
+import org.apache.avro.io.DatumWriter;
+import org.apache.avro.io.Decoder;
+import org.apache.avro.io.DecoderFactory;
+import org.apache.avro.io.Encoder;
+import org.apache.avro.io.EncoderFactory;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.io.ByteArrayOutputStream;
+
+import static 
org.apache.avro.TestSchemaCompatibility.validateIncompatibleSchemas;
+import static org.apache.avro.TestSchemas.*;
+import static org.junit.Assert.assertEquals;
+
+public class TestSchemaCompatibilityEnumDefaults {
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  @Test
+  public void testEnumDefaultNotAppliedWhenWriterFieldMissing() throws 
Exception {
+    expectedException.expect(AvroTypeException.class);
+    expectedException.expectMessage("Found Record1, expecting Record1, missing 
required field field1");
+
+    Schema writerSchema = SchemaBuilder.record("Record1").fields()
+      .name("field2").type(ENUM2_AB_SCHEMA).noDefault()
+      .endRecord();
+
+    Schema readerSchema = SchemaBuilder.record("Record1").fields()
+      .name("field1").type(ENUM_AB_ENUM_DEFAULT_A_SCHEMA).noDefault()
+      .endRecord();
+
+    GenericRecord datum = new GenericData.Record(writerSchema);
+    datum.put("field2", new GenericData.EnumSymbol(writerSchema, "B"));
+    serializeWithWriterThenDeserializeWithReader(writerSchema, datum, 
readerSchema);
+  }
+
+  @Test
+  public void testEnumDefaultAppliedWhenNoFieldDefaultDefined() throws 
Exception {
+    Schema writerSchema = SchemaBuilder.record("Record1").fields()
+      .name("field1").type(ENUM_ABC_ENUM_DEFAULT_A_SCHEMA).noDefault()
+      .endRecord();
+
+    Schema readerSchema = SchemaBuilder.record("Record1").fields()
+      .name("field1").type(ENUM_AB_ENUM_DEFAULT_A_SCHEMA).noDefault()
+      .endRecord();
+
+    GenericRecord datum = new GenericData.Record(writerSchema);
+    datum.put("field1", new GenericData.EnumSymbol(writerSchema, "C"));
+    GenericRecord decodedDatum = 
serializeWithWriterThenDeserializeWithReader(writerSchema, datum, readerSchema);
+    //The A is the Enum fallback value.
+    assertEquals("A", decodedDatum.get("field1").toString());
+  }
+
+  @Test
+  public void testEnumDefaultNotAppliedWhenCompatibleSymbolIsFound() throws 
Exception {
+    Schema writerSchema = SchemaBuilder.record("Record1").fields()
+      .name("field1").type(ENUM_ABC_ENUM_DEFAULT_A_SCHEMA).noDefault()
+      .endRecord();
+
+    Schema readerSchema = SchemaBuilder.record("Record1").fields()
+      .name("field1").type(ENUM_AB_ENUM_DEFAULT_A_SCHEMA).noDefault()
+      .endRecord();
+
+    GenericRecord datum = new GenericData.Record(writerSchema);
+    datum.put("field1", new GenericData.EnumSymbol(writerSchema, "B"));
+    GenericRecord decodedDatum = 
serializeWithWriterThenDeserializeWithReader(writerSchema, datum, readerSchema);
+    assertEquals("B", decodedDatum.get("field1").toString());
+  }
+
+  @Test
+  public void testEnumDefaultAppliedWhenFieldDefaultDefined() throws Exception 
{
+    Schema writerSchema = SchemaBuilder.record("Record1").fields()
+      .name("field1").type(ENUM_ABC_ENUM_DEFAULT_A_SCHEMA).noDefault()
+      .endRecord();
+
+    Schema readerSchema = SchemaBuilder.record("Record1").fields()
+      .name("field1").type(ENUM_AB_ENUM_DEFAULT_A_SCHEMA).withDefault("B")
+      .endRecord();
+
+    GenericRecord datum = new GenericData.Record(writerSchema);
+    datum.put("field1", new GenericData.EnumSymbol(writerSchema, "C"));
+    GenericRecord decodedDatum = 
serializeWithWriterThenDeserializeWithReader(writerSchema, datum, readerSchema);
+    //The A is the Enum default, which is assigned since C is not in [A,B].
+    assertEquals("A", decodedDatum.get("field1").toString());
+  }
+
+  @Test
+  public void testFieldDefaultNotAppliedForUnknownSymbol() throws Exception {
+    expectedException.expect(AvroTypeException.class);
+    expectedException.expectMessage("No match for C");
+
+    Schema writerSchema = SchemaBuilder.record("Record1").fields()
+      .name("field1").type(ENUM1_ABC_SCHEMA).noDefault()
+      .endRecord();
+    Schema readerSchema = SchemaBuilder.record("Record1").fields()
+      .name("field1").type(ENUM1_AB_SCHEMA).withDefault("A")
+      .endRecord();
+
+    GenericRecord datum = new GenericData.Record(writerSchema);
+    datum.put("field1", new GenericData.EnumSymbol(writerSchema, "C"));
+    serializeWithWriterThenDeserializeWithReader(writerSchema, datum, 
readerSchema);
+  }
+
+  private GenericRecord serializeWithWriterThenDeserializeWithReader(Schema 
writerSchema, GenericRecord datum, Schema readerSchema) throws Exception {
+    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+    Encoder encoder = EncoderFactory.get().binaryEncoder(baos, null);
+    DatumWriter<Object> datumWriter = new GenericDatumWriter<>(writerSchema);
+    datumWriter.write(datum, encoder);
+    encoder.flush();
+
+    byte[] bytes = baos.toByteArray();
+    Decoder decoder = DecoderFactory.get().resolvingDecoder(
+      writerSchema, readerSchema,
+      DecoderFactory.get().binaryDecoder(bytes, null));
+    DatumReader<Object> datumReader = new GenericDatumReader<>(readerSchema);
+    return (GenericRecord)datumReader.read(null, decoder);
+  }
+
+}
diff --git a/lang/java/avro/src/test/java/org/apache/avro/TestSchemas.java 
b/lang/java/avro/src/test/java/org/apache/avro/TestSchemas.java
index 39dd1a2..466f1fd 100644
--- a/lang/java/avro/src/test/java/org/apache/avro/TestSchemas.java
+++ b/lang/java/avro/src/test/java/org/apache/avro/TestSchemas.java
@@ -19,7 +19,10 @@ package org.apache.avro;
 
 import static org.junit.Assert.assertTrue;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
+import java.util.List;
+
 import org.apache.avro.Schema.Field;
 
 /** Schemas used by other tests in this package. Therefore package protected. 
*/
@@ -47,6 +50,22 @@ public class TestSchemas {
       list("A", "B", "C"));
   static final Schema ENUM1_BC_SCHEMA = Schema.createEnum("Enum1", null, null, 
list("B", "C"));
   static final Schema ENUM2_AB_SCHEMA = Schema.createEnum("Enum2", null, null, 
list("A", "B"));
+  static final Schema ENUM_ABC_ENUM_DEFAULT_A_SCHEMA = 
Schema.createEnum("Enum", null, null, list("A", "B", "C"), "A");
+  static final Schema ENUM_AB_ENUM_DEFAULT_A_SCHEMA = 
Schema.createEnum("Enum", null, null, list("A", "B"), "A");
+  static final Schema ENUM_ABC_ENUM_DEFAULT_A_RECORD = 
Schema.createRecord("Record", null, null, false);
+  static final Schema ENUM_AB_ENUM_DEFAULT_A_RECORD = 
Schema.createRecord("Record", null, null, false);
+  static final Schema ENUM_ABC_FIELD_DEFAULT_B_ENUM_DEFAULT_A_RECORD = 
Schema.createRecord("Record", null, null, false);
+  static final Schema ENUM_AB_FIELD_DEFAULT_A_ENUM_DEFAULT_B_RECORD = 
Schema.createRecord("Record", null, null, false);
+  static {
+    ENUM_ABC_ENUM_DEFAULT_A_RECORD.setFields(
+      list(new Schema.Field("Field", Schema.createEnum("Schema", null, null, 
list("A","B","C"), "A"), null, null)));
+    ENUM_AB_ENUM_DEFAULT_A_RECORD.setFields(
+      list(new Schema.Field("Field", Schema.createEnum("Schema", null, null, 
list("A","B"), "A"), null, null)));
+    ENUM_ABC_FIELD_DEFAULT_B_ENUM_DEFAULT_A_RECORD.setFields(
+      list(new Schema.Field("Field", Schema.createEnum("Schema", null, null, 
list("A","B","C"), "A"), null, "B")));
+    ENUM_AB_FIELD_DEFAULT_A_ENUM_DEFAULT_B_RECORD.setFields(
+      list(new Schema.Field("Field", Schema.createEnum("Schema", null, null, 
list("A","B"), "B"), null, "A")));
+  }
 
   static final Schema EMPTY_UNION_SCHEMA = Schema.createUnion(new 
ArrayList<>());
   static final Schema NULL_UNION_SCHEMA = 
Schema.createUnion(list(NULL_SCHEMA));
diff --git 
a/lang/java/compiler/src/main/java/org/apache/avro/compiler/idl/ResolvingVisitor.java
 
b/lang/java/compiler/src/main/java/org/apache/avro/compiler/idl/ResolvingVisitor.java
index 88b1d6f..9ccd7e7 100644
--- 
a/lang/java/compiler/src/main/java/org/apache/avro/compiler/idl/ResolvingVisitor.java
+++ 
b/lang/java/compiler/src/main/java/org/apache/avro/compiler/idl/ResolvingVisitor.java
@@ -73,7 +73,7 @@ public final class ResolvingVisitor implements 
SchemaVisitor<Schema> {
       break;
       case ENUM:
         newSchema = Schema.createEnum(terminal.getName(), terminal.getDoc(),
-                terminal.getNamespace(), terminal.getEnumSymbols());
+                terminal.getNamespace(), terminal.getEnumSymbols(), 
terminal.getEnumDefault());
         break;
       case FIXED:
         newSchema = Schema.createFixed(terminal.getName(), terminal.getDoc(),
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 c0ec7f5..cae241f 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
@@ -1099,13 +1099,16 @@ Schema EnumDeclaration():
 {
   String name;
   List<String> symbols;
+  String defaultSymbol = null;
 }
 {
-  "enum"
+  "enum" {   String doc = getDoc(); }
   name = Identifier()
   symbols = EnumBody()
+      [ <EQUALS> defaultSymbol=Identifier() <SEMICOLON>]
   {
-    Schema s = Schema.createEnum(name, getDoc(), this.namespace, symbols);
+    Schema s = Schema.createEnum(name, doc, this.namespace, symbols,
+                                 defaultSymbol);
     names.put(s.getFullName(), s);
     return s;
   }
diff --git a/lang/java/compiler/src/test/idl/input/simple.avdl 
b/lang/java/compiler/src/test/idl/input/simple.avdl
index 0ad7c7e..29b013c 100644
--- a/lang/java/compiler/src/test/idl/input/simple.avdl
+++ b/lang/java/compiler/src/test/idl/input/simple.avdl
@@ -30,6 +30,12 @@ protocol Simple {
     BAZ
   }
 
+  enum Status {
+    A,
+    B,
+    C
+  } = C;
+
   /** An MD5 hash. */
   fixed MD5(16);
 
@@ -41,6 +47,9 @@ protocol Simple {
     /** The kind of record. */
     Kind @order("descending") kind;
 
+    /** The status of the record. */
+    Status status = "A";
+
     @foo("bar") MD5 hash = "0000000000000000";
 
     union {null, MD5} @aliases(["hash", "hsh"]) nullableHash = null;
diff --git a/lang/java/compiler/src/test/idl/output/simple.avpr 
b/lang/java/compiler/src/test/idl/output/simple.avpr
index ae155f5..ddcb626 100644
--- a/lang/java/compiler/src/test/idl/output/simple.avpr
+++ b/lang/java/compiler/src/test/idl/output/simple.avpr
@@ -10,6 +10,11 @@
     "symbols" : [ "FOO", "BAR", "BAZ" ],
     "aliases" : [ "org.foo.KindOf" ]
   }, {
+   "type" : "enum",
+   "name" : "Status",
+   "symbols" : [ "A", "B", "C" ],
+   "default" : "C"
+  }, {
     "type" : "fixed",
     "name" : "MD5",
     "doc" : "An MD5 hash.",
@@ -30,6 +35,11 @@
       "doc" : "The kind of record.",
       "order" : "descending"
     }, {
+      "name" : "status",
+      "type" : "Status",
+      "doc" : "The status of the record.",
+      "default" : "A"
+    }, {
       "name" : "hash",
       "type" : "MD5",
       "default" : "0000000000000000"

-- 
To stop receiving notification emails like this one, please contact
[email protected].

Reply via email to