/**
 * 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 java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.avro.Schema.Field.Order;
import org.apache.avro.file.DataFileReader;
import org.apache.avro.file.DataFileWriter;
import org.apache.avro.generic.GenericData;
import org.apache.avro.generic.GenericDatumReader;
import org.apache.avro.generic.GenericDatumWriter;
import org.apache.avro.generic.GenericRecordBuilder;
import org.codehaus.jackson.node.NullNode;
import org.junit.Assert;
import org.junit.Test;

public class TestSchemaBuilder {

  private static final File DIR = new File(System.getProperty("test.dir", "/tmp"));
  private static final File FILE = new File(DIR, "test.avro");

  @Test
  public void testRecord() {
    Schema schema = SchemaBuilder
        .record("myrecord").namespace("org.example").aliases("oldrecord").fields()
        .name("f0").aliases("f0alias").type().stringType().noDefault()
        .name("f1").doc("This is f1").type().longType().noDefault()
        .name("f2").type().nullable().booleanType().booleanDefault(true)
        .endRecord();

    Assert.assertEquals("myrecord", schema.getName());
    Assert.assertEquals("org.example", schema.getNamespace());
    Assert.assertEquals("org.example.oldrecord", schema.getAliases().iterator().next());
    Assert.assertFalse(schema.isError());
    List<Schema.Field> fields = schema.getFields();
    Assert.assertEquals(3, fields.size());
    Assert.assertEquals(
        new Schema.Field("f0", Schema.create(Schema.Type.STRING), null, null),
        fields.get(0));
    Assert.assertTrue(fields.get(0).aliases().contains("f0alias"));
    Assert.assertEquals(
        new Schema.Field("f1", Schema.create(Schema.Type.LONG), "This is f1", null),
        fields.get(1));

    List<Schema> types = new ArrayList<Schema>();
    types.add(Schema.create(Schema.Type.BOOLEAN));
    types.add(Schema.create(Schema.Type.NULL));
    Schema optional = Schema.createUnion(types);
    Assert.assertEquals(new Schema.Field("f2", optional, null, true),
        fields.get(2));
  }

  @Test
  public void testDoc() {
    Schema s = SchemaBuilder.fixed("myfixed").doc("mydoc").size(1);
    Assert.assertEquals("mydoc", s.getDoc());
  }

  @Test
  public void testProps() {
    Schema s = SchemaBuilder.builder().intBuilder()
      .prop("p1", "v1")
      .prop("p2", "v2")
      .prop("p2", "v2real") // overwrite
      .endInt();
    @SuppressWarnings("deprecation")
    int size = s.getProps().size();
    Assert.assertEquals(2, size);
    Assert.assertEquals("v1", s.getProp("p1"));
    Assert.assertEquals("v2real", s.getProp("p2"));
  }

  @Test
  public void testNamespaces() {
    Schema s1 = SchemaBuilder.record("myrecord")
      .namespace("org.example")
      .fields()
        .name("myint").type().intType().noDefault()
        .endRecord();
    Schema s2 = SchemaBuilder.record("org.example.myrecord")
      .fields()
        .name("myint").type().intType().noDefault()
        .endRecord();
    Schema s3 = SchemaBuilder.record("org.example.myrecord")
      .namespace("org.example2")
      .fields()
        .name("myint").type().intType().noDefault()
        .endRecord();
    Schema s4 = SchemaBuilder.builder("org.example").record("myrecord")
      .fields()
        .name("myint").type().intType().noDefault()
        .endRecord();

    Assert.assertEquals("myrecord", s1.getName());
    Assert.assertEquals("myrecord", s2.getName());
    Assert.assertEquals("myrecord", s3.getName());
    Assert.assertEquals("myrecord", s4.getName());

    Assert.assertEquals("org.example", s1.getNamespace());
    Assert.assertEquals("org.example", s2.getNamespace());
    Assert.assertEquals("org.example", s3.getNamespace()); // namespace call is ignored
    Assert.assertEquals("org.example", s4.getNamespace());

    Assert.assertEquals("org.example.myrecord", s1.getFullName());
    Assert.assertEquals("org.example.myrecord", s2.getFullName());
    Assert.assertEquals("org.example.myrecord", s3.getFullName());
    Assert.assertEquals("org.example.myrecord", s4.getFullName());
  }

  @Test(expected = NullPointerException.class)
  public void testMissingRecordName() {
    SchemaBuilder
      .record(null).fields() // null name
        .name("f0").type().stringType().noDefault()
        .endRecord();
  }

  @Test
  public void testBoolean() {
    Schema.Type type = Schema.Type.BOOLEAN;
    Schema simple = SchemaBuilder.builder().booleanType();
    Schema expected = primitive(type, simple);
    Schema built1 = SchemaBuilder.builder()
        .booleanBuilder().prop("p", "v").endBoolean();
    Assert.assertEquals(expected, built1);
  }

  @Test
  public void testInt() {
    Schema.Type type = Schema.Type.INT;
    Schema simple = SchemaBuilder.builder().intType();
    Schema expected = primitive(type, simple);
    Schema built1 = SchemaBuilder.builder()
        .intBuilder().prop("p", "v").endInt();
    Assert.assertEquals(expected, built1);
  }

  @Test
  public void testLong() {
    Schema.Type type = Schema.Type.LONG;
    Schema simple = SchemaBuilder.builder().longType();
    Schema expected = primitive(type, simple);
    Schema built1 = SchemaBuilder.builder()
        .longBuilder().prop("p", "v").endLong();
    Assert.assertEquals(expected, built1);
  }

  @Test
  public void testFloat() {
    Schema.Type type = Schema.Type.FLOAT;
    Schema simple = SchemaBuilder.builder().floatType();
    Schema expected = primitive(type, simple);
    Schema built1 = SchemaBuilder.builder()
        .floatBuilder().prop("p", "v").endFloat();
    Assert.assertEquals(expected, built1);
  }

  @Test
  public void testDuble() {
    Schema.Type type = Schema.Type.DOUBLE;
    Schema simple = SchemaBuilder.builder().doubleType();
    Schema expected = primitive(type, simple);
    Schema built1 = SchemaBuilder.builder()
        .doubleBuilder().prop("p", "v").endDouble();
    Assert.assertEquals(expected, built1);
  }

  @Test
  public void testString() {
    Schema.Type type = Schema.Type.STRING;
    Schema simple = SchemaBuilder.builder().stringType();
    Schema expected = primitive(type, simple);
    Schema built1 = SchemaBuilder.builder()
        .stringBuilder().prop("p", "v").endString();
    Assert.assertEquals(expected, built1);
  }

  @Test
  public void testBytes() {
    Schema.Type type = Schema.Type.BYTES;
    Schema simple = SchemaBuilder.builder().bytesType();
    Schema expected = primitive(type, simple);
    Schema built1 = SchemaBuilder.builder()
        .bytesBuilder().prop("p", "v").endBytes();
    Assert.assertEquals(expected, built1);
  }

  @Test
  public void testNull() {
    Schema.Type type = Schema.Type.NULL;
    Schema simple = SchemaBuilder.builder().nullType();
    Schema expected = primitive(type, simple);
    Schema built1 = SchemaBuilder.builder()
        .nullBuilder().prop("p", "v").endNull();
    Assert.assertEquals(expected, built1);
  }


  private Schema primitive(Schema.Type type, Schema bare) {
    // test creation of bare schema by name
    Schema bareByName = SchemaBuilder.builder().type(type.getName());
    Assert.assertEquals(Schema.create(type), bareByName);
    Assert.assertEquals(bareByName, bare);
    // return a schema with custom prop set
    Schema p = Schema.create(type);
    p.addProp("p", "v");
    return p;
  }


//  @Test
//  public void testError() {
//    Schema schema = SchemaBuilder
//        .errorType("myerror")
//        .requiredString("message")
//        .build();
//
//    Assert.assertEquals("myerror", schema.getName());
//    Assert.assertTrue(schema.isError());
//  }

  @Test
  public void testRecursiveRecord() {
    Schema schema = SchemaBuilder.record("LongList").fields()
      .name("value").type().longType().noDefault()
      .name("next").type().optional().type("LongList")
      .endRecord();

    Assert.assertEquals("LongList", schema.getName());
    List<Schema.Field> fields = schema.getFields();
    Assert.assertEquals(2, fields.size());
    Assert.assertEquals(
        new Schema.Field("value", Schema.create(Schema.Type.LONG), null, null),
        fields.get(0));

    Assert.assertEquals(
        Schema.Type.UNION,
        fields.get(1).schema().getType());

    Assert.assertEquals(
        Schema.Type.NULL,
        fields.get(1).schema().getTypes().get(0).getType());
    Schema recordSchema = fields.get(1).schema().getTypes().get(1);
    Assert.assertEquals(Schema.Type.RECORD, recordSchema.getType());
    Assert.assertEquals("LongList", recordSchema.getName());
    Assert.assertEquals(NullNode.getInstance(), fields.get(1).defaultValue());
  }

  @Test
  public void testEnum() {
    List<String> symbols = Arrays.asList("a", "b");
    Schema expected = Schema.createEnum("myenum", null, null, symbols);
    expected.addProp("p", "v");
    Schema schema = SchemaBuilder.enumeration("myenum")
        .prop("p", "v").symbols("a", "b");
    Assert.assertEquals(expected, schema);
  }

  @Test
  public void testFixed() {
    Schema expected = Schema.createFixed("myfixed", null, null, 16);
    expected.addAlias("myOldFixed");
    Schema schema = SchemaBuilder.fixed("myfixed")
        .aliases("myOldFixed").size(16);
    Assert.assertEquals(expected, schema);
  }

  @Test
  public void testArray() {
    Schema longSchema = Schema.create(Schema.Type.LONG);
    Schema expected = Schema.createArray(longSchema);

    Schema schema1 = SchemaBuilder.array().items().longType();
    Assert.assertEquals(expected, schema1);

    Schema schema2 = SchemaBuilder.array().items(longSchema);
    Assert.assertEquals(expected, schema2);

    Schema schema3 = SchemaBuilder.array().prop("p", "v")
        .items().type("long");
    expected.addProp("p", "v");
    Assert.assertEquals(expected, schema3);
}

  @Test
  public void testMap() {
    Schema intSchema = Schema.create(Schema.Type.INT);
    Schema expected = Schema.createMap(intSchema);

    Schema schema1 = SchemaBuilder.map().values().intType();
    Assert.assertEquals(expected, schema1);

    Schema schema2 = SchemaBuilder.map().values(intSchema);
    Assert.assertEquals(expected, schema2);

    Schema schema3 = SchemaBuilder.map().prop("p", "v")
        .values().type("int");
    expected.addProp("p", "v");
    Assert.assertEquals(expected, schema3);
  }

  @Test
  public void testUnionAndNullable() {
    List<Schema> types = new ArrayList<Schema>();
    types.add(Schema.create(Schema.Type.LONG));
    types.add(Schema.create(Schema.Type.NULL));
    Schema expected = Schema.createUnion(types);

    Schema schema = SchemaBuilder.unionOf()
        .longType().and()
        .nullType().endUnion();
    Assert.assertEquals(expected, schema);

    schema = SchemaBuilder.nullable().longType();
    Assert.assertEquals(expected, schema);
  }

  @Test
  public void testFields() {
    Schema rec = SchemaBuilder.record("Rec").fields()
      .name("documented").doc("documented").type().nullType().noDefault()
      .name("ascending").orderAscending().type().booleanType().noDefault()
      .name("descending").orderDescending().type().floatType().noDefault()
      .name("ignored").orderIgnore().type().doubleType().noDefault()
      .name("aliased").aliases("anAlias").type().stringType().noDefault()
      .endRecord();
    Assert.assertEquals("documented", rec.getField("documented").doc());
    Assert.assertEquals(Order.ASCENDING, rec.getField("ascending").order());
    Assert.assertEquals(Order.DESCENDING, rec.getField("descending").order());
    Assert.assertEquals(Order.IGNORE, rec.getField("ignored").order());
    Assert.assertTrue(rec.getField("aliased").aliases().contains("anAlias"));
  }

  @Test
  public void testFieldShortcuts() {
    Schema full = SchemaBuilder.record("Blah").fields()
        .name("rbool").type().booleanType().noDefault()
        .name("obool").type().optional().booleanType()
        .name("nbool").type().nullable().booleanType().booleanDefault(true)
        .name("rint").type().intType().noDefault()
        .name("oint").type().optional().intType()
        .name("nint").type().nullable().intType().intDefault(1)
        .name("rlong").type().longType().noDefault()
        .name("olong").type().optional().longType()
        .name("nlong").type().nullable().longType().longDefault(2L)
        .name("rfloat").type().floatType().noDefault()
        .name("ofloat").type().optional().floatType()
        .name("nfloat").type().nullable().floatType().floatDefault(-1.1f)
        .name("rdouble").type().doubleType().noDefault()
        .name("odouble").type().optional().doubleType()
        .name("ndouble").type().nullable().doubleType().doubleDefault(99.9d)
        .name("rstring").type().stringType().noDefault()
        .name("ostring").type().optional().stringType()
        .name("nstring").type().nullable().stringType().stringDefault("def")
        .name("rbytes").type().bytesType().noDefault()
        .name("obytes").type().optional().bytesType()
        .name("nbytes").type().nullable().bytesType().bytesDefault(new byte[] {1,2,3})
        .endRecord();

    Schema shortcut = SchemaBuilder.record("Blah").fields()
        .requiredBoolean("rbool")
        .optionalBoolean("obool")
        .nullableBoolean("nbool", true)
        .requiredInt("rint")
        .optionalInt("oint")
        .nullableInt("nint", 1)
        .requiredLong("rlong")
        .optionalLong("olong")
        .nullableLong("nlong", 2L)
        .requiredFloat("rfloat")
        .optionalFloat("ofloat")
        .nullableFloat("nfloat", -1.1f)
        .requiredDouble("rdouble")
        .optionalDouble("odouble")
        .nullableDouble("ndouble", 99.9d)
        .requiredString("rstring")
        .optionalString("ostring")
        .nullableString("nstring", "def")
        .requiredBytes("rbytes")
        .optionalBytes("obytes")
        .nullableBytes("nbytes", new byte[] {1,2,3})
        .endRecord();

    Assert.assertEquals(full, shortcut);
  }

  @Test
  public void testNames() {
    // no contextual namespace
    Schema r = SchemaBuilder.record("Rec").fields()
      .name("f0").type().fixed("org.foo.MyFixed").size(1).noDefault()
      .name("f1").type("org.foo.MyFixed").noDefault()
      .name("f2").type("org.foo.MyFixed", "").noDefault()
      .name("f3").type("org.foo.MyFixed", null).noDefault()
      .name("f4").type("org.foo.MyFixed", "ignorethis").noDefault()
      .name("f5").type("MyFixed", "org.foo").noDefault()
      .endRecord();
    Schema expected = Schema.createFixed("org.foo.MyFixed", null, null, 1);
    checkField(r, expected, "f0");
    checkField(r, expected, "f1");
    checkField(r, expected, "f2");
    checkField(r, expected, "f3");
    checkField(r, expected, "f4");
    checkField(r, expected, "f5");

    // context namespace
    Schema f = SchemaBuilder.builder("").fixed("Foo").size(1);
    Assert.assertEquals(Schema.createFixed("Foo", null, null, 1), f);

    // context namespace from record matches
    r = SchemaBuilder.record("Rec").namespace("org.foo").fields()
        .name("f0").type().fixed("MyFixed").size(1).noDefault()
        .name("f1").type("org.foo.MyFixed").noDefault()
        .name("f2").type("org.foo.MyFixed", "").noDefault()
        .name("f3").type("org.foo.MyFixed", null).noDefault()
        .name("f4").type("org.foo.MyFixed", "ignorethis").noDefault()
        .name("f5").type("MyFixed", "org.foo").noDefault()
        .name("f6").type("MyFixed", null).noDefault()
        .name("f7").type("MyFixed").noDefault()
        .endRecord();
    checkField(r, expected, "f0");
    checkField(r, expected, "f1");
    checkField(r, expected, "f2");
    checkField(r, expected, "f3");
    checkField(r, expected, "f4");
    checkField(r, expected, "f5");
    checkField(r, expected, "f6");
    checkField(r, expected, "f7");

    // context namespace from record does not match
    r = SchemaBuilder.record("Rec").namespace("org.rec").fields()
        .name("f0").type().fixed("MyFixed").namespace("org.foo").size(1).noDefault()
        .name("f1").type("org.foo.MyFixed").noDefault()
        .name("f2").type("org.foo.MyFixed", "").noDefault()
        .name("f3").type("org.foo.MyFixed", null).noDefault()
        .name("f4").type("org.foo.MyFixed", "ignorethis").noDefault()
        .name("f5").type("MyFixed", "org.foo").noDefault()
        .endRecord();
    checkField(r, expected, "f0");
    checkField(r, expected, "f1");
    checkField(r, expected, "f2");
    checkField(r, expected, "f3");
    checkField(r, expected, "f4");
    checkField(r, expected, "f5");

    // context namespace from record, nested has no namespace
    expected = Schema.createFixed("MyFixed", null, null, 1);
    r = SchemaBuilder.record("Rec").namespace("org.rec").fields()
        .name("f0").type().fixed("MyFixed").namespace("").size(1).noDefault()
        .name("f1").type("MyFixed", "").noDefault()
        .endRecord();
    checkField(r, expected, "f0");
    checkField(r, expected, "f1");

    // mimic names of primitives, but with a namesapce.  This is OK
    SchemaBuilder.fixed("org.test.long").size(1);
    SchemaBuilder.fixed("long").namespace("org.test").size(1);
    SchemaBuilder.builder("org.test").fixed("long").size(1);

  }

  private void checkField(Schema r, Schema expected, String name) {
    Assert.assertEquals(expected, r.getField(name).schema());
  }

  @Test(expected=SchemaParseException.class)
  public void testNamesFailRedefined() {
    SchemaBuilder.record("Rec").fields()
      .name("f0").type().enumeration("MyEnum").symbols("A", "B").enumDefault("A")
      .name("f1").type().enumeration("MyEnum").symbols("X", "Y").noDefault()
      .endRecord();
  }

  @Test(expected=SchemaParseException.class)
  public void testNamesFailAbsent() {
    SchemaBuilder.builder().type("notdefined");
  }

  @Test(expected=AvroTypeException.class)
  public void testNameReserved() {
    SchemaBuilder.fixed("long").namespace("").size(1);
  }

  @Test
  public void testFieldTypesAndDefaultValues() {
    byte[] bytedef = new byte[]{3};
    ByteBuffer bufdef = ByteBuffer.wrap(bytedef);
    String strdef = "\u0003";
    HashMap<String, String> mapdef = new HashMap<String, String>();
    mapdef.put("a", "A");
    ArrayList<String> arrdef = new ArrayList<String>();
    arrdef.add("arr");

    Schema rec = SchemaBuilder.record("inner").fields()
      .name("f").type().intType().noDefault()
      .endRecord();

    Schema rec2 = SchemaBuilder.record("inner2").fields()
      .name("f2").type().intType().noDefault()
      .endRecord();

    GenericData.Record recdef =
        new GenericRecordBuilder(rec).set("f", 1).build();

    GenericData.Record recdef2 =
        new GenericRecordBuilder(rec2).set("f2", 2).build();

    Schema r = SchemaBuilder.record("r").fields()
      .name("boolF").type().booleanType().booleanDefault(false)
      .name("intF").type().intType().intDefault(1)
      .name("longF").type().longType().longDefault(2L)
      .name("floatF").type().floatType().floatDefault(3.0f)
      .name("doubleF").type().doubleType().doubleDefault(4.0d)
      .name("stringF").type().stringType().stringDefault("def")
      .name("bytesF1").type().bytesType().bytesDefault(bytedef)
      .name("bytesF2").type().bytesType().bytesDefault(bufdef)
      .name("bytesF3").type().bytesType().bytesDefault(strdef)
      .name("nullF").type().nullType().nullDefault()
      .name("fixedF1").type().fixed("F1").size(1).fixedDefault(bytedef)
      .name("fixedF2").type().fixed("F2").size(1).fixedDefault(bufdef)
      .name("fixedF3").type().fixed("F3").size(1).fixedDefault(strdef)
      .name("enumF").type().enumeration("E1").symbols("S").enumDefault("S")
      .name("mapF").type().map().values().stringType()
        .mapDefault(mapdef)
      .name("arrayF").type().array().items().stringType()
        .arrayDefault(arrdef)
      .name("recordF").type().record("inner").fields()
        .name("f").type().intType().noDefault()
        .endRecord().recordDefault(recdef)
      .name("byName").type("E1").withDefault("S")
      // union builders, one for each 'first type' in a union:
      .name("boolU").type().unionOf().booleanType().and()
        .intType().endUnion().booleanDefault(false)
      .name("intU").type().unionOf().intType().and()
        .longType().endUnion().intDefault(1)
      .name("longU").type().unionOf().longType().and()
        .intType().endUnion().longDefault(2L)
      .name("floatU").type().unionOf().floatType().and()
        .intType().endUnion().floatDefault(3.0f)
      .name("doubleU").type().unionOf().doubleType().and()
        .intType().endUnion().doubleDefault(4.0d)
      .name("stringU").type().unionOf().stringType().and()
        .intType().endUnion().stringDefault("def")
      .name("bytesU").type().unionOf().bytesType().and()
        .intType().endUnion().bytesDefault(bytedef)
      .name("nullU").type().unionOf().nullType().and()
        .intType().endUnion().nullDefault()
      .name("fixedU").type().unionOf().fixed("F4").size(1).and()
        .intType().endUnion().fixedDefault(bytedef)
      .name("enumU").type().unionOf().enumeration("E2").symbols("SS").and()
        .intType().endUnion().enumDefault("SS")
      .name("mapU").type().unionOf().map().values().stringType().and()
        .intType().endUnion().mapDefault(mapdef)
      .name("arrayU").type().unionOf().array().items().stringType().and()
        .intType().endUnion().arrayDefault(arrdef)
      .name("recordU").type().unionOf().record("inner2").fields()
        .name("f2").type().intType().noDefault()
        .endRecord().and().intType().endUnion().recordDefault(recdef2)
      .endRecord();

    GenericData.Record newRec =
        new GenericRecordBuilder(r).build();

    Assert.assertEquals(false, newRec.get("boolF"));
    Assert.assertEquals(false, newRec.get("boolU"));
    Assert.assertEquals(1, newRec.get("intF"));
    Assert.assertEquals(1, newRec.get("intU"));
    Assert.assertEquals(2L, newRec.get("longF"));
    Assert.assertEquals(2L, newRec.get("longU"));
    Assert.assertEquals(3f, newRec.get("floatF"));
    Assert.assertEquals(3f, newRec.get("floatU"));
    Assert.assertEquals(4d, newRec.get("doubleF"));
    Assert.assertEquals(4d, newRec.get("doubleU"));
    Assert.assertEquals("def", newRec.get("stringF").toString());
    Assert.assertEquals("def", newRec.get("stringU").toString());
    Assert.assertEquals(bufdef, newRec.get("bytesF1"));
    Assert.assertEquals(bufdef, newRec.get("bytesF2"));
    Assert.assertEquals(bufdef, newRec.get("bytesF3"));
    Assert.assertEquals(bufdef, newRec.get("bytesU"));
    Assert.assertNull(newRec.get("nullF"));
    Assert.assertNull(newRec.get("nullU"));
    Assert.assertArrayEquals(bytedef,
        ((GenericData.Fixed)newRec.get("fixedF1")).bytes());
    Assert.assertArrayEquals(bytedef,
        ((GenericData.Fixed)newRec.get("fixedF2")).bytes());
    Assert.assertArrayEquals(bytedef,
        ((GenericData.Fixed)newRec.get("fixedF3")).bytes());
    Assert.assertArrayEquals(bytedef,
        ((GenericData.Fixed)newRec.get("fixedU")).bytes());
    Assert.assertEquals("S", newRec.get("enumF").toString());
    Assert.assertEquals("SS", newRec.get("enumU").toString());
    @SuppressWarnings("unchecked")
    Map<CharSequence, CharSequence> map =
      (Map<CharSequence, CharSequence>) newRec.get("mapF");
    Assert.assertEquals(mapdef.size(), map.size());
    for(Map.Entry<CharSequence, CharSequence> e : map.entrySet()) {
      Assert.assertEquals(
          mapdef.get(e.getKey().toString()), e.getValue().toString());
    }
    Assert.assertEquals(newRec.get("mapF"), newRec.get("mapU"));
    @SuppressWarnings("unchecked")
    GenericData.Array<CharSequence> arr =
      (GenericData.Array<CharSequence>) newRec.get("arrayF");
    Assert.assertEquals(arrdef.size(), arr.size());
    for(CharSequence c : arr) {
      Assert.assertTrue(arrdef.contains(c.toString()));
    }
    Assert.assertEquals(newRec.get("arrF"), newRec.get("arrU"));
    Assert.assertEquals(recdef, newRec.get("recordF"));
    Assert.assertEquals(recdef2, newRec.get("recordU"));
    Assert.assertEquals("S", newRec.get("byName").toString());
  }

  @Test(expected=SchemaBuilderException.class)
  public void testBadDefault() {
    SchemaBuilder.record("r").fields()
      .name("f").type(Schema.create(Schema.Type.INT)).withDefault(new Object())
      .endRecord();
  }

  @Test
  public void testUnionFieldBuild() {
    SchemaBuilder.record("r").fields()
      .name("allUnion").type().unionOf()
        .booleanType().and()
        .intType().and()
        .longType().and()
        .floatType().and()
        .doubleType().and()
        .stringType().and()
        .bytesType().and()
        .nullType().and()
        .fixed("Fix").size(1).and()
        .enumeration("Enu").symbols("Q").and()
        .array().items().intType().and()
        .map().values().longType().and()
        .record("Rec").fields()
          .name("one").type("Fix").noDefault()
          .endRecord()
        .endUnion().booleanDefault(false)
      .endRecord();
  }

  @Test
  public void testDefaults() throws IOException {
    Schema writeSchema = SchemaBuilder.record("r").fields()
      .name("requiredInt").type().intType().noDefault()
      .name("optionalInt").type().optional().intType()
      .name("nullableIntWithDefault").type().nullable().intType().intDefault(3)
      .endRecord();

    GenericData.Record rec1 = new GenericRecordBuilder(writeSchema)
        .set("requiredInt", 1)
        .build();

    Assert.assertEquals(1, rec1.get("requiredInt"));
    Assert.assertEquals(null, rec1.get("optionalInt"));
    Assert.assertEquals(3, rec1.get("nullableIntWithDefault"));

    GenericData.Record rec2 = new GenericRecordBuilder(writeSchema)
        .set("requiredInt", 1)
        .set("optionalInt", 2)
        .set("nullableIntWithDefault", 13)
        .build();

    Assert.assertEquals(1, rec2.get("requiredInt"));
    Assert.assertEquals(2, rec2.get("optionalInt"));
    Assert.assertEquals(13, rec2.get("nullableIntWithDefault"));

    // write to file
    DataFileWriter<Object> writer =
        new DataFileWriter<Object>(new GenericDatumWriter<Object>());
    writer.create(writeSchema, FILE);
    writer.append(rec1);
    writer.append(rec2);
    writer.close();

    Schema readSchema = SchemaBuilder.record("r").fields()
        .name("requiredInt").type().intType().noDefault()
        .name("optionalInt").type().optional().intType()
        .name("nullableIntWithDefault").type().nullable().intType().intDefault(3)
        .name("newOptionalInt").type().optional().intType()
        .name("newNullableIntWithDefault").type().nullable().intType().intDefault(5)
        .endRecord();

    DataFileReader<GenericData.Record> reader =
        new DataFileReader<GenericData.Record>(FILE,
            new GenericDatumReader<GenericData.Record>(writeSchema, readSchema));

    GenericData.Record rec1read = reader.iterator().next();
    Assert.assertEquals(1, rec1read.get("requiredInt"));
    Assert.assertEquals(null, rec1read.get("optionalInt"));
    Assert.assertEquals(3, rec1read.get("nullableIntWithDefault"));
    Assert.assertEquals(null, rec1read.get("newOptionalInt"));
    Assert.assertEquals(5, rec1read.get("newNullableIntWithDefault"));

    GenericData.Record rec2read = reader.iterator().next();
    Assert.assertEquals(1, rec2read.get("requiredInt"));
    Assert.assertEquals(2, rec2read.get("optionalInt"));
    Assert.assertEquals(13, rec2read.get("nullableIntWithDefault"));
    Assert.assertEquals(null, rec2read.get("newOptionalInt"));
    Assert.assertEquals(5, rec2read.get("newNullableIntWithDefault"));
  }

  @Test
  public void testDefaultTypes() {
    Integer intDef = 1;
    Long longDef = 2L;
    Float floatDef = 3F;
    Double doubleDef = 4D;
    Schema schema = SchemaBuilder.record("r").fields()
        .name("int").type().intType().intDefault(intDef)
        .name("long").type().longType().longDefault(longDef)
        .name("float").type().floatType().floatDefault(floatDef)
        .name("double").type().doubleType().doubleDefault(doubleDef)
        .endRecord();

    Assert.assertEquals("int field default type or value mismatch", intDef, schema.getField("int").defaultVal());
    Assert.assertEquals("long field default type or value mismatch", longDef, schema.getField("long").defaultVal());
    Assert.assertEquals("float field default type or value mismatch", floatDef, schema.getField("float").defaultVal());
    Assert.assertEquals("double field default type or value mismatch", doubleDef,
        schema.getField("double").defaultVal());
  }

}
