This section describes the details of the Java classes (dataModels) generated by the code generator.
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
Java package and class
|
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
Java package and class
|
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
Java package and class
|
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
Java package and class
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 Java package and class
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
Java package and class
2. Union with typerefExample Schema Java package and class
|
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.
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.
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.
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:
java.util.List<E>
.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 |
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 . |
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:
java.util.Map<String, E>
.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 . |
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 . |
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
DEFAULT
STRICT
com.linkedin.data.template.RequiredFieldNotPresentException
.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
REMOVE_IF_NULL
REMOVE_OPTIONAL_IF_NULL
java.lang.IllegalArgumentException
.DISALLOW_NULL
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 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
.
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() { ... }
}
}
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:
A custom class must meet the following requirements:
DirectCoercer
interface.{
"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());
}
}
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. |
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.
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.
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.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
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.