Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright 2015 Coursera Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.coursera.courier.api;
import com.linkedin.data.DataMap;
import com.linkedin.data.schema.ArrayDataSchema;
import com.linkedin.data.schema.ComplexDataSchema;
import com.linkedin.data.schema.DataSchema;
import com.linkedin.data.schema.DataSchemaLocation;
import com.linkedin.data.schema.DataSchemaResolver;
import com.linkedin.data.schema.EnumDataSchema;
import com.linkedin.data.schema.FixedDataSchema;
import com.linkedin.data.schema.MapDataSchema;
import com.linkedin.data.schema.NamedDataSchema;
import com.linkedin.data.schema.PrimitiveDataSchema;
import com.linkedin.data.schema.RecordDataSchema;
import com.linkedin.data.schema.SchemaParser;
import com.linkedin.data.schema.TyperefDataSchema;
import com.linkedin.data.schema.UnionDataSchema;
import com.linkedin.data.template.DataTemplate;
import com.linkedin.pegasus.generator.CodeUtil;
import com.linkedin.pegasus.generator.spec.ArrayTemplateSpec;
import com.linkedin.pegasus.generator.spec.ClassTemplateSpec;
import com.linkedin.pegasus.generator.spec.CustomInfoSpec;
import com.linkedin.pegasus.generator.spec.EnumTemplateSpec;
import com.linkedin.pegasus.generator.spec.FixedTemplateSpec;
import com.linkedin.pegasus.generator.spec.ModifierSpec;
import com.linkedin.pegasus.generator.spec.PrimitiveTemplateSpec;
import com.linkedin.pegasus.generator.spec.RecordTemplateSpec;
import com.linkedin.pegasus.generator.spec.TyperefTemplateSpec;
import com.linkedin.pegasus.generator.spec.UnionTemplateSpec;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
/**
* Modified version of {@link com.linkedin.pegasus.generator.TemplateSpecGenerator} that
* adds support for Courier specific functionality:
*
*
*
Typed map key support.
*
Use 'scala' instead of 'java' for custom properties.
*
Use 'org.coursera.courier.data' as base namespace.
*
*
* @author Keren Jin
*/
// TODO(jbetz):
// Replace with https://github.com/coursera/courier/tree/with-restli-upstream-fixes
// once https://github.com/linkedin/rest.li/pull/61 is accepted.
public class CourierTemplateSpecGenerator {
private static final Logger _log = LoggerFactory.getLogger(CourierTemplateSpecGenerator.class);
private static final String CLASS_PROPERTY = "class";
// For Courier, use 'scala' as the property name for custom type properties.
//private static final String SCALA_PROPERTY = "scala";
private final String courierPackageName = "org.coursera.courier.data";
private static final String COERCER_CLASS_PROPERTY = "coercerClass";
private static final String ARRAY_SUFFIX = "Array";
private static final String MAP_SUFFIX = "Map";
private static final String[] SPECIAL_SUFFIXES = {ARRAY_SUFFIX, MAP_SUFFIX};
private static final String _templatePackageName = DataTemplate.class.getPackage().getName();
private final Collection _classTemplateSpecs = new HashSet();
/**
* Map of {@link com.linkedin.pegasus.generator.spec.ClassTemplateSpec} to {@link com.linkedin.data.schema.DataSchemaLocation}.
*/
private final Map _classToDataSchemaLocationMap = new HashMap();
/**
* Map of Java class name to a {@link com.linkedin.data.schema.DataSchema}.
*/
private final Map _classNameToSchemaMap = new HashMap(100);
/**
* Map of {@link com.linkedin.data.schema.DataSchema} to {@link com.linkedin.pegasus.generator.spec.ClassTemplateSpec}.
*/
private final IdentityHashMap _schemaToClassMap = new IdentityHashMap(100);
/**
* Map of {@link com.linkedin.data.schema.DataSchema} to the information about the immediate dereferenced {@link com.linkedin.data.schema.DataSchema} with custom Java class binding.
*/
private final Deque _locationStack = new ArrayDeque();
private final Map _immediateCustomMap = new IdentityHashMap();
private final DataSchemaResolver _schemaResolver;
private final SchemaParser _schemaParser;
private final String _dataNamespace;
private final String _customTypeLanguage;
/**
* Return Java class name for a {@link com.linkedin.data.schema.NamedDataSchema}.
*
* @param schema provides the {@link com.linkedin.data.schema.NamedDataSchema}.
*
* @return the fully qualified Java class name for the provided {@link com.linkedin.data.schema.NamedDataSchema}.
*/
public static String classNameForNamedSchema(NamedDataSchema schema)
{
final StringBuilder sb = new StringBuilder();
final String namespace = schema.getNamespace();
if (!namespace.isEmpty())
{
sb.append(namespace);
sb.append('.');
}
sb.append(schema.getName());
return sb.toString();
}
/**
* @deprecated Use below constructor and provide a customTypeLanguage.
*/
@Deprecated()
public CourierTemplateSpecGenerator(
DataSchemaResolver schemaResolver,
String dataNamespace)
{
this(schemaResolver, dataNamespace, "scala");
}
/**
* @param dataNamespace Provides the namespace to use for generated classes that have no other
* reasonable default namespace. E.g. An generated type for array of ints
* would be generated in this namespace.
*/
public CourierTemplateSpecGenerator(
DataSchemaResolver schemaResolver,
String dataNamespace,
String customTypeLanguage)
{
_schemaResolver = schemaResolver;
_schemaParser = new SchemaParser(schemaResolver);
_dataNamespace = dataNamespace;
_customTypeLanguage = customTypeLanguage;
}
/**
* @return location of the {@link com.linkedin.pegasus.generator.spec.ClassTemplateSpec} is originated, most likely the pdsc file that defines it
*/
public DataSchemaLocation getClassLocation(ClassTemplateSpec classSpec)
{
return _classToDataSchemaLocationMap.get(classSpec);
}
/**
* Instead of generate spec for the specify {@link com.linkedin.data.schema.DataSchema}, assume it is already defined in the system.
*/
public void registerDefinedSchema(DataSchema schema)
{
final ClassTemplateSpec spec = createFromDataSchema(schema);
_schemaToClassMap.put(schema, spec);
_classNameToSchemaMap.put(spec.getFullName(), schema);
}
/**
* Generate {@link com.linkedin.pegasus.generator.spec.ClassTemplateSpec} from the specified {@link com.linkedin.data.schema.DataSchema} without knowing the location.
*/
public ClassTemplateSpec generate(DataSchema schema)
{
return processSchema(schema, null, null);
}
/**
* Generate {@link com.linkedin.pegasus.generator.spec.ClassTemplateSpec} from the specified {@link com.linkedin.data.schema.DataSchema} and its location.
*/
public ClassTemplateSpec generate(DataSchema schema, DataSchemaLocation location)
{
pushCurrentLocation(location);
final ClassTemplateSpec result = generate(schema);
popCurrentLocation();
return result;
}
public Collection getGeneratedSpecs()
{
return _classTemplateSpecs;
}
/**
* Emit message if the schema is a {@link com.linkedin.data.schema.NamedDataSchema} and the class name ends with one of the special suffixes, e.g. "Array", "Map".
*
*
* This may potentially conflict with class names for Java binding for array or map of this type.
*
* @param className provides the class name.
*/
private static void checkClassNameForSpecialSuffix(String className)
{
for (String suffix : SPECIAL_SUFFIXES)
{
if (className.endsWith(suffix))
{
_log.warn("Class name for named type ends with a suffix that may conflict with derived class names for unnamed types" +
", name: " + className +
", suffix: " + suffix);
break;
}
}
}
/**
* Allow custom class to to bound to record or typeref of primitive types that are not enums.
*/
private static boolean allowCustomClass(DataSchema schema)
{
boolean result = false;
final DataSchema.Type type = schema.getType();
if (type == DataSchema.Type.TYPEREF || type == DataSchema.Type.RECORD)
{
// allow custom class only if the dereferenced type is a record or a primitive types that are not enums
final DataSchema dereferencedSchema = schema.getDereferencedDataSchema();
if (dereferencedSchema.getType() == DataSchema.Type.RECORD || (CodeUtil.isDirectType(dereferencedSchema) && dereferencedSchema.getType() != DataSchema.Type.ENUM))
{
result = true;
}
}
return result;
}
private static DataSchema dereferenceIfTyperef(DataSchema schema)
{
final DataSchema.Type type = schema.getType();
return type == DataSchema.Type.TYPEREF ? ((TyperefDataSchema) schema).getRef() : null;
}
/*
* Return exception for trying to use null type outside of a union.
*/
private static IllegalArgumentException nullTypeNotAllowed(ClassTemplateSpec enclosingClass, String memberName)
{
return new IllegalArgumentException("The null type can only be used in unions, null found" + enclosingClassAndMemberNameToString(enclosingClass, memberName));
}
/*
* Return exception for unrecognized schema type.
*/
private static IllegalStateException unrecognizedSchemaType(ClassTemplateSpec enclosingClass, String memberName, DataSchema schema)
{
return new IllegalStateException("Unrecognized schema: " + schema +
enclosingClassAndMemberNameToString(enclosingClass, memberName));
}
/*
* Generate human consumable representation of enclosing class and field name.
*/
private static String enclosingClassAndMemberNameToString(ClassTemplateSpec enclosingClass, String memberName)
{
final StringBuilder sb = new StringBuilder();
if (memberName != null)
{
sb.append(" in ");
sb.append(memberName);
}
if (enclosingClass != null)
{
sb.append(" in ");
sb.append(enclosingClass.getFullName());
}
return sb.toString();
}
/**
* Checks if a class name conflict occurs, if it occurs throws {@link IllegalArgumentException}.
*
* @param className provides the Java class name.
* @param schema provides the {@link com.linkedin.data.schema.DataSchema} that would be bound if there is no conflict.
*
* @throws IllegalArgumentException
*/
private void checkForClassNameConflict(String className, DataSchema schema)
throws IllegalArgumentException
{
final DataSchema schemaFromClassName = _classNameToSchemaMap.get(className);
boolean conflict = false;
if (schemaFromClassName != null && schemaFromClassName != schema)
{
final DataSchema.Type schemaType = schema.getType();
if (schemaFromClassName.getType() != schemaType)
{
conflict = true;
}
else if (schema instanceof NamedDataSchema)
{
conflict = true;
}
else if (!schemaFromClassName.equals(schema))
{
assert schemaType == DataSchema.Type.ARRAY || schemaType == DataSchema.Type.MAP;
//
// see schemaForArrayItemsOrMapValues
//
// When the schema bound to the specified class name is different
// from the specified schema, then emit a log message when this occurs.
//
_log.debug("Class name: " + className +
", bound to schema:" + schemaFromClassName +
", instead of schema: " + schema);
}
}
if (conflict)
{
throw new IllegalArgumentException("Class name conflict detected, class name: " + className +
", class already bound to schema: " + schemaFromClassName +
", attempting to rebind to schema: " + schema);
}
}
private DataSchemaLocation currentLocation()
{
return _locationStack.getLast();
}
private void pushCurrentLocation(DataSchemaLocation location)
{
_locationStack.addLast(location);
}
private void popCurrentLocation()
{
_locationStack.removeLast();
}
/**
* Register a new class TemplateSpec.
*
* Registration is necessary to associate the {@link com.linkedin.pegasus.generator.spec.ClassTemplateSpec} with the source file for which it was generated. This may be used later to determine if generated class should be emitted
* based on the location of the source file.
*
* Registration also associates the {@link com.linkedin.data.schema.DataSchema} to the generated {@link com.linkedin.pegasus.generator.spec.ClassTemplateSpec} and the generated class's full name to the the {@link com.linkedin.pegasus.generator.spec.ClassTemplateSpec}.
*
* @param schema provides the {@link com.linkedin.data.schema.DataSchema} of the generated class.
* @param classTemplateSpec provides the generated class.
*/
private void registerClassTemplateSpec(DataSchema schema, ClassTemplateSpec classTemplateSpec)
{
classTemplateSpec.setLocation(currentLocation().toString());
_schemaToClassMap.put(schema, classTemplateSpec);
_classNameToSchemaMap.put(classTemplateSpec.getFullName(), schema);
_classToDataSchemaLocationMap.put(classTemplateSpec, currentLocation());
if (schema instanceof NamedDataSchema)
{
checkClassNameForSpecialSuffix(classTemplateSpec.getFullName());
}
_classTemplateSpecs.add(classTemplateSpec);
}
private ClassTemplateSpec processSchema(DataSchema schema, ClassTemplateSpec enclosingClass, String memberName)
{
ClassTemplateSpec result = null;
TyperefDataSchema originalTyperefSchema = null;
final CustomInfoSpec customInfo = getImmediateCustomInfo(schema);
while (schema.getType() == DataSchema.Type.TYPEREF)
{
final TyperefDataSchema typerefSchema = (TyperefDataSchema) schema;
if (originalTyperefSchema == null)
{
originalTyperefSchema = typerefSchema;
}
final ClassTemplateSpec found = _schemaToClassMap.get(schema);
if (found == null)
{
if (typerefSchema.getRef().getType() == DataSchema.Type.UNION)
{
result = generateUnion((UnionDataSchema) typerefSchema.getRef(), typerefSchema);
break;
}
else
{
generateTyperef(typerefSchema);
}
}
else if (typerefSchema.getRef().getType() == DataSchema.Type.UNION)
{
result = found;
break;
}
schema = typerefSchema.getRef();
}
if (result == null)
{
assert schema == schema.getDereferencedDataSchema();
if (schema instanceof ComplexDataSchema)
{
final ClassTemplateSpec found = _schemaToClassMap.get(schema);
if (found == null)
{
if (schema instanceof NamedDataSchema)
{
result = generateNamedSchema((NamedDataSchema) schema);
}
else
{
result = generateUnnamedComplexSchema(schema, enclosingClass, memberName);
}
}
else
{
result = found;
}
if (customInfo != null)
{
result = customInfo.getCustomClass();
}
}
else if (schema instanceof PrimitiveDataSchema)
{
result = (customInfo != null) ? customInfo.getCustomClass() : getPrimitiveClassForSchema((PrimitiveDataSchema) schema, enclosingClass, memberName);
}
}
if (result == null)
{
throw unrecognizedSchemaType(enclosingClass, memberName, schema);
}
result.setOriginalTyperefSchema(originalTyperefSchema);
return result;
}
/**
* Determine whether a custom class has been defined for the {@link com.linkedin.data.schema.DataSchema}.
*
* A custom class is defined through the "java" property of the schema. Within this property, a custom class is specified if "java" is a map that contains a "class" property whose value is a string.
* This value specifies the Java class name of the custom class.
*
* The map may optionally include a "coercerClass" property to specify a coercer class that should be initialized.
*
* @param schema to look for custom class specification in.
*
* @return null if no custom class is specified, otherwise return the custom class and the coercer class, the coercer class may be null if no coercer class is specified.
*
* @see com.linkedin.data.template.Custom#initializeCoercerClass(Class)
*/
private CustomClasses getCustomClasses(DataSchema schema)
{
return getCustomClasses(schema, _customTypeLanguage);
}
public static CustomClasses getCustomClasses(DataSchema schema, String customTypeLanguage)
{
CustomClasses customClasses = null;
final Map properties = schema.getProperties();
if (customTypeLanguage != null) {
final Object java = properties.get(customTypeLanguage);
if (java != null) {
if (java.getClass() != DataMap.class) {
throw new IllegalArgumentException(schema + " has \"java\" property that is not a DataMap");
}
final DataMap map = (DataMap) java;
final Object custom = map.get(CLASS_PROPERTY);
if (custom != null) {
if (custom.getClass() != String.class) {
throw new IllegalArgumentException(schema + " has \"java\" property with \"class\" that is not a string");
}
// a custom class specification has been found
customClasses = new CustomClasses();
customClasses.customClass = new ClassTemplateSpec();
customClasses.customClass.setFullName((String) custom);
if (!allowCustomClass(schema)) {
throw new IllegalArgumentException(schema + " cannot have custom class binding");
}
}
// check for coercer class
final Object coercerClass = map.get(COERCER_CLASS_PROPERTY);
if (coercerClass != null) {
if (coercerClass.getClass() != String.class) {
throw new IllegalArgumentException(schema + " has \"java\" property with \"coercerClass\" that is not a string");
}
if (customClasses == null) {
throw new IllegalArgumentException(schema + " has \"java\" property with \"coercerClass\" but does not have \"class\" property");
}
// a custom class specification has been found
customClasses.customCoercerClass = new ClassTemplateSpec();
customClasses.customCoercerClass.setFullName((String) coercerClass);
}
}
}
return customClasses;
}
private CustomInfoSpec getImmediateCustomInfo(DataSchema schema)
{
if (_immediateCustomMap.containsKey(schema))
{
return _immediateCustomMap.get(schema);
}
CustomInfoSpec immediate = null;
for (DataSchema current = schema; current != null; current = dereferenceIfTyperef(current))
{
final CustomClasses customClasses = getCustomClasses(current);
if (customClasses != null)
{
immediate = new CustomInfoSpec((NamedDataSchema) schema, (NamedDataSchema) current, customClasses.customClass, customClasses.customCoercerClass);
break;
}
}
// immediate may be null
_immediateCustomMap.put(schema, immediate);
return immediate;
}
private ClassTemplateSpec getPrimitiveClassForSchema(PrimitiveDataSchema schema, ClassTemplateSpec enclosingClass, String memberName)
{
switch (schema.getType())
{
case INT:
case DOUBLE:
case BOOLEAN:
case STRING:
case LONG:
case FLOAT:
case BYTES:
return PrimitiveTemplateSpec.getInstance(schema.getType());
case NULL:
throw nullTypeNotAllowed(enclosingClass, memberName);
}
throw unrecognizedSchemaType(enclosingClass, memberName, schema);
}
private ClassTemplateSpec generateNamedSchema(NamedDataSchema schema)
{
pushCurrentLocation(_schemaResolver.nameToDataSchemaLocations().get(schema.getFullName()));
final String className = classNameForNamedSchema(schema);
checkForClassNameConflict(className, schema);
final ClassTemplateSpec templateClass;
switch (schema.getType())
{
case RECORD:
templateClass = generateRecord((RecordDataSchema) schema);
break;
case ENUM:
templateClass = generateEnum((EnumDataSchema) schema);
break;
case FIXED:
templateClass = generateFixed((FixedDataSchema) schema);
break;
default:
throw unrecognizedSchemaType(null, null, schema);
}
popCurrentLocation();
return templateClass;
}
private ClassTemplateSpec generateUnnamedComplexSchema(DataSchema schema, ClassTemplateSpec enclosingClass, String memberName)
{
if (schema instanceof ArrayDataSchema)
{
return generateArray((ArrayDataSchema) schema, enclosingClass, memberName);
}
else if (schema instanceof MapDataSchema)
{
return generateMap((MapDataSchema) schema, enclosingClass, memberName);
}
else if (schema instanceof UnionDataSchema)
{
return generateUnion((UnionDataSchema) schema, enclosingClass, memberName);
}
else
{
throw unrecognizedSchemaType(enclosingClass, memberName, schema);
}
}
private ClassTemplateSpec determineDataClass(DataSchema schema, ClassTemplateSpec enclosingClass, String memberName)
{
final ClassTemplateSpec result;
final DataSchema dereferencedSchema = schema.getDereferencedDataSchema();
if (dereferencedSchema.getType() == DataSchema.Type.ENUM)
{
result = PrimitiveTemplateSpec.getInstance(DataSchema.Type.STRING);
}
else if (CodeUtil.isDirectType(dereferencedSchema))
{
result = getPrimitiveClassForSchema((PrimitiveDataSchema) dereferencedSchema, enclosingClass, memberName);
}
else
{
result = null;
}
return result;
}
private ArrayTemplateSpec generateArray(ArrayDataSchema schema, ClassTemplateSpec enclosingClass, String memberName)
{
final DataSchema itemSchema = schema.getItems();
final ClassInfo classInfo = classInfoForUnnamed(enclosingClass, memberName, schema);
if (classInfo.existingClass != null)
{
/* When type refs are used as item types inside some unnamed complex schemas like map and array,
* the type refs are de-referenced and the underlying real type is used in the generated class.
* In those cases the type refs are not processed by the class generation logic, an explicit
* schema processing is necessary in order to processSchema the data template classes for those type
* refs.
*/
processSchema(itemSchema, enclosingClass, memberName);
return (ArrayTemplateSpec) classInfo.existingClass;
}
final ArrayTemplateSpec arrayClass = (ArrayTemplateSpec) classInfo.definedClass;
registerClassTemplateSpec(schema, arrayClass);
arrayClass.setItemClass(processSchema(itemSchema, enclosingClass, memberName));
arrayClass.setItemDataClass(determineDataClass(itemSchema, enclosingClass, memberName));
final CustomInfoSpec customInfo = getImmediateCustomInfo(itemSchema);
arrayClass.setCustomInfo(customInfo);
return arrayClass;
}
private CourierMapTemplateSpec generateMap(MapDataSchema schema, ClassTemplateSpec enclosingClass, String memberName)
{
final DataSchema valueSchema = schema.getValues();
final ClassInfo classInfo = classInfoForUnnamed(enclosingClass, memberName, schema);
// For Courier, check the keys property for a data schema.
final Object keysSchemaData = schema.getProperties().get("keys");
DataSchema keySchema;
if (keysSchemaData != null)
{
keySchema = _schemaParser.parseObject(keysSchemaData);
}
else
{
keySchema = null;
}
if (classInfo.existingClass != null)
{
/* When type refs are used as item types inside some unnamed complex schemas like map and array,
* the type refs are de-referenced and the underlying real type is used in the generated class.
* In those cases the type refs are not processed by the class generation logic, an explicit
* schema processing is necessary in order to processSchema the data template classes for those type
* refs.
*/
processSchema(valueSchema, enclosingClass, memberName);
// For Courier, if there is a schema for map keys, make sure we include in in generation.
if (keySchema != null)
{
if (classInfo.existingClass != null)
{
processSchema(keySchema, enclosingClass, memberName);
}
}
return (CourierMapTemplateSpec) classInfo.existingClass;
}
final CourierMapTemplateSpec mapClass = (CourierMapTemplateSpec) classInfo.definedClass;
registerClassTemplateSpec(schema, mapClass);
mapClass.setValueClass(processSchema(valueSchema, enclosingClass, memberName));
mapClass.setValueDataClass(determineDataClass(valueSchema, enclosingClass, memberName));
final CustomInfoSpec customInfo = getImmediateCustomInfo(valueSchema);
mapClass.setCustomInfo(customInfo);
// For Courier, if there is a schema for map keys, add it's information to the TemplateSpec.
if (keySchema != null)
{
mapClass.setKeySchema(keySchema);
mapClass.setKeyClass(processSchema(keySchema, enclosingClass, memberName));
mapClass.setKeyDataClass(determineDataClass(keySchema, enclosingClass, memberName));
final CustomInfoSpec keyCustomInfo = getImmediateCustomInfo(keySchema);
mapClass.setKeyCustomInfo(keyCustomInfo);
}
return mapClass;
}
private UnionTemplateSpec generateUnion(UnionDataSchema schema, ClassTemplateSpec enclosingClass, String memberName)
{
if (enclosingClass == null || memberName == null)
{
throw new IllegalArgumentException("Cannot processSchema template for top level union: " + schema);
}
final ClassInfo classInfo = classInfoForUnnamed(enclosingClass, memberName, schema);
if (classInfo.existingClass != null)
{
return (UnionTemplateSpec) classInfo.existingClass;
}
final UnionTemplateSpec unionClass = (UnionTemplateSpec) classInfo.definedClass;
registerClassTemplateSpec(schema, unionClass);
return generateUnion(schema, unionClass);
}
private ClassTemplateSpec generateUnion(UnionDataSchema schema, TyperefDataSchema typerefDataSchema)
{
assert typerefDataSchema.getRef() == schema;
pushCurrentLocation(_schemaResolver.nameToDataSchemaLocations().get(typerefDataSchema.getFullName()));
final UnionTemplateSpec unionClass = new UnionTemplateSpec(schema);
unionClass.setNamespace(typerefDataSchema.getNamespace());
unionClass.setClassName(typerefDataSchema.getName());
unionClass.setModifiers(ModifierSpec.PUBLIC);
registerClassTemplateSpec(typerefDataSchema, unionClass);
final TyperefTemplateSpec typerefInfoClass = new TyperefTemplateSpec(typerefDataSchema);
typerefInfoClass.setEnclosingClass(unionClass);
typerefInfoClass.setClassName("UnionTyperefInfo");
typerefInfoClass.setModifiers(ModifierSpec.PRIVATE, ModifierSpec.STATIC, ModifierSpec.FINAL);
final UnionTemplateSpec result = generateUnion(schema, unionClass);
result.setTyperefClass(typerefInfoClass);
popCurrentLocation();
return result;
}
private UnionTemplateSpec generateUnion(UnionDataSchema schema, UnionTemplateSpec unionClass)
{
for (DataSchema memberType : schema.getTypes())
{
final UnionTemplateSpec.Member newMember = new UnionTemplateSpec.Member();
unionClass.getMembers().add(newMember);
newMember.setSchema(memberType);
if (memberType.getDereferencedType() != DataSchema.Type.NULL)
{
newMember.setClassTemplateSpec(processSchema(memberType, unionClass, memberType.getUnionMemberKey()));
newMember.setDataClass(determineDataClass(memberType, unionClass, memberType.getUnionMemberKey()));
}
}
return unionClass;
}
private ClassTemplateSpec generateEnum(EnumDataSchema schema)
{
final EnumTemplateSpec enumClass = new EnumTemplateSpec(schema);
enumClass.setNamespace(schema.getNamespace());
enumClass.setClassName(schema.getName());
enumClass.setModifiers(ModifierSpec.PUBLIC);
registerClassTemplateSpec(schema, enumClass);
return enumClass;
}
private ClassTemplateSpec generateFixed(FixedDataSchema schema)
{
final FixedTemplateSpec fixedClass = new FixedTemplateSpec(schema);
fixedClass.setNamespace(schema.getNamespace());
fixedClass.setClassName(schema.getName());
fixedClass.setModifiers(ModifierSpec.PUBLIC);
registerClassTemplateSpec(schema, fixedClass);
return fixedClass;
}
private TyperefTemplateSpec generateTyperef(TyperefDataSchema schema)
{
final TyperefTemplateSpec typerefClass = new TyperefTemplateSpec(schema);
typerefClass.setNamespace(schema.getNamespace());
typerefClass.setClassName(schema.getName());
typerefClass.setModifiers(ModifierSpec.PUBLIC);
registerClassTemplateSpec(schema, typerefClass);
return typerefClass;
}
private RecordTemplateSpec generateRecord(RecordDataSchema schema)
{
final RecordTemplateSpec recordClass = new RecordTemplateSpec(schema);
recordClass.setNamespace(schema.getNamespace());
recordClass.setClassName(schema.getName());
recordClass.setModifiers(ModifierSpec.PUBLIC);
registerClassTemplateSpec(schema, recordClass);
// processSchema included schemas first, so that unnamed classes will belong to the defining class
// instead of the current class
final List includes = schema.getInclude();
for (NamedDataSchema includedSchema : includes)
{
generate(includedSchema);
}
final Map customInfoMap = new IdentityHashMap(schema.getFields().size() * 2);
for (RecordDataSchema.Field field : schema.getFields())
{
final ClassTemplateSpec fieldClass = processSchema(field.getType(), recordClass, field.getName());
final RecordTemplateSpec.Field newField = new RecordTemplateSpec.Field();
newField.setSchemaField(field);
newField.setType(fieldClass);
newField.setDataClass(determineDataClass(field.getType(), recordClass, field.getName()));
final CustomInfoSpec customInfo = getImmediateCustomInfo(field.getType());
if (customInfo != null && !customInfoMap.containsKey(customInfo))
{
customInfoMap.put(customInfo, null);
newField.setCustomInfo(customInfo);
}
recordClass.addField(newField);
}
return recordClass;
}
/*
* Determine name and class for unnamed types.
*/
private ClassInfo classInfoForUnnamed(ClassTemplateSpec enclosingClass, String name, DataSchema schema)
{
assert !(schema instanceof NamedDataSchema);
assert !(schema instanceof PrimitiveDataSchema);
final ClassInfo classInfo = classNameForUnnamedTraverse(enclosingClass, name, schema);
final String className = classInfo.fullName();
final DataSchema schemaFromClassName = _classNameToSchemaMap.get(className);
if (schemaFromClassName == null)
{
final ClassTemplateSpec classTemplateSpec = createFromDataSchema(schema);
if (enclosingClass != null && classInfo.namespace.equals(enclosingClass.getFullName()))
{
classTemplateSpec.setEnclosingClass(enclosingClass);
classTemplateSpec.setClassName(classInfo.name);
classTemplateSpec.setModifiers(ModifierSpec.PUBLIC, ModifierSpec.STATIC, ModifierSpec.FINAL);
}
else
{
classTemplateSpec.setNamespace(classInfo.namespace);
classTemplateSpec.setClassName(classInfo.name);
classTemplateSpec.setModifiers(ModifierSpec.PUBLIC);
}
classInfo.definedClass = classTemplateSpec;
}
else
{
checkForClassNameConflict(className, schema);
classInfo.existingClass = _schemaToClassMap.get(schemaFromClassName);
}
return classInfo;
}
// For Courier, use CourierMapTemplateSpec for maps.
private ClassTemplateSpec createFromDataSchema(DataSchema schema)
{
if (schema instanceof MapDataSchema)
{
return new CourierMapTemplateSpec((MapDataSchema) schema);
}
return ClassTemplateSpec.createFromDataSchema(schema);
}
private ClassInfo classNameForUnnamedTraverse(ClassTemplateSpec enclosingClass, String memberName, DataSchema schema)
{
final DataSchema dereferencedDataSchema = schema.getDereferencedDataSchema();
switch (dereferencedDataSchema.getType())
{
case ARRAY:
final ArrayDataSchema arraySchema = (ArrayDataSchema) dereferencedDataSchema;
CustomInfoSpec customInfo = getImmediateCustomInfo(arraySchema.getItems());
if (customInfo != null)
{
return new ClassInfo(customInfo.getCustomSchema().getNamespace(), customInfo.getCustomSchema().getName() + ARRAY_SUFFIX);
}
else
{
final ClassInfo classInfo = classNameForUnnamedTraverse(enclosingClass, memberName, arraySchema.getItems());
classInfo.name += ARRAY_SUFFIX;
return classInfo;
}
case MAP:
final MapDataSchema mapSchema = (MapDataSchema) dereferencedDataSchema;
// For Courier, if the schema has a key type, add the name of the type to the generated
// class name.
ClassAndSchema keyClassInfo = getMapKeyName(mapSchema, enclosingClass, memberName);
ClassAndSchema valueClassInfo = getMapValueName(mapSchema, enclosingClass, memberName);
String className;
if (keyClassInfo != null)
{
className = keyClassInfo.classInfo.name + "To" + valueClassInfo.classInfo.name + MAP_SUFFIX;
} else {
className = valueClassInfo.classInfo.name + MAP_SUFFIX;
}
// Pick the best possible namespace
String namespace;
if (valueClassInfo.schema instanceof PrimitiveDataSchema)
{
if (keyClassInfo != null && !(keyClassInfo.schema instanceof PrimitiveDataSchema))
{
namespace = keyClassInfo.classInfo.namespace;
}
else
{
namespace = _dataNamespace;
}
}
else
{
namespace = valueClassInfo.classInfo.namespace;
}
return new ClassInfo(namespace, className);
case UNION:
if (schema.getType() == DataSchema.Type.TYPEREF)
{
DataSchema referencedDataSchema;
TyperefDataSchema typerefDataSchema = (TyperefDataSchema) schema;
while ((referencedDataSchema = typerefDataSchema.getDereferencedDataSchema()) != dereferencedDataSchema)
{
typerefDataSchema = (TyperefDataSchema) referencedDataSchema;
}
return new ClassInfo(typerefDataSchema.getNamespace(), CodeUtil.capitalize(typerefDataSchema.getName()));
}
else
{
return new ClassInfo(enclosingClass.getFullName(), CodeUtil.capitalize(memberName));
}
case FIXED:
case RECORD:
case ENUM:
final NamedDataSchema namedSchema = (NamedDataSchema) dereferencedDataSchema;
return new ClassInfo(namedSchema.getNamespace(), CodeUtil.capitalize(namedSchema.getName()));
case BOOLEAN:
return new ClassInfo(courierPackageName, "Boolean");
case INT:
return new ClassInfo(courierPackageName, "Int");
case LONG:
return new ClassInfo(courierPackageName, "Long");
case FLOAT:
return new ClassInfo(courierPackageName, "Float");
case DOUBLE:
return new ClassInfo(courierPackageName, "Double");
case STRING:
return new ClassInfo(courierPackageName, "String");
case BYTES:
return new ClassInfo(courierPackageName, "ByteString");
case NULL:
throw nullTypeNotAllowed(enclosingClass, memberName);
default:
throw unrecognizedSchemaType(enclosingClass, memberName, dereferencedDataSchema);
}
}
private ClassAndSchema getMapKeyName(MapDataSchema mapSchema, ClassTemplateSpec enclosingClass, String memberName)
{
final Object keysSchemaData = mapSchema.getProperties().get("keys");
if (keysSchemaData != null)
{
// TODO: for some reason this code path to look up a schema doesn't account for the local files namespace.
DataSchema keySchema = _schemaParser.parseObject(keysSchemaData);
if (keySchema == null) {
throw unrecognizedSchemaType(enclosingClass, "keys", mapSchema);
}
final ClassInfo keyClassInfo = classNameForUnnamedTraverse(enclosingClass, memberName, keySchema);
final CustomInfoSpec keyCustomInfo = getImmediateCustomInfo(keySchema);
if (keyCustomInfo != null)
{
NamedDataSchema customSchema = keyCustomInfo.getCustomSchema();
return new ClassAndSchema(new ClassInfo(customSchema.getNamespace(), customSchema.getName()), customSchema);
}
else
{
if (keyClassInfo.name.equals("String"))
{
return null;
}
else
{
return new ClassAndSchema(keyClassInfo, keySchema);
}
}
}
else
{
return null;
}
}
private ClassAndSchema getMapValueName(MapDataSchema mapSchema, ClassTemplateSpec enclosingClass, String memberName)
{
CustomInfoSpec customInfo = getImmediateCustomInfo(mapSchema.getValues());
if (customInfo != null)
{
return new ClassAndSchema(new ClassInfo(customInfo.getCustomSchema().getNamespace(), customInfo.getCustomSchema().getName()), customInfo.getCustomSchema());
}
else
{
return new ClassAndSchema(classNameForUnnamedTraverse(enclosingClass, memberName, mapSchema.getValues()), mapSchema.getValues());
}
}
private static class ClassAndSchema {
private ClassInfo classInfo;
private DataSchema schema;
private ClassAndSchema(ClassInfo classInfo, DataSchema schema)
{
this.classInfo = classInfo;
this.schema = schema;
}
}
public static class CustomClasses
{
public ClassTemplateSpec customClass;
public ClassTemplateSpec customCoercerClass;
}
private static class ClassInfo
{
private String namespace;
private String name;
private ClassTemplateSpec existingClass;
private ClassTemplateSpec definedClass;
private ClassInfo(String namespace, String name)
{
this.namespace = namespace;
this.name = name;
}
private String fullName()
{
return namespace.isEmpty() ? name : namespace + '.' + name;
}
}
}