content: /*
   * 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
   *
   *     https://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.IOException;
  import java.nio.Buffer;
  import java.nio.ByteBuffer;
  import java.nio.charset.StandardCharsets;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collections;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Map;
  import java.util.Objects;
  import java.util.Set;
  
  import com.fasterxml.jackson.core.io.JsonStringEncoder;
  import org.apache.avro.Schema.Field;
  import org.apache.avro.generic.GenericData;
  import org.apache.avro.generic.GenericRecord;
  import org.apache.avro.util.internal.JacksonUtils;
  
  import com.fasterxml.jackson.databind.JsonNode;
  import com.fasterxml.jackson.databind.ObjectMapper;
  import com.fasterxml.jackson.databind.node.NullNode;
  import com.fasterxml.jackson.databind.node.TextNode;
  
  /**
   * <p>
   * A fluent interface for building {@link Schema} instances. The flow of the API
   * is designed to mimic the
   * <a href="https://avro.apache.org/docs/current/spec.html#schemas">Avro Schema
   * Specification</a>
   * </p>
   * For example, the below JSON schema and the fluent builder code to create it
   * are very similar:
   *
   * <pre>
   * {
   *   "type": "record",
   *   "name": "HandshakeRequest", "namespace":"org.apache.avro.ipc",
   *   "fields": [
   *     {"name": "clientHash",
   *      "type": {"type": "fixed", "name": "MD5", "size": 16}},
   *     {"name": "clientProtocol", "type": ["null", "string"]},
   *     {"name": "serverHash", "type": "MD5"},
   *     {"name": "meta", "type": ["null", {"type": "map", "values": "bytes"}]}
   *   ]
   * }
   * </pre>
   *
   * <pre>
   * Schema schema = SchemaBuilder.record("HandshakeRequest").namespace("org.apache.avro.ipc").fields().name("clientHash")
   *     .type().fixed("MD5").size(16).noDefault().name("clientProtocol").type().nullable().stringType().noDefault()
   *     .name("serverHash").type("MD5").noDefault().name("meta").type().nullable().map().values().bytesType().noDefault()
   *     .endRecord();
   * </pre>
   * <p/>
   *
   * <h5>Usage Guide</h5> SchemaBuilder chains together many smaller builders and
   * maintains nested context in order to mimic the Avro Schema specification.
   * Every Avro type in JSON has required and optional JSON properties, as well as
   * user-defined properties.
   * <p/>
   * <h6>Selecting and Building an Avro Type</h6> The API analogy for the right
   * hand side of the Avro Schema JSON
   *
   * <pre>
   * "type":
   * </pre>
   *
   * is a {@link TypeBuilder}, {@link FieldTypeBuilder}, or
   * {@link UnionFieldTypeBuilder}, depending on the context. These types all
   * share a similar API for selecting and building types.
   * <p/>
   * <h5>Primitive Types</h5> All Avro primitive types are trivial to configure. A
   * primitive type in Avro JSON can be declared two ways, one that supports
   * custom properties and one that does not:
   *
   * <pre>
   * {"type":"int"}
   * {"type":{"name":"int"}}
   * {"type":{"name":"int", "customProp":"val"}}
   * </pre>
   *
   * The analogous code form for the above three JSON lines are the below three
   * lines:
   *
   * <pre>
   *  .intType()
   *  .intBuilder().endInt()
   *  .intBuilder().prop("customProp", "val").endInt()
   * </pre>
   *
   * Every primitive type has a shortcut to create the trivial type, and a builder
   * when custom properties are required. The first line above is a shortcut for
   * the second, analogous to the JSON case.
   * <h6>Named Types</h6> Avro named types have names, namespace, aliases, and
   * doc. In this API these share a common parent, {@link NamespacedBuilder}. The
   * builders for named types require a name to be constructed, and optional
   * configuration via:
   * <li>{@link NamespacedBuilder#doc()}</li>
   * <li>{@link NamespacedBuilder#namespace(String)}</li>
   * <li>{@link NamespacedBuilder#aliases(String...)}</li>
   * <li>{@link PropBuilder#prop(String, String)}</li>
   * <p/>
   * Each named type completes configuration of the optional properties with its
   * own method:
   * <li>{@link FixedBuilder#size(int)}</li>
   * <li>{@link EnumBuilder#symbols(String...)}</li>
   * <li>{@link RecordBuilder#fields()}</li> Example use of a named type with all
   * optional parameters:
   *
   * <pre>
   * .enumeration("Suit").namespace("org.apache.test")
   *   .aliases("org.apache.test.OldSuit")
   *   .doc("CardSuits")
   *   .prop("customProp", "val")
   *   .symbols("SPADES", "HEARTS", "DIAMONDS", "CLUBS")
   * </pre>
   *
   * Which is equivalent to the JSON:
   *
   * <pre>
   * { "type":"enum",
   *   "name":"Suit", "namespace":"org.apache.test",
   *   "aliases":["org.apache.test.OldSuit"],
   *   "doc":"Card Suits",
   *   "customProp":"val",
   *   "symbols":["SPADES", "HEARTS", "DIAMONDS", "CLUBS"]
   * }
   * </pre>
   *
   * <h6>Nested Types</h6> The Avro nested types, map and array, can have custom
   * properties like all avro types, are not named, and must specify a nested
   * type. After configuration of optional properties, an array or map builds or
   * selects its nested type with {@link ArrayBuilder#items()} and
   * {@link MapBuilder#values()}, respectively.
   *
   * <h6>Fields</h6> {@link RecordBuilder#fields()} returns a
   * {@link FieldAssembler} for defining the fields of the record and completing
   * it. Each field must have a name, specified via
   * {@link FieldAssembler#name(String)}, which returns a {@link FieldBuilder} for
   * defining aliases, custom properties, and documentation of the field. After
   * configuring these optional values for a field, the type is selected or built
   * with {@link FieldBuilder#type()}.
   * <p/>
   * Fields have default values that must be specified to complete the field.
   * {@link FieldDefault#noDefault()} is available for all field types, and a
   * specific method is available for each type to use a default, for example
   * {@link IntDefault#intDefault(int)}
   * <p/>
   * There are field shortcut methods on {@link FieldAssembler} for primitive
   * types. These shortcuts create required, optional, and nullable fields, but do
   * not support field aliases, doc, or custom properties.
   *
   * <h6>Unions</h6> Union types are built via {@link TypeBuilder#unionOf()} or
   * {@link FieldTypeBuilder#unionOf()} in the context of type selection. This
   * chains together multiple types, in union order. For example:
   *
   * <pre>
   * .unionOf()
   *   .fixed("IPv4").size(4).and()
   *   .fixed("IPv6").size(16).and()
   *   .nullType().endUnion()
   * </pre>
   *
   * is equivalent to the Avro schema JSON:
   *
   * <pre>
   * [
   *   {"type":"fixed", "name":"IPv4", "size":4},
   *   {"type":"fixed", "name":"IPv6", "size":16},
   *   "null"
   * ]
   * </pre>
   *
   * In a field context, the first type of a union defines what default type is
   * allowed.
   * </p>
   * Unions have two shortcuts for common cases. nullable() creates a union of a
   * type and null. In a field type context, optional() is available and creates a
   * union of null and a type, with a null default. The below two are equivalent:
   *
   * <pre>
   *   .unionOf().intType().and().nullType().endUnion()
   *   .nullable().intType()
   * </pre>
   *
   * The below two field declarations are equivalent:
   *
   * <pre>
   *   .name("f").type().unionOf().nullType().and().longType().endUnion().nullDefault()
   *   .name("f").type().optional().longType()
   * </pre>
   *
   * <h6>Explicit Types and Types by Name</h6> Types can also be specified
   * explicitly by passing in a Schema, or by name:
   *
   * <pre>
   *   .type(Schema.create(Schema.Type.INT)) // explicitly specified
   *   .type("MD5")                       // reference by full name or short name
   *   .type("MD5", "org.apache.avro.test")  // reference by name and namespace
   * </pre>
   *
   * When a type is specified by name, and the namespace is absent or null, the
   * namespace is inherited from the enclosing context. A namespace will propagate
   * as a default to child fields, nested types, or later defined types in a
   * union. To specify a name that has no namespace and ignore the inherited
   * namespace, set the namespace to "".
   * <p/>
   * {@link SchemaBuilder#builder(String)} returns a type builder with a default
   * namespace. {@link SchemaBuilder#builder()} returns a type builder with no
   * default namespace.
   */
  public class SchemaBuilder {
  
    private static final ObjectMapper MAPPER = new ObjectMapper();
  
    private SchemaBuilder() {
    }
  
    /**
     * Create a builder for Avro schemas.
     */
    public static TypeBuilder<Schema> builder() {
      return new TypeBuilder<>(new SchemaCompletion(), new NameContext());
    }
  
    /**
     * Create a builder for Avro schemas with a default namespace. Types created
     * without namespaces will inherit the namespace provided.
     */
    public static TypeBuilder<Schema> builder(String namespace) {
      return new TypeBuilder<>(new SchemaCompletion(), new NameContext().namespace(namespace));
    }
  
    /**
     * Create a builder for an Avro record with the specified name. This is
     * equivalent to:
     *
     * <pre>
     * builder().record(name);
     * </pre>
     *
     * @param name the record name
     */
    public static RecordBuilder<Schema> record(String name) {
      return builder().record(name);
    }
  
    /**
     * Create a builder for an Avro enum with the specified name and symbols
     * (values). This is equivalent to:
     *
     * <pre>
     * builder().enumeration(name);
     * </pre>
     *
     * @param name the enum name
     */
    public static EnumBuilder<Schema> enumeration(String name) {
      return builder().enumeration(name);
    }
  
    /**
     * Create a builder for an Avro fixed type with the specified name and size.
     * This is equivalent to:
     *
     * <pre>
     * builder().fixed(name);
     * </pre>
     *
     * @param name the fixed name
     */
    public static FixedBuilder<Schema> fixed(String name) {
      return builder().fixed(name);
    }
  
    /**
     * Create a builder for an Avro array This is equivalent to:
     *
     * <pre>
     * builder().array();
     * </pre>
     */
    public static ArrayBuilder<Schema> array() {
      return builder().array();
    }
  
    /**
     * Create a builder for an Avro map This is equivalent to:
     *
     * <pre>
     * builder().map();
     * </pre>
     */
    public static MapBuilder<Schema> map() {
      return builder().map();
    }
  
    /**
     * Create a builder for an Avro union This is equivalent to:
     *
     * <pre>
     * builder().unionOf();
     * </pre>
     */
    public static BaseTypeBuilder<UnionAccumulator<Schema>> unionOf() {
      return builder().unionOf();
    }
  
    /**
     * Create a builder for a union of a type and null. This is a shortcut for:
     *
     * <pre>
     * builder().nullable();
     * </pre>
     *
     * and the following two lines are equivalent:
     *
     * <pre>
     * nullable().intType();
     * </pre>
     *
     * <pre>
     * unionOf().intType().and().nullType().endUnion();
     * </pre>
     */
    public static BaseTypeBuilder<Schema> nullable() {
      return builder().nullable();
    }
  
    /**
     * An abstract builder for all Avro types. All Avro types can have arbitrary
     * string key-value properties.
     */
    public static abstract class PropBuilder<S extends PropBuilder<S>> {
      private Map<String, JsonNode> props = null;
  
      protected PropBuilder() {
      }
  
      /**
       * Set name-value pair properties for this type or field.
       */
      public final S prop(String name, String val) {
        return prop(name, TextNode.valueOf(val));
      }
  
      /**
       * Set name-value pair properties for this type or field.
       */
      public final S prop(String name, Object value) {
        return prop(name, JacksonUtils.toJsonNode(value));
      }
  
      // for internal use by the Parser
      final S prop(String name, JsonNode val) {
        if (!hasProps()) {
          props = new HashMap<>();
        }
        props.put(name, val);
        return self();
      }
  
      private boolean hasProps() {
        return (props != null);
      }
  
      final <T extends JsonProperties> T addPropsTo(T jsonable) {
        if (hasProps()) {
          for (Map.Entry<String, JsonNode> prop : props.entrySet()) {
            jsonable.addProp(prop.getKey(), prop.getValue());
          }
        }
        return jsonable;
      }
  
      /**
       * a self-type for chaining builder subclasses. Concrete subclasses must return
       * 'this'
       **/
      protected abstract S self();
    }
  
    /**
     * An abstract type that provides builder methods for configuring the name, doc,
     * and aliases of all Avro types that have names (fields, Fixed, Record, and
     * Enum).
     * <p/>
     * All Avro named types and fields have 'doc', 'aliases', and 'name' components.
     * 'name' is required, and provided to this builder. 'doc' and 'aliases' are
     * optional.
     */
    public static abstract class NamedBuilder<S extends NamedBuilder<S>> extends PropBuilder<S> {
      private final String name;
      private final NameContext names;
      private String doc;
      private String[] aliases;
  
      protected NamedBuilder(NameContext names, String name) {
        this.name = Objects.requireNonNull(name, "Type must have a name");
        this.names = names;
      }
  
      /** configure this type's optional documentation string **/
      public final S doc(String doc) {
        this.doc = doc;
        return self();
      }
  
      /** configure this type's optional name aliases **/
      public final S aliases(String... aliases) {
        this.aliases = aliases;
        return self();
      }
  
      final String doc() {
        return doc;
      }
  
      final String name() {
        return name;
      }
  
      final NameContext names() {
        return names;
      }
  
      final Schema addAliasesTo(Schema schema) {
        if (null != aliases) {
          for (String alias : aliases) {
            schema.addAlias(alias);
          }
        }
        return schema;
      }
  
      final Field addAliasesTo(Field field) {
        if (null != aliases) {
          for (String alias : aliases) {
            field.addAlias(alias);
          }
        }
        return field;
      }
    }
  
    /**
     * An abstract type that provides builder methods for configuring the namespace
     * for all Avro types that have namespaces (Fixed, Record, and Enum).
     */
    public static abstract class NamespacedBuilder<R, S extends NamespacedBuilder<R, S>> extends NamedBuilder<S> {
      private final Completion<R> context;
      private String namespace;
  
      protected NamespacedBuilder(Completion<R> context, NameContext names, String name) {
        super(names, name);
        this.context = context;
      }
  
      /**
       * Set the namespace of this type. To clear the namespace, set empty string.
       * <p/>
       * When the namespace is null or unset, the namespace of the type defaults to
       * the namespace of the enclosing context.
       **/
      public final S namespace(String namespace) {
        this.namespace = namespace;
        return self();
      }
  
      final String space() {
        if (null == namespace) {
          return names().namespace;
        }
        return namespace;
      }
  
      final Schema completeSchema(Schema schema) {
        addPropsTo(schema);
        addAliasesTo(schema);
        names().put(schema);
        return schema;
      }
  
      final Completion<R> context() {
        return context;
      }
    }
  
    /**
     * An abstraction for sharing code amongst all primitive type builders.
     */
    private static abstract class PrimitiveBuilder<R, P extends PrimitiveBuilder<R, P>> extends PropBuilder<P> {
      private final Completion<R> context;
      private final Schema immutable;
  
      protected PrimitiveBuilder(Completion<R> context, NameContext names, Schema.Type type) {
        this.context = context;
        this.immutable = names.getFullname(type.getName());
      }
  
      private R end() {
        Schema schema = immutable;
        if (super.hasProps()) {
          schema = Schema.create(immutable.getType());
          addPropsTo(schema);
        }
        return context.complete(schema);
      }
    }
  
    /**
     * Builds an Avro boolean type with optional properties. Set properties with
     * {@link #prop(String, String)}, and finalize with {@link #endBoolean()}
     **/
    public static final class BooleanBuilder<R> extends PrimitiveBuilder<R, BooleanBuilder<R>> {
      private BooleanBuilder(Completion<R> context, NameContext names) {
        super(context, names, Schema.Type.BOOLEAN);
      }
  
      private static <R> BooleanBuilder<R> create(Completion<R> context, NameContext names) {
        return new BooleanBuilder<>(context, names);
      }
  
      @Override
      protected BooleanBuilder<R> self() {
        return this;
      }
  
      /** complete building this type, return control to context **/
      public R endBoolean() {
        return super.end();
      }
    }
  
    /**
     * Builds an Avro int type with optional properties. Set properties with
     * {@link #prop(String, String)}, and finalize with {@link #endInt()}
     **/
    public static final class IntBuilder<R> extends PrimitiveBuilder<R, IntBuilder<R>> {
      private IntBuilder(Completion<R> context, NameContext names) {
        super(context, names, Schema.Type.INT);
      }
  
      private static <R> IntBuilder<R> create(Completion<R> context, NameContext names) {
        return new IntBuilder<>(context, names);
      }
  
      @Override
      protected IntBuilder<R> self() {
        return this;
      }
  
      /** complete building this type, return control to context **/
      public R endInt() {
        return super.end();
      }
    }
  
    /**
     * Builds an Avro long type with optional properties. Set properties with
     * {@link #prop(String, String)}, and finalize with {@link #endLong()}
     **/
    public static final class LongBuilder<R> extends PrimitiveBuilder<R, LongBuilder<R>> {
      private LongBuilder(Completion<R> context, NameContext names) {
        super(context, names, Schema.Type.LONG);
      }
  
      private static <R> LongBuilder<R> create(Completion<R> context, NameContext names) {
        return new LongBuilder<>(context, names);
      }
  
      @Override
      protected LongBuilder<R> self() {
        return this;
      }
  
      /** complete building this type, return control to context **/
      public R endLong() {
        return super.end();
      }
    }
  
    /**
     * Builds an Avro float type with optional properties. Set properties with
     * {@link #prop(String, String)}, and finalize with {@link #endFloat()}
     **/
    public static final class FloatBuilder<R> extends PrimitiveBuilder<R, FloatBuilder<R>> {
      private FloatBuilder(Completion<R> context, NameContext names) {
        super(context, names, Schema.Type.FLOAT);
      }
  
      private static <R> FloatBuilder<R> create(Completion<R> context, NameContext names) {
        return new FloatBuilder<>(context, names);
      }
  
      @Override
      protected FloatBuilder<R> self() {
        return this;
      }
  
      /** complete building this type, return control to context **/
      public R endFloat() {
        return super.end();
      }
    }
  
    /**
     * Builds an Avro double type with optional properties. Set properties with
     * {@link #prop(String, String)}, and finalize with {@link #endDouble()}
     **/
    public static final class DoubleBuilder<R> extends PrimitiveBuilder<R, DoubleBuilder<R>> {
      private DoubleBuilder(Completion<R> context, NameContext names) {
        super(context, names, Schema.Type.DOUBLE);
      }
  
      private static <R> DoubleBuilder<R> create(Completion<R> context, NameContext names) {
        return new DoubleBuilder<>(context, names);
      }
  
      @Override
      protected DoubleBuilder<R> self() {
        return this;
      }
  
      /** complete building this type, return control to context **/
      public R endDouble() {
        return super.end();
      }
    }
  
    /**
     * Builds an Avro string type with optional properties. Set properties with
     * {@link #prop(String, String)}, and finalize with {@link #endString()}
     **/
    public static final class StringBldr<R> extends PrimitiveBuilder<R, StringBldr<R>> {
      private StringBldr(Completion<R> context, NameContext names) {
        super(context, names, Schema.Type.STRING);
      }
  
      private static <R> StringBldr<R> create(Completion<R> context, NameContext names) {
        return new StringBldr<>(context, names);
      }
  
      @Override
      protected StringBldr<R> self() {
        return this;
      }
  
      /** complete building this type, return control to context **/
      public R endString() {
        return super.end();
      }
    }
  
    /**
     * Builds an Avro bytes type with optional properties. Set properties with
     * {@link #prop(String, String)}, and finalize with {@link #endBytes()}
     **/
    public static final class BytesBuilder<R> extends PrimitiveBuilder<R, BytesBuilder<R>> {
      private BytesBuilder(Completion<R> context, NameContext names) {
        super(context, names, Schema.Type.BYTES);
      }
  
      private static <R> BytesBuilder<R> create(Completion<R> context, NameContext names) {
        return new BytesBuilder<>(context, names);
      }
  
      @Override
      protected BytesBuilder<R> self() {
        return this;
      }
  
      /** complete building this type, return control to context **/
      public R endBytes() {
        return super.end();
      }
    }
  
    /**
     * Builds an Avro null type with optional properties. Set properties with
     * {@link #prop(String, String)}, and finalize with {@link #endNull()}
     **/
    public static final class NullBuilder<R> extends PrimitiveBuilder<R, NullBuilder<R>> {
      private NullBuilder(Completion<R> context, NameContext names) {
        super(context, names, Schema.Type.NULL);
      }
  
      private static <R> NullBuilder<R> create(Completion<R> context, NameContext names) {
        return new NullBuilder<>(context, names);
      }
  
      @Override
      protected NullBuilder<R> self() {
        return this;
      }
  
      /** complete building this type, return control to context **/
      public R endNull() {
        return super.end();
      }
    }
  
    /**
     * Builds an Avro Fixed type with optional properties, namespace, doc, and
     * aliases.
     * <p/>
     * Set properties with {@link #prop(String, String)}, namespace with
     * {@link #namespace(String)}, doc with {@link #doc(String)}, and aliases with
     * {@link #aliases(String[])}.
     * <p/>
     * The Fixed schema is finalized when its required size is set via
     * {@link #size(int)}.
     **/
    public static final class FixedBuilder<R> extends NamespacedBuilder<R, FixedBuilder<R>> {
      private FixedBuilder(Completion<R> context, NameContext names, String name) {
        super(context, names, name);
      }
  
      private static <R> FixedBuilder<R> create(Completion<R> context, NameContext names, String name) {
        return new FixedBuilder<>(context, names, name);
      }
  
      @Override
      protected FixedBuilder<R> self() {
        return this;
      }
  
      /** Configure this fixed type's size, and end its configuration. **/
      public R size(int size) {
        Schema schema = Schema.createFixed(name(), super.doc(), space(), size);
        completeSchema(schema);
        return context().complete(schema);
      }
    }
  
    /**
     * Builds an Avro Enum type with optional properties, namespace, doc, and
     * aliases.
     * <p/>
     * Set properties with {@link #prop(String, String)}, namespace with
     * {@link #namespace(String)}, doc with {@link #doc(String)}, and aliases with
     * {@link #aliases(String[])}.
     * <p/>
     * The Enum schema is finalized when its required symbols are set via
     * {@link #symbols(String[])}.
     **/
    public static final class EnumBuilder<R> extends NamespacedBuilder<R, EnumBuilder<R>> {
      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) {
        return new EnumBuilder<>(context, names, name);
      }
  
      @Override
      protected EnumBuilder<R> self() {
        return this;
      }
  
      /**
       * 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), 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();
      }
    }
  
    /**
     * Builds an Avro Map type with optional properties.
     * <p/>
     * Set properties with {@link #prop(String, String)}.
     * <p/>
     * The Map schema's properties are finalized when {@link #values()} or
     * {@link #values(Schema)} is called.
     **/
    public static final class MapBuilder<R> extends PropBuilder<MapBuilder<R>> {
      private final Completion<R> context;
      private final NameContext names;
  
      private MapBuilder(Completion<R> context, NameContext names) {
        this.context = context;
        this.names = names;
      }
  
      private static <R> MapBuilder<R> create(Completion<R> context, NameContext names) {
        return new MapBuilder<>(context, names);
      }
  
      @Override
      protected MapBuilder<R> self() {
        return this;
      }
  
      /**
       * Return a type builder for configuring the map's nested values schema. This
       * builder will return control to the map's enclosing context when complete.
       **/
      public TypeBuilder<R> values() {
        return new TypeBuilder<>(new MapCompletion<>(this, context), names);
      }
  
      /**
       * Complete configuration of this map, setting the schema of the map values to
       * the schema provided. Returns control to the enclosing context.
       **/
      public R values(Schema valueSchema) {
        return new MapCompletion<>(this, context).complete(valueSchema);
      }
    }
  
    /**
     * Builds an Avro Array type with optional properties.
     * <p/>
     * Set properties with {@link #prop(String, String)}.
     * <p/>
     * The Array schema's properties are finalized when {@link #items()} or
     * {@link #items(Schema)} is called.
     **/
    public static final class ArrayBuilder<R> extends PropBuilder<ArrayBuilder<R>> {
      private final Completion<R> context;
      private final NameContext names;
  
      public ArrayBuilder(Completion<R> context, NameContext names) {
        this.context = context;
        this.names = names;
      }
  
      private static <R> ArrayBuilder<R> create(Completion<R> context, NameContext names) {
        return new ArrayBuilder<>(context, names);
      }
  
      @Override
      protected ArrayBuilder<R> self() {
        return this;
      }
  
      /**
       * Return a type builder for configuring the array's nested items schema. This
       * builder will return control to the array's enclosing context when complete.
       **/
      public TypeBuilder<R> items() {
        return new TypeBuilder<>(new ArrayCompletion<>(this, context), names);
      }
  
      /**
       * Complete configuration of this array, setting the schema of the array items
       * to the schema provided. Returns control to the enclosing context.
       **/
      public R items(Schema itemsSchema) {
        return new ArrayCompletion<>(this, context).complete(itemsSchema);
      }
    }
  
    /**
     * internal class for passing the naming context around. This allows for the
     * following:
     * <li>Cache and re-use primitive schemas when they do not set properties.</li>
     * <li>Provide a default namespace for nested contexts (as the JSON Schema spec
     * does).</li>
     * <li>Allow previously defined named types or primitive types to be referenced
     * by name.</li>
     **/
    private static class NameContext {
      private static final Set<String> PRIMITIVES = new HashSet<>();
      static {
        PRIMITIVES.add("null");
        PRIMITIVES.add("boolean");
        PRIMITIVES.add("int");
        PRIMITIVES.add("long");
        PRIMITIVES.add("float");
        PRIMITIVES.add("double");
        PRIMITIVES.add("bytes");
        PRIMITIVES.add("string");
      }
      private final HashMap<String, Schema> schemas;
      private final String namespace;
  
      private NameContext() {
        this.schemas = new HashMap<>();
        this.namespace = null;
        schemas.put("null", Schema.create(Schema.Type.NULL));
        schemas.put("boolean", Schema.create(Schema.Type.BOOLEAN));
        schemas.put("int", Schema.create(Schema.Type.INT));
        schemas.put("long", Schema.create(Schema.Type.LONG));
        schemas.put("float", Schema.create(Schema.Type.FLOAT));
        schemas.put("double", Schema.create(Schema.Type.DOUBLE));
        schemas.put("bytes", Schema.create(Schema.Type.BYTES));
        schemas.put("string", Schema.create(Schema.Type.STRING));
      }
  
      private NameContext(HashMap<String, Schema> schemas, String namespace) {
        this.schemas = schemas;
        this.namespace = "".equals(namespace) ? null : namespace;
      }
  
      private NameContext namespace(String namespace) {
        return new NameContext(schemas, namespace);
      }
  
      private Schema get(String name, String namespace) {
        return getFullname(resolveName(name, namespace));
      }
  
      private Schema getFullname(String fullName) {
        Schema schema = schemas.get(fullName);
        if (schema == null) {
          throw new SchemaParseException("Undefined name: " + fullName);
        }
        return schema;
      }
  
      private void put(Schema schema) {
        String fullName = schema.getFullName();
        if (schemas.containsKey(fullName)) {
          throw new SchemaParseException("Can't redefine: " + fullName);
        }
        schemas.put(fullName, schema);
      }
  
      private String resolveName(String name, String space) {
        if (PRIMITIVES.contains(name) && space == null) {
          return name;
        }
        int lastDot = name.lastIndexOf('.');
        if (lastDot < 0) { // short name
          if (space == null) {
            space = namespace;
          }
          if (space != null && !"".equals(space)) {
            return space + "." + name;
          }
        }
        return name;
      }
    }
  
    /**
     * A common API for building types within a context. BaseTypeBuilder can build
     * all types other than Unions. {@link TypeBuilder} can additionally build
     * Unions.
     * <p/>
     * The builder has two contexts:
     * <li>A naming context provides a default namespace and allows for previously
     * defined named types to be referenced from {@link #type(String)}</li>
     * <li>A completion context representing the scope that the builder was created
     * in. A builder created in a nested context (for example,
     * {@link MapBuilder#values()} will have a completion context assigned by the
     * {@link MapBuilder}</li>
     **/
    public static class BaseTypeBuilder<R> {
      private final Completion<R> context;
      private final NameContext names;
  
      private BaseTypeBuilder(Completion<R> context, NameContext names) {
        this.context = context;
        this.names = names;
      }
  
      /** Use the schema provided as the type. **/
      public final R type(Schema schema) {
        return context.complete(schema);
      }
  
      /**
       * Look up the type by name. This type must be previously defined in the context
       * of this builder.
       * <p/>
       * The name may be fully qualified or a short name. If it is a short name, the
       * default namespace of the current context will additionally be searched.
       **/
      public final R type(String name) {
        return type(name, null);
      }
  
      /**
       * Look up the type by name and namespace. This type must be previously defined
       * in the context of this builder.
       * <p/>
       * The name may be fully qualified or a short name. If it is a fully qualified
       * name, the namespace provided is ignored. If it is a short name, the namespace
       * provided is used if not null, else the default namespace of the current
       * context will be used.
       **/
      public final R type(String name, String namespace) {
        return type(names.get(name, namespace));
      }
  
      /**
       * A plain boolean type without custom properties. This is equivalent to:
       *
       * <pre>
       * booleanBuilder().endBoolean();
       * </pre>
       */
      public final R booleanType() {
        return booleanBuilder().endBoolean();
      }
  
      /**
       * Build a boolean type that can set custom properties. If custom properties are
       * not needed it is simpler to use {@link #booleanType()}.
       */
      public final BooleanBuilder<R> booleanBuilder() {
        return BooleanBuilder.create(context, names);
      }
  
      /**
       * A plain int type without custom properties. This is equivalent to:
       *
       * <pre>
       * intBuilder().endInt();
       * </pre>
       */
      public final R intType() {
        return intBuilder().endInt();
      }
  
      /**
       * Build an int type that can set custom properties. If custom properties are
       * not needed it is simpler to use {@link #intType()}.
       */
      public final IntBuilder<R> intBuilder() {
        return IntBuilder.create(context, names);
      }
  
      /**
       * A plain long type without custom properties. This is equivalent to:
       *
       * <pre>
       * longBuilder().endLong();
       * </pre>
       */
      public final R longType() {
        return longBuilder().endLong();
      }
  
      /**
       * Build a long type that can set custom properties. If custom properties are
       * not needed it is simpler to use {@link #longType()}.
       */
      public final LongBuilder<R> longBuilder() {
        return LongBuilder.create(context, names);
      }
  
      /**
       * A plain float type without custom properties. This is equivalent to:
       *
       * <pre>
       * floatBuilder().endFloat();
       * </pre>
       */
      public final R floatType() {
        return floatBuilder().endFloat();
      }
  
      /**
       * Build a float type that can set custom properties. If custom properties are
       * not needed it is simpler to use {@link #floatType()}.
       */
      public final FloatBuilder<R> floatBuilder() {
        return FloatBuilder.create(context, names);
      }
  
      /**
       * A plain double type without custom properties. This is equivalent to:
       *
       * <pre>
       * doubleBuilder().endDouble();
       * </pre>
       */
      public final R doubleType() {
        return doubleBuilder().endDouble();
      }
  
      /**
       * Build a double type that can set custom properties. If custom properties are
       * not needed it is simpler to use {@link #doubleType()}.
       */
      public final DoubleBuilder<R> doubleBuilder() {
        return DoubleBuilder.create(context, names);
      }
  
      /**
       * A plain string type without custom properties. This is equivalent to:
       *
       * <pre>
       * stringBuilder().endString();
       * </pre>
       */
      public final R stringType() {
        return stringBuilder().endString();
      }
  
      /**
       * Build a string type that can set custom properties. If custom properties are
       * not needed it is simpler to use {@link #stringType()}.
       */
      public final StringBldr<R> stringBuilder() {
        return StringBldr.create(context, names);
      }
  
      /**
       * A plain bytes type without custom properties. This is equivalent to:
       *
       * <pre>
       * bytesBuilder().endBytes();
       * </pre>
       */
      public final R bytesType() {
        return bytesBuilder().endBytes();
      }
  
      /**
       * Build a bytes type that can set custom properties. If custom properties are
       * not needed it is simpler to use {@link #bytesType()}.
       */
      public final BytesBuilder<R> bytesBuilder() {
        return BytesBuilder.create(context, names);
      }
  
      /**
       * A plain null type without custom properties. This is equivalent to:
       *
       * <pre>
       * nullBuilder().endNull();
       * </pre>
       */
      public final R nullType() {
        return nullBuilder().endNull();
      }
  
      /**
       * Build a null type that can set custom properties. If custom properties are
       * not needed it is simpler to use {@link #nullType()}.
       */
      public final NullBuilder<R> nullBuilder() {
        return NullBuilder.create(context, names);
      }
  
      /**
       * Build an Avro map type Example usage:
       *
       * <pre>
       * map().values().intType()
       * </pre>
       *
       * Equivalent to Avro JSON Schema:
       *
       * <pre>
       * {"type":"map", "values":"int"}
       * </pre>
       **/
      public final MapBuilder<R> map() {
        return MapBuilder.create(context, names);
      }
  
      /**
       * Build an Avro array type Example usage:
       *
       * <pre>
       * array().items().longType()
       * </pre>
       *
       * Equivalent to Avro JSON Schema:
       *
       * <pre>
       * {"type":"array", "values":"long"}
       * </pre>
       **/
      public final ArrayBuilder<R> array() {
        return ArrayBuilder.create(context, names);
      }
  
      /**
       * Build an Avro fixed type. Example usage:
       *
       * <pre>
       * fixed("com.foo.IPv4").size(4)
       * </pre>
       *
       * Equivalent to Avro JSON Schema:
       *
       * <pre>
       * {"type":"fixed", "name":"com.foo.IPv4", "size":4}
       * </pre>
       **/
      public final FixedBuilder<R> fixed(String name) {
        return FixedBuilder.create(context, names, name);
      }
  
      /**
       * Build an Avro enum type. Example usage:
       *
       * <pre>
       * 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"], "default":"HEART"}
       * </pre>
       **/
      public final EnumBuilder<R> enumeration(String name) {
        return EnumBuilder.create(context, names, name);
      }
  
      /**
       * Build an Avro record type. Example usage:
       *
       * <pre>
       * record("com.foo.Foo").fields().name("field1").typeInt().intDefault(1).name("field2").typeString().noDefault()
       *     .name("field3").optional().typeFixed("FooFixed").size(4).endRecord()
       * </pre>
       *
       * Equivalent to Avro JSON Schema:
       *
       * <pre>
       * {"type":"record", "name":"com.foo.Foo", "fields": [
       *   {"name":"field1", "type":"int", "default":1},
       *   {"name":"field2", "type":"string"},
       *   {"name":"field3", "type":[
       *     null, {"type":"fixed", "name":"FooFixed", "size":4}
       *     ]}
       *   ]}
       * </pre>
       **/
      public final RecordBuilder<R> record(String name) {
        return RecordBuilder.create(context, names, name);
      }
  
      /**
       * Build an Avro union schema type. Example usage:
       *
       * <pre>
       * unionOf().stringType().and().bytesType().endUnion()
       * </pre>
       **/
      protected BaseTypeBuilder<UnionAccumulator<R>> unionOf() {
        return UnionBuilder.create(context, names);
      }
  
      /**
       * A shortcut for building a union of a type and null.
       * <p/>
       * For example, the code snippets below are equivalent:
       *
       * <pre>
       * nullable().booleanType()
       * </pre>
       *
       * <pre>
       * unionOf().booleanType().and().nullType().endUnion()
       * </pre>
       **/
      protected BaseTypeBuilder<R> nullable() {
        return new BaseTypeBuilder<>(new NullableCompletion<>(context), names);
      }
  
    }
  
    /**
     * A Builder for creating any Avro schema type.
     **/
    public static final class TypeBuilder<R> extends BaseTypeBuilder<R> {
      private TypeBuilder(Completion<R> context, NameContext names) {
        super(context, names);
      }
  
      @Override
      public BaseTypeBuilder<UnionAccumulator<R>> unionOf() {
        return super.unionOf();
      }
  
      @Override
      public BaseTypeBuilder<R> nullable() {
        return super.nullable();
      }
    }
  
    /** A special builder for unions. Unions cannot nest unions directly **/
    private static final class UnionBuilder<R> extends BaseTypeBuilder<UnionAccumulator<R>> {
      private UnionBuilder(Completion<R> context, NameContext names) {
        this(context, names, Collections.emptyList());
      }
  
      private static <R> UnionBuilder<R> create(Completion<R> context, NameContext names) {
        return new UnionBuilder<>(context, names);
      }
  
      private UnionBuilder(Completion<R> context, NameContext names, List<Schema> schemas) {
        super(new UnionCompletion<>(context, names, schemas), names);
      }
    }
  
    /**
     * A special Builder for Record fields. The API is very similar to
     * {@link BaseTypeBuilder}. However, fields have their own names, properties,
     * and default values.
     * <p/>
     * The methods on this class create builder instances that return their control
     * to the {@link FieldAssembler} of the enclosing record context after
     * configuring a default for the field.
     * <p/>
     * For example, an int field with default value 1:
     *
     * <pre>
     * intSimple().withDefault(1);
     * </pre>
     *
     * or an array with items that are optional int types:
     *
     * <pre>
     * array().items().optional().intType();
     * </pre>
     */
    public static class BaseFieldTypeBuilder<R> {
      protected final FieldBuilder<R> bldr;
      protected final NameContext names;
      private final CompletionWrapper wrapper;
  
      protected BaseFieldTypeBuilder(FieldBuilder<R> bldr, CompletionWrapper wrapper) {
        this.bldr = bldr;
        this.names = bldr.names();
        this.wrapper = wrapper;
      }
  
      /**
       * A plain boolean type without custom properties. This is equivalent to:
       *
       * <pre>
       * booleanBuilder().endBoolean();
       * </pre>
       */
      public final BooleanDefault<R> booleanType() {
        return booleanBuilder().endBoolean();
      }
  
      /**
       * Build a boolean type that can set custom properties. If custom properties are
       * not needed it is simpler to use {@link #booleanType()}.
       */
      public final BooleanBuilder<BooleanDefault<R>> booleanBuilder() {
        return BooleanBuilder.create(wrap(new BooleanDefault<>(bldr)), names);
      }
  
      /**
       * A plain int type without custom properties. This is equivalent to:
       *
       * <pre>
       * intBuilder().endInt();
       * </pre>
       */
      public final IntDefault<R> intType() {
        return intBuilder().endInt();
      }
  
      /**
       * Build an int type that can set custom properties. If custom properties are
       * not needed it is simpler to use {@link #intType()}.
       */
      public final IntBuilder<IntDefault<R>> intBuilder() {
        return IntBuilder.create(wrap(new IntDefault<>(bldr)), names);
      }
  
      /**
       * A plain long type without custom properties. This is equivalent to:
       *
       * <pre>
       * longBuilder().endLong();
       * </pre>
       */
      public final LongDefault<R> longType() {
        return longBuilder().endLong();
      }
  
      /**
       * Build a long type that can set custom properties. If custom properties are
       * not needed it is simpler to use {@link #longType()}.
       */
      public final LongBuilder<LongDefault<R>> longBuilder() {
        return LongBuilder.create(wrap(new LongDefault<>(bldr)), names);
      }
  
      /**
       * A plain float type without custom properties. This is equivalent to:
       *
       * <pre>
       * floatBuilder().endFloat();
       * </pre>
       */
      public final FloatDefault<R> floatType() {
        return floatBuilder().endFloat();
      }
  
      /**
       * Build a float type that can set custom properties. If custom properties are
       * not needed it is simpler to use {@link #floatType()}.
       */
      public final FloatBuilder<FloatDefault<R>> floatBuilder() {
        return FloatBuilder.create(wrap(new FloatDefault<>(bldr)), names);
      }
  
      /**
       * A plain double type without custom properties. This is equivalent to:
       *
       * <pre>
       * doubleBuilder().endDouble();
       * </pre>
       */
      public final DoubleDefault<R> doubleType() {
        return doubleBuilder().endDouble();
      }
  
      /**
       * Build a double type that can set custom properties. If custom properties are
       * not needed it is simpler to use {@link #doubleType()}.
       */
      public final DoubleBuilder<DoubleDefault<R>> doubleBuilder() {
        return DoubleBuilder.create(wrap(new DoubleDefault<>(bldr)), names);
      }
  
      /**
       * A plain string type without custom properties. This is equivalent to:
       *
       * <pre>
       * stringBuilder().endString();
       * </pre>
       */
      public final StringDefault<R> stringType() {
        return stringBuilder().endString();
      }
  
      /**
       * Build a string type that can set custom properties. If custom properties are
       * not needed it is simpler to use {@link #stringType()}.
       */
      public final StringBldr<StringDefault<R>> stringBuilder() {
        return StringBldr.create(wrap(new StringDefault<>(bldr)), names);
      }
  
      /**
       * A plain bytes type without custom properties. This is equivalent to:
       *
       * <pre>
       * bytesBuilder().endBytes();
       * </pre>
       */
      public final BytesDefault<R> bytesType() {
        return bytesBuilder().endBytes();
      }
  
      /**
       * Build a bytes type that can set custom properties. If custom properties are
       * not needed it is simpler to use {@link #bytesType()}.
       */
      public final BytesBuilder<BytesDefault<R>> bytesBuilder() {
        return BytesBuilder.create(wrap(new BytesDefault<>(bldr)), names);
      }
  
      /**
       * A plain null type without custom properties. This is equivalent to:
       *
       * <pre>
       * nullBuilder().endNull();
       * </pre>
       */
      public final NullDefault<R> nullType() {
        return nullBuilder().endNull();
      }
  
      /**
       * Build a null type that can set custom properties. If custom properties are
       * not needed it is simpler to use {@link #nullType()}.
       */
      public final NullBuilder<NullDefault<R>> nullBuilder() {
        return NullBuilder.create(wrap(new NullDefault<>(bldr)), names);
      }
  
      /** Build an Avro map type **/
      public final MapBuilder<MapDefault<R>> map() {
        return MapBuilder.create(wrap(new MapDefault<>(bldr)), names);
      }
  
      /** Build an Avro array type **/
      public final ArrayBuilder<ArrayDefault<R>> array() {
        return ArrayBuilder.create(wrap(new ArrayDefault<>(bldr)), names);
      }
  
      /** Build an Avro fixed type. **/
      public final FixedBuilder<FixedDefault<R>> fixed(String name) {
        return FixedBuilder.create(wrap(new FixedDefault<>(bldr)), names, name);
      }
  
      /** Build an Avro enum type. **/
      public final EnumBuilder<EnumDefault<R>> enumeration(String name) {
        return EnumBuilder.create(wrap(new EnumDefault<>(bldr)), names, name);
      }
  
      /** Build an Avro record type. **/
      public final RecordBuilder<RecordDefault<R>> record(String name) {
        return RecordBuilder.create(wrap(new RecordDefault<>(bldr)), names, name);
      }
  
      private <C> Completion<C> wrap(Completion<C> completion) {
        if (wrapper != null) {
          return wrapper.wrap(completion);
        }
        return completion;
      }
    }
  
    /**
     * FieldTypeBuilder adds {@link #unionOf()}, {@link #nullable()}, and
     * {@link #optional()} to BaseFieldTypeBuilder.
     **/
    public static final class FieldTypeBuilder<R> extends BaseFieldTypeBuilder<R> {
      private FieldTypeBuilder(FieldBuilder<R> bldr) {
        super(bldr, null);
      }
  
      /** Build an Avro union schema type. **/
      public UnionFieldTypeBuilder<R> unionOf() {
        return new UnionFieldTypeBuilder<>(bldr);
      }
  
      /**
       * A shortcut for building a union of a type and null, with an optional default
       * value of the non-null type.
       * <p/>
       * For example, the two code snippets below are equivalent:
       *
       * <pre>
       * nullable().booleanType().booleanDefault(true)
       * </pre>
       *
       * <pre>
       * unionOf().booleanType().and().nullType().endUnion().booleanDefault(true)
       * </pre>
       **/
      public BaseFieldTypeBuilder<R> nullable() {
        return new BaseFieldTypeBuilder<>(bldr, new NullableCompletionWrapper());
      }
  
      /**
       * A shortcut for building a union of null and a type, with a null default.
       * <p/>
       * For example, the two code snippets below are equivalent:
       *
       * <pre>
       * optional().booleanType()
       * </pre>
       *
       * <pre>
       * unionOf().nullType().and().booleanType().endUnion().nullDefault()
       * </pre>
       */
      public BaseTypeBuilder<FieldAssembler<R>> optional() {
        return new BaseTypeBuilder<>(new OptionalCompletion<>(bldr), names);
      }
    }
  
    /**
     * Builder for a union field. The first type in the union corresponds to the
     * possible default value type.
     */
    public static final class UnionFieldTypeBuilder<R> {
      private final FieldBuilder<R> bldr;
      private final NameContext names;
  
      private UnionFieldTypeBuilder(FieldBuilder<R> bldr) {
        this.bldr = bldr;
        this.names = bldr.names();
      }
  
      /**
       * A plain boolean type without custom properties. This is equivalent to:
       *
       * <pre>
       * booleanBuilder().endBoolean();
       * </pre>
       */
      public UnionAccumulator<BooleanDefault<R>> booleanType() {
        return booleanBuilder().endBoolean();
      }
  
      /**
       * Build a boolean type that can set custom properties. If custom properties are
       * not needed it is simpler to use {@link #booleanType()}.
       */
      public BooleanBuilder<UnionAccumulator<BooleanDefault<R>>> booleanBuilder() {
        return BooleanBuilder.create(completion(new BooleanDefault<>(bldr)), names);
      }
  
      /**
       * A plain int type without custom properties. This is equivalent to:
       *
       * <pre>
       * intBuilder().endInt();
       * </pre>
       */
      public UnionAccumulator<IntDefault<R>> intType() {
        return intBuilder().endInt();
      }
  
      /**
       * Build an int type that can set custom properties. If custom properties are
       * not needed it is simpler to use {@link #intType()}.
       */
      public IntBuilder<UnionAccumulator<IntDefault<R>>> intBuilder() {
        return IntBuilder.create(completion(new IntDefault<>(bldr)), names);
      }
  
      /**
       * A plain long type without custom properties. This is equivalent to:
       *
       * <pre>
       * longBuilder().endLong();
       * </pre>
       */
      public UnionAccumulator<LongDefault<R>> longType() {
        return longBuilder().endLong();
      }
  
      /**
       * Build a long type that can set custom properties. If custom properties are
       * not needed it is simpler to use {@link #longType()}.
       */
      public LongBuilder<UnionAccumulator<LongDefault<R>>> longBuilder() {
        return LongBuilder.create(completion(new LongDefault<>(bldr)), names);
      }
  
      /**
       * A plain float type without custom properties. This is equivalent to:
       *
       * <pre>
       * floatBuilder().endFloat();
       * </pre>
       */
      public UnionAccumulator<FloatDefault<R>> floatType() {
        return floatBuilder().endFloat();
      }
  
      /**
       * Build a float type that can set custom properties. If custom properties are
       * not needed it is simpler to use {@link #floatType()}.
       */
      public FloatBuilder<UnionAccumulator<FloatDefault<R>>> floatBuilder() {
        return FloatBuilder.create(completion(new FloatDefault<>(bldr)), names);
      }
  
      /**
       * A plain double type without custom properties. This is equivalent to:
       *
       * <pre>
       * doubleBuilder().endDouble();
       * </pre>
       */
      public UnionAccumulator<DoubleDefault<R>> doubleType() {
        return doubleBuilder().endDouble();
      }
  
      /**
       * Build a double type that can set custom properties. If custom properties are
       * not needed it is simpler to use {@link #doubleType()}.
       */
      public DoubleBuilder<UnionAccumulator<DoubleDefault<R>>> doubleBuilder() {
        return DoubleBuilder.create(completion(new DoubleDefault<>(bldr)), names);
      }
  
      /**
       * A plain string type without custom properties. This is equivalent to:
       *
       * <pre>
       * stringBuilder().endString();
       * </pre>
       */
      public UnionAccumulator<StringDefault<R>> stringType() {
        return stringBuilder().endString();
      }
  
      /**
       * Build a string type that can set custom properties. If custom properties are
       * not needed it is simpler to use {@link #stringType()}.
       */
      public StringBldr<UnionAccumulator<StringDefault<R>>> stringBuilder() {
        return StringBldr.create(completion(new StringDefault<>(bldr)), names);
      }
  
      /**
       * A plain bytes type without custom properties. This is equivalent to:
       *
       * <pre>
       * bytesBuilder().endBytes();
       * </pre>
       */
      public UnionAccumulator<BytesDefault<R>> bytesType() {
        return bytesBuilder().endBytes();
      }
  
      /**
       * Build a bytes type that can set custom properties. If custom properties are
       * not needed it is simpler to use {@link #bytesType()}.
       */
      public BytesBuilder<UnionAccumulator<BytesDefault<R>>> bytesBuilder() {
        return BytesBuilder.create(completion(new BytesDefault<>(bldr)), names);
      }
  
      /**
       * A plain null type without custom properties. This is equivalent to:
       *
       * <pre>
       * nullBuilder().endNull();
       * </pre>
       */
      public UnionAccumulator<NullDefault<R>> nullType() {
        return nullBuilder().endNull();
      }
  
      /**
       * Build a null type that can set custom properties. If custom properties are
       * not needed it is simpler to use {@link #nullType()}.
       */
      public NullBuilder<UnionAccumulator<NullDefault<R>>> nullBuilder() {
        return NullBuilder.create(completion(new NullDefault<>(bldr)), names);
      }
  
      /** Build an Avro map type **/
      public MapBuilder<UnionAccumulator<MapDefault<R>>> map() {
        return MapBuilder.create(completion(new MapDefault<>(bldr)), names);
      }
  
      /** Build an Avro array type **/
      public ArrayBuilder<UnionAccumulator<ArrayDefault<R>>> array() {
        return ArrayBuilder.create(completion(new ArrayDefault<>(bldr)), names);
      }
  
      /** Build an Avro fixed type. **/
      public FixedBuilder<UnionAccumulator<FixedDefault<R>>> fixed(String name) {
        return FixedBuilder.create(completion(new FixedDefault<>(bldr)), names, name);
      }
  
      /** Build an Avro enum type. **/
      public EnumBuilder<UnionAccumulator<EnumDefault<R>>> enumeration(String name) {
        return EnumBuilder.create(completion(new EnumDefault<>(bldr)), names, name);
      }
  
      /** Build an Avro record type. **/
      public RecordBuilder<UnionAccumulator<RecordDefault<R>>> record(String name) {
        return RecordBuilder.create(completion(new RecordDefault<>(bldr)), names, name);
      }
  
      private <C> UnionCompletion<C> completion(Completion<C> context) {
        return new UnionCompletion<>(context, names, Collections.emptyList());
      }
    }
  
    public final static class RecordBuilder<R> extends NamespacedBuilder<R, RecordBuilder<R>> {
      private RecordBuilder(Completion<R> context, NameContext names, String name) {
        super(context, names, name);
      }
  
      private static <R> RecordBuilder<R> create(Completion<R> context, NameContext names, String name) {
        return new RecordBuilder<>(context, names, name);
      }
  
      @Override
      protected RecordBuilder<R> self() {
        return this;
      }
  
      public FieldAssembler<R> fields() {
        Schema record = Schema.createRecord(name(), doc(), space(), false);
        // place the record in the name context, fields yet to be set.
        completeSchema(record);
        return new FieldAssembler<>(context(), names().namespace(record.getNamespace()), record);
      }
    }
  
    public final static class FieldAssembler<R> {
      private final List<Field> fields = new ArrayList<>();
      private final Completion<R> context;
      private final NameContext names;
      private final Schema record;
  
      private FieldAssembler(Completion<R> context, NameContext names, Schema record) {
        this.context = context;
        this.names = names;
        this.record = record;
      }
  
      /**
       * Add a field with the given name.
       *
       * @return A {@link FieldBuilder} for the given name.
       */
      public FieldBuilder<R> name(String fieldName) {
        return new FieldBuilder<>(this, names, fieldName);
      }
  
      /**
       * Shortcut for creating a boolean field with the given name and no default.
       * <p/>
       * This is equivalent to:
       *
       * <pre>
       * name(fieldName).type().booleanType().noDefault()
       * </pre>
       */
      public FieldAssembler<R> requiredBoolean(String fieldName) {
        return name(fieldName).type().booleanType().noDefault();
      }
  
      /**
       * Shortcut for creating an optional boolean field: a union of null and boolean
       * with null default.
       * <p/>
       * This is equivalent to:
       *
       * <pre>
       * name(fieldName).type().optional().booleanType()
       * </pre>
       */
      public FieldAssembler<R> optionalBoolean(String fieldName) {
        return name(fieldName).type().optional().booleanType();
      }
  
      /**
       * Shortcut for creating a nullable boolean field: a union of boolean and null
       * with an boolean default.
       * <p/>
       * This is equivalent to:
       *
       * <pre>
       * name(fieldName).type().nullable().booleanType().booleanDefault(defaultVal)
       * </pre>
       */
      public FieldAssembler<R> nullableBoolean(String fieldName, boolean defaultVal) {
        return name(fieldName).type().nullable().booleanType().booleanDefault(defaultVal);
      }
  
      /**
       * Shortcut for creating an int field with the given name and no default.
       * <p/>
       * This is equivalent to:
       *
       * <pre>
       * name(fieldName).type().intType().noDefault()
       * </pre>
       */
      public FieldAssembler<R> requiredInt(String fieldName) {
        return name(fieldName).type().intType().noDefault();
      }
  
      /**
       * Shortcut for creating an optional int field: a union of null and int with
       * null default.
       * <p/>
       * This is equivalent to:
       *
       * <pre>
       * name(fieldName).type().optional().intType()
       * </pre>
       */
      public FieldAssembler<R> optionalInt(String fieldName) {
        return name(fieldName).type().optional().intType();
      }
  
      /**
       * Shortcut for creating a nullable int field: a union of int and null with an
       * int default.
       * <p/>
       * This is equivalent to:
       *
       * <pre>
       * name(fieldName).type().nullable().intType().intDefault(defaultVal)
       * </pre>
       */
      public FieldAssembler<R> nullableInt(String fieldName, int defaultVal) {
        return name(fieldName).type().nullable().intType().intDefault(defaultVal);
      }
  
      /**
       * Shortcut for creating a long field with the given name and no default.
       * <p/>
       * This is equivalent to:
       *
       * <pre>
       * name(fieldName).type().longType().noDefault()
       * </pre>
       */
      public FieldAssembler<R> requiredLong(String fieldName) {
        return name(fieldName).type().longType().noDefault();
      }
  
      /**
       * Shortcut for creating an optional long field: a union of null and long with
       * null default.
       * <p/>
       * This is equivalent to:
       *
       * <pre>
       * name(fieldName).type().optional().longType()
       * </pre>
       */
      public FieldAssembler<R> optionalLong(String fieldName) {
        return name(fieldName).type().optional().longType();
      }
  
      /**
       * Shortcut for creating a nullable long field: a union of long and null with a
       * long default.
       * <p/>
       * This is equivalent to:
       *
       * <pre>
       * name(fieldName).type().nullable().longType().longDefault(defaultVal)
       * </pre>
       */
      public FieldAssembler<R> nullableLong(String fieldName, long defaultVal) {
        return name(fieldName).type().nullable().longType().longDefault(defaultVal);
      }
  
      /**
       * Shortcut for creating a float field with the given name and no default.
       * <p/>
       * This is equivalent to:
       *
       * <pre>
       * name(fieldName).type().floatType().noDefault()
       * </pre>
       */
      public FieldAssembler<R> requiredFloat(String fieldName) {
        return name(fieldName).type().floatType().noDefault();
      }
  
      /**
       * Shortcut for creating an optional float field: a union of null and float with
       * null default.
       * <p/>
       * This is equivalent to:
       *
       * <pre>
       * name(fieldName).type().optional().floatType()
       * </pre>
       */
      public FieldAssembler<R> optionalFloat(String fieldName) {
        return name(fieldName).type().optional().floatType();
      }
  
      /**
       * Shortcut for creating a nullable float field: a union of float and null with
       * a float default.
       * <p/>
       * This is equivalent to:
       *
       * <pre>
       * name(fieldName).type().nullable().floatType().floatDefault(defaultVal)
       * </pre>
       */
      public FieldAssembler<R> nullableFloat(String fieldName, float defaultVal) {
        return name(fieldName).type().nullable().floatType().floatDefault(defaultVal);
      }
  
      /**
       * Shortcut for creating a double field with the given name and no default.
       * <p/>
       * This is equivalent to:
       *
       * <pre>
       * name(fieldName).type().doubleType().noDefault()
       * </pre>
       */
      public FieldAssembler<R> requiredDouble(String fieldName) {
        return name(fieldName).type().doubleType().noDefault();
      }
  
      /**
       * Shortcut for creating an optional double field: a union of null and double
       * with null default.
       * <p/>
       * This is equivalent to:
       *
       * <pre>
       * name(fieldName).type().optional().doubleType()
       * </pre>
       */
      public FieldAssembler<R> optionalDouble(String fieldName) {
        return name(fieldName).type().optional().doubleType();
      }
  
      /**
       * Shortcut for creating a nullable double field: a union of double and null
       * with a double default.
       * <p/>
       * This is equivalent to:
       *
       * <pre>
       * name(fieldName).type().nullable().doubleType().doubleDefault(defaultVal)
       * </pre>
       */
      public FieldAssembler<R> nullableDouble(String fieldName, double defaultVal) {
        return name(fieldName).type().nullable().doubleType().doubleDefault(defaultVal);
      }
  
      /**
       * Shortcut for creating a string field with the given name and no default.
       * <p/>
       * This is equivalent to:
       *
       * <pre>
       * name(fieldName).type().stringType().noDefault()
       * </pre>
       */
      public FieldAssembler<R> requiredString(String fieldName) {
        return name(fieldName).type().stringType().noDefault();
      }
  
      /**
       * Shortcut for creating an optional string field: a union of null and string
       * with null default.
       * <p/>
       * This is equivalent to:
       *
       * <pre>
       * name(fieldName).type().optional().stringType()
       * </pre>
       */
      public FieldAssembler<R> optionalString(String fieldName) {
        return name(fieldName).type().optional().stringType();
      }
  
      /**
       * Shortcut for creating a nullable string field: a union of string and null
       * with a string default.
       * <p/>
       * This is equivalent to:
       *
       * <pre>
       * name(fieldName).type().nullable().stringType().stringDefault(defaultVal)
       * </pre>
       */
      public FieldAssembler<R> nullableString(String fieldName, String defaultVal) {
        return name(fieldName).type().nullable().stringType().stringDefault(defaultVal);
      }
  
      /**
       * Shortcut for creating a bytes field with the given name and no default.
       * <p/>
       * This is equivalent to:
       *
       * <pre>
       * name(fieldName).type().bytesType().noDefault()
       * </pre>
       */
      public FieldAssembler<R> requiredBytes(String fieldName) {
        return name(fieldName).type().bytesType().noDefault();
      }
  
      /**
       * Shortcut for creating an optional bytes field: a union of null and bytes with
       * null default.
       * <p/>
       * This is equivalent to:
       *
       * <pre>
       * name(fieldName).type().optional().bytesType()
       * </pre>
       */
      public FieldAssembler<R> optionalBytes(String fieldName) {
        return name(fieldName).type().optional().bytesType();
      }
  
      /**
       * Shortcut for creating a nullable bytes field: a union of bytes and null with
       * a bytes default.
       * <p/>
       * This is equivalent to:
       *
       * <pre>
       * name(fieldName).type().nullable().bytesType().bytesDefault(defaultVal)
       * </pre>
       */
      public FieldAssembler<R> nullableBytes(String fieldName, byte[] defaultVal) {
        return name(fieldName).type().nullable().bytesType().bytesDefault(defaultVal);
      }
  
      /**
       * End adding fields to this record, returning control to the context that this
       * record builder was created in.
       */
      public R endRecord() {
        record.setFields(fields);
        return context.complete(record);
      }
  
      private FieldAssembler<R> addField(Field field) {
        fields.add(field);
        return this;
      }
  
    }
  
    /**
     * Builds a Field in the context of a {@link FieldAssembler}.
     *
     * Usage is to first configure any of the optional parameters and then to call
     * one of the type methods to complete the field. For example
     *
     * <pre>
     *   .namespace("org.apache.example").orderDescending().type()
     * </pre>
     *
     * Optional parameters for a field are namespace, doc, order, and aliases.
     */
    public final static class FieldBuilder<R> extends NamedBuilder<FieldBuilder<R>> {
      private final FieldAssembler<R> fields;
      private Schema.Field.Order order = Schema.Field.Order.ASCENDING;
      private boolean validatingDefaults = true;
  
      private FieldBuilder(FieldAssembler<R> fields, NameContext names, String name) {
        super(names, name);
        this.fields = fields;
      }
  
      /** Set this field to have ascending order. Ascending is the default **/
      public FieldBuilder<R> orderAscending() {
        order = Schema.Field.Order.ASCENDING;
        return self();
      }
  
      /** Set this field to have descending order. Descending is the default **/
      public FieldBuilder<R> orderDescending() {
        order = Schema.Field.Order.DESCENDING;
        return self();
      }
  
      /** Set this field to ignore order. **/
      public FieldBuilder<R> orderIgnore() {
        order = Schema.Field.Order.IGNORE;
        return self();
      }
  
      /**
       * Validate field default value during {@link #completeField(Schema, JsonNode)}.
       **/
      public FieldBuilder<R> validatingDefaults() {
        validatingDefaults = true;
        return self();
      }
  
      /**
       * Skip field default value validation during
       * {@link #completeField(Schema, JsonNode)}}
       **/
      public FieldBuilder<R> notValidatingDefaults() {
        validatingDefaults = false;
        return self();
      }
  
      /**
       * Final step in configuring this field, finalizing name, namespace, alias, and
       * order.
       *
       * @return A builder for the field's type and default value.
       */
      public FieldTypeBuilder<R> type() {
        return new FieldTypeBuilder<>(this);
      }
  
      /**
       * Final step in configuring this field, finalizing name, namespace, alias, and
       * order. Sets the field's type to the provided schema, returns a
       * {@link GenericDefault}.
       */
      public GenericDefault<R> type(Schema type) {
        return new GenericDefault<>(this, type);
      }
  
      /**
       * Final step in configuring this field, finalizing name, namespace, alias, and
       * order. Sets the field's type to the schema by name reference.
       * <p/>
       * The name must correspond with a named schema that has already been created in
       * the context of this builder. The name may be a fully qualified name, or a
       * short name. If it is a short name, the namespace context of this builder will
       * be used.
       * <p/>
       * The name and namespace context rules are the same as the Avro schema JSON
       * specification.
       */
      public GenericDefault<R> type(String name) {
        return type(name, null);
      }
  
      /**
       * Final step in configuring this field, finalizing name, namespace, alias, and
       * order. Sets the field's type to the schema by name reference.
       * <p/>
       * The name must correspond with a named schema that has already been created in
       * the context of this builder. The name may be a fully qualified name, or a
       * short name. If it is a full name, the namespace is ignored. If it is a short
       * name, the namespace provided is used. If the namespace provided is null, the
       * namespace context of this builder will be used.
       * <p/>
       * The name and namespace context rules are the same as the Avro schema JSON
       * specification.
       */
      public GenericDefault<R> type(String name, String namespace) {
        Schema schema = names().get(name, namespace);
        return type(schema);
      }
  
      private FieldAssembler<R> completeField(Schema schema, Object defaultVal) {
        JsonNode defaultNode = defaultVal == null ? NullNode.getInstance() : toJsonNode(defaultVal);
        return completeField(schema, defaultNode);
      }
  
      private FieldAssembler<R> completeField(Schema schema) {
        return completeField(schema, (JsonNode) null);
      }
  
      private FieldAssembler<R> completeField(Schema schema, JsonNode defaultVal) {
        Field field = new Field(name(), schema, doc(), defaultVal, validatingDefaults, order);
        addPropsTo(field);
        addAliasesTo(field);
        return fields.addField(field);
      }
  
      @Override
      protected FieldBuilder<R> self() {
        return this;
      }
    }
  
    /** Abstract base class for field defaults. **/
    public static abstract class FieldDefault<R, S extends FieldDefault<R, S>> extends Completion<S> {
      private final FieldBuilder<R> field;
      private Schema schema;
  
      FieldDefault(FieldBuilder<R> field) {
        this.field = field;
      }
  
      /** Completes this field with no default value **/
      public final FieldAssembler<R> noDefault() {
        return field.completeField(schema);
      }
  
      private FieldAssembler<R> usingDefault(Object defaultVal) {
        return field.completeField(schema, defaultVal);
      }
  
      @Override
      final S complete(Schema schema) {
        this.schema = schema;
        return self();
      }
  
      abstract S self();
    }
  
    /** Choose whether to use a default value for the field or not. **/
    public static class BooleanDefault<R> extends FieldDefault<R, BooleanDefault<R>> {
      private BooleanDefault(FieldBuilder<R> field) {
        super(field);
      }
  
      /** Completes this field with the default value provided **/
      public final FieldAssembler<R> booleanDefault(boolean defaultVal) {
        return super.usingDefault(defaultVal);
      }
  
      @Override
      final BooleanDefault<R> self() {
        return this;
      }
    }
  
    /** Choose whether to use a default value for the field or not. **/
    public static class IntDefault<R> extends FieldDefault<R, IntDefault<R>> {
      private IntDefault(FieldBuilder<R> field) {
        super(field);
      }
  
      /** Completes this field with the default value provided **/
      public final FieldAssembler<R> intDefault(int defaultVal) {
        return super.usingDefault(defaultVal);
      }
  
      @Override
      final IntDefault<R> self() {
        return this;
      }
    }
  
    /** Choose whether to use a default value for the field or not. **/
    public static class LongDefault<R> extends FieldDefault<R, LongDefault<R>> {
      private LongDefault(FieldBuilder<R> field) {
        super(field);
      }
  
      /** Completes this field with the default value provided **/
      public final FieldAssembler<R> longDefault(long defaultVal) {
        return super.usingDefault(defaultVal);
      }
  
      @Override
      final LongDefault<R> self() {
        return this;
      }
    }
  
    /** Choose whether to use a default value for the field or not. **/
    public static class FloatDefault<R> extends FieldDefault<R, FloatDefault<R>> {
      private FloatDefault(FieldBuilder<R> field) {
        super(field);
      }
  
      /** Completes this field with the default value provided **/
      public final FieldAssembler<R> floatDefault(float defaultVal) {
        return super.usingDefault(defaultVal);
      }
  
      @Override
      final FloatDefault<R> self() {
        return this;
      }
    }
  
    /** Choose whether to use a default value for the field or not. **/
    public static class DoubleDefault<R> extends FieldDefault<R, DoubleDefault<R>> {
      private DoubleDefault(FieldBuilder<R> field) {
        super(field);
      }
  
      /** Completes this field with the default value provided **/
      public final FieldAssembler<R> doubleDefault(double defaultVal) {
        return super.usingDefault(defaultVal);
      }
  
      @Override
      final DoubleDefault<R> self() {
        return this;
      }
    }
  
    /** Choose whether to use a default value for the field or not. **/
    public static class StringDefault<R> extends FieldDefault<R, StringDefault<R>> {
      private StringDefault(FieldBuilder<R> field) {
        super(field);
      }
  
      /** Completes this field with the default value provided. Cannot be null. **/
      public final FieldAssembler<R> stringDefault(String defaultVal) {
        return super.usingDefault(defaultVal);
      }
  
      @Override
      final StringDefault<R> self() {
        return this;
      }
    }
  
    /** Choose whether to use a default value for the field or not. **/
    public static class BytesDefault<R> extends FieldDefault<R, BytesDefault<R>> {
      private BytesDefault(FieldBuilder<R> field) {
        super(field);
      }
  
      /** Completes this field with the default value provided, cannot be null **/
      public final FieldAssembler<R> bytesDefault(byte[] defaultVal) {
        return super.usingDefault(ByteBuffer.wrap(defaultVal));
      }
  
      /** Completes this field with the default value provided, cannot be null **/
      public final FieldAssembler<R> bytesDefault(ByteBuffer defaultVal) {
        return super.usingDefault(defaultVal);
      }
  
      /**
       * Completes this field with the default value provided, cannot be null. The
       * string is interpreted as a byte[], with each character code point value
       * equalling the byte value, as in the Avro spec JSON default.
       **/
      public final FieldAssembler<R> bytesDefault(String defaultVal) {
        return super.usingDefault(defaultVal);
      }
  
      @Override
      final BytesDefault<R> self() {
        return this;
      }
    }
  
    /** Choose whether to use a default value for the field or not. **/
    public static class NullDefault<R> extends FieldDefault<R, NullDefault<R>> {
      private NullDefault(FieldBuilder<R> field) {
        super(field);
      }
  
      /** Completes this field with a default value of null **/
      public final FieldAssembler<R> nullDefault() {
        return super.usingDefault(null);
      }
  
      @Override
      final NullDefault<R> self() {
        return this;
      }
    }
  
    /** Choose whether to use a default value for the field or not. **/
    public static class MapDefault<R> extends FieldDefault<R, MapDefault<R>> {
      private MapDefault(FieldBuilder<R> field) {
        super(field);
      }
  
      /** Completes this field with the default value provided, cannot be null **/
      public final <K, V> FieldAssembler<R> mapDefault(Map<K, V> defaultVal) {
        return super.usingDefault(defaultVal);
      }
  
      @Override
      final MapDefault<R> self() {
        return this;
      }
    }
  
    /** Choose whether to use a default value for the field or not. **/
    public static class ArrayDefault<R> extends FieldDefault<R, ArrayDefault<R>> {
      private ArrayDefault(FieldBuilder<R> field) {
        super(field);
      }
  
      /** Completes this field with the default value provided, cannot be null **/
      public final <V> FieldAssembler<R> arrayDefault(List<V> defaultVal) {
        return super.usingDefault(defaultVal);
      }
  
      @Override
      final ArrayDefault<R> self() {
        return this;
      }
    }
  
    /** Choose whether to use a default value for the field or not. **/
    public static class FixedDefault<R> extends FieldDefault<R, FixedDefault<R>> {
      private FixedDefault(FieldBuilder<R> field) {
        super(field);
      }
  
      /** Completes this field with the default value provided, cannot be null **/
      public final FieldAssembler<R> fixedDefault(byte[] defaultVal) {
        return super.usingDefault(ByteBuffer.wrap(defaultVal));
      }
  
      /** Completes this field with the default value provided, cannot be null **/
      public final FieldAssembler<R> fixedDefault(ByteBuffer defaultVal) {
        return super.usingDefault(defaultVal);
      }
  
      /**
       * Completes this field with the default value provided, cannot be null. The
       * string is interpreted as a byte[], with each character code point value
       * equalling the byte value, as in the Avro spec JSON default.
       **/
      public final FieldAssembler<R> fixedDefault(String defaultVal) {
        return super.usingDefault(defaultVal);
      }
  
      @Override
      final FixedDefault<R> self() {
        return this;
      }
    }
  
    /** Choose whether to use a default value for the field or not. **/
    public static class EnumDefault<R> extends FieldDefault<R, EnumDefault<R>> {
      private EnumDefault(FieldBuilder<R> field) {
        super(field);
      }
  
      /** Completes this field with the default value provided, cannot be null **/
      public final FieldAssembler<R> enumDefault(String defaultVal) {
        return super.usingDefault(defaultVal);
      }
  
      @Override
      final EnumDefault<R> self() {
        return this;
      }
    }
  
    /** Choose whether to use a default value for the field or not. **/
    public static class RecordDefault<R> extends FieldDefault<R, RecordDefault<R>> {
      private RecordDefault(FieldBuilder<R> field) {
        super(field);
      }
  
      /** Completes this field with the default value provided, cannot be null **/
      public final FieldAssembler<R> recordDefault(GenericRecord defaultVal) {
        return super.usingDefault(defaultVal);
      }
  
      @Override
      final RecordDefault<R> self() {
        return this;
      }
    }
  
    public final static class GenericDefault<R> {
      private final FieldBuilder<R> field;
      private final Schema schema;
  
      private GenericDefault(FieldBuilder<R> field, Schema schema) {
        this.field = field;
        this.schema = schema;
      }
  
      /** Do not use a default value for this field. **/
      public FieldAssembler<R> noDefault() {
        return field.completeField(schema);
      }
  
      /**
       * Completes this field with the default value provided. The value must conform
       * to the schema of the field.
       **/
      public FieldAssembler<R> withDefault(Object defaultVal) {
        return field.completeField(schema, defaultVal);
      }
    }
  
    /**
     * Completion<R> is for internal builder use, all subclasses are private.
     *
     * Completion is an object that takes a Schema and returns some result.
     */
    private abstract static class Completion<R> {
      abstract R complete(Schema schema);
    }
  
    private static class SchemaCompletion extends Completion<Schema> {
      @Override
      protected Schema complete(Schema schema) {
        return schema;
      }
    }
  
    private static final Schema NULL_SCHEMA = Schema.create(Schema.Type.NULL);
  
    private static class NullableCompletion<R> extends Completion<R> {
      private final Completion<R> context;
  
      private NullableCompletion(Completion<R> context) {
        this.context = context;
      }
  
      @Override
      protected R complete(Schema schema) {
        // wrap the schema as a union of the schema and null
        Schema nullable = Schema.createUnion(Arrays.asList(schema, NULL_SCHEMA));
        return context.complete(nullable);
      }
    }
  
    private static class OptionalCompletion<R> extends Completion<FieldAssembler<R>> {
      private final FieldBuilder<R> bldr;
  
      public OptionalCompletion(FieldBuilder<R> bldr) {
        this.bldr = bldr;
      }
  
      @Override
      protected FieldAssembler<R> complete(Schema schema) {
        // wrap the schema as a union of null and the schema
        Schema optional = Schema.createUnion(Arrays.asList(NULL_SCHEMA, schema));
        return bldr.completeField(optional, (Object) null);
      }
    }
  
    private abstract static class CompletionWrapper {
      abstract <R> Completion<R> wrap(Completion<R> completion);
    }
  
    private static final class NullableCompletionWrapper extends CompletionWrapper {
      @Override
      <R> Completion<R> wrap(Completion<R> completion) {
        return new NullableCompletion<>(completion);
      }
    }
  
    private static abstract class NestedCompletion<R> extends Completion<R> {
      private final Completion<R> context;
      private final PropBuilder<?> assembler;
  
      private NestedCompletion(PropBuilder<?> assembler, Completion<R> context) {
        this.context = context;
        this.assembler = assembler;
      }
  
      @Override
      protected final R complete(Schema schema) {
        Schema outer = outerSchema(schema);
        assembler.addPropsTo(outer);
        return context.complete(outer);
      }
  
      protected abstract Schema outerSchema(Schema inner);
    }
  
    private static class MapCompletion<R> extends NestedCompletion<R> {
      private MapCompletion(MapBuilder<R> assembler, Completion<R> context) {
        super(assembler, context);
      }
  
      @Override
      protected Schema outerSchema(Schema inner) {
        return Schema.createMap(inner);
      }
    }
  
    private static class ArrayCompletion<R> extends NestedCompletion<R> {
      private ArrayCompletion(ArrayBuilder<R> assembler, Completion<R> context) {
        super(assembler, context);
      }
  
      @Override
      protected Schema outerSchema(Schema inner) {
        return Schema.createArray(inner);
      }
    }
  
    private static class UnionCompletion<R> extends Completion<UnionAccumulator<R>> {
      private final Completion<R> context;
      private final NameContext names;
      private final List<Schema> schemas;
  
      private UnionCompletion(Completion<R> context, NameContext names, List<Schema> schemas) {
        this.context = context;
        this.names = names;
        this.schemas = schemas;
      }
  
      @Override
      protected UnionAccumulator<R> complete(Schema schema) {
        List<Schema> updated = new ArrayList<>(this.schemas);
        updated.add(schema);
        return new UnionAccumulator<>(context, names, updated);
      }
    }
  
    /**
     * Accumulates all of the types in a union. Add an additional type with
     * {@link #and()}. Complete the union with {@link #endUnion()}
     */
    public static final class UnionAccumulator<R> {
      private final Completion<R> context;
      private final NameContext names;
      private final List<Schema> schemas;
  
      private UnionAccumulator(Completion<R> context, NameContext names, List<Schema> schemas) {
        this.context = context;
        this.names = names;
        this.schemas = schemas;
      }
  
      /** Add an additional type to this union **/
      public BaseTypeBuilder<UnionAccumulator<R>> and() {
        return new UnionBuilder<>(context, names, schemas);
      }
  
      /** Complete this union **/
      public R endUnion() {
        Schema schema = Schema.createUnion(schemas);
        return context.complete(schema);
      }
    }
  
    // create default value JsonNodes from objects
    private static JsonNode toJsonNode(Object o) {
      try {
        String s;
        if (o instanceof ByteBuffer) {
          // special case since GenericData.toString() is incorrect for bytes
          // note that this does not handle the case of a default value with nested bytes
          ByteBuffer bytes = ((ByteBuffer) o);
          ((Buffer) bytes).mark();
          byte[] data = new byte[bytes.remaining()];
          bytes.get(data);
          ((Buffer) bytes).reset(); // put the buffer back the way we got it
          s = new String(data, StandardCharsets.ISO_8859_1);
          char[] quoted = JsonStringEncoder.getInstance().quoteAsString(s);
          s = "\"" + new String(quoted) + "\"";
        } else if (o instanceof byte[]) {
          s = new String((byte[]) o, StandardCharsets.ISO_8859_1);
          char[] quoted = JsonStringEncoder.getInstance().quoteAsString(s);
          s = '\"' + new String(quoted) + '\"';
        } else {
          s = GenericData.get().toString(o);
        }
        return MAPPER.readTree(s);
      } catch (IOException e) {
        throw new SchemaBuilderException(e);
      }
    }
  }
  