All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.avro.SchemaBuilder Maven / Gradle / Ivy

There is a newer version: 3.1.2-23
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.avro;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
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 io.trino.hive.$internal.com.fasterxml.jackson.core.io.JsonStringEncoder;
import org.apache.avro.Schema.Field;
import org.apache.avro.generic.GenericData;
import org.apache.avro.generic.GenericRecord;
import org.apache.avro.util.internal.JacksonUtils;

import io.trino.hive.$internal.com.fasterxml.jackson.databind.JsonNode;
import io.trino.hive.$internal.com.fasterxml.jackson.databind.ObjectMapper;
import io.trino.hive.$internal.com.fasterxml.jackson.databind.node.NullNode;
import io.trino.hive.$internal.com.fasterxml.jackson.databind.node.TextNode;

/**
 * 

* A fluent interface for building {@link Schema} instances. The flow of the API * is designed to mimic the * Avro Schema * Specification *

* For example, the below JSON schema and the fluent builder code to create it * are very similar: * *
 * {
 *   "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"}]}
 *   ]
 * }
 * 
* *
 * Schema schema = SchemaBuilder.record("HandshakeRequest").namespace("org.apache.avro.ipc").fields().name("clientHash")
 *     .type().fixed("MD5").size(16).noDefault().name("clientProtocol").type().nullable().stringType().noDefault()
 *     .name("serverHash").type("MD5").noDefault().name("meta").type().nullable().map().values().bytesType().noDefault()
 *     .endRecord();
 * 
*

* *

Usage Guide
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. *

*

Selecting and Building an Avro Type
The API analogy for the right * hand side of the Avro Schema JSON * *
 * "type":
 * 
* * 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. *

*

Primitive Types
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: * *
 * {"type":"int"}
 * {"type":{"name":"int"}}
 * {"type":{"name":"int", "customProp":"val"}}
 * 
* * The analogous code form for the above three JSON lines are the below three * lines: * *
 *  .intType()
 *  .intBuilder().endInt()
 *  .intBuilder().prop("customProp", "val").endInt()
 * 
* * 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. *
Named Types
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: *
  • {@link NamespacedBuilder#doc()}
  • *
  • {@link NamespacedBuilder#namespace(String)}
  • *
  • {@link NamespacedBuilder#aliases(String...)}
  • *
  • {@link PropBuilder#prop(String, String)}
  • *

    * Each named type completes configuration of the optional properties with its * own method: *

  • {@link FixedBuilder#size(int)}
  • *
  • {@link EnumBuilder#symbols(String...)}
  • *
  • {@link RecordBuilder#fields()}
  • Example use of a named type with all * optional parameters: * *
     * .enumeration("Suit").namespace("org.apache.test")
     *   .aliases("org.apache.test.OldSuit")
     *   .doc("CardSuits")
     *   .prop("customProp", "val")
     *   .symbols("SPADES", "HEARTS", "DIAMONDS", "CLUBS")
     * 
    * * Which is equivalent to the JSON: * *
     * { "type":"enum",
     *   "name":"Suit", "namespace":"org.apache.test",
     *   "aliases":["org.apache.test.OldSuit"],
     *   "doc":"Card Suits",
     *   "customProp":"val",
     *   "symbols":["SPADES", "HEARTS", "DIAMONDS", "CLUBS"]
     * }
     * 
    * *
    Nested Types
    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. * *
    Fields
    {@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()}. *

    * 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)} *

    * 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. * *

    Unions
    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: * *
     * .unionOf()
     *   .fixed("IPv4").size(4).and()
     *   .fixed("IPv6").size(16).and()
     *   .nullType().endUnion()
     * 
    * * is equivalent to the Avro schema JSON: * *
     * [
     *   {"type":"fixed", "name":"IPv4", "size":4},
     *   {"type":"fixed", "name":"IPv6", "size":16},
     *   "null"
     * ]
     * 
    * * In a field context, the first type of a union defines what default type is * allowed. *

    * 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: * *
     *   .unionOf().intType().and().nullType().endUnion()
     *   .nullable().intType()
     * 
    * * The below two field declarations are equivalent: * *
     *   .name("f").type().unionOf().nullType().and().longType().endUnion().nullDefault()
     *   .name("f").type().optional().longType()
     * 
    * *
    Explicit Types and Types by Name
    Types can also be specified * explicitly by passing in a Schema, or by name: * *
     *   .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
     * 
    * * 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 "". *

    * {@link SchemaBuilder#builder(String)} returns a type builder with a default * namespace. {@link SchemaBuilder#builder()} returns a type builder with no * default namespace. */ public class SchemaBuilder { private SchemaBuilder() { } /** * Create a builder for Avro schemas. */ public static TypeBuilder builder() { return new TypeBuilder<>(new SchemaCompletion(), new NameContext()); } /** * Create a builder for Avro schemas with a default namespace. Types created * without namespaces will inherit the namespace provided. */ public static TypeBuilder builder(String namespace) { return new TypeBuilder<>(new SchemaCompletion(), new NameContext().namespace(namespace)); } /** * Create a builder for an Avro record with the specified name. This is * equivalent to: * *

       * builder().record(name);
       * 
    * * @param name the record name */ public static RecordBuilder 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: * *
       * builder().enumeration(name);
       * 
    * * @param name the enum name */ public static EnumBuilder enumeration(String name) { return builder().enumeration(name); } /** * Create a builder for an Avro fixed type with the specified name and size. * This is equivalent to: * *
       * builder().fixed(name);
       * 
    * * @param name the fixed name */ public static FixedBuilder fixed(String name) { return builder().fixed(name); } /** * Create a builder for an Avro array This is equivalent to: * *
       * builder().array();
       * 
    */ public static ArrayBuilder array() { return builder().array(); } /** * Create a builder for an Avro map This is equivalent to: * *
       * builder().map();
       * 
    */ public static MapBuilder map() { return builder().map(); } /** * Create a builder for an Avro union This is equivalent to: * *
       * builder().unionOf();
       * 
    */ public static BaseTypeBuilder> unionOf() { return builder().unionOf(); } /** * Create a builder for a union of a type and null. This is a shortcut for: * *
       * builder().nullable();
       * 
    * * and the following two lines are equivalent: * *
       * nullable().intType();
       * 
    * *
       * unionOf().intType().and().nullType().endUnion();
       * 
    */ public static BaseTypeBuilder nullable() { return builder().nullable(); } /** * An abstract builder for all Avro types. All Avro types can have arbitrary * string key-value properties. */ public static abstract class PropBuilder> { private Map props = null; protected PropBuilder() { } /** * Set name-value pair properties for this type or field. */ public final S prop(String name, String val) { return prop(name, TextNode.valueOf(val)); } /** * Set name-value pair properties for this type or field. */ public final S prop(String name, Object value) { return prop(name, JacksonUtils.toJsonNode(value)); } // for internal use by the Parser final S prop(String name, JsonNode val) { if (!hasProps()) { props = new HashMap<>(); } props.put(name, val); return self(); } private boolean hasProps() { return (props != null); } final T addPropsTo(T jsonable) { if (hasProps()) { for (Map.Entry 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). *

    * All Avro named types and fields have 'doc', 'aliases', and 'name' components. * 'name' is required, and provided to this builder. 'doc' and 'aliases' are * optional. */ public static abstract class NamedBuilder> extends PropBuilder { private final String name; private final NameContext names; private String doc; private String[] aliases; protected NamedBuilder(NameContext names, String name) { checkRequired(name, "Type must have a name"); this.names = names; this.name = 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> extends NamedBuilder { private final Completion context; private String namespace; protected NamespacedBuilder(Completion context, NameContext names, String name) { super(names, name); this.context = context; } /** * Set the namespace of this type. To clear the namespace, set empty string. *

    * 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 context() { return context; } } /** * An abstraction for sharing code amongst all primitive type builders. */ private static abstract class PrimitiveBuilder> extends PropBuilder

    { private final Completion context; private final Schema immutable; protected PrimitiveBuilder(Completion 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 extends PrimitiveBuilder> { private BooleanBuilder(Completion context, NameContext names) { super(context, names, Schema.Type.BOOLEAN); } private static BooleanBuilder create(Completion context, NameContext names) { return new BooleanBuilder<>(context, names); } @Override protected BooleanBuilder 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 extends PrimitiveBuilder> { private IntBuilder(Completion context, NameContext names) { super(context, names, Schema.Type.INT); } private static IntBuilder create(Completion context, NameContext names) { return new IntBuilder<>(context, names); } @Override protected IntBuilder 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 extends PrimitiveBuilder> { private LongBuilder(Completion context, NameContext names) { super(context, names, Schema.Type.LONG); } private static LongBuilder create(Completion context, NameContext names) { return new LongBuilder<>(context, names); } @Override protected LongBuilder 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 extends PrimitiveBuilder> { private FloatBuilder(Completion context, NameContext names) { super(context, names, Schema.Type.FLOAT); } private static FloatBuilder create(Completion context, NameContext names) { return new FloatBuilder<>(context, names); } @Override protected FloatBuilder 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 extends PrimitiveBuilder> { private DoubleBuilder(Completion context, NameContext names) { super(context, names, Schema.Type.DOUBLE); } private static DoubleBuilder create(Completion context, NameContext names) { return new DoubleBuilder<>(context, names); } @Override protected DoubleBuilder 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 extends PrimitiveBuilder> { private StringBldr(Completion context, NameContext names) { super(context, names, Schema.Type.STRING); } private static StringBldr create(Completion context, NameContext names) { return new StringBldr<>(context, names); } @Override protected StringBldr 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 extends PrimitiveBuilder> { private BytesBuilder(Completion context, NameContext names) { super(context, names, Schema.Type.BYTES); } private static BytesBuilder create(Completion context, NameContext names) { return new BytesBuilder<>(context, names); } @Override protected BytesBuilder 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 extends PrimitiveBuilder> { private NullBuilder(Completion context, NameContext names) { super(context, names, Schema.Type.NULL); } private static NullBuilder create(Completion context, NameContext names) { return new NullBuilder<>(context, names); } @Override protected NullBuilder 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. *

    * Set properties with {@link #prop(String, String)}, namespace with * {@link #namespace(String)}, doc with {@link #doc(String)}, and aliases with * {@link #aliases(String[])}. *

    * The Fixed schema is finalized when its required size is set via * {@link #size(int)}. **/ public static final class FixedBuilder extends NamespacedBuilder> { private FixedBuilder(Completion context, NameContext names, String name) { super(context, names, name); } private static FixedBuilder create(Completion context, NameContext names, String name) { return new FixedBuilder<>(context, names, name); } @Override protected FixedBuilder 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. *

    * Set properties with {@link #prop(String, String)}, namespace with * {@link #namespace(String)}, doc with {@link #doc(String)}, and aliases with * {@link #aliases(String[])}. *

    * The Enum schema is finalized when its required symbols are set via * {@link #symbols(String[])}. **/ public static final class EnumBuilder extends NamespacedBuilder> { private EnumBuilder(Completion context, NameContext names, String name) { super(context, names, name); } private String enumDefault = null; private static EnumBuilder create(Completion context, NameContext names, String name) { return new EnumBuilder<>(context, names, name); } @Override protected EnumBuilder self() { return this; } /** * Configure this enum type's symbols, and end its configuration. Populates the * default if it was set. **/ public R symbols(String... symbols) { Schema schema = Schema.createEnum(name(), doc(), space(), Arrays.asList(symbols), this.enumDefault); completeSchema(schema); return context().complete(schema); } /** Set the default value of the enum. */ public EnumBuilder defaultSymbol(String enumDefault) { this.enumDefault = enumDefault; return self(); } } /** * Builds an Avro Map type with optional properties. *

    * Set properties with {@link #prop(String, String)}. *

    * The Map schema's properties are finalized when {@link #values()} or * {@link #values(Schema)} is called. **/ public static final class MapBuilder extends PropBuilder> { private final Completion context; private final NameContext names; private MapBuilder(Completion context, NameContext names) { this.context = context; this.names = names; } private static MapBuilder create(Completion context, NameContext names) { return new MapBuilder<>(context, names); } @Override protected MapBuilder 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 values() { return new TypeBuilder<>(new MapCompletion<>(this, context), names); } /** * Complete configuration of this map, setting the schema of the map values to * the schema provided. Returns control to the enclosing context. **/ public R values(Schema valueSchema) { return new MapCompletion<>(this, context).complete(valueSchema); } } /** * Builds an Avro Array type with optional properties. *

    * Set properties with {@link #prop(String, String)}. *

    * The Array schema's properties are finalized when {@link #items()} or * {@link #items(Schema)} is called. **/ public static final class ArrayBuilder extends PropBuilder> { private final Completion context; private final NameContext names; public ArrayBuilder(Completion context, NameContext names) { this.context = context; this.names = names; } private static ArrayBuilder create(Completion context, NameContext names) { return new ArrayBuilder<>(context, names); } @Override protected ArrayBuilder 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 items() { return new TypeBuilder<>(new ArrayCompletion<>(this, context), names); } /** * Complete configuration of this array, setting the schema of the array items * to the schema provided. Returns control to the enclosing context. **/ public R items(Schema itemsSchema) { return new ArrayCompletion<>(this, context).complete(itemsSchema); } } /** * internal class for passing the naming context around. This allows for the * following: *

  • Cache and re-use primitive schemas when they do not set properties.
  • *
  • Provide a default namespace for nested contexts (as the JSON Schema spec * does).
  • *
  • Allow previously defined named types or primitive types to be referenced * by name.
  • **/ private static class NameContext { private static final Set PRIMITIVES = new HashSet<>(); static { PRIMITIVES.add("null"); PRIMITIVES.add("boolean"); PRIMITIVES.add("int"); PRIMITIVES.add("long"); PRIMITIVES.add("float"); PRIMITIVES.add("double"); PRIMITIVES.add("bytes"); PRIMITIVES.add("string"); } private final HashMap schemas; private final String namespace; private NameContext() { this.schemas = new HashMap<>(); this.namespace = null; schemas.put("null", Schema.create(Schema.Type.NULL)); schemas.put("boolean", Schema.create(Schema.Type.BOOLEAN)); schemas.put("int", Schema.create(Schema.Type.INT)); schemas.put("long", Schema.create(Schema.Type.LONG)); schemas.put("float", Schema.create(Schema.Type.FLOAT)); schemas.put("double", Schema.create(Schema.Type.DOUBLE)); schemas.put("bytes", Schema.create(Schema.Type.BYTES)); schemas.put("string", Schema.create(Schema.Type.STRING)); } private NameContext(HashMap 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. *

    * The builder has two contexts: *

  • A naming context provides a default namespace and allows for previously * defined named types to be referenced from {@link #type(String)}
  • *
  • 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}
  • **/ public static class BaseTypeBuilder { private final Completion context; private final NameContext names; private BaseTypeBuilder(Completion 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. *

    * 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. *

    * 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: * *

         * booleanBuilder().endBoolean();
         * 
    */ 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 booleanBuilder() { return BooleanBuilder.create(context, names); } /** * A plain int type without custom properties. This is equivalent to: * *
         * intBuilder().endInt();
         * 
    */ 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 intBuilder() { return IntBuilder.create(context, names); } /** * A plain long type without custom properties. This is equivalent to: * *
         * longBuilder().endLong();
         * 
    */ 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 longBuilder() { return LongBuilder.create(context, names); } /** * A plain float type without custom properties. This is equivalent to: * *
         * floatBuilder().endFloat();
         * 
    */ 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 floatBuilder() { return FloatBuilder.create(context, names); } /** * A plain double type without custom properties. This is equivalent to: * *
         * doubleBuilder().endDouble();
         * 
    */ 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 doubleBuilder() { return DoubleBuilder.create(context, names); } /** * A plain string type without custom properties. This is equivalent to: * *
         * stringBuilder().endString();
         * 
    */ 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 stringBuilder() { return StringBldr.create(context, names); } /** * A plain bytes type without custom properties. This is equivalent to: * *
         * bytesBuilder().endBytes();
         * 
    */ 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 bytesBuilder() { return BytesBuilder.create(context, names); } /** * A plain null type without custom properties. This is equivalent to: * *
         * nullBuilder().endNull();
         * 
    */ 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 nullBuilder() { return NullBuilder.create(context, names); } /** * Build an Avro map type Example usage: * *
         * map().values().intType()
         * 
    * * Equivalent to Avro JSON Schema: * *
         * {"type":"map", "values":"int"}
         * 
    **/ public final MapBuilder map() { return MapBuilder.create(context, names); } /** * Build an Avro array type Example usage: * *
         * array().items().longType()
         * 
    * * Equivalent to Avro JSON Schema: * *
         * {"type":"array", "values":"long"}
         * 
    **/ public final ArrayBuilder array() { return ArrayBuilder.create(context, names); } /** * Build an Avro fixed type. Example usage: * *
         * fixed("com.foo.IPv4").size(4)
         * 
    * * Equivalent to Avro JSON Schema: * *
         * {"type":"fixed", "name":"com.foo.IPv4", "size":4}
         * 
    **/ public final FixedBuilder fixed(String name) { return FixedBuilder.create(context, names, name); } /** * Build an Avro enum type. Example usage: * *
         * enumeration("Suits").namespace("org.cards").doc("card suit names").defaultSymbol("HEART").symbols("HEART", "SPADE",
         *     "DIAMOND", "CLUB")
         * 
    * * Equivalent to Avro JSON Schema: * *
         * {"type":"enum", "name":"Suits", "namespace":"org.cards",
         *  "doc":"card suit names", "symbols":[
         *    "HEART", "SPADE", "DIAMOND", "CLUB"], "default":"HEART"}
         * 
    **/ public final EnumBuilder enumeration(String name) { return EnumBuilder.create(context, names, name); } /** * Build an Avro record type. Example usage: * *
         * record("com.foo.Foo").fields().name("field1").typeInt().intDefault(1).name("field2").typeString().noDefault()
         *     .name("field3").optional().typeFixed("FooFixed").size(4).endRecord()
         * 
    * * Equivalent to Avro JSON Schema: * *
         * {"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}
         *     ]}
         *   ]}
         * 
    **/ public final RecordBuilder record(String name) { return RecordBuilder.create(context, names, name); } /** * Build an Avro union schema type. Example usage: * *
         * unionOf().stringType().and().bytesType().endUnion()
         * 
    **/ protected BaseTypeBuilder> unionOf() { return UnionBuilder.create(context, names); } /** * A shortcut for building a union of a type and null. *

    * For example, the code snippets below are equivalent: * *

         * nullable().booleanType()
         * 
    * *
         * unionOf().booleanType().and().nullType().endUnion()
         * 
    **/ protected BaseTypeBuilder nullable() { return new BaseTypeBuilder<>(new NullableCompletion<>(context), names); } } /** * A Builder for creating any Avro schema type. **/ public static final class TypeBuilder extends BaseTypeBuilder { private TypeBuilder(Completion context, NameContext names) { super(context, names); } @Override public BaseTypeBuilder> unionOf() { return super.unionOf(); } @Override public BaseTypeBuilder nullable() { return super.nullable(); } } /** A special builder for unions. Unions cannot nest unions directly **/ private static final class UnionBuilder extends BaseTypeBuilder> { private UnionBuilder(Completion context, NameContext names) { this(context, names, new ArrayList<>()); } private static UnionBuilder create(Completion context, NameContext names) { return new UnionBuilder<>(context, names); } private UnionBuilder(Completion context, NameContext names, List schemas) { super(new UnionCompletion<>(context, names, schemas), names); } } /** * A special Builder for Record fields. The API is very similar to * {@link BaseTypeBuilder}. However, fields have their own names, properties, * and default values. *

    * 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. *

    * For example, an int field with default value 1: * *

       * intSimple().withDefault(1);
       * 
    * * or an array with items that are optional int types: * *
       * array().items().optional().intType();
       * 
    */ public static class BaseFieldTypeBuilder { protected final FieldBuilder bldr; protected final NameContext names; private final CompletionWrapper wrapper; protected BaseFieldTypeBuilder(FieldBuilder bldr, CompletionWrapper wrapper) { this.bldr = bldr; this.names = bldr.names(); this.wrapper = wrapper; } /** * A plain boolean type without custom properties. This is equivalent to: * *
         * booleanBuilder().endBoolean();
         * 
    */ public final BooleanDefault 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> booleanBuilder() { return BooleanBuilder.create(wrap(new BooleanDefault<>(bldr)), names); } /** * A plain int type without custom properties. This is equivalent to: * *
         * intBuilder().endInt();
         * 
    */ public final IntDefault 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> intBuilder() { return IntBuilder.create(wrap(new IntDefault<>(bldr)), names); } /** * A plain long type without custom properties. This is equivalent to: * *
         * longBuilder().endLong();
         * 
    */ public final LongDefault 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> longBuilder() { return LongBuilder.create(wrap(new LongDefault<>(bldr)), names); } /** * A plain float type without custom properties. This is equivalent to: * *
         * floatBuilder().endFloat();
         * 
    */ public final FloatDefault 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> floatBuilder() { return FloatBuilder.create(wrap(new FloatDefault<>(bldr)), names); } /** * A plain double type without custom properties. This is equivalent to: * *
         * doubleBuilder().endDouble();
         * 
    */ public final DoubleDefault 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> doubleBuilder() { return DoubleBuilder.create(wrap(new DoubleDefault<>(bldr)), names); } /** * A plain string type without custom properties. This is equivalent to: * *
         * stringBuilder().endString();
         * 
    */ public final StringDefault 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> stringBuilder() { return StringBldr.create(wrap(new StringDefault<>(bldr)), names); } /** * A plain bytes type without custom properties. This is equivalent to: * *
         * bytesBuilder().endBytes();
         * 
    */ public final BytesDefault 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> bytesBuilder() { return BytesBuilder.create(wrap(new BytesDefault<>(bldr)), names); } /** * A plain null type without custom properties. This is equivalent to: * *
         * nullBuilder().endNull();
         * 
    */ public final NullDefault 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> nullBuilder() { return NullBuilder.create(wrap(new NullDefault<>(bldr)), names); } /** Build an Avro map type **/ public final MapBuilder> map() { return MapBuilder.create(wrap(new MapDefault<>(bldr)), names); } /** Build an Avro array type **/ public final ArrayBuilder> array() { return ArrayBuilder.create(wrap(new ArrayDefault<>(bldr)), names); } /** Build an Avro fixed type. **/ public final FixedBuilder> fixed(String name) { return FixedBuilder.create(wrap(new FixedDefault<>(bldr)), names, name); } /** Build an Avro enum type. **/ public final EnumBuilder> enumeration(String name) { return EnumBuilder.create(wrap(new EnumDefault<>(bldr)), names, name); } /** Build an Avro record type. **/ public final RecordBuilder> record(String name) { return RecordBuilder.create(wrap(new RecordDefault<>(bldr)), names, name); } private Completion wrap(Completion 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 extends BaseFieldTypeBuilder { private FieldTypeBuilder(FieldBuilder bldr) { super(bldr, null); } /** Build an Avro union schema type. **/ public UnionFieldTypeBuilder unionOf() { return new UnionFieldTypeBuilder<>(bldr); } /** * A shortcut for building a union of a type and null, with an optional default * value of the non-null type. *

    * For example, the two code snippets below are equivalent: * *

         * nullable().booleanType().booleanDefault(true)
         * 
    * *
         * unionOf().booleanType().and().nullType().endUnion().booleanDefault(true)
         * 
    **/ public BaseFieldTypeBuilder nullable() { return new BaseFieldTypeBuilder<>(bldr, new NullableCompletionWrapper()); } /** * A shortcut for building a union of null and a type, with a null default. *

    * For example, the two code snippets below are equivalent: * *

         * optional().booleanType()
         * 
    * *
         * unionOf().nullType().and().booleanType().endUnion().nullDefault()
         * 
    */ public BaseTypeBuilder> optional() { return new BaseTypeBuilder<>(new OptionalCompletion<>(bldr), names); } } /** * Builder for a union field. The first type in the union corresponds to the * possible default value type. */ public static final class UnionFieldTypeBuilder { private final FieldBuilder bldr; private final NameContext names; private UnionFieldTypeBuilder(FieldBuilder bldr) { this.bldr = bldr; this.names = bldr.names(); } /** * A plain boolean type without custom properties. This is equivalent to: * *
         * booleanBuilder().endBoolean();
         * 
    */ public UnionAccumulator> booleanType() { return booleanBuilder().endBoolean(); } /** * Build a boolean type that can set custom properties. If custom properties are * not needed it is simpler to use {@link #booleanType()}. */ public BooleanBuilder>> booleanBuilder() { return BooleanBuilder.create(completion(new BooleanDefault<>(bldr)), names); } /** * A plain int type without custom properties. This is equivalent to: * *
         * intBuilder().endInt();
         * 
    */ public UnionAccumulator> intType() { return intBuilder().endInt(); } /** * Build an int type that can set custom properties. If custom properties are * not needed it is simpler to use {@link #intType()}. */ public IntBuilder>> intBuilder() { return IntBuilder.create(completion(new IntDefault<>(bldr)), names); } /** * A plain long type without custom properties. This is equivalent to: * *
         * longBuilder().endLong();
         * 
    */ public UnionAccumulator> longType() { return longBuilder().endLong(); } /** * Build a long type that can set custom properties. If custom properties are * not needed it is simpler to use {@link #longType()}. */ public LongBuilder>> longBuilder() { return LongBuilder.create(completion(new LongDefault<>(bldr)), names); } /** * A plain float type without custom properties. This is equivalent to: * *
         * floatBuilder().endFloat();
         * 
    */ public UnionAccumulator> floatType() { return floatBuilder().endFloat(); } /** * Build a float type that can set custom properties. If custom properties are * not needed it is simpler to use {@link #floatType()}. */ public FloatBuilder>> floatBuilder() { return FloatBuilder.create(completion(new FloatDefault<>(bldr)), names); } /** * A plain double type without custom properties. This is equivalent to: * *
         * doubleBuilder().endDouble();
         * 
    */ public UnionAccumulator> doubleType() { return doubleBuilder().endDouble(); } /** * Build a double type that can set custom properties. If custom properties are * not needed it is simpler to use {@link #doubleType()}. */ public DoubleBuilder>> doubleBuilder() { return DoubleBuilder.create(completion(new DoubleDefault<>(bldr)), names); } /** * A plain string type without custom properties. This is equivalent to: * *
         * stringBuilder().endString();
         * 
    */ public UnionAccumulator> stringType() { return stringBuilder().endString(); } /** * Build a string type that can set custom properties. If custom properties are * not needed it is simpler to use {@link #stringType()}. */ public StringBldr>> stringBuilder() { return StringBldr.create(completion(new StringDefault<>(bldr)), names); } /** * A plain bytes type without custom properties. This is equivalent to: * *
         * bytesBuilder().endBytes();
         * 
    */ public UnionAccumulator> bytesType() { return bytesBuilder().endBytes(); } /** * Build a bytes type that can set custom properties. If custom properties are * not needed it is simpler to use {@link #bytesType()}. */ public BytesBuilder>> bytesBuilder() { return BytesBuilder.create(completion(new BytesDefault<>(bldr)), names); } /** * A plain null type without custom properties. This is equivalent to: * *
         * nullBuilder().endNull();
         * 
    */ public UnionAccumulator> nullType() { return nullBuilder().endNull(); } /** * Build a null type that can set custom properties. If custom properties are * not needed it is simpler to use {@link #nullType()}. */ public NullBuilder>> nullBuilder() { return NullBuilder.create(completion(new NullDefault<>(bldr)), names); } /** Build an Avro map type **/ public MapBuilder>> map() { return MapBuilder.create(completion(new MapDefault<>(bldr)), names); } /** Build an Avro array type **/ public ArrayBuilder>> array() { return ArrayBuilder.create(completion(new ArrayDefault<>(bldr)), names); } /** Build an Avro fixed type. **/ public FixedBuilder>> fixed(String name) { return FixedBuilder.create(completion(new FixedDefault<>(bldr)), names, name); } /** Build an Avro enum type. **/ public EnumBuilder>> enumeration(String name) { return EnumBuilder.create(completion(new EnumDefault<>(bldr)), names, name); } /** Build an Avro record type. **/ public RecordBuilder>> record(String name) { return RecordBuilder.create(completion(new RecordDefault<>(bldr)), names, name); } private UnionCompletion completion(Completion context) { return new UnionCompletion<>(context, names, new ArrayList<>()); } } public final static class RecordBuilder extends NamespacedBuilder> { private RecordBuilder(Completion context, NameContext names, String name) { super(context, names, name); } private static RecordBuilder create(Completion context, NameContext names, String name) { return new RecordBuilder<>(context, names, name); } @Override protected RecordBuilder self() { return this; } public FieldAssembler fields() { Schema record = Schema.createRecord(name(), doc(), space(), false); // place the record in the name context, fields yet to be set. completeSchema(record); return new FieldAssembler<>(context(), names().namespace(record.getNamespace()), record); } } public final static class FieldAssembler { private final List fields = new ArrayList<>(); private final Completion context; private final NameContext names; private final Schema record; private FieldAssembler(Completion context, NameContext names, Schema record) { this.context = context; this.names = names; this.record = record; } /** * Add a field with the given name. * * @return A {@link FieldBuilder} for the given name. */ public FieldBuilder name(String fieldName) { return new FieldBuilder<>(this, names, fieldName); } /** * Shortcut for creating a boolean field with the given name and no default. *

    * This is equivalent to: * *

         * name(fieldName).type().booleanType().noDefault()
         * 
    */ public FieldAssembler requiredBoolean(String fieldName) { return name(fieldName).type().booleanType().noDefault(); } /** * Shortcut for creating an optional boolean field: a union of null and boolean * with null default. *

    * This is equivalent to: * *

         * name(fieldName).type().optional().booleanType()
         * 
    */ public FieldAssembler optionalBoolean(String fieldName) { return name(fieldName).type().optional().booleanType(); } /** * Shortcut for creating a nullable boolean field: a union of boolean and null * with an boolean default. *

    * This is equivalent to: * *

         * name(fieldName).type().nullable().booleanType().booleanDefault(defaultVal)
         * 
    */ public FieldAssembler nullableBoolean(String fieldName, boolean defaultVal) { return name(fieldName).type().nullable().booleanType().booleanDefault(defaultVal); } /** * Shortcut for creating an int field with the given name and no default. *

    * This is equivalent to: * *

         * name(fieldName).type().intType().noDefault()
         * 
    */ public FieldAssembler requiredInt(String fieldName) { return name(fieldName).type().intType().noDefault(); } /** * Shortcut for creating an optional int field: a union of null and int with * null default. *

    * This is equivalent to: * *

         * name(fieldName).type().optional().intType()
         * 
    */ public FieldAssembler optionalInt(String fieldName) { return name(fieldName).type().optional().intType(); } /** * Shortcut for creating a nullable int field: a union of int and null with an * int default. *

    * This is equivalent to: * *

         * name(fieldName).type().nullable().intType().intDefault(defaultVal)
         * 
    */ public FieldAssembler nullableInt(String fieldName, int defaultVal) { return name(fieldName).type().nullable().intType().intDefault(defaultVal); } /** * Shortcut for creating a long field with the given name and no default. *

    * This is equivalent to: * *

         * name(fieldName).type().longType().noDefault()
         * 
    */ public FieldAssembler requiredLong(String fieldName) { return name(fieldName).type().longType().noDefault(); } /** * Shortcut for creating an optional long field: a union of null and long with * null default. *

    * This is equivalent to: * *

         * name(fieldName).type().optional().longType()
         * 
    */ public FieldAssembler optionalLong(String fieldName) { return name(fieldName).type().optional().longType(); } /** * Shortcut for creating a nullable long field: a union of long and null with a * long default. *

    * This is equivalent to: * *

         * name(fieldName).type().nullable().longType().longDefault(defaultVal)
         * 
    */ public FieldAssembler nullableLong(String fieldName, long defaultVal) { return name(fieldName).type().nullable().longType().longDefault(defaultVal); } /** * Shortcut for creating a float field with the given name and no default. *

    * This is equivalent to: * *

         * name(fieldName).type().floatType().noDefault()
         * 
    */ public FieldAssembler requiredFloat(String fieldName) { return name(fieldName).type().floatType().noDefault(); } /** * Shortcut for creating an optional float field: a union of null and float with * null default. *

    * This is equivalent to: * *

         * name(fieldName).type().optional().floatType()
         * 
    */ public FieldAssembler optionalFloat(String fieldName) { return name(fieldName).type().optional().floatType(); } /** * Shortcut for creating a nullable float field: a union of float and null with * a float default. *

    * This is equivalent to: * *

         * name(fieldName).type().nullable().floatType().floatDefault(defaultVal)
         * 
    */ public FieldAssembler nullableFloat(String fieldName, float defaultVal) { return name(fieldName).type().nullable().floatType().floatDefault(defaultVal); } /** * Shortcut for creating a double field with the given name and no default. *

    * This is equivalent to: * *

         * name(fieldName).type().doubleType().noDefault()
         * 
    */ public FieldAssembler requiredDouble(String fieldName) { return name(fieldName).type().doubleType().noDefault(); } /** * Shortcut for creating an optional double field: a union of null and double * with null default. *

    * This is equivalent to: * *

         * name(fieldName).type().optional().doubleType()
         * 
    */ public FieldAssembler optionalDouble(String fieldName) { return name(fieldName).type().optional().doubleType(); } /** * Shortcut for creating a nullable double field: a union of double and null * with a double default. *

    * This is equivalent to: * *

         * name(fieldName).type().nullable().doubleType().doubleDefault(defaultVal)
         * 
    */ public FieldAssembler nullableDouble(String fieldName, double defaultVal) { return name(fieldName).type().nullable().doubleType().doubleDefault(defaultVal); } /** * Shortcut for creating a string field with the given name and no default. *

    * This is equivalent to: * *

         * name(fieldName).type().stringType().noDefault()
         * 
    */ public FieldAssembler requiredString(String fieldName) { return name(fieldName).type().stringType().noDefault(); } /** * Shortcut for creating an optional string field: a union of null and string * with null default. *

    * This is equivalent to: * *

         * name(fieldName).type().optional().stringType()
         * 
    */ public FieldAssembler optionalString(String fieldName) { return name(fieldName).type().optional().stringType(); } /** * Shortcut for creating a nullable string field: a union of string and null * with a string default. *

    * This is equivalent to: * *

         * name(fieldName).type().nullable().stringType().stringDefault(defaultVal)
         * 
    */ public FieldAssembler nullableString(String fieldName, String defaultVal) { return name(fieldName).type().nullable().stringType().stringDefault(defaultVal); } /** * Shortcut for creating a bytes field with the given name and no default. *

    * This is equivalent to: * *

         * name(fieldName).type().bytesType().noDefault()
         * 
    */ public FieldAssembler requiredBytes(String fieldName) { return name(fieldName).type().bytesType().noDefault(); } /** * Shortcut for creating an optional bytes field: a union of null and bytes with * null default. *

    * This is equivalent to: * *

         * name(fieldName).type().optional().bytesType()
         * 
    */ public FieldAssembler optionalBytes(String fieldName) { return name(fieldName).type().optional().bytesType(); } /** * Shortcut for creating a nullable bytes field: a union of bytes and null with * a bytes default. *

    * This is equivalent to: * *

         * name(fieldName).type().nullable().bytesType().bytesDefault(defaultVal)
         * 
    */ public FieldAssembler nullableBytes(String fieldName, byte[] defaultVal) { return name(fieldName).type().nullable().bytesType().bytesDefault(defaultVal); } /** * End adding fields to this record, returning control to the context that this * record builder was created in. */ public R endRecord() { record.setFields(fields); return context.complete(record); } private FieldAssembler addField(Field field) { fields.add(field); return this; } } /** * Builds a Field in the context of a {@link FieldAssembler}. * * Usage is to first configure any of the optional parameters and then to call * one of the type methods to complete the field. For example * *
       *   .namespace("org.apache.example").orderDescending().type()
       * 
    * * Optional parameters for a field are namespace, doc, order, and aliases. */ public final static class FieldBuilder extends NamedBuilder> { private final FieldAssembler fields; private Schema.Field.Order order = Schema.Field.Order.ASCENDING; private FieldBuilder(FieldAssembler fields, NameContext names, String name) { super(names, name); this.fields = fields; } /** Set this field to have ascending order. Ascending is the default **/ public FieldBuilder orderAscending() { order = Schema.Field.Order.ASCENDING; return self(); } /** Set this field to have descending order. Descending is the default **/ public FieldBuilder orderDescending() { order = Schema.Field.Order.DESCENDING; return self(); } /** Set this field to ignore order. **/ public FieldBuilder orderIgnore() { order = Schema.Field.Order.IGNORE; return self(); } /** * Final step in configuring this field, finalizing name, namespace, alias, and * order. * * @return A builder for the field's type and default value. */ public FieldTypeBuilder type() { return new FieldTypeBuilder<>(this); } /** * Final step in configuring this field, finalizing name, namespace, alias, and * order. Sets the field's type to the provided schema, returns a * {@link GenericDefault}. */ public GenericDefault type(Schema type) { return new GenericDefault<>(this, type); } /** * Final step in configuring this field, finalizing name, namespace, alias, and * order. Sets the field's type to the schema by name reference. *

    * The name must correspond with a named schema that has already been created in * the context of this builder. The name may be a fully qualified name, or a * short name. If it is a short name, the namespace context of this builder will * be used. *

    * The name and namespace context rules are the same as the Avro schema JSON * specification. */ public GenericDefault type(String name) { return type(name, null); } /** * Final step in configuring this field, finalizing name, namespace, alias, and * order. Sets the field's type to the schema by name reference. *

    * The name must correspond with a named schema that has already been created in * the context of this builder. The name may be a fully qualified name, or a * short name. If it is a full name, the namespace is ignored. If it is a short * name, the namespace provided is used. If the namespace provided is null, the * namespace context of this builder will be used. *

    * The name and namespace context rules are the same as the Avro schema JSON * specification. */ public GenericDefault type(String name, String namespace) { Schema schema = names().get(name, namespace); return type(schema); } private FieldAssembler completeField(Schema schema, Object defaultVal) { JsonNode defaultNode = defaultVal == null ? NullNode.getInstance() : toJsonNode(defaultVal); return completeField(schema, defaultNode); } private FieldAssembler completeField(Schema schema) { return completeField(schema, (JsonNode) null); } private FieldAssembler completeField(Schema schema, JsonNode defaultVal) { Field field = new Field(name(), schema, doc(), defaultVal, true, order); addPropsTo(field); addAliasesTo(field); return fields.addField(field); } @Override protected FieldBuilder self() { return this; } } /** Abstract base class for field defaults. **/ public static abstract class FieldDefault> extends Completion { private final FieldBuilder field; private Schema schema; FieldDefault(FieldBuilder field) { this.field = field; } /** Completes this field with no default value **/ public final FieldAssembler noDefault() { return field.completeField(schema); } private FieldAssembler usingDefault(Object defaultVal) { return field.completeField(schema, defaultVal); } @Override final S complete(Schema schema) { this.schema = schema; return self(); } abstract S self(); } /** Choose whether to use a default value for the field or not. **/ public static class BooleanDefault extends FieldDefault> { private BooleanDefault(FieldBuilder field) { super(field); } /** Completes this field with the default value provided **/ public final FieldAssembler booleanDefault(boolean defaultVal) { return super.usingDefault(defaultVal); } @Override final BooleanDefault self() { return this; } } /** Choose whether to use a default value for the field or not. **/ public static class IntDefault extends FieldDefault> { private IntDefault(FieldBuilder field) { super(field); } /** Completes this field with the default value provided **/ public final FieldAssembler intDefault(int defaultVal) { return super.usingDefault(defaultVal); } @Override final IntDefault self() { return this; } } /** Choose whether to use a default value for the field or not. **/ public static class LongDefault extends FieldDefault> { private LongDefault(FieldBuilder field) { super(field); } /** Completes this field with the default value provided **/ public final FieldAssembler longDefault(long defaultVal) { return super.usingDefault(defaultVal); } @Override final LongDefault self() { return this; } } /** Choose whether to use a default value for the field or not. **/ public static class FloatDefault extends FieldDefault> { private FloatDefault(FieldBuilder field) { super(field); } /** Completes this field with the default value provided **/ public final FieldAssembler floatDefault(float defaultVal) { return super.usingDefault(defaultVal); } @Override final FloatDefault self() { return this; } } /** Choose whether to use a default value for the field or not. **/ public static class DoubleDefault extends FieldDefault> { private DoubleDefault(FieldBuilder field) { super(field); } /** Completes this field with the default value provided **/ public final FieldAssembler doubleDefault(double defaultVal) { return super.usingDefault(defaultVal); } @Override final DoubleDefault self() { return this; } } /** Choose whether to use a default value for the field or not. **/ public static class StringDefault extends FieldDefault> { private StringDefault(FieldBuilder field) { super(field); } /** Completes this field with the default value provided. Cannot be null. **/ public final FieldAssembler stringDefault(String defaultVal) { return super.usingDefault(defaultVal); } @Override final StringDefault self() { return this; } } /** Choose whether to use a default value for the field or not. **/ public static class BytesDefault extends FieldDefault> { private BytesDefault(FieldBuilder field) { super(field); } /** Completes this field with the default value provided, cannot be null **/ public final FieldAssembler bytesDefault(byte[] defaultVal) { return super.usingDefault(ByteBuffer.wrap(defaultVal)); } /** Completes this field with the default value provided, cannot be null **/ public final FieldAssembler bytesDefault(ByteBuffer defaultVal) { return super.usingDefault(defaultVal); } /** * Completes this field with the default value provided, cannot be null. The * string is interpreted as a byte[], with each character code point value * equalling the byte value, as in the Avro spec JSON default. **/ public final FieldAssembler bytesDefault(String defaultVal) { return super.usingDefault(defaultVal); } @Override final BytesDefault self() { return this; } } /** Choose whether to use a default value for the field or not. **/ public static class NullDefault extends FieldDefault> { private NullDefault(FieldBuilder field) { super(field); } /** Completes this field with a default value of null **/ public final FieldAssembler nullDefault() { return super.usingDefault(null); } @Override final NullDefault self() { return this; } } /** Choose whether to use a default value for the field or not. **/ public static class MapDefault extends FieldDefault> { private MapDefault(FieldBuilder field) { super(field); } /** Completes this field with the default value provided, cannot be null **/ public final FieldAssembler mapDefault(Map defaultVal) { return super.usingDefault(defaultVal); } @Override final MapDefault self() { return this; } } /** Choose whether to use a default value for the field or not. **/ public static class ArrayDefault extends FieldDefault> { private ArrayDefault(FieldBuilder field) { super(field); } /** Completes this field with the default value provided, cannot be null **/ public final FieldAssembler arrayDefault(List defaultVal) { return super.usingDefault(defaultVal); } @Override final ArrayDefault self() { return this; } } /** Choose whether to use a default value for the field or not. **/ public static class FixedDefault extends FieldDefault> { private FixedDefault(FieldBuilder field) { super(field); } /** Completes this field with the default value provided, cannot be null **/ public final FieldAssembler fixedDefault(byte[] defaultVal) { return super.usingDefault(ByteBuffer.wrap(defaultVal)); } /** Completes this field with the default value provided, cannot be null **/ public final FieldAssembler fixedDefault(ByteBuffer defaultVal) { return super.usingDefault(defaultVal); } /** * Completes this field with the default value provided, cannot be null. The * string is interpreted as a byte[], with each character code point value * equalling the byte value, as in the Avro spec JSON default. **/ public final FieldAssembler fixedDefault(String defaultVal) { return super.usingDefault(defaultVal); } @Override final FixedDefault self() { return this; } } /** Choose whether to use a default value for the field or not. **/ public static class EnumDefault extends FieldDefault> { private EnumDefault(FieldBuilder field) { super(field); } /** Completes this field with the default value provided, cannot be null **/ public final FieldAssembler enumDefault(String defaultVal) { return super.usingDefault(defaultVal); } @Override final EnumDefault self() { return this; } } /** Choose whether to use a default value for the field or not. **/ public static class RecordDefault extends FieldDefault> { private RecordDefault(FieldBuilder field) { super(field); } /** Completes this field with the default value provided, cannot be null **/ public final FieldAssembler recordDefault(GenericRecord defaultVal) { return super.usingDefault(defaultVal); } @Override final RecordDefault self() { return this; } } public final static class GenericDefault { private final FieldBuilder field; private final Schema schema; private GenericDefault(FieldBuilder field, Schema schema) { this.field = field; this.schema = schema; } /** Do not use a default value for this field. **/ public FieldAssembler noDefault() { return field.completeField(schema); } /** * Completes this field with the default value provided. The value must conform * to the schema of the field. **/ public FieldAssembler withDefault(Object defaultVal) { return field.completeField(schema, defaultVal); } } /** * Completion is for internal builder use, all subclasses are private. * * Completion is an object that takes a Schema and returns some result. */ private abstract static class Completion { abstract R complete(Schema schema); } private static class SchemaCompletion extends Completion { @Override protected Schema complete(Schema schema) { return schema; } } private static final Schema NULL_SCHEMA = Schema.create(Schema.Type.NULL); private static class NullableCompletion extends Completion { private final Completion context; private NullableCompletion(Completion context) { this.context = context; } @Override protected R complete(Schema schema) { // wrap the schema as a union of the schema and null Schema nullable = Schema.createUnion(Arrays.asList(schema, NULL_SCHEMA)); return context.complete(nullable); } } private static class OptionalCompletion extends Completion> { private final FieldBuilder bldr; public OptionalCompletion(FieldBuilder bldr) { this.bldr = bldr; } @Override protected FieldAssembler complete(Schema schema) { // wrap the schema as a union of null and the schema Schema optional = Schema.createUnion(Arrays.asList(NULL_SCHEMA, schema)); return bldr.completeField(optional, (Object) null); } } private abstract static class CompletionWrapper { abstract Completion wrap(Completion completion); } private static final class NullableCompletionWrapper extends CompletionWrapper { @Override Completion wrap(Completion completion) { return new NullableCompletion<>(completion); } } private static abstract class NestedCompletion extends Completion { private final Completion context; private final PropBuilder assembler; private NestedCompletion(PropBuilder assembler, Completion context) { this.context = context; this.assembler = assembler; } @Override protected final R complete(Schema schema) { Schema outer = outerSchema(schema); assembler.addPropsTo(outer); return context.complete(outer); } protected abstract Schema outerSchema(Schema inner); } private static class MapCompletion extends NestedCompletion { private MapCompletion(MapBuilder assembler, Completion context) { super(assembler, context); } @Override protected Schema outerSchema(Schema inner) { return Schema.createMap(inner); } } private static class ArrayCompletion extends NestedCompletion { private ArrayCompletion(ArrayBuilder assembler, Completion context) { super(assembler, context); } @Override protected Schema outerSchema(Schema inner) { return Schema.createArray(inner); } } private static class UnionCompletion extends Completion> { private final Completion context; private final NameContext names; private final List schemas; private UnionCompletion(Completion context, NameContext names, List schemas) { this.context = context; this.names = names; this.schemas = schemas; } @Override protected UnionAccumulator complete(Schema schema) { List updated = new ArrayList<>(this.schemas); updated.add(schema); return new UnionAccumulator<>(context, names, updated); } } /** * Accumulates all of the types in a union. Add an additional type with * {@link #and()}. Complete the union with {@link #endUnion()} */ public static final class UnionAccumulator { private final Completion context; private final NameContext names; private final List schemas; private UnionAccumulator(Completion context, NameContext names, List schemas) { this.context = context; this.names = names; this.schemas = schemas; } /** Add an additional type to this union **/ public BaseTypeBuilder> and() { return new UnionBuilder<>(context, names, schemas); } /** Complete this union **/ public R endUnion() { Schema schema = Schema.createUnion(schemas); return context.complete(schema); } } private static void checkRequired(Object reference, String errorMessage) { if (reference == null) { throw new NullPointerException(errorMessage); } } // create default value JsonNodes from objects private static JsonNode toJsonNode(Object o) { try { String s; if (o instanceof ByteBuffer) { // special case since GenericData.toString() is incorrect for bytes // note that this does not handle the case of a default value with nested bytes ByteBuffer bytes = ((ByteBuffer) o); bytes.mark(); byte[] data = new byte[bytes.remaining()]; bytes.get(data); bytes.reset(); // put the buffer back the way we got it s = new String(data, StandardCharsets.ISO_8859_1); char[] quoted = JsonStringEncoder.getInstance().quoteAsString(s); s = "\"" + new String(quoted) + "\""; } else if (o instanceof byte[]) { s = new String((byte[]) o, StandardCharsets.ISO_8859_1); char[] quoted = JsonStringEncoder.getInstance().quoteAsString(s); s = '\"' + new String(quoted) + '\"'; } else { s = GenericData.get().toString(o); } return new ObjectMapper().readTree(s); } catch (IOException e) { throw new SchemaBuilderException(e); } } }





    © 2015 - 2024 Weber Informatics LLC | Privacy Policy