You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@avro.apache.org by to...@apache.org on 2013/04/29 13:37:58 UTC
svn commit: r1476973 - in /avro/trunk: ./
lang/java/avro/src/main/java/org/apache/avro/
lang/java/avro/src/main/java/org/apache/avro/generic/
lang/java/avro/src/test/java/org/apache/avro/
lang/java/avro/src/test/resources/
Author: tomwhite
Date: Mon Apr 29 11:37:57 2013
New Revision: 1476973
URL: http://svn.apache.org/r1476973
Log:
AVRO-1274. Java: Add a schema builder API.
Added:
avro/trunk/lang/java/avro/src/main/java/org/apache/avro/SchemaBuilder.java (with props)
avro/trunk/lang/java/avro/src/main/java/org/apache/avro/SchemaBuilderException.java (with props)
avro/trunk/lang/java/avro/src/test/java/org/apache/avro/TestSchemaBuilder.java (with props)
avro/trunk/lang/java/avro/src/test/resources/SchemaBuilder.avsc
Modified:
avro/trunk/CHANGES.txt
avro/trunk/lang/java/avro/src/main/java/org/apache/avro/Schema.java
avro/trunk/lang/java/avro/src/main/java/org/apache/avro/generic/GenericData.java
avro/trunk/lang/java/avro/src/main/java/org/apache/avro/generic/GenericRecordBuilder.java
Modified: avro/trunk/CHANGES.txt
URL: http://svn.apache.org/viewvc/avro/trunk/CHANGES.txt?rev=1476973&r1=1476972&r2=1476973&view=diff
==============================================================================
--- avro/trunk/CHANGES.txt (original)
+++ avro/trunk/CHANGES.txt Mon Apr 29 11:37:57 2013
@@ -7,6 +7,8 @@ Trunk (not yet released)
AVRO-1307. Java: Add 'cat' tool to append and sample data files.
(Vincenz Priesnitz via cutting)
+ AVRO-1274. Java: Add a schema builder API. (tomwhite)
+
IMPROVEMENTS
AVRO-1260. Ruby: Improve read performance. (Martin Kleppmann via cutting)
Modified: avro/trunk/lang/java/avro/src/main/java/org/apache/avro/Schema.java
URL: http://svn.apache.org/viewvc/avro/trunk/lang/java/avro/src/main/java/org/apache/avro/Schema.java?rev=1476973&r1=1476972&r2=1476973&view=diff
==============================================================================
--- avro/trunk/lang/java/avro/src/main/java/org/apache/avro/Schema.java (original)
+++ avro/trunk/lang/java/avro/src/main/java/org/apache/avro/Schema.java Mon Apr 29 11:37:57 2013
@@ -63,7 +63,8 @@ import org.codehaus.jackson.node.DoubleN
* </ul>
*
* A schema can be constructed using one of its static <tt>createXXX</tt>
- * methods. The schema objects are <i>logically</i> immutable.
+ * methods, or more conveniently using {@link SchemaBuilder}. The schema objects are
+ * <i>logically</i> immutable.
* There are only two mutating methods - {@link #setFields(List)} and
* {@link #addProp(String, String)}. The following restrictions apply on these
* two methods.
Added: 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=1476973&view=auto
==============================================================================
--- avro/trunk/lang/java/avro/src/main/java/org/apache/avro/SchemaBuilder.java (added)
+++ avro/trunk/lang/java/avro/src/main/java/org/apache/avro/SchemaBuilder.java Mon Apr 29 11:37:57 2013
@@ -0,0 +1,1107 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.avro;
+
+import java.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.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * <p>
+ * A fluent interface for building {@link Schema} instances. Example usage:
+ * </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>
+ */
+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
+ */
+ public static RecordBuilder recordType(String name) {
+ return new RecordBuilder(name);
+ }
+
+ /**
+ * Create a builder for an Avro error with the specified name.
+ * @param name the error name
+ */
+ public static RecordBuilder errorType(String name) {
+ return new RecordBuilder(name, true);
+ }
+
+ /**
+ * 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
+ */
+ public static RecordReferenceBuilder recordReference(String name) {
+ return new RecordReferenceBuilder(name);
+ }
+
+ /**
+ * Create a builder for an Avro enum with the specified name and symbols (values).
+ * @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);
+ }
+
+ /**
+ * 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
+ */
+ public static ArrayBuilder arrayType(Schema schema) {
+ return new ArrayBuilder(schema);
+ }
+
+ /**
+ * 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
+ */
+ public static MapBuilder mapType(Schema schema) {
+ return new MapBuilder(schema);
+ }
+
+ /**
+ * 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
+ */
+ public static FixedBuilder fixedType(String name, int size) {
+ return new FixedBuilder(name, size);
+ }
+
+ /**
+ * Create a builder for an Avro union with the specified types.
+ * @param types the types in the union
+ */
+ public static UnionBuilder unionType(Schema... types) {
+ return new UnionBuilder(types);
+ }
+
+ public static class RecordReferenceBuilder {
+
+ private final String name;
+ private String namespace;
+
+ RecordReferenceBuilder(String name) {
+ checkRequired(name, "Record name is required.");
+ this.name = name;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Build a record schema referencing another record schema.
+ * @return a record schema
+ */
+ public Schema build() {
+ return Schema.createRecord(name, null, namespace, false);
+ }
+
+ }
+
+ abstract static class FieldBuilderBase {
+
+ /**
+ * 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);
+ }
+
+ /**
+ * Create a builder for an optional boolean field with the specified name and a
+ * default value of null.
+ * @param name the field name
+ */
+ public FieldBuilder optionalBoolean(String name) {
+ return new FieldBuilder(this, name, BOOLEAN, true);
+ }
+
+ /**
+ * 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
+ */
+ public FieldBuilder optionalBoolean(String name, boolean defaultValue) {
+ return new FieldBuilder(this, name, BOOLEAN, true, toJsonNode(defaultValue));
+ }
+
+ /**
+ * Create a builder for a required int field with the specified name.
+ * @param name the field name
+ */
+ public FieldBuilder requiredInt(String name) {
+ return new FieldBuilder(this, name, INT);
+ }
+
+ /**
+ * Create a builder for an optional int field with the specified name and a
+ * default value of null.
+ * @param name the field name
+ */
+ public FieldBuilder optionalInt(String name) {
+ return new FieldBuilder(this, name, INT, true);
+ }
+
+ /**
+ * 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
+ */
+ public FieldBuilder optionalInt(String name, int defaultValue) {
+ return new FieldBuilder(this, name, INT, true, toJsonNode(defaultValue));
+ }
+
+ /**
+ * Create a builder for a required long field with the specified name.
+ * @param name the field name
+ */
+ public FieldBuilder requiredLong(String name) {
+ return new FieldBuilder(this, name, LONG);
+ }
+
+ /**
+ * Create a builder for an optional long field with the specified name and a
+ * default value of null.
+ * @param name the field name
+ */
+ public FieldBuilder optionalLong(String name) {
+ return new FieldBuilder(this, name, LONG, true);
+ }
+
+ /**
+ * 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
+ */
+ public FieldBuilder optionalLong(String name, long defaultValue) {
+ return new FieldBuilder(this, name, LONG, true, toJsonNode(defaultValue));
+ }
+
+ /**
+ * Create a builder for a required float field with the specified name.
+ * @param name the field name
+ */
+ public FieldBuilder requiredFloat(String name) {
+ return new FieldBuilder(this, name, FLOAT);
+ }
+
+ /**
+ * Create a builder for an optional float field with the specified name and a
+ * default value of null.
+ * @param name the field name
+ */
+ public FieldBuilder optionalFloat(String name) {
+ return new FieldBuilder(this, name, FLOAT, true);
+ }
+
+ /**
+ * 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
+ */
+ public FieldBuilder optionalFloat(String name, float defaultValue) {
+ return new FieldBuilder(this, name, FLOAT, true, toJsonNode(defaultValue));
+ }
+
+ /**
+ * Create a builder for a required double field with the specified name.
+ * @param name the field name
+ */
+ public FieldBuilder requiredDouble(String name) {
+ return new FieldBuilder(this, name, DOUBLE);
+ }
+
+ /**
+ * Create a builder for an optional double field with the specified name and a
+ * default value of null.
+ * @param name the field name
+ */
+ public FieldBuilder optionalDouble(String name) {
+ return new FieldBuilder(this, name, DOUBLE, true);
+ }
+
+ /**
+ * 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
+ */
+ public FieldBuilder optionalDouble(String name, double defaultValue) {
+ return new FieldBuilder(this, name, DOUBLE, true, toJsonNode(defaultValue));
+ }
+
+ /**
+ * Create a builder for a required bytes field with the specified name.
+ * @param name the field name
+ */
+ public FieldBuilder requiredBytes(String name) {
+ return new FieldBuilder(this, name, BYTES);
+ }
+
+ /**
+ * Create a builder for an optional bytes field with the specified name and a
+ * default value of null.
+ * @param name the field name
+ */
+ public FieldBuilder optionalBytes(String name) {
+ return new FieldBuilder(this, name, BYTES, true);
+ }
+
+ /**
+ * 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)));
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * 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));
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * 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));
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * 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
+ */
+ public FieldBuilder optionalEnum(String name, Schema schema,
+ CharSequence defaultValue) {
+ return new FieldBuilder(this, name, schema, true, toJsonNode(defaultValue));
+ }
+
+ /**
+ * Create a builder for a required array with the specified name and type.
+ * @param name the field name
+ * @param schema the array type
+ */
+ public FieldBuilder requiredArray(String name, Schema schema) {
+ return new FieldBuilder(this, name, schema);
+ }
+
+ /**
+ * 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
+ */
+ public FieldBuilder optionalArray(String name, Schema schema) {
+ return new FieldBuilder(this, name, schema, true);
+ }
+
+ /**
+ * 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
+ */
+ public <E> FieldBuilder optionalArray(String name, Schema schema,
+ Collection<E> defaultValue) {
+ return new FieldBuilder(this, name, schema, true, toJsonNode(defaultValue));
+ }
+
+ /**
+ * Create a builder for a required map with the specified name and type.
+ * @param name the field name
+ * @param schema the map type
+ */
+ public FieldBuilder requiredMap(String name, Schema schema) {
+ return new FieldBuilder(this, name, schema);
+ }
+
+ /**
+ * 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
+ */
+ public FieldBuilder optionalMap(String name, Schema schema) {
+ return new FieldBuilder(this, name, schema, true);
+ }
+
+ /**
+ * 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
+ */
+ public <K, V> FieldBuilder optionalMap(String name, Schema schema,
+ Map<K, V> defaultValue) {
+ return new FieldBuilder(this, name, schema, true, toJsonNode(defaultValue));
+ }
+
+ /**
+ * Create a builder for a required fixed with the specified name and type.
+ * @param name the field name
+ * @param schema the fixed type
+ */
+ public FieldBuilder requiredFixed(String name, Schema schema) {
+ return new FieldBuilder(this, name, schema);
+ }
+
+ /**
+ * 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
+ */
+ public FieldBuilder optionalFixed(String name, Schema schema) {
+ return new FieldBuilder(this, name, schema, true);
+ }
+
+ /**
+ * 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
+ */
+ public FieldBuilder optionalFixed(String name, Schema schema,
+ byte[] defaultValue) {
+ return new FieldBuilder(this, name, schema, true,
+ toJsonNode(ByteBuffer.wrap(defaultValue)));
+ }
+
+ /**
+ * Create a builder for a union with the specified name and type.
+ * @param name the field name
+ * @param schema the union type
+ */
+ public FieldBuilder unionType(String name, Schema schema) {
+ return new FieldBuilder(this, name, schema);
+ }
+
+ /**
+ * 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
+ */
+ public FieldBuilder unionBoolean(String name, boolean defaultValue,
+ Schema... remainingTypes) {
+ return new UnionFieldBuilder(this, name, BOOLEAN, toJsonNode(defaultValue),
+ remainingTypes);
+ }
+
+ /**
+ * 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
+ */
+ public FieldBuilder unionInt(String name, int defaultValue,
+ Schema... remainingTypes) {
+ return new UnionFieldBuilder(this, name, INT, toJsonNode(defaultValue),
+ remainingTypes);
+ }
+
+ /**
+ * 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
+ */
+ public FieldBuilder unionLong(String name, long defaultValue,
+ Schema... remainingTypes) {
+ return new UnionFieldBuilder(this, name, LONG, toJsonNode(defaultValue),
+ remainingTypes);
+ }
+
+ /**
+ * 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
+ */
+ public FieldBuilder unionFloat(String name, float defaultValue,
+ Schema... remainingTypes) {
+ return new UnionFieldBuilder(this, name, FLOAT, toJsonNode(defaultValue),
+ remainingTypes);
+ }
+
+ /**
+ * 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
+ */
+ public FieldBuilder unionDouble(String name, double defaultValue,
+ Schema... remainingTypes) {
+ return new UnionFieldBuilder(this, name, DOUBLE, toJsonNode(defaultValue),
+ remainingTypes);
+ }
+
+ /**
+ * 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
+ */
+ public FieldBuilder unionBytes(String name, byte[] defaultValue,
+ Schema... remainingTypes) {
+ return new UnionFieldBuilder(this, name, BYTES,
+ toJsonNode(ByteBuffer.wrap(defaultValue)), remainingTypes);
+ }
+
+ /**
+ * 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
+ */
+ public FieldBuilder unionString(String name, CharSequence defaultValue,
+ Schema... remainingTypes) {
+ return new UnionFieldBuilder(this, name, STRING, toJsonNode(defaultValue),
+ remainingTypes);
+ }
+
+ /**
+ * 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
+ */
+ public FieldBuilder unionRecord(String name, Schema type, GenericRecord defaultValue,
+ Schema... remainingTypes) {
+ return new UnionFieldBuilder(this, name, type, toJsonNode(defaultValue),
+ remainingTypes);
+ }
+
+ /**
+ * 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
+ */
+ public FieldBuilder unionEnum(String name, Schema type, CharSequence defaultValue,
+ Schema... remainingTypes) {
+ return new UnionFieldBuilder(this, name, type, toJsonNode(defaultValue),
+ remainingTypes);
+ }
+
+ /**
+ * 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
+ */
+ public <E> FieldBuilder unionArray(String name, Schema type,
+ Collection<E> defaultValue, Schema... remainingTypes) {
+ return new UnionFieldBuilder(this, name, type, toJsonNode(defaultValue),
+ remainingTypes);
+ }
+
+ /**
+ * 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
+ */
+ public <K, V> FieldBuilder unionMap(String name, Schema type, Map<K, V> defaultValue,
+ Schema... remainingTypes) {
+ return new UnionFieldBuilder(this, name, type, toJsonNode(defaultValue),
+ remainingTypes);
+ }
+
+ /**
+ * 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
+ */
+ public FieldBuilder unionFixed(String name, Schema type, byte[] defaultValue,
+ Schema... remainingTypes) {
+ return new UnionFieldBuilder(this, name, type,
+ toJsonNode(ByteBuffer.wrap(defaultValue)), remainingTypes);
+ }
+
+ 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);
+ }
+ }
+
+ 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;
+ }
+
+ /**
+ * 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 RecordBuilder namespace(String namespace) {
+ this.namespace = namespace;
+ return this;
+ }
+
+ /**
+ * Specify the optional documentation string for this schema.
+ * @param doc a documentation string
+ */
+ public RecordBuilder doc(String doc) {
+ this.doc = doc;
+ return this;
+ }
+
+ /**
+ * Specify one or more optional aliases providing alternate names for this schema.
+ * @param aliases an array or varargs of one or more string names
+ */
+ public RecordBuilder aliases(String... aliases) {
+ for (String alias : aliases) {
+ this.aliases.add(alias);
+ }
+ return this;
+ }
+
+ Schema getRecordSchema() {
+ Schema schema = Schema.createRecord(name, doc, namespace, error);
+ for (String alias : aliases) {
+ schema.addAlias(alias);
+ }
+ return schema;
+ }
+
+ @Override
+ List<Schema.Field> updateAndGetFields() {
+ return new ArrayList<Schema.Field>(); // no previous fields
+ }
+ }
+
+ public static class FieldBuilder extends FieldBuilderBase {
+
+ private final Schema recordSchema;
+ private final List<Schema.Field> fields;
+ private final String name;
+ private final Schema fieldSchema;
+ private final boolean optional;
+ private JsonNode defaultValue;
+ private String doc;
+ private Schema.Field.Order order;
+ private Set<String> aliases = new HashSet<String>();
+
+ FieldBuilder(FieldBuilderBase builder, String name, Schema fieldSchema) {
+ this(builder, name, fieldSchema, false, null); // no default specified
+ }
+
+ FieldBuilder(FieldBuilderBase builder, String name, Schema fieldSchema,
+ JsonNode defaultValue) {
+ this(builder, name, fieldSchema, false, defaultValue);
+ }
+
+ FieldBuilder(FieldBuilderBase builder, String name, Schema fieldSchema,
+ boolean optional) {
+ this(builder, name, fieldSchema, optional, NullNode.getInstance()); // null default
+ }
+
+ FieldBuilder(FieldBuilderBase builder, String name, Schema fieldSchema,
+ boolean optional, JsonNode defaultValue) {
+ checkRequired(name, "Field name is required.");
+ checkRequired(fieldSchema, "Field schema is required for %s.", name);
+
+ this.recordSchema = builder.getRecordSchema();
+ this.fields = builder.updateAndGetFields();
+ this.name = name;
+ this.fieldSchema = fieldSchema;
+ this.optional = optional;
+ this.defaultValue = defaultValue;
+ }
+
+ /**
+ * Specify the optional documentation string for this field.
+ * @param doc a documentation string
+ */
+ public FieldBuilder doc(String doc) {
+ this.doc = doc;
+ return this;
+ }
+
+ /**
+ * Specify that this field should use the value's natural sort order. This is the
+ * default if no sort order is specified.
+ */
+ public FieldBuilder orderAscending() {
+ this.order = Schema.Field.Order.ASCENDING;
+ return this;
+ }
+
+ /**
+ * Specify that this field should use the value's reverse sort order.
+ */
+ public FieldBuilder orderDescending() {
+ this.order = Schema.Field.Order.DESCENDING;
+ return this;
+ }
+
+ /**
+ * Specify that this field should be ignored when sorting records.
+ */
+ public FieldBuilder orderIgnore() {
+ this.order = Schema.Field.Order.IGNORE;
+ return this;
+ }
+
+ /**
+ * Specify one or more optional aliases providing alternate names for this field.
+ * @param aliases an array or varargs of one or more string names
+ */
+ public FieldBuilder aliases(String... aliases) {
+ for (String alias : aliases) {
+ this.aliases.add(alias);
+ }
+ return this;
+ }
+
+ /**
+ * <b>Expert</b>. Specify the default value for this field. In most cases, the
+ * <code>requiredX()</code> and <code>optionalX()</code> methods suffice,
+ * but this method can be used to create a non-nullable (required) field with a
+ * default value.
+ * @param defaultValue
+ */
+ public FieldBuilder defaultValue(Object defaultValue) {
+ this.defaultValue = toJsonNode(defaultValue);
+ return this;
+ }
+
+ @Override
+ Schema getRecordSchema() {
+ return recordSchema;
+ }
+
+ @Override
+ List<Schema.Field> updateAndGetFields() {
+ Schema s;
+ if (optional) {
+ List<Schema> types = new ArrayList<Schema>();
+ if (NullNode.getInstance().equals(defaultValue)) {
+ types.add(Schema.create(Schema.Type.NULL));
+ types.add(fieldSchema);
+ } else {
+ types.add(fieldSchema);
+ types.add(Schema.create(Schema.Type.NULL));
+ }
+ s = Schema.createUnion(types);
+ } else {
+ s = fieldSchema;
+ }
+ Schema.Field field;
+ if (order == null) {
+ field = new Schema.Field(name, s, doc, defaultValue);
+ } else {
+ field = new Schema.Field(name, s, doc, defaultValue, order);
+ }
+ for (String alias : aliases) {
+ field.addAlias(alias);
+ }
+ fields.add(field);
+ return fields;
+ }
+
+ /**
+ * Build a record schema with the fields specified.
+ * @return a record schema
+ */
+ public Schema build() {
+ recordSchema.setFields(updateAndGetFields());
+ return recordSchema;
+ }
+
+ }
+
+ public static class UnionFieldBuilder extends FieldBuilder {
+ public UnionFieldBuilder(FieldBuilderBase builder, String name, Schema firstType,
+ Schema... remainingTypes) {
+ super(builder, name, union(firstType, remainingTypes));
+ }
+
+ public UnionFieldBuilder(FieldBuilderBase builder, String name, Schema firstType,
+ JsonNode defaultValue, Schema... remainingTypes) {
+ super(builder, name, union(firstType, remainingTypes), defaultValue);
+ }
+
+ private static Schema union(Schema firstType, Schema... remainingTypes) {
+ List<Schema> types = new ArrayList<Schema>();
+ types.add(firstType);
+ types.addAll(Arrays.asList(remainingTypes));
+ return Schema.createUnion(types);
+ }
+ }
+
+ public static class EnumBuilder {
+
+ private final String name;
+ private final List<String> values;
+ private String namespace;
+ private String doc;
+ private Set<String> aliases = new HashSet<String>();
+
+ EnumBuilder(String name, String... values) {
+ checkRequired(name, "Enum name is required.");
+ checkRequired(values, "Enum values are required for %s.", name);
+
+ this.name = name;
+ this.values = Arrays.asList(values);
+ }
+
+ /**
+ * 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 EnumBuilder namespace(String namespace) {
+ this.namespace = namespace;
+ return this;
+ }
+
+ /**
+ * Specify the optional documentation string for this schema.
+ * @param doc a documentation string
+ */
+ public EnumBuilder doc(String doc) {
+ this.doc = doc;
+ return this;
+ }
+
+ /**
+ * Specify one or more optional aliases providing alternate names for this schema.
+ * @param aliases an array or varargs of one or more string names
+ */
+ public EnumBuilder aliases(String... aliases) {
+ for (String alias : aliases) {
+ this.aliases.add(alias);
+ }
+ return this;
+ }
+
+ /**
+ * Build an enum schema.
+ * @return an enum schema
+ */
+ public Schema build() {
+ Schema schema = Schema.createEnum(name, doc, namespace, values);
+ for (String alias : aliases) {
+ schema.addAlias(alias);
+ }
+ return schema;
+ }
+
+ }
+
+ public static class ArrayBuilder {
+
+ private final Schema itemsSchema;
+
+ ArrayBuilder(Schema itemsSchema) {
+ checkRequired(itemsSchema, "Array items schema is required.");
+ this.itemsSchema = itemsSchema;
+ }
+
+ /**
+ * Build an array schema.
+ * @return aa array schema
+ */
+ public Schema build() {
+ return Schema.createArray(itemsSchema);
+ }
+
+ }
+
+ public static class MapBuilder {
+
+ private final Schema valuesSchema;
+
+ MapBuilder(Schema valuesSchema) {
+ checkRequired(valuesSchema, "Map values schema is required.");
+ this.valuesSchema = valuesSchema;
+ }
+
+ /**
+ * Build a map schema.
+ * @return a map schema
+ */
+ public Schema build() {
+ return Schema.createMap(valuesSchema);
+ }
+
+ }
+
+ public static class FixedBuilder {
+
+ private final String name;
+ private final Integer size;
+ private String namespace;
+ private String doc;
+ private Set<String> aliases = new HashSet<String>();
+
+ FixedBuilder(String name, int size) {
+ checkRequired(name, "Fixed name is required.");
+ checkRequired(size, "Fixed size is required for %s.", name);
+ this.name = name;
+ this.size = size;
+ }
+
+ /**
+ * 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 FixedBuilder namespace(String namespace) {
+ this.namespace = namespace;
+ return this;
+ }
+
+ /**
+ * Specify the optional documentation string for this schema.
+ * @param doc a documentation string
+ */
+ public FixedBuilder doc(String doc) {
+ this.doc = doc;
+ return this;
+ }
+
+ /**
+ * Specify one or more optional aliases providing alternate names for this schema.
+ * @param aliases an array or varargs of one or more string names
+ */
+ public FixedBuilder aliases(String... aliases) {
+ for (String alias : aliases) {
+ this.aliases.add(alias);
+ }
+ return this;
+ }
+
+ /**
+ * Build a fixed schema.
+ * @return a fixed schema
+ */
+ public Schema build() {
+ Schema schema = Schema.createFixed(name, doc, namespace, size);
+ for (String alias : aliases) {
+ schema.addAlias(alias);
+ }
+ return schema;
+ }
+
+ }
+
+ public static class UnionBuilder {
+
+ private final List<Schema> types;
+
+ UnionBuilder(Schema... types) {
+ this(Arrays.asList(types));
+ }
+
+ UnionBuilder(List<Schema> types) {
+ checkRequired(types, "Union types are required.");
+ this.types = types;
+ }
+
+ /**
+ * Build a union schema.
+ * @return a union schema
+ */
+ public Schema build() {
+ return Schema.createUnion(types);
+ }
+
+ }
+
+ private static void checkRequired(Object reference, String errorMessage) {
+ if (reference == null) {
+ throw new NullPointerException(errorMessage);
+ }
+ }
+
+ private static void checkRequired(Object reference, String errorMessage,
+ Object... errorMessageArgs) {
+ if (reference == null) {
+ throw new NullPointerException(String.format(errorMessage, errorMessageArgs));
+ }
+ }
+}
Propchange: avro/trunk/lang/java/avro/src/main/java/org/apache/avro/SchemaBuilder.java
------------------------------------------------------------------------------
svn:eol-style = native
Added: avro/trunk/lang/java/avro/src/main/java/org/apache/avro/SchemaBuilderException.java
URL: http://svn.apache.org/viewvc/avro/trunk/lang/java/avro/src/main/java/org/apache/avro/SchemaBuilderException.java?rev=1476973&view=auto
==============================================================================
--- avro/trunk/lang/java/avro/src/main/java/org/apache/avro/SchemaBuilderException.java (added)
+++ avro/trunk/lang/java/avro/src/main/java/org/apache/avro/SchemaBuilderException.java Mon Apr 29 11:37:57 2013
@@ -0,0 +1,26 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.avro;
+
+/** Thrown for errors building schemas. */
+public class SchemaBuilderException extends AvroRuntimeException {
+ public SchemaBuilderException(Throwable cause) { super(cause); }
+ public SchemaBuilderException(String message) { super(message); }
+}
+
Propchange: avro/trunk/lang/java/avro/src/main/java/org/apache/avro/SchemaBuilderException.java
------------------------------------------------------------------------------
svn:eol-style = native
Modified: avro/trunk/lang/java/avro/src/main/java/org/apache/avro/generic/GenericData.java
URL: http://svn.apache.org/viewvc/avro/trunk/lang/java/avro/src/main/java/org/apache/avro/generic/GenericData.java?rev=1476973&r1=1476972&r2=1476973&view=diff
==============================================================================
--- avro/trunk/lang/java/avro/src/main/java/org/apache/avro/generic/GenericData.java (original)
+++ avro/trunk/lang/java/avro/src/main/java/org/apache/avro/generic/GenericData.java Mon Apr 29 11:37:57 2013
@@ -47,7 +47,10 @@ import org.apache.avro.util.Utf8;
import org.codehaus.jackson.JsonNode;
-/** Utilities for generic Java data. */
+/** Utilities for generic Java data. See {@link GenericRecordBuilder} for a convenient
+ * way to build {@link GenericRecord} instances.
+ * @see GenericRecordBuilder
+ */
public class GenericData {
private static final GenericData INSTANCE = new GenericData();
@@ -72,7 +75,11 @@ public class GenericData {
protected GenericData() {}
- /** Default implementation of {@link GenericRecord}. */
+ /** Default implementation of {@link GenericRecord}. Note that this implementation
+ * does not fill in default values for fields if they are not specified; use {@link
+ * GenericRecordBuilder} in that case.
+ * @see GenericRecordBuilder
+ */
public static class Record implements GenericRecord, Comparable<Record> {
private final Schema schema;
private final Object[] values;
Modified: avro/trunk/lang/java/avro/src/main/java/org/apache/avro/generic/GenericRecordBuilder.java
URL: http://svn.apache.org/viewvc/avro/trunk/lang/java/avro/src/main/java/org/apache/avro/generic/GenericRecordBuilder.java?rev=1476973&r1=1476972&r2=1476973&view=diff
==============================================================================
--- avro/trunk/lang/java/avro/src/main/java/org/apache/avro/generic/GenericRecordBuilder.java (original)
+++ avro/trunk/lang/java/avro/src/main/java/org/apache/avro/generic/GenericRecordBuilder.java Mon Apr 29 11:37:57 2013
@@ -25,7 +25,8 @@ import org.apache.avro.Schema.Field;
import org.apache.avro.data.RecordBuilderBase;
import org.apache.avro.generic.GenericData.Record;
-/** A RecordBuilder for generic records */
+/** A RecordBuilder for generic records. GenericRecordBuilder fills in default values
+ * for fields if they are not specified. */
public class GenericRecordBuilder extends RecordBuilderBase<Record> {
private final GenericData.Record record;
Added: avro/trunk/lang/java/avro/src/test/java/org/apache/avro/TestSchemaBuilder.java
URL: http://svn.apache.org/viewvc/avro/trunk/lang/java/avro/src/test/java/org/apache/avro/TestSchemaBuilder.java?rev=1476973&view=auto
==============================================================================
--- avro/trunk/lang/java/avro/src/test/java/org/apache/avro/TestSchemaBuilder.java (added)
+++ avro/trunk/lang/java/avro/src/test/java/org/apache/avro/TestSchemaBuilder.java Mon Apr 29 11:37:57 2013
@@ -0,0 +1,348 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.avro;
+
+import junit.framework.Assert;
+import org.apache.avro.file.DataFileReader;
+import org.apache.avro.file.DataFileWriter;
+import org.apache.avro.generic.GenericData;
+import org.apache.avro.generic.GenericDatumReader;
+import org.apache.avro.generic.GenericDatumWriter;
+import org.apache.avro.generic.GenericRecordBuilder;
+import org.apache.avro.reflect.ReflectDatumWriter;
+import org.codehaus.jackson.node.BooleanNode;
+import org.codehaus.jackson.node.NullNode;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+public class TestSchemaBuilder {
+
+ private static final File DIR = new File(System.getProperty("test.dir", "/tmp"));
+ private static final File FILE = new File(DIR, "test.avro");
+
+ @Test
+ public void testRecord() {
+ Schema schema = SchemaBuilder
+ .recordType("myrecord").namespace("org.example").aliases("oldrecord")
+ .requiredString("f0")
+ .requiredLong("f1").doc("This is f1")
+ .optionalBoolean("f2", true)
+ .build();
+
+ Assert.assertEquals("myrecord", schema.getName());
+ Assert.assertEquals("org.example", schema.getNamespace());
+ Assert.assertEquals("org.example.oldrecord", schema.getAliases().iterator().next());
+ Assert.assertFalse(schema.isError());
+ List<Schema.Field> fields = schema.getFields();
+ Assert.assertEquals(3, fields.size());
+ Assert.assertEquals(
+ new Schema.Field("f0", Schema.create(Schema.Type.STRING), null, null),
+ fields.get(0));
+ Assert.assertEquals(
+ new Schema.Field("f1", Schema.create(Schema.Type.LONG), "This is f1", null),
+ fields.get(1));
+
+ List<Schema> types = new ArrayList<Schema>();
+ types.add(Schema.create(Schema.Type.BOOLEAN));
+ types.add(Schema.create(Schema.Type.NULL));
+ Schema optional = Schema.createUnion(types);
+ Assert.assertEquals(new Schema.Field("f2", optional, null, BooleanNode.getTrue()),
+ fields.get(2));
+ }
+
+ @Test
+ public void testNamespaces() {
+ Schema s1 = SchemaBuilder.recordType("myrecord").namespace("org.example")
+ .requiredInt("myint").build();
+ Schema s2 = SchemaBuilder.recordType("org.example.myrecord")
+ .requiredInt("myint").build();
+ Schema s3 = SchemaBuilder.recordType("org.example.myrecord").namespace("org.example2")
+ .requiredInt("myint").build();
+
+ Assert.assertEquals("myrecord", s1.getName());
+ Assert.assertEquals("myrecord", s2.getName());
+ Assert.assertEquals("myrecord", s3.getName());
+
+ Assert.assertEquals("org.example", s1.getNamespace());
+ Assert.assertEquals("org.example", s2.getNamespace());
+ Assert.assertEquals("org.example", s3.getNamespace()); // namespace call is ignored
+
+ Assert.assertEquals("org.example.myrecord", s1.getFullName());
+ Assert.assertEquals("org.example.myrecord", s2.getFullName());
+ Assert.assertEquals("org.example.myrecord", s3.getFullName());
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testMissingRecordName() {
+ SchemaBuilder
+ .recordType(null) // null name
+ .requiredString("f0")
+ .build();
+ }
+
+ @Test
+ public void testError() {
+ Schema schema = SchemaBuilder
+ .errorType("myerror")
+ .requiredString("message")
+ .build();
+
+ Assert.assertEquals("myerror", schema.getName());
+ Assert.assertTrue(schema.isError());
+ }
+
+ @Test
+ public void testRecordAll() throws IOException {
+ GenericData.Record defaultRecord = new GenericData.Record(
+ SchemaBuilder.recordType("nestedOptionalRecordWithDefault")
+ .requiredBoolean("nestedRequiredBoolean").build());
+ defaultRecord.put("nestedRequiredBoolean", true);
+
+ Schema schema = SchemaBuilder.recordType("recordAll")
+ .requiredBoolean("requiredBoolean")
+ .requiredBoolean("requiredBooleanWithDefault").defaultValue(true) // expert
+ .optionalBoolean("optionalBoolean")
+ .optionalBoolean("optionalBooleanWithDefault", true)
+ .requiredInt("requiredInt")
+ .optionalInt("optionalInt")
+ .optionalInt("optionalIntWithDefault", 1)
+ .requiredLong("requiredLong")
+ .optionalLong("optionalLong")
+ .optionalLong("optionalLongWithDefault", 1L)
+ .requiredFloat("requiredFloat")
+ .optionalFloat("optionalFloat")
+ .optionalFloat("optionalFloatWithDefault", 1.0f)
+ .requiredDouble("requiredDouble")
+ .optionalDouble("optionalDouble")
+ .optionalDouble("optionalDoubleWithDefault", 1.0)
+ .requiredBytes("requiredBytes")
+ .optionalBytes("optionalBytes")
+ .optionalBytes("optionalBytesWithDefault", new byte[]{(byte) 65})
+ .requiredString("requiredString")
+ .optionalString("optionalString")
+ .optionalString("optionalStringWithDefault", "a")
+ .requiredRecord("requiredRecord",
+ SchemaBuilder.recordType("nestedRequiredRecord")
+ .requiredBoolean("nestedRequiredBoolean").build())
+ .optionalRecord("optionalRecord",
+ SchemaBuilder.recordType("nestedOptionalRecord")
+ .requiredBoolean("nestedRequiredBoolean").build())
+ .optionalRecord("optionalRecordWithDefault",
+ SchemaBuilder.recordType("nestedOptionalRecordWithDefault")
+ .requiredBoolean("nestedRequiredBoolean").build(), defaultRecord)
+ .requiredEnum("requiredEnum",
+ SchemaBuilder.enumType("requiredEnum", "a", "b").build())
+ .optionalEnum("optionalEnum",
+ SchemaBuilder.enumType("optionalEnum", "a", "b").build())
+ .optionalEnum("optionalEnumWithDefault",
+ SchemaBuilder.enumType("optionalEnumWithDefault", "a", "b").build(), "b")
+ .requiredArray("requiredArray",
+ SchemaBuilder.arrayType(SchemaBuilder.STRING).build())
+ .optionalArray("optionalArray",
+ SchemaBuilder.arrayType(SchemaBuilder.STRING).build())
+ .optionalArray("optionalArrayWithDefault",
+ SchemaBuilder.arrayType(SchemaBuilder.STRING).build(),
+ Collections.singletonList("a"))
+ .requiredMap("requiredMap",
+ SchemaBuilder.mapType(SchemaBuilder.STRING).build())
+ .optionalMap("optionalMap",
+ SchemaBuilder.mapType(SchemaBuilder.STRING).build())
+ .optionalMap("optionalMapWithDefault",
+ SchemaBuilder.mapType(SchemaBuilder.STRING).build(),
+ Collections.singletonMap("a", "b"))
+ .requiredFixed("requiredFixed",
+ SchemaBuilder.fixedType("requiredFixed", 1).build())
+ .optionalFixed("optionalFixed",
+ SchemaBuilder.fixedType("optionalFixed", 1).build())
+ .optionalFixed("optionalFixedWithDefault",
+ SchemaBuilder.fixedType("optionalFixedWithDefault", 1).build(),
+ new byte[]{(byte) 65})
+ .unionType("unionType", SchemaBuilder.unionType(SchemaBuilder.LONG,
+ SchemaBuilder.NULL).build())
+ .unionBoolean("unionBooleanWithDefault", true, SchemaBuilder.INT)
+ .unionInt("unionIntWithDefault", 1, SchemaBuilder.NULL)
+ .unionLong("unionLongWithDefault", 1L, SchemaBuilder.INT)
+ .unionFloat("unionFloatWithDefault", 1.0f, SchemaBuilder.INT)
+ .unionDouble("unionDoubleWithDefault", 1.0, SchemaBuilder.INT)
+ .unionBytes("unionBytesWithDefault", new byte[]{(byte) 65}, SchemaBuilder.INT)
+ .unionString("unionStringWithDefault", "a", SchemaBuilder.INT)
+ .unionRecord("unionRecordWithDefault",
+ SchemaBuilder.recordType("nestedUnionRecordWithDefault")
+ .requiredBoolean("nestedRequiredBoolean").build(), defaultRecord,
+ SchemaBuilder.INT)
+ .unionEnum("unionEnumWithDefault",
+ SchemaBuilder.enumType("nestedUnionEnumWithDefault", "a", "b").build(), "b",
+ SchemaBuilder.INT)
+ .unionArray("unionArrayWithDefault",
+ SchemaBuilder.arrayType(SchemaBuilder.STRING).build(),
+ Collections.singletonList("a"),
+ SchemaBuilder.INT)
+ .unionMap("unionMapWithDefault",
+ SchemaBuilder.mapType(SchemaBuilder.STRING).build(),
+ Collections.singletonMap("a", "b"),
+ SchemaBuilder.INT)
+ .unionFixed("unionFixedWithDefault",
+ SchemaBuilder.fixedType("nestedUnionFixedWithDefault", 1).build(),
+ new byte[]{(byte) 65},
+ SchemaBuilder.INT)
+ .build();
+
+ Schema expected = new Schema.Parser().parse
+ (getClass().getResourceAsStream("/SchemaBuilder.avsc"));
+
+ // To regenerate the expected schema, uncomment the following line
+ // and copy the test output to TestSchemaBuilder.avsc
+ //System.out.println(schema.toString(true));
+
+ Assert.assertEquals(expected.toString(true), schema.toString(true));
+
+ }
+
+ private List<Schema.Field> fields(Schema.Field... fields) {
+ return Arrays.asList(fields);
+ }
+
+ @Test
+ public void testRecursiveRecord() {
+ Schema schema = SchemaBuilder
+ .recordType("LongList")
+ .requiredLong("value")
+ .optionalRecord("next", SchemaBuilder.recordReference("LongList").build())
+ .build();
+
+ Assert.assertEquals("LongList", schema.getName());
+ List<Schema.Field> fields = schema.getFields();
+ Assert.assertEquals(2, fields.size());
+ Assert.assertEquals(new Schema.Field("value", Schema.create(Schema.Type.LONG), null, null),
+ fields.get(0));
+
+ Assert.assertEquals(Schema.Type.UNION, fields.get(1).schema().getType());
+
+ Assert.assertEquals(Schema.Type.NULL, fields.get(1).schema().getTypes().get(0)
+ .getType());
+ Schema recordSchema = fields.get(1).schema().getTypes().get(1);
+ Assert.assertEquals(Schema.Type.RECORD, recordSchema.getType());
+ Assert.assertEquals("LongList", recordSchema.getName());
+ Assert.assertEquals(NullNode.getInstance(), fields.get(1).defaultValue());
+ }
+
+ @Test
+ public void testEnum() {
+ Schema schema = SchemaBuilder.enumType("myenum", "a", "b").build();
+ Assert.assertEquals(Schema.createEnum("myenum", null, null,
+ Arrays.asList("a", "b")), schema);
+ }
+
+ @Test
+ public void testArray() {
+ Schema schema = SchemaBuilder.arrayType(SchemaBuilder.LONG).build();
+ Assert.assertEquals(Schema.createArray(Schema.create(Schema.Type.LONG)), schema);
+ }
+
+ @Test
+ public void testMap() {
+ Schema schema = SchemaBuilder.mapType(SchemaBuilder.LONG).build();
+ Assert.assertEquals(Schema.createMap(Schema.create(Schema.Type.LONG)), schema);
+ }
+
+ @Test
+ public void testFixed() {
+ Schema schema = SchemaBuilder.fixedType("myfixed", 16).build();
+ Assert.assertEquals(Schema.createFixed("myfixed", null, null, 16), schema);
+ }
+
+ @Test
+ public void testUnion() {
+ Schema schema = SchemaBuilder
+ .unionType(SchemaBuilder.LONG, SchemaBuilder.NULL)
+ .build();
+ List<Schema> types = new ArrayList<Schema>();
+ types.add(Schema.create(Schema.Type.LONG));
+ types.add(Schema.create(Schema.Type.NULL));
+ Assert.assertEquals(Schema.createUnion(types), schema);
+ }
+
+ @Test
+ public void testDefaults() throws IOException {
+ Schema writeSchema = SchemaBuilder.recordType("r")
+ .requiredInt("requiredInt")
+ .optionalInt("optionalInt")
+ .optionalInt("optionalIntWithDefault", 3)
+ .build();
+
+ GenericData.Record rec1 = new GenericRecordBuilder(writeSchema)
+ .set("requiredInt", 1)
+ .build();
+
+ Assert.assertEquals(1, rec1.get("requiredInt"));
+ Assert.assertEquals(null, rec1.get("optionalInt"));
+ Assert.assertEquals(3, rec1.get("optionalIntWithDefault"));
+
+ GenericData.Record rec2 = new GenericRecordBuilder(writeSchema)
+ .set("requiredInt", 1)
+ .set("optionalInt", 2)
+ .set("optionalIntWithDefault", 13)
+ .build();
+
+ Assert.assertEquals(1, rec2.get("requiredInt"));
+ Assert.assertEquals(2, rec2.get("optionalInt"));
+ Assert.assertEquals(13, rec2.get("optionalIntWithDefault"));
+
+ // write to file
+ DataFileWriter<Object> writer =
+ new DataFileWriter<Object>(new GenericDatumWriter<Object>());
+ writer.create(writeSchema, FILE);
+ writer.append(rec1);
+ writer.append(rec2);
+ writer.close();
+
+ Schema readSchema = SchemaBuilder.recordType("r")
+ .requiredInt("requiredInt")
+ .optionalInt("optionalInt")
+ .optionalInt("optionalIntWithDefault", 3)
+ .optionalInt("newOptionalInt")
+ .optionalInt("newOptionalIntWithDefault", 5)
+ .build();
+
+ DataFileReader<GenericData.Record> reader =
+ new DataFileReader<GenericData.Record>(FILE,
+ new GenericDatumReader<GenericData.Record>(writeSchema, readSchema));
+
+ GenericData.Record rec1read = reader.iterator().next();
+ Assert.assertEquals(1, rec1read.get("requiredInt"));
+ Assert.assertEquals(null, rec1read.get("optionalInt"));
+ Assert.assertEquals(3, rec1read.get("optionalIntWithDefault"));
+ Assert.assertEquals(null, rec1read.get("newOptionalInt"));
+ Assert.assertEquals(5, rec1read.get("newOptionalIntWithDefault"));
+
+ GenericData.Record rec2read = reader.iterator().next();
+ Assert.assertEquals(1, rec2read.get("requiredInt"));
+ Assert.assertEquals(2, rec2read.get("optionalInt"));
+ Assert.assertEquals(13, rec2read.get("optionalIntWithDefault"));
+ Assert.assertEquals(null, rec2read.get("newOptionalInt"));
+ Assert.assertEquals(5, rec2read.get("newOptionalIntWithDefault"));
+ }
+
+}
Propchange: avro/trunk/lang/java/avro/src/test/java/org/apache/avro/TestSchemaBuilder.java
------------------------------------------------------------------------------
svn:eol-style = native
Added: avro/trunk/lang/java/avro/src/test/resources/SchemaBuilder.avsc
URL: http://svn.apache.org/viewvc/avro/trunk/lang/java/avro/src/test/resources/SchemaBuilder.avsc?rev=1476973&view=auto
==============================================================================
--- avro/trunk/lang/java/avro/src/test/resources/SchemaBuilder.avsc (added)
+++ avro/trunk/lang/java/avro/src/test/resources/SchemaBuilder.avsc Mon Apr 29 11:37:57 2013
@@ -0,0 +1,284 @@
+{
+ "type" : "record",
+ "name" : "recordAll",
+ "fields" : [ {
+ "name" : "requiredBoolean",
+ "type" : "boolean"
+ }, {
+ "name" : "requiredBooleanWithDefault",
+ "type" : "boolean",
+ "default" : true
+ }, {
+ "name" : "optionalBoolean",
+ "type" : [ "null", "boolean" ],
+ "default" : null
+ }, {
+ "name" : "optionalBooleanWithDefault",
+ "type" : [ "boolean", "null" ],
+ "default" : true
+ }, {
+ "name" : "requiredInt",
+ "type" : "int"
+ }, {
+ "name" : "optionalInt",
+ "type" : [ "null", "int" ],
+ "default" : null
+ }, {
+ "name" : "optionalIntWithDefault",
+ "type" : [ "int", "null" ],
+ "default" : 1
+ }, {
+ "name" : "requiredLong",
+ "type" : "long"
+ }, {
+ "name" : "optionalLong",
+ "type" : [ "null", "long" ],
+ "default" : null
+ }, {
+ "name" : "optionalLongWithDefault",
+ "type" : [ "long", "null" ],
+ "default" : 1
+ }, {
+ "name" : "requiredFloat",
+ "type" : "float"
+ }, {
+ "name" : "optionalFloat",
+ "type" : [ "null", "float" ],
+ "default" : null
+ }, {
+ "name" : "optionalFloatWithDefault",
+ "type" : [ "float", "null" ],
+ "default" : 1.0
+ }, {
+ "name" : "requiredDouble",
+ "type" : "double"
+ }, {
+ "name" : "optionalDouble",
+ "type" : [ "null", "double" ],
+ "default" : null
+ }, {
+ "name" : "optionalDoubleWithDefault",
+ "type" : [ "double", "null" ],
+ "default" : 1.0
+ }, {
+ "name" : "requiredBytes",
+ "type" : "bytes"
+ }, {
+ "name" : "optionalBytes",
+ "type" : [ "null", "bytes" ],
+ "default" : null
+ }, {
+ "name" : "optionalBytesWithDefault",
+ "type" : [ "bytes", "null" ],
+ "default" : "A"
+ }, {
+ "name" : "requiredString",
+ "type" : "string"
+ }, {
+ "name" : "optionalString",
+ "type" : [ "null", "string" ],
+ "default" : null
+ }, {
+ "name" : "optionalStringWithDefault",
+ "type" : [ "string", "null" ],
+ "default" : "a"
+ }, {
+ "name" : "requiredRecord",
+ "type" : {
+ "type" : "record",
+ "name" : "nestedRequiredRecord",
+ "fields" : [ {
+ "name" : "nestedRequiredBoolean",
+ "type" : "boolean"
+ } ]
+ }
+ }, {
+ "name" : "optionalRecord",
+ "type" : [ "null", {
+ "type" : "record",
+ "name" : "nestedOptionalRecord",
+ "fields" : [ {
+ "name" : "nestedRequiredBoolean",
+ "type" : "boolean"
+ } ]
+ } ],
+ "default" : null
+ }, {
+ "name" : "optionalRecordWithDefault",
+ "type" : [ {
+ "type" : "record",
+ "name" : "nestedOptionalRecordWithDefault",
+ "fields" : [ {
+ "name" : "nestedRequiredBoolean",
+ "type" : "boolean"
+ } ]
+ }, "null" ],
+ "default" : {
+ "nestedRequiredBoolean" : true
+ }
+ }, {
+ "name" : "requiredEnum",
+ "type" : {
+ "type" : "enum",
+ "name" : "requiredEnum",
+ "symbols" : [ "a", "b" ]
+ }
+ }, {
+ "name" : "optionalEnum",
+ "type" : [ "null", {
+ "type" : "enum",
+ "name" : "optionalEnum",
+ "symbols" : [ "a", "b" ]
+ } ],
+ "default" : null
+ }, {
+ "name" : "optionalEnumWithDefault",
+ "type" : [ {
+ "type" : "enum",
+ "name" : "optionalEnumWithDefault",
+ "symbols" : [ "a", "b" ]
+ }, "null" ],
+ "default" : "b"
+ }, {
+ "name" : "requiredArray",
+ "type" : {
+ "type" : "array",
+ "items" : "string"
+ }
+ }, {
+ "name" : "optionalArray",
+ "type" : [ "null", {
+ "type" : "array",
+ "items" : "string"
+ } ],
+ "default" : null
+ }, {
+ "name" : "optionalArrayWithDefault",
+ "type" : [ {
+ "type" : "array",
+ "items" : "string"
+ }, "null" ],
+ "default" : [ "a" ]
+ }, {
+ "name" : "requiredMap",
+ "type" : {
+ "type" : "map",
+ "values" : "string"
+ }
+ }, {
+ "name" : "optionalMap",
+ "type" : [ "null", {
+ "type" : "map",
+ "values" : "string"
+ } ],
+ "default" : null
+ }, {
+ "name" : "optionalMapWithDefault",
+ "type" : [ {
+ "type" : "map",
+ "values" : "string"
+ }, "null" ],
+ "default" : {
+ "a" : "b"
+ }
+ }, {
+ "name" : "requiredFixed",
+ "type" : {
+ "type" : "fixed",
+ "name" : "requiredFixed",
+ "size" : 1
+ }
+ }, {
+ "name" : "optionalFixed",
+ "type" : [ "null", {
+ "type" : "fixed",
+ "name" : "optionalFixed",
+ "size" : 1
+ } ],
+ "default" : null
+ }, {
+ "name" : "optionalFixedWithDefault",
+ "type" : [ {
+ "type" : "fixed",
+ "name" : "optionalFixedWithDefault",
+ "size" : 1
+ }, "null" ],
+ "default" : "A"
+ }, {
+ "name" : "unionType",
+ "type" : [ "long", "null" ]
+ }, {
+ "name" : "unionBooleanWithDefault",
+ "type" : [ "boolean", "int" ],
+ "default" : true
+ }, {
+ "name" : "unionIntWithDefault",
+ "type" : [ "int", "null" ],
+ "default" : 1
+ }, {
+ "name" : "unionLongWithDefault",
+ "type" : [ "long", "int" ],
+ "default" : 1
+ }, {
+ "name" : "unionFloatWithDefault",
+ "type" : [ "float", "int" ],
+ "default" : 1.0
+ }, {
+ "name" : "unionDoubleWithDefault",
+ "type" : [ "double", "int" ],
+ "default" : 1.0
+ }, {
+ "name" : "unionBytesWithDefault",
+ "type" : [ "bytes", "int" ],
+ "default" : "A"
+ }, {
+ "name" : "unionStringWithDefault",
+ "type" : [ "string", "int" ],
+ "default" : "a"
+ }, {
+ "name" : "unionRecordWithDefault",
+ "type" : [ {
+ "type" : "record",
+ "name" : "nestedUnionRecordWithDefault",
+ "fields" : [ {
+ "name" : "nestedRequiredBoolean",
+ "type" : "boolean"
+ } ]
+ }, "int" ],
+ "default" : {
+ "nestedRequiredBoolean" : true
+ }
+ }, {
+ "name" : "unionEnumWithDefault",
+ "type" : [ {
+ "type" : "enum",
+ "name" : "nestedUnionEnumWithDefault",
+ "symbols" : [ "a", "b" ]
+ }, "int" ],
+ "default" : "b"
+ }, {
+ "name" : "unionArrayWithDefault",
+ "type" : [ {
+ "type" : "array",
+ "items" : "string"
+ }, "int" ],
+ "default" : [ "a" ]
+ }, {
+ "name" : "unionMapWithDefault",
+ "type" : [ {
+ "type" : "map",
+ "values" : "string"
+ }, "int" ],
+ "default" : {
+ "a" : "b"
+ }
+ }, {
+ "name" : "unionFixedWithDefault",
+ "type" : [ {
+ "type" : "fixed",
+ "name" : "nestedUnionFixedWithDefault",
+ "size" : 1
+ }, "int" ],
+ "default" : "A"
+ } ]
+}