avro-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From scottca...@apache.org
Subject svn commit: r1507862 [2/3] - in /avro/trunk: ./ lang/java/ lang/java/avro/src/main/java/org/apache/avro/ lang/java/avro/src/test/java/org/apache/avro/ lang/java/compiler/src/test/java/org/apache/avro/compiler/idl/
Date Sun, 28 Jul 2013 22:10:12 GMT
Modified: avro/trunk/lang/java/avro/src/main/java/org/apache/avro/SchemaBuilder.java
URL: http://svn.apache.org/viewvc/avro/trunk/lang/java/avro/src/main/java/org/apache/avro/SchemaBuilder.java?rev=1507862&r1=1507861&r2=1507862&view=diff
==============================================================================
--- avro/trunk/lang/java/avro/src/main/java/org/apache/avro/SchemaBuilder.java (original)
+++ avro/trunk/lang/java/avro/src/main/java/org/apache/avro/SchemaBuilder.java Sun Jul 28 22:10:12 2013
@@ -17,1079 +17,2552 @@
  */
 package org.apache.avro;
 
-import java.util.Collection;
-import java.util.Map;
-import org.apache.avro.generic.GenericData;
-import org.apache.avro.generic.GenericRecord;
-import org.codehaus.jackson.JsonNode;
-import org.codehaus.jackson.map.ObjectMapper;
-import org.codehaus.jackson.node.NullNode;
-
 import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
+import org.apache.avro.Schema.Field;
+import org.apache.avro.generic.GenericData;
+import org.apache.avro.generic.GenericRecord;
+import org.codehaus.jackson.JsonNode;
+import org.codehaus.jackson.io.JsonStringEncoder;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.node.TextNode;
+
 /**
  * <p>
- * A fluent interface for building {@link Schema} instances. Example usage:
+ * A fluent interface for building {@link Schema} instances. The flow of the API
+ * is designed to mimic the <a
+ * href="http://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")
+ *     .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>
- * <pre><code>Schema schema = SchemaBuilder
- *   .recordType("myrecord").namespace("org.example").aliases("oldrecord")
- *   .requiredString("f0")
- *   .requiredLong("f1").doc("This is f1")
- *   .optionalBoolean("f2", true)
- *   .build();
- </code></pre>
+ * 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 {
 
-  public static final Schema NULL = Schema.create(Schema.Type.NULL);
-  public static final Schema BOOLEAN = Schema.create(Schema.Type.BOOLEAN);
-  public static final Schema INT = Schema.create(Schema.Type.INT);
-  public static final Schema LONG = Schema.create(Schema.Type.LONG);
-  public static final Schema FLOAT = Schema.create(Schema.Type.FLOAT);
-  public static final Schema DOUBLE = Schema.create(Schema.Type.DOUBLE);
-  public static final Schema BYTES = Schema.create(Schema.Type.BYTES);
-  public static final Schema STRING = Schema.create(Schema.Type.STRING);
-
   private SchemaBuilder() {
   }
-
+  
   /**
-   * Create a builder for an Avro record with the specified name.
-   * @param name the record name
+   * Create a builder for Avro schemas.
    */
-  public static RecordBuilder recordType(String name) {
-    return new RecordBuilder(name);
+  public static TypeBuilder<Schema> builder() {
+    return new TypeBuilder<Schema>(new SchemaCompletion(), new NameContext());
   }
 
   /**
-   * Create a builder for an Avro error with the specified name.
-   * @param name the error name
+   * Create a builder for Avro schemas with a default namespace. Types created
+   * without namespaces will inherit the namespace provided.
    */
-  public static RecordBuilder errorType(String name) {
-    return new RecordBuilder(name, true);
+  public static TypeBuilder<Schema> builder(String namespace) {
+    return new TypeBuilder<Schema>(new SchemaCompletion(),
+        new NameContext().namespace(namespace));
   }
-
+  
   /**
-   * Create a builder for a reference to an Avro record with the specified name; used
-   * when constructing recursive schemas.
-   * @param name the name of the referenced record
+   * 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 RecordReferenceBuilder recordReference(String name) {
-    return new RecordReferenceBuilder(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
-   * @param values the symbols of the enum
    */
-  public static EnumBuilder enumType(String name, String... values) {
-    return new EnumBuilder(name, values);
+  public static EnumBuilder<Schema> enumeration(String name) {
+    return builder().enumeration(name);
   }
 
   /**
-   * Create a builder for an Avro array with the specified schema for the array's items.
-   * @param schema the schema for the array's items
+   * 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 ArrayBuilder arrayType(Schema schema) {
-    return new ArrayBuilder(schema);
+  public static FixedBuilder<Schema> fixed(String name) {
+    return builder().fixed(name);
   }
 
   /**
-   * Create a builder for an Avro map with the specified schema for the map's values.
-   * @param schema the schema for the map's values
+   * Create a builder for an Avro array
+   * This is equivalent to:
+   * <pre>
+   *   builder().array();
+   * </pre>
    */
-  public static MapBuilder mapType(Schema schema) {
-    return new MapBuilder(schema);
+  public static ArrayBuilder<Schema> array() {
+    return builder().array();
   }
 
   /**
-   * Create a builder for an Avro fixed type with the specified name and size.
-   * @param name the fixed name
-   * @param size the the number of bytes per value
+   * 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 FixedBuilder fixedType(String name, int size) {
-    return new FixedBuilder(name, size);
+  public static BaseTypeBuilder<Schema> nullable() {
+    return builder().nullable();
   }
 
+  
   /**
-   * Create a builder for an Avro union with the specified types.
-   * @param types the types in the union
+   * An abstract builder for all Avro types.  All Avro types
+   * can have arbitrary string key-value properties.
    */
-  public static UnionBuilder unionType(Schema... types) {
-    return new UnionBuilder(types);
+  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));
+    }
+    
+    // for internal use by the Parser
+    final S prop(String name, JsonNode val) {
+      if(!hasProps()) {
+        props = new HashMap<String, JsonNode>();
+      }
+      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
+   * 'alises' 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;
 
-  public static class RecordReferenceBuilder {
+    protected NamedBuilder(NameContext names, String name) {
+      checkRequired(name, "Type must have a name");
+      this.names = names;
+      this.name = name;
+    }
 
-    private final String name;
+    /** 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;
 
-    RecordReferenceBuilder(String name) {
-      checkRequired(name, "Record name is required.");
-      this.name = name;
+    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<R>(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<R>(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<R>(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<R>(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<R>(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<R>(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<R>(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<R>(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<R>(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 static <R> EnumBuilder<R> create(Completion<R> context,
+        NameContext names, String name) {
+      return new EnumBuilder<R>(context, names, name);
+    }
+
+    @Override
+    protected EnumBuilder<R> self() {
+      return this;
+    }
+
+    /** Configure this enum type's symbols, and end its configuration. **/
+    public R symbols(String... symbols) {
+      Schema schema = Schema.createEnum(name(), doc(), space(),
+          Arrays.asList(symbols));
+      completeSchema(schema);
+      return context().complete(schema);
+    }
+
+  }
+  
+  /**
+   * 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<R>(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<R>(new MapCompletion<R>(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<R>(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<R>(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<R>(new ArrayCompletion<R>(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<R>(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<String>();
+    {
+      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<String, Schema>();
+      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")
+     *   .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"]}
+     * </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<R>(new NullableCompletion<R>(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, new ArrayList<Schema>());
+    }
+
+    private static <R> UnionBuilder<R> create(Completion<R> context, NameContext names) {
+      return new UnionBuilder<R>(context, names);
+    }
+
+    private UnionBuilder(Completion<R> context, NameContext names, List<Schema> schemas) {
+      super(new UnionCompletion<R>(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<R>(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<R>(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<R>(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<R>(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<R>(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<R>(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<R>(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<R>(bldr)), names);
+    }
+
+    /** Build an Avro map type **/ 
+    public final MapBuilder<MapDefault<R>> map() {
+      return MapBuilder.create(wrap(new MapDefault<R>(bldr)), names);
+    }
+
+    /** Build an Avro array type **/ 
+    public final ArrayBuilder<ArrayDefault<R>> array() {
+      return ArrayBuilder.create(wrap(new ArrayDefault<R>(bldr)), names);
+    }
+
+    /** Build an Avro fixed type. **/ 
+    public final FixedBuilder<FixedDefault<R>> fixed(String name) {
+      return FixedBuilder.create(wrap(new FixedDefault<R>(bldr)), names, name);
+    }
+    
+    /** Build an Avro enum type. **/ 
+    public final EnumBuilder<EnumDefault<R>> enumeration(String name) {
+      return EnumBuilder.create(wrap(new EnumDefault<R>(bldr)), names, name);
+    }
+
+    /** Build an Avro record type. **/ 
+    public final RecordBuilder<RecordDefault<R>> record(String name) {
+      return RecordBuilder.create(wrap(new RecordDefault<R>(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<R>(bldr);
     }
 
     /**
-     * Specify the optional namespace for this schema. If the name already specifies a
-     * namespace then this call has no effect.
-     * @param namespace a string qualifying the name
-     */
-    public RecordReferenceBuilder namespace(String namespace) {
-      this.namespace = namespace;
-      return this;
+     * 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<R>(bldr, new NullableCompletionWrapper());
     }
 
     /**
-     * Build a record schema referencing another record schema.
-     * @return a record schema
+     * 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 Schema build() {
-      return Schema.createRecord(name, null, namespace, false);
+    public BaseTypeBuilder<FieldAssembler<R>> optional() {
+      return new BaseTypeBuilder<FieldAssembler<R>>(
+          new OptionalCompletion<R>(bldr), names);
     }
-
   }
 
-  abstract static class FieldBuilderBase {
+  /** 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;
 
-    /**
-     * Create a builder for a required boolean field with the specified name.
-     * @param name the field name
-     */
-    public FieldBuilder requiredBoolean(String name) {
-      return new FieldBuilder(this, name, BOOLEAN);
+    private UnionFieldTypeBuilder(FieldBuilder<R> bldr) {
+      this.bldr = bldr;
+      this.names = bldr.names();
     }
-
+    
     /**
-     * Create a builder for an optional boolean field with the specified name and a
-     * default value of null.
-     * @param name the field name
+     * A plain boolean type without custom properties. This is equivalent to:
+     * <pre>
+     * booleanBuilder().endBoolean();
+     * </pre>
      */
-    public FieldBuilder optionalBoolean(String name) {
-      return new FieldBuilder(this, name, BOOLEAN, true);
+    public UnionAccumulator<BooleanDefault<R>> booleanType() {
+      return booleanBuilder().endBoolean();
     }
 
     /**
-     * Create a builder for an optional boolean field with the specified name and
-     * default value.
-     * @param name the field name
-     * @param defaultValue the default value of the field if unspecified
+     * Build a boolean type that can set custom properties. If custom properties
+     * are not needed it is simpler to use {@link #booleanType()}.
      */
-    public FieldBuilder optionalBoolean(String name, boolean defaultValue) {
-      return new FieldBuilder(this, name, BOOLEAN, true, toJsonNode(defaultValue));
+    public BooleanBuilder<UnionAccumulator<BooleanDefault<R>>> booleanBuilder() {
+      return BooleanBuilder.create(completion(new BooleanDefault<R>(bldr)), names);
     }
 
     /**
-     * Create a builder for a required int field with the specified name.
-     * @param name the field name
+     * A plain int type without custom properties. This is equivalent to:
+     * <pre>
+     * intBuilder().endInt();
+     * </pre>
      */
-    public FieldBuilder requiredInt(String name) {
-      return new FieldBuilder(this, name, INT);
+    public UnionAccumulator<IntDefault<R>> intType() {
+      return intBuilder().endInt();
     }
 
     /**
-     * Create a builder for an optional int field with the specified name and a
-     * default value of null.
-     * @param name the field name
+     * Build an int type that can set custom properties. If custom properties
+     * are not needed it is simpler to use {@link #intType()}.
      */
-    public FieldBuilder optionalInt(String name) {
-      return new FieldBuilder(this, name, INT, true);
+    public IntBuilder<UnionAccumulator<IntDefault<R>>> intBuilder() {
+      return IntBuilder.create(completion(new IntDefault<R>(bldr)), names);
     }
-
+    
     /**
-     * Create a builder for an optional int field with the specified name and
-     * default value.
-     * @param name the field name
-     * @param defaultValue the default value of the field if unspecified
+     * A plain long type without custom properties. This is equivalent to:
+     * <pre>
+     * longBuilder().endLong();
+     * </pre>
      */
-    public FieldBuilder optionalInt(String name, int defaultValue) {
-      return new FieldBuilder(this, name, INT, true, toJsonNode(defaultValue));
+    public UnionAccumulator<LongDefault<R>> longType() {
+      return longBuilder().endLong();
     }
 
     /**
-     * Create a builder for a required long field with the specified name.
-     * @param name the field name
+     * Build a long type that can set custom properties. If custom properties
+     * are not needed it is simpler to use {@link #longType()}.
      */
-    public FieldBuilder requiredLong(String name) {
-      return new FieldBuilder(this, name, LONG);
+    public LongBuilder<UnionAccumulator<LongDefault<R>>> longBuilder() {
+      return LongBuilder.create(completion(new LongDefault<R>(bldr)), names);
     }
 
     /**
-     * Create a builder for an optional long field with the specified name and a
-     * default value of null.
-     * @param name the field name
+     * A plain float type without custom properties. This is equivalent to:
+     * <pre>
+     * floatBuilder().endFloat();
+     * </pre>
      */
-    public FieldBuilder optionalLong(String name) {
-      return new FieldBuilder(this, name, LONG, true);
+    public UnionAccumulator<FloatDefault<R>> floatType() {
+      return floatBuilder().endFloat();
     }
 
     /**
-     * Create a builder for an optional long field with the specified name and
-     * default value.
-     * @param name the field name
-     * @param defaultValue the default value of the field if unspecified
+     * Build a float type that can set custom properties. If custom properties
+     * are not needed it is simpler to use {@link #floatType()}.
      */
-    public FieldBuilder optionalLong(String name, long defaultValue) {
-      return new FieldBuilder(this, name, LONG, true, toJsonNode(defaultValue));
+    public FloatBuilder<UnionAccumulator<FloatDefault<R>>> floatBuilder() {
+      return FloatBuilder.create(completion(new FloatDefault<R>(bldr)), names);
     }
 
     /**
-     * Create a builder for a required float field with the specified name.
-     * @param name the field name
+     * A plain double type without custom properties. This is equivalent to:
+     * <pre>
+     * doubleBuilder().endDouble();
+     * </pre>
      */
-    public FieldBuilder requiredFloat(String name) {
-      return new FieldBuilder(this, name, FLOAT);
+    public UnionAccumulator<DoubleDefault<R>> doubleType() {
+      return doubleBuilder().endDouble();
     }
 
     /**
-     * Create a builder for an optional float field with the specified name and a
-     * default value of null.
-     * @param name the field name
+     * Build a double type that can set custom properties. If custom properties
+     * are not needed it is simpler to use {@link #doubleType()}.
      */
-    public FieldBuilder optionalFloat(String name) {
-      return new FieldBuilder(this, name, FLOAT, true);
+    public DoubleBuilder<UnionAccumulator<DoubleDefault<R>>> doubleBuilder() {
+      return DoubleBuilder.create(completion(new DoubleDefault<R>(bldr)), names);
     }
 
     /**
-     * Create a builder for an optional float field with the specified name and
-     * default value.
-     * @param name the field name
-     * @param defaultValue the default value of the field if unspecified
+     * A plain string type without custom properties. This is equivalent to:
+     * <pre>
+     * stringBuilder().endString();
+     * </pre>
      */
-    public FieldBuilder optionalFloat(String name, float defaultValue) {
-      return new FieldBuilder(this, name, FLOAT, true, toJsonNode(defaultValue));
+    public UnionAccumulator<StringDefault<R>> stringType() {
+      return stringBuilder().endString();
     }
 
     /**
-     * Create a builder for a required double field with the specified name.
-     * @param name the field name
+     * Build a string type that can set custom properties. If custom properties
+     * are not needed it is simpler to use {@link #stringType()}.
      */
-    public FieldBuilder requiredDouble(String name) {
-      return new FieldBuilder(this, name, DOUBLE);
+    public StringBldr<UnionAccumulator<StringDefault<R>>> stringBuilder() {
+      return StringBldr.create(completion(new StringDefault<R>(bldr)), names);
     }
 
     /**
-     * Create a builder for an optional double field with the specified name and a
-     * default value of null.
-     * @param name the field name
+     * A plain bytes type without custom properties. This is equivalent to:
+     * <pre>
+     * bytesBuilder().endBytes();
+     * </pre>
      */
-    public FieldBuilder optionalDouble(String name) {
-      return new FieldBuilder(this, name, DOUBLE, true);
+    public UnionAccumulator<BytesDefault<R>> bytesType() {
+      return bytesBuilder().endBytes();
     }
 
     /**
-     * Create a builder for an optional double field with the specified name and
-     * default value.
-     * @param name the field name
-     * @param defaultValue the default value of the field if unspecified
+     * Build a bytes type that can set custom properties. If custom properties
+     * are not needed it is simpler to use {@link #bytesType()}.
      */
-    public FieldBuilder optionalDouble(String name, double defaultValue) {
-      return new FieldBuilder(this, name, DOUBLE, true, toJsonNode(defaultValue));
+    public BytesBuilder<UnionAccumulator<BytesDefault<R>>> bytesBuilder() {
+      return BytesBuilder.create(completion(new BytesDefault<R>(bldr)), names);
     }
 
     /**
-     * Create a builder for a required bytes field with the specified name.
-     * @param name the field name
+     * A plain null type without custom properties. This is equivalent to:
+     * <pre>
+     * nullBuilder().endNull();
+     * </pre>
      */
-    public FieldBuilder requiredBytes(String name) {
-      return new FieldBuilder(this, name, BYTES);
+    public UnionAccumulator<NullDefault<R>> nullType() {
+      return nullBuilder().endNull();
     }
 
     /**
-     * Create a builder for an optional bytes field with the specified name and a
-     * default value of null.
-     * @param name the field name
+     * Build a null type that can set custom properties. If custom properties
+     * are not needed it is simpler to use {@link #nullType()}.
      */
-    public FieldBuilder optionalBytes(String name) {
-      return new FieldBuilder(this, name, BYTES, true);
+    public NullBuilder<UnionAccumulator<NullDefault<R>>> nullBuilder() {
+      return NullBuilder.create(completion(new NullDefault<R>(bldr)), names);
     }
 
-    /**
-     * Create a builder for an optional bytes field with the specified name and
-     * default value.
-     * @param name the field name
-     * @param defaultValue the default value of the field if unspecified
-     */
-    public FieldBuilder optionalBytes(String name, byte[] defaultValue) {
-      return new FieldBuilder(this, name, BYTES, true,
-          toJsonNode(ByteBuffer.wrap(defaultValue)));
+    /** Build an Avro map type **/ 
+    public MapBuilder<UnionAccumulator<MapDefault<R>>> map() {
+      return MapBuilder.create(completion(new MapDefault<R>(bldr)), names);
     }
 
-    /**
-     * Create a builder for a required string field with the specified name.
-     * @param name the field name
-     */
-    public FieldBuilder requiredString(String name) {
-      return new FieldBuilder(this, name, STRING);
+    /** Build an Avro array type **/ 
+    public ArrayBuilder<UnionAccumulator<ArrayDefault<R>>> array() {
+      return ArrayBuilder.create(completion(new ArrayDefault<R>(bldr)), names);
     }
 
-    /**
-     * Create a builder for an optional string field with the specified name and a
-     * default value of null.
-     * @param name the field name
-     */
-    public FieldBuilder optionalString(String name) {
-      return new FieldBuilder(this, name, STRING, true);
+    /** Build an Avro fixed type. **/ 
+    public FixedBuilder<UnionAccumulator<FixedDefault<R>>> fixed(String name) {
+      return FixedBuilder.create(completion(new FixedDefault<R>(bldr)), names, name);
+    }
+    
+    /** Build an Avro enum type. **/ 
+    public EnumBuilder<UnionAccumulator<EnumDefault<R>>> enumeration(String name) {
+      return EnumBuilder.create(completion(new EnumDefault<R>(bldr)), names, name);
     }
 
-    /**
-     * Create a builder for an optional string field with the specified name and
-     * default value.
-     * @param name the field name
-     * @param defaultValue the default value of the field if unspecified
-     */
-    public FieldBuilder optionalString(String name, CharSequence defaultValue) {
-      return new FieldBuilder(this, name, STRING, true, toJsonNode(defaultValue));
+    /** Build an Avro record type. **/ 
+    public RecordBuilder<UnionAccumulator<RecordDefault<R>>> record(String name) {
+      return RecordBuilder.create(completion(new RecordDefault<R>(bldr)), names, name); 
+    }
+    
+    private <C> UnionCompletion<C> completion(Completion<C> context) {
+      return new UnionCompletion<C>(context, names, new ArrayList<Schema>());
     }
+  }
 
-    /**
-     * Create a builder for a required record with the specified name and type.
-     * @param name the field name
-     * @param schema the record type
-     */
-    public FieldBuilder requiredRecord(String name, Schema schema) {
-      return new FieldBuilder(this, name, schema);
+  public final static class RecordBuilder<R> extends
+      NamespacedBuilder<R, RecordBuilder<R>> {
+    private RecordBuilder(Completion<R> context, NameContext names, String name) {
+      super(context, names, name);
     }
 
-    /**
-     * Create a builder for an optional record with the specified name and type,
-     * and a default value of null.
-     * @param name the field name
-     * @param schema the record type
-     */
-    public FieldBuilder optionalRecord(String name, Schema schema) {
-      return new FieldBuilder(this, name, schema, true);
+    private static <R> RecordBuilder<R> create(Completion<R> context,
+        NameContext names, String name) {
+      return new RecordBuilder<R>(context, names, name);
     }
 
-    /**
-     * Create a builder for an optional record with the specified name, type, and default
-     * value.
-     * @param name the field name
-     * @param schema the record type
-     * @param defaultValue the default value of the field if unspecified
-     */
-    public FieldBuilder optionalRecord(String name, Schema schema,
-        GenericRecord defaultValue) {
-      return new FieldBuilder(this, name, schema, true, toJsonNode(defaultValue));
+    @Override
+    protected RecordBuilder<R> self() {
+      return this;
     }
 
-    /**
-     * Create a builder for a required enum with the specified name and type.
-     * @param name the field name
-     * @param schema the field type (record, enum, array, map, fixed, or union)
-     */
-    public FieldBuilder requiredEnum(String name, Schema schema) {
-      return new FieldBuilder(this, name, schema);
+    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<R>(
+          context(), names().namespace(record.getNamespace()), record);
     }
+  }
 
-    /**
-     * Create a builder for an optional enum with the specified name and type,
-     * and a default value of null.
-     * @param name the field name
-     * @param schema the enum type
-     */
-    public FieldBuilder optionalEnum(String name, Schema schema) {
-      return new FieldBuilder(this, name, schema, true);
+  public final static class FieldAssembler<R> {
+    private final List<Field> fields = new ArrayList<Field>();
+    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;
     }
 
     /**
-     * Create a builder for an optional enum with the specified name, type, and default
-     * value.
-     * @param name the field name
-     * @param schema the enum type
-     * @param defaultValue the default value of the field if unspecified
+     * Add a field with the given name.
+     * @return A {@link FieldBuilder} for the given name.
      */
-    public FieldBuilder optionalEnum(String name, Schema schema,
-        CharSequence defaultValue) {
-      return new FieldBuilder(this, name, schema, true, toJsonNode(defaultValue));
+    public FieldBuilder<R> name(String fieldName) {
+      return new FieldBuilder<R>(this, names, fieldName);
     }
-
+    
     /**
-     * Create a builder for a required array with the specified name and type.
-     * @param name the field name
-     * @param schema the array type
+     * 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 FieldBuilder requiredArray(String name, Schema schema) {
-      return new FieldBuilder(this, name, schema);
+    public FieldAssembler<R> requiredBoolean(String fieldName) {
+      return name(fieldName).type().booleanType().noDefault();
     }
-
+    
     /**
-     * Create a builder for an optional array with the specified name and type,
-     * and a default value of null.
-     * @param name the field name
-     * @param schema the array type
+     * 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 FieldBuilder optionalArray(String name, Schema schema) {
-      return new FieldBuilder(this, name, schema, true);
+    public FieldAssembler<R> optionalBoolean(String fieldName) {
+      return name(fieldName).type().optional().booleanType();
     }
-
+    
     /**
-     * Create a builder for an optional array with the specified name, type, and default
-     * value.
-     * @param name the field name
-     * @param schema the array type
-     * @param defaultValue the default value of the field if unspecified
+     * 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 <E> FieldBuilder optionalArray(String name, Schema schema,
-        Collection<E> defaultValue) {
-      return new FieldBuilder(this, name, schema, true, toJsonNode(defaultValue));
+    public FieldAssembler<R> nullableBoolean(String fieldName, boolean defaultVal) {
+      return name(fieldName)
+          .type().nullable().booleanType().booleanDefault(defaultVal);
     }
 
     /**
-     * Create a builder for a required map with the specified name and type.
-     * @param name the field name
-     * @param schema the map type
+     * 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 FieldBuilder requiredMap(String name, Schema schema) {
-      return new FieldBuilder(this, name, schema);
+    public FieldAssembler<R> requiredInt(String fieldName) {
+      return name(fieldName).type().intType().noDefault();
     }
-
+    
     /**
-     * Create a builder for an optional map with the specified name and type,
-     * and a default value of null.
-     * @param name the field name
-     * @param schema the map type
+     * 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 FieldBuilder optionalMap(String name, Schema schema) {
-      return new FieldBuilder(this, name, schema, true);
+    public FieldAssembler<R> optionalInt(String fieldName) {
+      return name(fieldName).type().optional().intType();
     }
-
+    
     /**
-     * Create a builder for an optional map with the specified name, type, and default
-     * value.
-     * @param name the field name
-     * @param schema the map type
-     * @param defaultValue the default value of the field if unspecified
+     * 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 <K, V> FieldBuilder optionalMap(String name, Schema schema,
-        Map<K, V> defaultValue) {
-      return new FieldBuilder(this, name, schema, true, toJsonNode(defaultValue));
+    public FieldAssembler<R> nullableInt(String fieldName, int defaultVal) {
+      return name(fieldName).type().nullable().intType().intDefault(defaultVal);
     }
 
     /**
-     * Create a builder for a required fixed with the specified name and type.
-     * @param name the field name
-     * @param schema the fixed type
+     * 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 FieldBuilder requiredFixed(String name, Schema schema) {
-      return new FieldBuilder(this, name, schema);
+    public FieldAssembler<R> requiredLong(String fieldName) {
+      return name(fieldName).type().longType().noDefault();
     }
-
+    
     /**
-     * Create a builder for an optional fixed with the specified name and type,
-     * and a default value of null.
-     * @param name the field name
-     * @param schema the fixed type
+     * 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 FieldBuilder optionalFixed(String name, Schema schema) {
-      return new FieldBuilder(this, name, schema, true);
+    public FieldAssembler<R> optionalLong(String fieldName) {
+      return name(fieldName).type().optional().longType();
     }
-
+    
     /**
-     * Create a builder for an optional fixed with the specified name, type, and default
-     * value.
-     * @param name the field name
-     * @param schema the fixed type
-     * @param defaultValue the default value of the field if unspecified
+     * 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 FieldBuilder optionalFixed(String name, Schema schema,
-        byte[] defaultValue) {
-      return new FieldBuilder(this, name, schema, true,
-          toJsonNode(ByteBuffer.wrap(defaultValue)));
+    public FieldAssembler<R> nullableLong(String fieldName, long defaultVal) {
+      return name(fieldName).type().nullable().longType().longDefault(defaultVal);
     }
-
+    
     /**
-     * Create a builder for a union with the specified name and type.
-     * @param name the field name
-     * @param schema the union type
+     * 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 FieldBuilder unionType(String name, Schema schema) {
-      return new FieldBuilder(this, name, schema);
+    public FieldAssembler<R> requiredFloat(String fieldName) {
+      return name(fieldName).type().floatType().noDefault();
     }
-
+    
     /**
-     * Create a builder for a union of a boolean with the default value and further
-     * types.
-     * @param name the field name
-     * @param defaultValue the default value of the field if unspecified
-     * @param remainingTypes an array or varargs of one or more types in the union
+     * 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 FieldBuilder unionBoolean(String name, boolean defaultValue,
-        Schema... remainingTypes) {
-      return new UnionFieldBuilder(this, name, BOOLEAN, toJsonNode(defaultValue),
-          remainingTypes);
+    public FieldAssembler<R> optionalFloat(String fieldName) {
+      return name(fieldName).type().optional().floatType();
     }
-
+    
     /**
-     * Create a builder for a union of a int with the default value and further types.
-     * @param name the field name
-     * @param defaultValue the default value of the field if unspecified
-     * @param remainingTypes an array or varargs of one or more types in the union
+     * 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 FieldBuilder unionInt(String name, int defaultValue,
-        Schema... remainingTypes) {
-      return new UnionFieldBuilder(this, name, INT, toJsonNode(defaultValue),
-          remainingTypes);
+    public FieldAssembler<R> nullableFloat(String fieldName, float defaultVal) {
+      return name(fieldName).type().nullable().floatType().floatDefault(defaultVal);
     }
 
     /**
-     * Create a builder for a union of a long with the default value and further types.
-     * @param name the field name
-     * @param defaultValue the default value of the field if unspecified
-     * @param remainingTypes an array or varargs of one or more types in the union
+     * 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 FieldBuilder unionLong(String name, long defaultValue,
-        Schema... remainingTypes) {
-      return new UnionFieldBuilder(this, name, LONG, toJsonNode(defaultValue),
-          remainingTypes);
+    public FieldAssembler<R> requiredDouble(String fieldName) {
+      return name(fieldName).type().doubleType().noDefault();
     }
-
+    
     /**
-     * Create a builder for a union of a float with the default value and further types.
-     * @param name the field name
-     * @param defaultValue the default value of the field if unspecified
-     * @param remainingTypes an array or varargs of one or more types in the union
+     * 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 FieldBuilder unionFloat(String name, float defaultValue,
-        Schema... remainingTypes) {
-      return new UnionFieldBuilder(this, name, FLOAT, toJsonNode(defaultValue),
-          remainingTypes);
+    public FieldAssembler<R> optionalDouble(String fieldName) {
+      return name(fieldName).type().optional().doubleType();
     }
-
+    
     /**
-     * Create a builder for a union of a double with the default value and further types.
-     * @param name the field name
-     * @param defaultValue the default value of the field if unspecified
-     * @param remainingTypes an array or varargs of one or more types in the union
+     * 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 FieldBuilder unionDouble(String name, double defaultValue,
-        Schema... remainingTypes) {
-      return new UnionFieldBuilder(this, name, DOUBLE, toJsonNode(defaultValue),
-          remainingTypes);
+    public FieldAssembler<R> nullableDouble(String fieldName, double defaultVal) {
+      return name(fieldName).type().nullable().doubleType().doubleDefault(defaultVal);
     }
-
+    
     /**
-     * Create a builder for a union of a bytes with the default value and further types.
-     * @param name the field name
-     * @param defaultValue the default value of the field if unspecified
-     * @param remainingTypes an array or varargs of one or more types in the union
+     * 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 FieldBuilder unionBytes(String name, byte[] defaultValue,
-        Schema... remainingTypes) {
-      return new UnionFieldBuilder(this, name, BYTES,
-          toJsonNode(ByteBuffer.wrap(defaultValue)), remainingTypes);
+    public FieldAssembler<R> requiredString(String fieldName) {
+      return name(fieldName).type().stringType().noDefault();
     }
-
+    
     /**
-     * Create a builder for a union of a string with the default value and further types.
-     * @param name the field name
-     * @param defaultValue the default value of the field if unspecified
-     * @param remainingTypes an array or varargs of one or more types in the union
+     * 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 FieldBuilder unionString(String name, CharSequence defaultValue,
-        Schema... remainingTypes) {
-      return new UnionFieldBuilder(this, name, STRING, toJsonNode(defaultValue),
-          remainingTypes);
+    public FieldAssembler<R> optionalString(String fieldName) {
+      return name(fieldName).type().optional().stringType();
     }
-
+    
     /**
-     * Create a builder for a union of a record with the default value and further types.
-     * @param name the field name
-     * @param type the record type
-     * @param defaultValue the default value of the field if unspecified
-     * @param remainingTypes an array or varargs of one or more types in the union
+     * 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 FieldBuilder unionRecord(String name, Schema type, GenericRecord defaultValue,
-        Schema... remainingTypes) {
-      return new UnionFieldBuilder(this, name, type, toJsonNode(defaultValue),
-          remainingTypes);
+    public FieldAssembler<R> nullableString(String fieldName, String defaultVal) {
+      return name(fieldName).type().nullable().stringType().stringDefault(defaultVal);
     }
 
     /**
-     * Create a builder for a union of an enum with the default value and further types.
-     * @param name the field name
-     * @param type the enum type
-     * @param defaultValue the default value of the field if unspecified
-     * @param remainingTypes an array or varargs of one or more types in the union
+     * 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 FieldBuilder unionEnum(String name, Schema type, CharSequence defaultValue,
-        Schema... remainingTypes) {
-      return new UnionFieldBuilder(this, name, type, toJsonNode(defaultValue),
-          remainingTypes);
+    public FieldAssembler<R> requiredBytes(String fieldName) {
+      return name(fieldName).type().bytesType().noDefault();
     }
-
+    
     /**
-     * Create a builder for a union of an array with the default value and further types.
-     * @param name the field name
-     * @param type the array type
-     * @param defaultValue the default value of the field if unspecified
-     * @param remainingTypes an array or varargs of one or more types in the union
+     * 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 <E> FieldBuilder unionArray(String name, Schema type,
-        Collection<E> defaultValue, Schema... remainingTypes) {
-      return new UnionFieldBuilder(this, name, type, toJsonNode(defaultValue),
-          remainingTypes);
+    public FieldAssembler<R> optionalBytes(String fieldName) {
+      return name(fieldName).type().optional().bytesType();
     }
-
+    
     /**
-     * Create a builder for a union of a map with the default value and further types.
-     * @param name the field name
-     * @param type the map type
-     * @param defaultValue the default value of the field if unspecified
-     * @param remainingTypes an array or varargs of one or more types in the union
+     * 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 <K, V> FieldBuilder unionMap(String name, Schema type, Map<K, V> defaultValue,
-        Schema... remainingTypes) {
-      return new UnionFieldBuilder(this, name, type, toJsonNode(defaultValue),
-          remainingTypes);
+    public FieldAssembler<R> nullableBytes(String fieldName, byte[] defaultVal) {
+      return name(fieldName).type().nullable().bytesType().bytesDefault(defaultVal);
     }
 
     /**
-     * Create a builder for a union of a fixed with the default value and further types.
-     * @param name the field name
-     * @param type the fixed type
-     * @param defaultValue the default value of the field if unspecified
-     * @param remainingTypes an array or varargs of one or more types in the union
+     * End adding fields to this record, returning control
+     * to the context that this record builder was created in.
      */
-    public FieldBuilder unionFixed(String name, Schema type, byte[] defaultValue,
-        Schema... remainingTypes) {
-      return new UnionFieldBuilder(this, name, type,
-          toJsonNode(ByteBuffer.wrap(defaultValue)), remainingTypes);
+    public R endRecord() {
+      record.setFields(fields);
+      return context.complete(record);
     }
 
-    protected 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);
-          StringBuilder buffer = new StringBuilder();
-          buffer.append('"');
-          for (int i = bytes.position(); i < bytes.limit(); i++)
-            buffer.append((char)bytes.get(i));
-          buffer.append('"');
-          s = buffer.toString();
-        } else {
-          s = GenericData.get().toString(o);
-        }
-        return new ObjectMapper().readTree(s);
-      } catch (IOException e) {
-        throw new SchemaBuilderException(e);
-      }
+    private FieldAssembler<R> addField(Field field) {
+      fields.add(field);
+      return this;
     }
-
-    abstract Schema getRecordSchema();
-
-    abstract List<Schema.Field> updateAndGetFields();
+    
   }
-
-  public static class RecordBuilder extends FieldBuilderBase {
-
-    private final String name;
-    private final boolean error;
-    private String namespace;
-    private String doc;
-    private Set<String> aliases = new HashSet<String>();
-
-    RecordBuilder(String name) {
-      this(name, false);
-    }
-
-    RecordBuilder(String name, boolean error) {
-      checkRequired(name, "Record name is required.");
-      this.name = name;
-      this.error = error;
+  
+  /**
+   * 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").orderDecending().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 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 decending order.  Decending is the default **/
+    public FieldBuilder<R> orderDescending() {
+      order = Schema.Field.Order.DESCENDING;
+      return self();
+    }
+

[... 850 lines stripped ...]


Mime
View raw message