Java Binding

Contents

This section describes the details of the Java classes (dataModels) generated by the code generator.

Package and Class Names

The Java binding determines the package and class names of the generated and/or built-in classes using the following rules.

Schema Type
Java Package and Class Name
maps and arrays of primitive types Package name is com.linkedin.data.template.
Class name is computed by appending "Map" or "Array" to the corresponding boxed type's class name.
For multi-dimensional maps and arrays, a "Map" or "Array" is appended for each dimension starting with the inner most dimension first.

Example Schema

{ "type" : "map", "values" : "boolean" }
{ "type" : "array", "items" : { "type" : "map" : "values" : "string" } }
Java package and class

package com.linkedin.data.template;
public class BooleanMap extends DirectArrayTemplate&ltBoolean&gt ...
public class StringMapArray extends DirectArrayTemplate&ltStringMap&gt ...
enum, fixed, record types
(named schema types)
Package name is the package of the named schema type if it is specified, otherwise package name will use the namespace of the named schema type by default.
Class name is the name of the named schema type.

Example Schema

{ "type" : "record", "name" : "a.b.c.d.Foo", "fields" : ... }
{ "type" : "enum", "name" : "Bar", "namespace" : "x.y.z", "package": "x.y.z.test", symbols" : ... }
Java package and class

package a.b.c.d;
public class Foo extends RecordTemplate ...

package x.y.z.test; public enum class Bar ...
maps and arrays of enum, fixed, record
(maps and arrays of named schema types)
Package name is the package name of the named schema type, which follows the rule documented in this table for named schema.
Class name is computed by appending "Map" or "Array" to name of generated class for the named schema type.
For multi-dimensional maps and arrays, a "Map" or "Array" is appended for each dimension starting with the inner most dimension first.

Example Schema

{ "type" : "map", "values" : "a.b.c.d.Foo" }
{ "type" : "map", "values" : { "type" : "array", "items" : "a.b.c.d.Foo" } }

{ "type" : "array", "items" : "x.y.z.Bar" } { "type" : "array", "items" : { "type" : "map", "values" : "x.y.z.Bar" } }
Java package and class

package a.b.c.d;
 
public class FooMap extends WrappingMapTemplate<Foo> ...
public class FooArrayMap extends WrappingMapTemplate<Foo&gt ...
 
package x.y.z.test;
public class BarArray extends DirectArrayTemplate<Bar&gt ...
public class BarMapArray extends DirectArrayTemplate<Bar&gt ...
unions
The name of the union class is determined in two ways.

1. Union without typeref
If there is no typeref for the union, the code generator makes up the class name from the name of the closest enclosing field that declared the union type.
Package name is the package name of the closest outer record type, which follows the rule documented in this table for that closest outer record type.
The generated union class will be declared in the generated class of the closest outer record type.
Class name will be name of the field in the closest outer record that declared the union with the first character capitalized.

Example Schema

{
  "type" : "record",
  "name" : "a.b.c.d.Foo",
  "fields" : [ { "name" : "bar", "type" : [ "int", "string" ] } ]
}
Java package and class

package a.b.c.d;
public class Foo extends RecordTemplate {
  public class Bar extends UnionTemplate ...
}
2. Union with typeref
If there is a typeref for the union, the code generator will use the name of typeref for the generated union class.
Package name is the package of the typeref if it is specified, otherwise package name is the namespace of the typeref by default.
Class name is the name of the typeref.

Example Schema

{
  "type" : "typeref",
  "name" : "a.b.c.d.Bar",
  "package" : "a.b.c.d.test",
  "ref"  : [ "int", "string" ] 
}
Java package and class

package a.b.c.d.test;
public class Bar extends UnionTemplate implements HasTyperefInfo {
  ...
  public TyperefInfo typerefInfo() 
  {
    ... 
  }
}
When the typeref provides the name of the generated union class. The generated class will also implement the HasTyperefInfo interface. This interface declares the typerefInfo() method that will be implemented by the generated class.
To avoid generating duplicate classes for the duplicate declarations of unions, it is a good practice to declare unions with a typeref when the same union type is used more than once.
maps and arrays of unions
Package name is the package name of the union, which follows the rule documented in this table for unions.
The generated class will be declared in the same outer class (for unions without typeref) or same package (for unions with typeref) as the generated class for the union.
Class name is computed by appending "Map" or "Array" to the name of the generated class for the union.
For multi-dimensional maps and arrays, a "Map" or "Array" is appended for each dimension starting the inner most dimension first.

1. Union without typeref

Example Schema

{
  "type" : "record",
  "name" : "a.b.c.d.Foo",
  "fields" : [
    { "name" : "members", "type" : { "type" : "array", "items" : [ "int", "string" ] } }
    { "name" : "locations", "type" : { "type" : "map", "values" : [ "int", "string" ]  } }
  ]
}

Java package and class

package a.b.c.d;
public class Foo extends RecordTemplate {
  public class Members extends UnionTemplate ...
  public class MembersArray extends WrappingArrayTemplate<Members> ...
  public class Locations extends UnionTemplate ...
  public class LocationsMap extends WrappingMapTemplate<Locations&t; ...
 
public MembersArray getMembers() ... public LocationsMap getLocations() ... }
2. Union with typeref
Example Schema

{
  "type" : "typeref",
  "name" : "a.b.c.d.Bar",
  "package": "a.b.c.d.test",
  "ref"  : [ "int", "string" ] 
}

{ "type" : "record", "name" : "a.b.c.d.Foo", "package" : "a.b.c.d.test", "fields" : [ { "name" : "members", "type" : { "type" : "array", "items" : "Bar" } } { "name" : "locations", "type" : { "type" : "map", "values" : "Bar" } } ] }
Java package and class

package a.b.c.d.test;

public class Bar extends UnionTemplate ... public class BarArray extends WrappingArrayTemplate<Bar> ... public class BarMap extends WrappingMapTemplate<Bar> ...
public class Foo extends RecordTemplate { public BarArray getMembers() ... public BarMap getLocations() ... }

Primitive Types

The Java binding for primitive schema types are as follows:

Schema Type Java Type
int
java.lang.Integer or int (1)
long
java.lang.Long or long (1)
float
java.lang.Float or float (1)
double
java.lang.Double or double (1)
boolean
java.lang.Boolean or boolean (1)
string
java.lang.String
bytes
com.linkedin.data.ByteString

(1) Depending on the method, un-boxed types will be preferred to boxed types if applicable when input or output arguments can never be null.

In addition to the standard bindings, custom Java class bindings may be defined for these types to specify a user-defined class as substitute for the standard Java class bindings. For additional details, see Custom Java Class Binding for Primitive Types.

Enum Type

The code generator generates a Java enum class. There will be a corresponding symbol in the Java enum class for each symbol in the enum schema. In addition, the code generator will add a $UNKNOWN symbol to the generated enum class. $UNKNOWN will be returned if the value stored in the Data layer cannot be mapped to a symbol present in the Java enum class. For example, this may occur if an enum symbol has been added to a new version of the enum schema and is transmitted to client that has not been updated with the new enum schema.

Enums also supports a symbolDocs attribute to provide documentation for each enum symbol. E.g.

...
"symbols" : [ "APPLE", "BANANA", ... ],
"symbolDocs" : { "APPLE":"A red, yellow or green fruit.", "BANANA":"A yellow fruit.", ... } 
...

package com.linkedin.pegasus.generator.examples;

...
/**
* A fruit
*
*/
public enum Fruits {

  /**
   * A red, yellow or green fruit.
   * 
   */
  APPLE,

  /**
   * A yellow fruit.
   * 
   */
  BANANA,

  /**
   * An orange fruit.
   * 
   */
  ORANGE,

  /**
   * A yellow fruit.
   * 
   */
  PINEAPPLE,
  $UNKNOWN;
}

Note: Due to the addition of doclint in JDK8, anything under the symbolDocs attribute must be W3C HTML 4.01 compliant. This is because the contents of this string will appear as Javadocs in the generated Java ‘data template’ classes later. Please take this into consideration when writing your documentation.

Fixed Type

The code generator generates a class that extends com.linkedin.data.template.FixedTemplate. This class provides the following methods:

Method
Implemented by
Description
Constructor(String arg)
Generated class
Construct with an instance whose value is provided by the input string representing the bytes in the fixed.
Constructor(Object obj)
Generated class
Construct with an instance whose value is provided by the input string representing the bytes in the fixed or a ByteString.
ByteString bytes()
Base class
Returns the bytes of the fixed type.
FixedDataSchema schema()
Generated class
Returns the DataSchema of the instance. The size of the fixed type can be obtained from this schema.
Object data()
Base class
Returns the underlying data of the instance. This is the same as bytes().
String toString()
Base class
Returns the string representation of the bytes in the instance.

A fixed instance is immutable once constructed.

Array Type

The code generator generates a class that extends com.linkedin.data.template.DirectArrayTemplate<E> or com.linkedin.data.template.WrappingArrayTemplate<E extends DataTemplate<?>>. The latter is used for item types whose Java binding require wrapping. The former is used for items types whose Java binding that do not require wrapping. The E generic type variable is the Java class of the array’s item type. By creating a concrete subclass per map type, this binding avoids lost of item type information due to Java generics type erasure.

The primary characteristics of both base classes are as follows:

  • They implement java.util.List<E>.
  • Their methods perform runtime checks on updates and inserts to ensure that the value types of arguments are either the exact type specified by the type variable or types that can be coerced to the specified type.

The methods with more specific behavior are described below.

Method Implemented by
Description
Constructor() Generated class
Constructs an empty array.
Constructor(int initialCapacity) Generated class Constructs an empty array with the specified initial capacity.
Constructor(Collection c) Generated class Constructs an array by inserting each element of the provided collection into the constructed array.
Constructor(DataList list) Generated class Constructs an array that wraps the provided DataList.
ArrayDataSchema schema() Generated class Returns the DataSchema of the instance. The schema of the items in the array can be obtained from this schema.
int hashCode()
Base class
Returns the hashCode() of the underlying DataList wrapped by this instance.
boolean equals(Object object)
Base class
If object is an instance of AbstractArrayTemplate, invoke equals on the underlying DataList of this instance and the object’s underlying DataList. Otherwise, invoke super.equals(object) which is AbstractMap ’s equals method.
String toString()
Base class
Returns the result of calling toString() on the underlying DataList wrapped by this instance.
java.util.List methods Base class
See java.util.List.

Map Type

The code generator generates a class that extends com.linkedin.data.template.DirectMapTemplate<E> or com.linkedin.data.template.WrappingMapTemplate<E extends DataTemplate<?>>. The latter is used for item types whose Java binding require wrapping. The former is used for items types whose Java binding that do not require wrapping. The E generic type variable is the Java class of the map’s value type. By creating a concrete subclass per map type, this binding avoids lost of value type information due to Java generics type erasure.

The primary characteristics of both base classes are as follows:

  • They implement java.util.Map<String, E>.
  • Their methods perform runtime checks on updates and inserts to ensure that the value types of arguments are either the exact type specified by the type variable or are types that can be coerced to the specified type.

The methods with somewhat specialized behavior are described below.

Method Implemented by
Description
Constructor() Generated class
Constructs an empty map.
Constructor(int initialCapacity) Generated class Constructs an empty map with the specified initial capacity.
Constructor(int initialCapacity, float loadFactor) Generated class Constructs an empty map with the specified initial capacity and load factor.
Constructor(Map<String, E> c) Generated class Constructs a map by inserting each entry of the provided map into new instance.
Constructor(DataMap map) Generated class Constructs an array that wraps the provided DataMap.
MapDataSchema schema() Generated class Returns the DataSchema of the instance. The schema of the values in the map can be obtained from this schema.
int hashCode()
Base class
Returns the hashCode() of the underlying DataMap wrapped by this instance.
boolean equals(Object object)
Base class
If object is an instance of AbstractMapTemplate, invoke equals on the underlying DataMap of this instance and the object’s underlying DataMap. Otherwise, invoke super.equals(object) which is AbstractMap ’s equals method.
String toString()
Base class
Returns the result of calling toString() on the underlying DataMap wrapped by this instance.
java.util.Map methods Base class
See java.util.Map.

Record Type

The code generator generates a class that extends com.linkedin.data.template.RecordTemplate. This class provides the following methods:

Method
Implemented by
Description
Constructor()
Generated class
Construct instance that wraps an empty DataMap. Even mandatory fields are not present.
Constructor(DataMap map)
Generated class Construct instance that wraps the provided DataMap. Method invocations on the RecordTemplate translates to accesses to the underlying DataMap.
RecordDataSchema schema()
Generated class Returns the DataSchema of this instance. The fields of the record can be obtained from this schema.
static Fields fields()
Generated class Returns a generated Fields class that provides identifiers for fields of this record and certain nested types. See Fields section below.
DataMap data()
Base class Returns the underlying DataMap wrapped by this instance.
String toString()
Base class Equivalent to data().toString().

The code generator generates the following methods in the generated class for each field. FieldName is the name of field with the first character capitalized.

Method Description
boolean hasFieldName()
Returns whether the field is present in the underlying DataMap.
void removeFieldName()
Removes the field from the underlying DataMap.
T getFieldName(GetMode mode)
Returns the value of the field. The mode parameter allows the client to specify the desired behavior if the field is not present. T is the Java type of the field.
T getFieldName()
Returns the value of the field. This is equivalent to getFieldName(GetMode.STRICT). T is the Java type of the field.
R setFieldName(T value, SetMode mode) Sets the specified value into the field. The mode parameter allows the client to specify the desired behavior if the provided value is null. Returns this. R is the generated Java class. T is the Java type of the field.
R setFieldName(T value)
Sets the specified value into the field. This is equivalent to setFieldName(value, SetMode.DISALLOW_NULL). Returns this. R is the generated Java class. T is the native type rather than the corresponding boxed type where applicable, e.g. int instead of Integer.

GetMode

When getting a field from a record, the caller must specify the behavior of the function in case the requested field does not exist in the record.

The available GetModes are:

  • NULL
    If the field is present, then return the value of the field. If the field is not present, then return null (even if there is a default value).
  • DEFAULT
    If the field is present, then return the value of the field. If the field is not present and there is a default value, then return the default value. If the field is not present and there is no default value, then return null.
  • STRICT
    If the field is present, then return the value of the field.
    If the field is not present and the field has a default value, then return the default value.
    If the field is not present and the field is not optional, then throw com.linkedin.data.template.RequiredFieldNotPresentException.
    If the field is not present and the field is optional, then return null.

SetMode

When setting a field in a record, the caller must specify the behavior of the function in case the field is attempted to be set to null.

The available SetModes are:

  • IGNORE_NULL
    If the provided value is null, then do nothing i.e. the value of the field is not changed. The field may or may be present.
  • REMOVE_IF_NULL
    If the provided value is null, then remove the field. This occurs regardless of whether the field is optional.
  • REMOVE_OPTIONAL_IF_NULL
    If the provided value is null and the field is an optional field, then remove the field. If the provided value is null and the field is a mandatory field, then throw java.lang.IllegalArgumentException.
  • DISALLOW_NULL
    The provided value cannot be null. If the provided value is null, then throw java.lang.NullPointerException.
package com.linkedin.pegasus.generator.examples;

...

public class Foo extends RecordTemplate
{
    public Foo() ...
    public Foo(DataMap data) ...
    ...

    // intField - field of int type
    public boolean hasIntField() ...
    public void removeIntField() ...
    public Integer getIntField(GetMode mode) ...
    public Integer getIntField() { return getIntField(GetMode.STRICT); }
    public Foo setIntField(int value) { ... ; return this; }
    public Foo setIntField(Integer value, SetMode mode { ... ; return this; }
    ...

    // bytesField - field of bytes, Java binding for bytes is ByteString
    public boolean hasBytesField() ...
    public void removeBytesField() ...
    public ByteString getBytesField(GetMode mode) { return getBytesField(GetMode.STRICT); }
    public ByteString getBytesField() ...
    public Foo setBytesField(ByteString value) { ... ; return this; }
    public Foo setBytesField(ByteString value, SetMode mode) { ... ; return this; }
    ...

    // fruitsField - field of enum
    public boolean hasFruitsField() ...
    public void removeFruitsField() ...
    public Fruits getFruitsField(GetMode mode) ...
    public Fruits getFruitsField() { return getFruitsField(GetMode.STRICT); }
    public Foo setFruitsField(Fruits value) { ... ; return this; }
    public Foo setFruitsField(Fruits value, SetMode mode) { ... ; return this; }
    ...

    // intArrayField - field of { "type" : "array", "items" : "int" }
    public boolean hasIntArrayField() ...
    public void removeIntArrayField() ...
    public IntegerArray getIntArrayField(GetMode mode) ...
    public IntegerArray getIntArrayField() { return getIntArrayField(GetMode.STRICT); }
    public Foo setIntArrayField(IntegerArray value) { ... ; return this; }
    public Foo setIntArrayField(IntegerArray value, SetMode mode) { ... ; return this; }

    // stringMapField - field of { "type" : "map", "values" : "string" }
    public boolean hasStringMapField() ...
    public void removeStringMapField() ...
    public StringMap getStringMapField(GetMode mode) ...
    public StringMap getStringMapField() { return getIntStringMapField(GetMode.STRICT); }
    public Foo setStringMapField(StringMap value) { ... ; return this; }
    public Foo setStringMapField(StringMap value, SetMode mode) { ... ; return this; }
    ...

    // unionField - field of union
    public boolean hasUnionField() ...
    public void removeUnionField() ...
    public Foo.UnionField getUnionField(GetMode mode) ...
    public Foo.UnionField getUnionField() { return getUnionField(GetMode.STRICT); }
    public Foo setUnionField(Foo.UnionField value) { ... ; return this; }
    public Foo setUnionField(Foo.UnionField value, SetMode mode) { ... ; return this; }

    // get fields
    public static Foo.Fields fields() {
        ...;
    }

    public static class Fields
        extends PathSpec
    {
        ...
        public PathSpec intField() { ... }
        public PathSpec longField() { ... }
        public PathSpec bytesField() { ... }
        public PathSpec fruitsField() { ... }
        public PathSpec intArrayField() { ... }
        public PathSpec stringMapField() { ... }
        public Foo.UnionField.Fields unionField() { ... }
    }
}

Error Type

Error types are specialized record types. The code generator generates a class that extends com.linkedin.data.template.ExceptionTemplate. The generated class has the same methods as a generated class for a record with the same fields. Unlike RecordTemplate instances, ExceptionTemplate instances can be thrown and caught. ExceptionTemplate extends java.lang.Exception.

Union Type

The code generator generates a class that extends com.linkedin.data.template.UnionTemplate. This class provides the following methods:

Method
Implemented by
Description
Constructor()
Generated class
Construct a union with null as it value. An instance with null as its value cannot be assigned another value.
Constructor(DataMap map)
Generated class If the argument is null or Data.NULL, then construct a union with a null value.
If the argument is not null, then construct a union whose value is provided by the DataMap. Method invocations on the UnionTemplate translates to accesses to the underlying DataMap.
An instance with null as its value cannot be assigned another value.
An instance that has a non-null value cannot be later assigned a null value.
Note: This limitation is because the underlying data types that back the union for null verus non-null values are different. For non-null values, the underlying data type is a DataMap. For null values, the underlying data type is a string.
UnionDataSchema schema()
Generated class Returns the DataSchema of the instance. The members of the union can be obtained from this schema.
DataScheme memberType()
Base class
Returns DataSchemaConstants.NULL_TYPE if the union has a null value, else return the schema for the value.
If the schema cannot be determined, then throw TemplateOutputCastException. The schema cannot be determined if the content of the underlying DataMap cannot be resolved to a known member type of the union schema. See serialization format for details. This exception is thrown if the DataMap has more than one entry and the key of the only entry does not identify one of the member types of the union.
boolean memberIs(String key)
Base class
Returns whether the union member key of the current value is equal the specified key. The type of the current value is identified by the specified key if the underlying DataMap has a single entry and the entry’s key equals the specified key.
boolean isNull()
Base class
Returns whether the value of the union is null.
Object data()
Base class Returns Data.NULL if the union has a null value, else return the underlying DataMap fronted by the instance.
String toString()
Base class Equivalent to data().toString().

The code generator generates the following methods in the generated class for each member type of the union. In the following table, MemberKey is either the member’s alias (if specified) or the member’s non-fully qualified type name with the first character capitalized.

Method Description
U createWithMemberKey(T value)
Create a union instance with the specified value for the member identified by MemberKey.
boolean isMemberKey()
Returns whether the value of the union is of the member identified by the MemberKey.
T getMemberKey()
Returns the value of the union if it is for the member identified by MemberKey. T is the Java type of the value and if the current value is not of this type, then throw TemplateOutputCastException.
void setMemberKey(T value)
Sets the specified value into the union.

Here is an example generated class for a union who’s members are not aliased.

package com.linkedin.pegasus.examples;

...
public class Foo extends RecordTemplate
{
  ...
  public final static class UnionField extends UnionTemplate
  {
    public UnionField() ...
    public UnionField(Object data) ...

    // int value
    public boolean isInt() ...
    public Integer getInt() ...
    public void setInt(Integer value) ...

    // string value
    public boolean isString() ...
    public String getString() ...
    public void setString(String value) ...

    // com.linkedin.pegasus.generator.examples.Fruits enum value
    public boolean isFruits() ...
    public Fruits getFruits() ...
    public void setFruits(Fruits value) ...

    // com.linkedin.pegasus.generator.examples.Foo record value
    public boolean isFoo() ...
    public Foo getFoo() ...
    public void setFoo(Foo value) ...

    // array value ({ "type" : "array", "items" : "string" })
    public boolean isArray() ...
    public StringArray getArray() ...
    public void setArray(StringArray value) ...

    // map value ({ "type" : "map", "values" : "long" })
    public boolean isMap() ...
    public LongMap getMap() ...
    public void setMap(LongMap value) ...
  }

  public static class Fields extends PathSpec
  {
    ...
    public Foo.Fields Foo() { ... }
  }
}

For a union who’s members are aliased, the generated methods will use the alias instead of the member’s type name like illustrated below.

package com.linkedin.pegasus.examples;

...
public class Foo extends RecordTemplate
{
  ...
  public final static class UnionField extends UnionTemplate
  {
    public UnionField() ...
    public UnionField(Object data) ...

    // int with alias ({ "type" : "int", "alias" : "count" })
    public UnionField createWithCount(Integer value) ...
    public boolean isCount() ...
    public Integer getCount() ...
    public void setCount(Integer value) ...

    // string with alias ({ "type" : "string", "alias" : "message" })
    public UnionField createWithMessage(String value) ...
    public boolean isMessage() ...
    public String getMessage() ...
    public void setMessage(String value) ...

    // another string with alias ({ "type" : "string", "alias" : "greeting" })
    public UnionField createWithGreeting(String value) ...
    public boolean isGreeting() ...
    public String getGreeting() ...
    public void setGreeting(String value) ...
  }

  public static class Fields extends PathSpec
  {
    ...
    public Foo.Fields Foo() { ... }
  }
}

Custom Java Class Binding for Primitive Types

A typeref can also be used to define a custom Java class binding for a primitive type. The primary intended use is to provide a more developer friendly experience by having the framework perform conversions from primitive type to a more friendly Java class that can implement methods for manipulating the underlying primitive data. Custom Java class binding also provides additional type-safety by allowing typerefs of the same primitive type to be bound to different custom Java classes. This enables compile time type-checking to disambiguate typeref’s, e.g. a Urn typeref to a string can be bound to a different Java class than a FileName typeref to a string.

When a typeref has a custom Java binding, the generated Java data templates that reference this type will accept and return parameters of the custom Java class instead of standard Java class for the primitive type. The value stored in the underlying DataMap or DataList will always be of the corresponding primitive Java type (not the custom Java type.)

A custom Java class binding is declared by:

  • defining a typeref of the primitive type,
  • adding a “java” attribute whose value is a map to the typeref declaration,
  • adding a “class” attribute to the “java” map whose value is a string that identifies the name of custom Java class.

A custom class must meet the following requirements:

  1. Instances of the custom class must be immutable.
  2. A coercer must be defined that can coerce the primitive Java class of the type to the custom Java class of the type, in both the input and output directions. The coercer implements the DirectCoercer interface.
  3. An instance of the coercer must be registered with the data template framework.
{
  "type" : "typeref",
  "name" : "CustomPoint",
  "ref"  : "string",
  "java" : {
    "class" : "CustomPoint"
  }
}
//
// The custom class
// It has to be immutable.
//
public class CustomPoint
{
  private int _x;
  private int _y;

  public CustomPoint(String s)
  {
    String parts[] = s.split(",");
    _x = Integer.parseInt(parts"0":0);
    _y = Integer.parseInt(parts"1":1);
  }

  public CustomPoint(int x, int y)
  {
    _x = x;
    _y = y;
  }

  public int getX()
  {
    return _x;
  }

  public int getY()
  {
    return _y;
  }

  // Implement equals, hashCode, toString, ...

  //
  // The custom class's DirectCoercer.
  //
  public static class CustomPointCoercer implements DirectCoercer<CustomPoint>
  {
    @Override
    public Object coerceInput(CustomPoint object)
      throws ClassCastException
    {
      return object.toString();
    }

    @Override
    public CustomPoint coerceOutput(Object object)
      throws TemplateOutputCastException
    {
      if (object instanceof String == false)
      {
        throw new TemplateOutputCastException("Output " + object + 
                                              " is not a string, and cannot be coerced to " + 
                                              CustomPoint.class.getName());
      }
      return new CustomPoint((String) object);
    }
  }

  //
  // Automatically register Java custom class and its coercer.
  //
  static
  {
    Custom.registerCoercer(CustomPoint.class, new CustomPointCoercer());
  }
}

Fields class

The code generator also generates a Fields class within the generated class for certain complex types. The primary use case for the Fields class is to provide a type-safe way to refer or identify a field within a record or a member of a union. The Fields class of a record is accessed through the generated fields() method of the generated class for a record. Only record types have the generated fields() method.

If there are nested complex types, a path to a particular nested field may be obtained by chaining method invocations on Fields classes along the path to the field, e.g. Foo.fields().barField().bazField(). The path may be used to specify the nested fields to return from a resource request, i.e. deep projection.

The following table summarizes which complex types will have a generated Fields class and the content of the Fields class.

Complex type
Whether type will have a generated Fields class
Content of generated Fields class
record
A Fields class always generated.
A method returning a PathSpec will be generated for each field of the record.
union
A Fields class always generated.
A method returning a PathSpec will be generated for each member of the union.
array
A Fields class will be generated if the array directly or indirectly contains a nested record or union.
An items() method returning a PathSpec will be generated for the array.
map
A Fields class will be generated if the map directly or indirectly contains a nested record or union.
A values() method returning a PathSpec will be generated for the map.

Clone Method

For the classes that wrap DataMap or DataList, their clone method will clone the underlying DataMap or DataList and then create and return a new DataTemplate of the same class to wrap the clone. This clone operation performs a shallow copy.

Escaping for Reserved Words

When symbols such as schema names or enum symbol names are the same as one of the reserved words in Java, the code generator will escape these symbols by appending an underscore (“_”) to the name to obtain the Java name of the symbol.

Exceptions

The Data layer can throw two exceptions:

  • java.lang.ClassCastException - This exception is thrown if the input argument to a method is not the expected type, cannot be cast or coerced to the expected type.
  • com.linkedin.data.TemplateOutputCastException - This exception if the underlying data cannot be wrapped, cast or coerced to the type to type of the output argument.

Running the Code Generator

The code generator that generates the Java bindings is the com.linkedin.pegasus.generator.PegasusDataTemplateGenerator class.

The arguments to the main method of this class are targetDirectoryPath [sourceFile or schemaName]+”.

  • targetDirectoryPath provides the root of the output directory for Java source files generated by the code generator. The output directory structure will follow the Java convention, with Java source files residing in sub-directories corresponding to the package name of the classes in the Java source files.
  • sourceFile provides the name of a file. Files containing schemas should have an .pdsc extension. Although a file name provided as an argument to the code generator need not end with .pdsc, only .pdsc files will be read by the schema resolver when trying to resolve a name to schema. Java type: String.
  • schemaName provides the fully qualified name of a schema. The schema resolver computes a relative path name from this argument and enumerate through resolver paths to locate a file with this relative name. If a file is found, the code generator will parse the file looking for a schema with the specified name. Java type: String[].

The resolver path is provided by the “generator.resolver.path” property. Its format is the same as the format for Java classpath. Each path is separated by a colon (“:”). Only file system directory paths may be specified (i.e. the resolver does not comprehend .jar files in the resolver path.). You can set this in java by System.setProperty().

The dependencies of the code generator are:

  • com.sun.codemodel:codemodel:2.2
  • org.codehaus.jackson-core-asl:jackson-core-asl:1.4.0
  • com.linkedin.pegasus:cow
  • com.linkedin.pegasus:r2
  • com.linkedin.pegasus:generator

Running the Code Generator from Command Line

Running the Code Generator with Gradle

A dataTemplate.gradle script is available in the build_script/ directory of pegasus. To use it, add the script to your project, then add this to your build.gradle file:

apply from: "${buildScriptDirPath}/dataTemplate.gradle"

and put the .pdl files in a directory structure of the form: ‘src/\<sourceset\>/pegasus’, where typically it would be src/main/pegasus. The plugin is set to trigger on this sort of directory structure and have the files laid out like a java source tree, ie if the namespace of my foo schema is “com.linkedin.foo.rest.api”, the file would be located at src/main/pegasus/com/linkedin/foo/rest/api/Foo.pdl. See restli-example-api/build.gradle in the Pegasus codebase for an example. This script will generate the required Java classes before the compileJava task, so that other classes can refer to it.

Note this will only generate the data templates, but further steps will be needed to generate the rest.li IDL and the clientModel.