*/
package org.opendaylight.mdsal.binding.java.api.generator
-import com.google.common.base.MoreObjects
-import com.google.common.collect.ImmutableMap
-import com.google.common.collect.ImmutableSortedSet
+import static extension org.apache.commons.text.StringEscapeUtils.escapeJava
+import static org.opendaylight.mdsal.binding.model.ri.BindingTypes.DATA_OBJECT
+import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.AUGMENTABLE_AUGMENTATION_NAME
+import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.AUGMENTATION_FIELD
+import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.BINDING_CONTRACT_IMPLEMENTED_INTERFACE_NAME
+
+import com.google.common.collect.ImmutableList
+import com.google.common.collect.ImmutableSet
+import com.google.common.collect.Sets
import java.util.ArrayList
-import java.util.Arrays
import java.util.Collection
-import java.util.Collections
-import java.util.HashMap
import java.util.HashSet
-import java.util.LinkedHashSet
import java.util.List
import java.util.Map
-import java.util.Objects
import java.util.Set
-import org.opendaylight.mdsal.binding.model.api.ConcreteType
+import org.opendaylight.mdsal.binding.model.api.AnnotationType
import org.opendaylight.mdsal.binding.model.api.GeneratedProperty
import org.opendaylight.mdsal.binding.model.api.GeneratedTransferObject
import org.opendaylight.mdsal.binding.model.api.GeneratedType
-import org.opendaylight.mdsal.binding.model.api.MethodSignature
+import org.opendaylight.mdsal.binding.model.api.JavaTypeName
+import org.opendaylight.mdsal.binding.model.api.MethodSignature;
+import org.opendaylight.mdsal.binding.model.api.ParameterizedType
import org.opendaylight.mdsal.binding.model.api.Type
-import org.opendaylight.mdsal.binding.model.util.ReferencedTypeImpl
-import org.opendaylight.mdsal.binding.model.util.Types
-import org.opendaylight.mdsal.binding.model.util.generated.type.builder.CodegenGeneratedTOBuilder
-import org.opendaylight.yangtools.concepts.Builder
-import org.opendaylight.yangtools.yang.binding.Augmentable
-import org.opendaylight.yangtools.yang.binding.AugmentationHolder
-import org.opendaylight.yangtools.yang.binding.CodeHelpers
-import org.opendaylight.yangtools.yang.binding.DataObject
-import org.opendaylight.yangtools.yang.binding.Identifiable
+import org.opendaylight.mdsal.binding.model.ri.TypeConstants
+import org.opendaylight.mdsal.binding.model.ri.Types
+import org.opendaylight.mdsal.binding.spec.naming.BindingMapping
/**
* Template for generating JAVA builder classes.
*/
-
-class BuilderTemplate extends BaseTemplate {
-
- /**
- * Constant with the name of the concrete method.
- */
- val static GET_AUGMENTATION_METHOD_NAME = "getAugmentation"
-
- /**
- * Constant with the suffix for builder classes.
- */
- val static BUILDER = 'Builder'
-
- /**
- * Constant with the name of the BuilderFor interface
- */
- val static BUILDERFOR = Builder.simpleName;
-
- /**
- * Constant with suffix for the classes which are generated from the builder classes.
- */
- val static IMPL = 'Impl'
-
- /**
- * Generated property is set if among methods is found one with the name GET_AUGMENTATION_METHOD_NAME
- */
- var GeneratedProperty augmentField
-
- /**
- * Set of class attributes (fields) which are derived from the getter methods names
- */
- val Set<GeneratedProperty> properties
-
- private static val METHOD_COMPARATOR = new AlphabeticallyTypeMemberComparator<MethodSignature>();
+class BuilderTemplate extends AbstractBuilderTemplate {
+ val BuilderImplTemplate implTemplate
/**
* Constructs new instance of this class.
* @throws IllegalArgumentException if <code>genType</code> equals <code>null</code>
*/
- new(GeneratedType genType) {
- super(genType)
- this.properties = propertiesFromMethods(createMethods)
- addImport(Builder.simpleName, Builder.package.name)
- }
-
- /**
- * Returns set of method signature instances which contains all the methods of the <code>genType</code>
- * and all the methods of the implemented interfaces.
- *
- * @returns set of method signature instances
- */
- def private Set<MethodSignature> createMethods() {
- val Set<MethodSignature> methods = new LinkedHashSet();
- methods.addAll(type.methodDefinitions)
- collectImplementedMethods(methods, type.implements)
- val Set<MethodSignature> sortedMethods = ImmutableSortedSet.orderedBy(METHOD_COMPARATOR).addAll(methods).build()
-
- return sortedMethods
- }
-
- /**
- * Adds to the <code>methods</code> set all the methods of the <code>implementedIfcs</code>
- * and recursively their implemented interfaces.
- *
- * @param methods set of method signatures
- * @param implementedIfcs list of implemented interfaces
- */
- def private void collectImplementedMethods(Set<MethodSignature> methods, List<Type> implementedIfcs) {
- if (implementedIfcs === null || implementedIfcs.empty) {
- return
- }
- for (implementedIfc : implementedIfcs) {
- if ((implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))) {
- val ifc = implementedIfc as GeneratedType
- methods.addAll(ifc.methodDefinitions)
- collectImplementedMethods(methods, ifc.implements)
- } else if (implementedIfc.fullyQualifiedName == Augmentable.name) {
- for (m : Augmentable.methods) {
- if (m.name == GET_AUGMENTATION_METHOD_NAME) {
- val fullyQualifiedName = m.returnType.name
- val pkg = fullyQualifiedName.package
- val name = fullyQualifiedName.name
- val tmpGenTO = new CodegenGeneratedTOBuilder(pkg, name)
- val refType = new ReferencedTypeImpl(pkg, name)
- val generic = new ReferencedTypeImpl(type.packageName, type.name)
- val parametrizedReturnType = Types.parameterizedTypeFor(refType, generic)
- tmpGenTO.addMethod(m.name).setReturnType(parametrizedReturnType)
- augmentField = tmpGenTO.build.methodDefinitions.first.propertyFromGetter
- }
- }
- }
- }
- }
-
- /**
- * Returns the first element of the list <code>elements</code>.
- *
- * @param list of elements
- */
- def private <E> first(List<E> elements) {
- elements.get(0)
- }
-
- /**
- * Returns the name of the package from <code>fullyQualifiedName</code>.
- *
- * @param fullyQualifiedName string with fully qualified type name (package + type)
- * @return string with the package name
- */
- def private String getPackage(String fullyQualifiedName) {
- val lastDotIndex = fullyQualifiedName.lastIndexOf(Constants.DOT)
- return if (lastDotIndex == -1) "" else fullyQualifiedName.substring(0, lastDotIndex)
- }
-
- /**
- * Returns the name of tye type from <code>fullyQualifiedName</code>
- *
- * @param fullyQualifiedName string with fully qualified type name (package + type)
- * @return string with the name of the type
- */
- def private String getName(String fullyQualifiedName) {
- val lastDotIndex = fullyQualifiedName.lastIndexOf(Constants.DOT)
- return if (lastDotIndex == -1) fullyQualifiedName else fullyQualifiedName.substring(lastDotIndex + 1)
- }
-
- /**
- * Creates set of generated property instances from getter <code>methods</code>.
- *
- * @param set of method signature instances which should be transformed to list of properties
- * @return set of generated property instances which represents the getter <code>methods</code>
- */
- def private propertiesFromMethods(Collection<MethodSignature> methods) {
- if (methods === null || methods.isEmpty()) {
- return Collections.emptySet
- }
- val Set<GeneratedProperty> result = new LinkedHashSet
- for (m : methods) {
- val createdField = m.propertyFromGetter
- if (createdField !== null) {
- result.add(createdField)
- }
- }
- return result
- }
-
- /**
- * Creates generated property instance from the getter <code>method</code> name and return type.
- *
- * @param method method signature from which is the method name and return type obtained
- * @return generated property instance for the getter <code>method</code>
- * @throws IllegalArgumentException<ul>
- * <li>if the <code>method</code> equals <code>null</code></li>
- * <li>if the name of the <code>method</code> equals <code>null</code></li>
- * <li>if the name of the <code>method</code> is empty</li>
- * <li>if the return type of the <code>method</code> equals <code>null</code></li>
- * </ul>
- */
- def private GeneratedProperty propertyFromGetter(MethodSignature method) {
- if (method === null || method.name === null || method.name.empty || method.returnType === null) {
- throw new IllegalArgumentException("Method, method name, method return type reference cannot be NULL or empty!")
- }
- var prefix = "get";
- if (Types.BOOLEAN.equals(method.returnType)) {
- prefix = "is";
- }
- if (method.name.startsWith(prefix)) {
- val fieldName = method.getName().substring(prefix.length()).toFirstLower
- val tmpGenTO = new CodegenGeneratedTOBuilder("foo", "foo")
- tmpGenTO.addProperty(fieldName).setReturnType(method.returnType)
- return tmpGenTO.build.properties.first
- }
+ new(GeneratedType genType, GeneratedType targetType, Type keyType) {
+ super(genType, targetType, keyType)
+ implTemplate = new BuilderImplTemplate(this, type.enclosedTypes.get(0))
}
- override isLocalInnerClass(String importedTypePackageName) {
+ override isLocalInnerClass(JavaTypeName name) {
// Builders do not have inner types
return false;
}
* @return string with JAVA source code
*/
override body() '''
- «wrapToDocumentation(formatDataForJavaDoc(type))»
- public class «type.name»«BUILDER» implements «BUILDERFOR»<«type.importedName»> {
+ «wrapToDocumentation(formatDataForJavaDoc(targetType))»
+ «targetType.annotations.generateDeprecatedAnnotation»
+ «generatedAnnotation»
+ public class «type.name» {
«generateFields(false)»
- «generateAugmentField(false)»
-
- «generateConstructorsFromIfcs(type)»
-
- «generateCopyConstructor(false)»
+ «constantsDeclarations()»
- «generateMethodFieldsFrom(type)»
-
- «generateGetters(false)»
-
- «generateSetters»
+ «IF augmentType !== null»
+ «val augmentTypeRef = augmentType.importedName»
+ «val mapTypeRef = JU_MAP.importedName»
+ «mapTypeRef»<«CLASS.importedName»<? extends «augmentTypeRef»>, «augmentTypeRef»> «AUGMENTATION_FIELD» = «mapTypeRef».of();
+ «ENDIF»
- @Override
- public «type.name» build() {
- return new «type.name»«IMPL»(this);
+ /**
+ * Construct an empty builder.
+ */
+ public «type.name»() {
+ // No-op
}
- private static final class «type.name»«IMPL» implements «type.name» {
-
- «implementedInterfaceGetter»
-
- «generateFields(true)»
+ «generateConstructorsFromIfcs()»
- «generateAugmentField(true)»
+ «val targetTypeName = targetType.importedName»
+ /**
+ * Construct a builder initialized with state from specified {@link «targetTypeName»}.
+ *
+ * @param base «targetTypeName» from which the builder should be initialized
+ */
+ public «generateCopyConstructor(targetType, type.enclosedTypes.get(0))»
- «generateCopyConstructor(true)»
+ «generateMethodFieldsFrom()»
- «generateGetters(true)»
+ «generateGetters(false)»
+ «IF augmentType !== null»
- «generateHashCode()»
+ «generateAugmentation()»
+ «ENDIF»
- «generateEquals()»
+ «generateSetters»
- «generateToString(properties)»
+ /**
+ * A new {@link «targetTypeName»} instance.
+ *
+ * @return A new {@link «targetTypeName»} instance.
+ */
+ public «targetType.importedNonNull» build() {
+ return new «type.enclosedTypes.get(0).importedName»(this);
}
+ «implTemplate.body»
}
'''
+ override generateDeprecatedAnnotation(AnnotationType ann) {
+ val forRemoval = ann.getParameter("forRemoval")
+ if (forRemoval !== null) {
+ return "@" + DEPRECATED.importedName + "(forRemoval = " + forRemoval.value + ")"
+ }
+ return "@" + SUPPRESS_WARNINGS.importedName + "(\"deprecation\")"
+ }
+
/**
* Generate default constructor and constructor for every implemented interface from uses statements.
*/
- def private generateConstructorsFromIfcs(Type type) '''
- public «type.name»«BUILDER»() {
- }
- «IF (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject))»
- «val ifc = type as GeneratedType»
- «FOR impl : ifc.implements»
+ def private generateConstructorsFromIfcs() '''
+ «IF (!(targetType instanceof GeneratedTransferObject))»
+ «FOR impl : targetType.implements SEPARATOR "\n"»
«generateConstructorFromIfc(impl)»
«ENDFOR»
«ENDIF»
*/
def private Object generateConstructorFromIfc(Type impl) '''
«IF (impl instanceof GeneratedType)»
- «IF !(impl.methodDefinitions.empty)»
- public «type.name»«BUILDER»(«impl.fullyQualifiedName» arg) {
+ «IF impl.hasNonDefaultMethods»
+ «val typeName = impl.importedName»
+ /**
+ * Construct a new builder initialized from specified {@link «typeName»}.
+ *
+ * @param arg «typeName» from which the builder should be initialized
+ */
+ public «type.name»(«typeName» arg) {
«printConstructorPropertySetter(impl)»
}
+
«ENDIF»
«FOR implTypeImplement : impl.implements»
«generateConstructorFromIfc(implTypeImplement)»
def private Object printConstructorPropertySetter(Type implementedIfc) '''
«IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
«val ifc = implementedIfc as GeneratedType»
- «FOR getter : ifc.methodDefinitions»
- this._«getter.propertyNameFromGetter» = arg.«getter.name»();
+ «FOR getter : ifc.nonDefaultMethods»
+ «IF BindingMapping.isGetterMethodName(getter.name)»
+ «val propertyName = getter.propertyNameFromGetter»
+ «printPropertySetter(getter, '''arg.«getter.name»()''', propertyName)»;
+ «ENDIF»
«ENDFOR»
«FOR impl : ifc.implements»
- «printConstructorPropertySetter(impl)»
+ «printConstructorPropertySetter(impl, getSpecifiedGetters(ifc))»
+ «ENDFOR»
+ «ENDIF»
+ '''
+
+ def private Object printConstructorPropertySetter(Type implementedIfc, Set<MethodSignature> alreadySetProperties) '''
+ «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
+ «val ifc = implementedIfc as GeneratedType»
+ «FOR getter : ifc.nonDefaultMethods»
+ «IF BindingMapping.isGetterMethodName(getter.name) && getterByName(alreadySetProperties, getter.name).isEmpty»
+ «val propertyName = getter.propertyNameFromGetter»
+ «printPropertySetter(getter, '''arg.«getter.name»()''', propertyName)»;
+ «ENDIF»
+ «ENDFOR»
+ «FOR descendant : ifc.implements»
+ «printConstructorPropertySetter(descendant, Sets.union(alreadySetProperties, getSpecifiedGetters(ifc)))»
«ENDFOR»
«ENDIF»
'''
+ def static Set<MethodSignature> getSpecifiedGetters(GeneratedType type) {
+ val ImmutableSet.Builder<MethodSignature> setBuilder = new ImmutableSet.Builder
+ for (MethodSignature method : type.getMethodDefinitions()) {
+ if (method.hasOverrideAnnotation) {
+ setBuilder.add(method)
+ }
+ }
+ return setBuilder.build()
+ }
+
/**
* Generate 'fieldsFrom' method to set builder properties based on type of given argument.
*/
- def private generateMethodFieldsFrom(Type type) '''
- «IF (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject))»
- «val ifc = type as GeneratedType»
- «IF ifc.hasImplementsFromUses»
- «val List<Type> done = ifc.getBaseIfcs»
- «generateMethodFieldsFromComment(ifc)»
- public void fieldsFrom(«DataObject.importedName» arg) {
+ def private generateMethodFieldsFrom() '''
+ «IF (!(targetType instanceof GeneratedTransferObject))»
+ «IF targetType.hasImplementsFromUses»
+ «val List<Type> done = targetType.getBaseIfcs»
+ «generateMethodFieldsFromComment(targetType)»
+ public void fieldsFrom(«DATA_OBJECT.importedName» arg) {
boolean isValidArg = false;
- «FOR impl : ifc.getAllIfcs»
+ «FOR impl : targetType.getAllIfcs»
«generateIfCheck(impl, done)»
«ENDFOR»
- «CodeHelpers.importedName».validValue(isValidArg, arg, "«ifc.getAllIfcs.toListOfNames»");
+ «CODEHELPERS.importedName».validValue(isValidArg, arg, "«targetType.getAllIfcs.toListOfNames»");
}
«ENDIF»
«ENDIF»
* Set fields from given grouping argument. Valid argument is instance of one of following types:
* <ul>
«FOR impl : type.getAllIfcs»
- * <li>«impl.fullyQualifiedName»</li>
+ * <li>{@link «impl.importedName»}</li>
«ENDFOR»
* </ul>
*
* @param arg grouping object
- * @throws IllegalArgumentException if given argument is none of valid types
+ * @throws IllegalArgumentException if given argument is none of valid types or has property with incompatible value
*/
'''
def boolean hasImplementsFromUses(GeneratedType type) {
var i = 0
for (impl : type.getAllIfcs) {
- if ((impl instanceof GeneratedType) && !((impl as GeneratedType).methodDefinitions.empty)) {
+ if (impl instanceof GeneratedType && (impl as GeneratedType).hasNonDefaultMethods) {
i = i + 1
}
}
}
def private generateIfCheck(Type impl, List<Type> done) '''
- «IF (impl instanceof GeneratedType) && !((impl as GeneratedType).methodDefinitions.empty)»
+ «IF (impl instanceof GeneratedType && (impl as GeneratedType).hasNonDefaultMethods)»
«val implType = impl as GeneratedType»
- if (arg instanceof «implType.fullyQualifiedName») {
+ if (arg instanceof «implType.importedName») {
«printPropertySetter(implType)»
isValidArg = true;
}
def private printPropertySetter(Type implementedIfc) '''
«IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
«val ifc = implementedIfc as GeneratedType»
- «FOR getter : ifc.methodDefinitions»
- this._«getter.propertyNameFromGetter» = ((«implementedIfc.fullyQualifiedName»)arg).«getter.name»();
+ «FOR getter : ifc.nonDefaultMethods»
+ «IF BindingMapping.isGetterMethodName(getter.name) && !hasOverrideAnnotation(getter)»
+ «printPropertySetter(getter, '''((«ifc.importedName»)arg).«getter.name»()''', getter.propertyNameFromGetter)»;
+ «ENDIF»
«ENDFOR»
«ENDIF»
'''
+ def private printPropertySetter(MethodSignature getter, String retrieveProperty, String propertyName) {
+ val ownGetter = implTemplate.findGetter(getter.name)
+ val ownGetterType = ownGetter.returnType
+ if (Types.strictTypeEquals(getter.returnType, ownGetterType)) {
+ return "this._" + propertyName + " = " + retrieveProperty
+ }
+ if (ownGetterType instanceof ParameterizedType) {
+ val itemType = ownGetterType.actualTypeArguments.get(0)
+ if (Types.isListType(ownGetterType)) {
+ return printPropertySetter(retrieveProperty, propertyName, "checkListFieldCast", itemType.importedName)
+ }
+ if (Types.isSetType(ownGetterType)) {
+ return printPropertySetter(retrieveProperty, propertyName, "checkSetFieldCast", itemType.importedName)
+ }
+ }
+ return printPropertySetter(retrieveProperty, propertyName, "checkFieldCast", ownGetterType.importedName)
+ }
+
+ def private printPropertySetter(String retrieveProperty, String propertyName, String checkerName, String className) '''
+ this._«propertyName» = «CODEHELPERS.importedName».«checkerName»(«className».class, "«propertyName»", «retrieveProperty»)'''
+
private def List<Type> getBaseIfcs(GeneratedType type) {
val List<Type> baseIfcs = new ArrayList();
for (ifc : type.implements) {
- if (ifc instanceof GeneratedType && !(ifc as GeneratedType).methodDefinitions.empty) {
+ if (ifc instanceof GeneratedType && (ifc as GeneratedType).hasNonDefaultMethods) {
baseIfcs.add(ifc)
}
}
if (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject)) {
val ifc = type as GeneratedType
for (impl : ifc.implements) {
- if (impl instanceof GeneratedType && !(impl as GeneratedType).methodDefinitions.empty) {
+ if (impl instanceof GeneratedType && (impl as GeneratedType).hasNonDefaultMethods) {
baseIfcs.add(impl)
}
baseIfcs.addAll(impl.getAllIfcs)
private def List<String> toListOfNames(Collection<Type> types) {
val List<String> names = new ArrayList
for (type : types) {
- names.add(type.fullyQualifiedName)
+ names.add(type.importedName)
}
return names
}
- /**
- * Template method which generates class attributes.
- *
- * @param boolean value which specify whether field is|isn't final
- * @return string with class attributes and their types
- */
- def private generateFields(boolean _final) '''
- «IF properties !== null»
- «FOR f : properties»
- private«IF _final» final«ENDIF» «f.returnType.importedName» «f.fieldName»;
- «ENDFOR»
- «ENDIF»
- '''
-
- def private generateAugmentField(boolean isPrivate) '''
- «IF augmentField !== null»
- «IF isPrivate»private «ENDIF»«Map.importedName»<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»> «augmentField.name» = «Collections.importedName».emptyMap();
- «ENDIF»
- '''
-
- /**
- * Template method which generates setter methods
- *
- * @return string with the setter methods
- */
- def private generateSetters() '''
- «FOR field : properties SEPARATOR '\n'»
- «/* FIXME: generate checkers as simple blocks and embed them directly in setters */»
- «val restrictions = field.returnType.restrictions»
- «IF !(field.returnType instanceof GeneratedType) && restrictions !== null»
- «IF restrictions.rangeConstraint.present»
- «val rangeGenerator = AbstractRangeGenerator.forType(field.returnType)»
- «rangeGenerator.generateRangeChecker(field.name.toFirstUpper, restrictions.rangeConstraint.get, this)»
-
- «ENDIF»
- «IF restrictions.lengthConstraint.present»
- «LengthGenerator.generateLengthChecker(field.fieldName.toString, field.returnType, restrictions.lengthConstraint.get, this)»
-
- «ENDIF»
- «ENDIF»
- public «type.name»«BUILDER» set«field.name.toFirstUpper»(final «field.returnType.importedName» value) {
- «IF !(field.returnType instanceof GeneratedType) && restrictions !== null»
- «IF restrictions !== null && (restrictions.rangeConstraint.present || restrictions.lengthConstraint.present)»
- if (value != null) {
- «IF restrictions.rangeConstraint.present»
- «val rangeGenerator = AbstractRangeGenerator.forType(field.returnType)»
- «IF field.returnType instanceof ConcreteType»
- «rangeGenerator.generateRangeCheckerCall(field.name.toFirstUpper, "value")»
- «ELSE»
- «rangeGenerator.generateRangeCheckerCall(field.name.toFirstUpper, "value.getValue()")»
- «ENDIF»
- «ENDIF»
- «IF restrictions.lengthConstraint.present»
- «IF field.returnType instanceof ConcreteType»
- «LengthGenerator.generateLengthCheckerCall(field.fieldName.toString, "value")»
- «ELSE»
- «LengthGenerator.generateLengthCheckerCall(field.fieldName.toString, "value.getValue()")»
- «ENDIF»
- «ENDIF»
- }
+ def private constantsDeclarations() '''
+ «FOR c : type.getConstantDefinitions»
+ «IF c.getName.startsWith(TypeConstants.PATTERN_CONSTANT_NAME)»
+ «val cValue = c.value as Map<String, String>»
+ «val String fieldSuffix = c.getName.substring(TypeConstants.PATTERN_CONSTANT_NAME.length)»
+ «val jurPatternRef = JUR_PATTERN.importedName»
+ «IF cValue.size == 1»
+ «val firstEntry = cValue.entrySet.iterator.next»
+ private static final «jurPatternRef» «Constants.MEMBER_PATTERN_LIST»«fieldSuffix» = «jurPatternRef».compile("«firstEntry.key.escapeJava»");
+ private static final String «Constants.MEMBER_REGEX_LIST»«fieldSuffix» = "«firstEntry.value.escapeJava»";
+ «ELSE»
+ private static final «jurPatternRef»[] «Constants.MEMBER_PATTERN_LIST»«fieldSuffix» = «CODEHELPERS.importedName».compilePatterns(«ImmutableList.importedName».of(
+ «FOR v : cValue.keySet SEPARATOR ", "»"«v.escapeJava»"«ENDFOR»));
+ private static final String[] «Constants.MEMBER_REGEX_LIST»«fieldSuffix» = { «
+ FOR v : cValue.values SEPARATOR ", "»"«v.escapeJava»"«ENDFOR» };
«ENDIF»
+ «ELSE»
+ «emitConstant(c)»
«ENDIF»
- this.«field.fieldName» = value;
- return this;
- }
«ENDFOR»
- «IF augmentField !== null»
-
- public «type.name»«BUILDER» add«augmentField.name.toFirstUpper»(«Class.importedName»<? extends «augmentField.returnType.importedName»> augmentationType, «augmentField.returnType.importedName» augmentationValue) {
- if (augmentationValue == null) {
- return remove«augmentField.name.toFirstUpper»(augmentationType);
- }
-
- if (!(this.«augmentField.name» instanceof «HashMap.importedName»)) {
- this.«augmentField.name» = new «HashMap.importedName»<>();
- }
-
- this.«augmentField.name».put(augmentationType, augmentationValue);
- return this;
- }
-
- public «type.name»«BUILDER» remove«augmentField.name.toFirstUpper»(«Class.importedName»<? extends «augmentField.returnType.importedName»> augmentationType) {
- if (this.«augmentField.name» instanceof «HashMap.importedName») {
- this.«augmentField.name».remove(augmentationType);
- }
- return this;
- }
- «ENDIF»
'''
- def private CharSequence generateCopyConstructor(boolean impl) '''
- «IF impl»private«ELSE»public«ENDIF» «type.name»«IF impl»«IMPL»«ELSE»«BUILDER»«ENDIF»(«type.name»«IF impl»«BUILDER»«ENDIF» base) {
- «val allProps = new ArrayList(properties)»
- «val isList = implementsIfc(type, Types.parameterizedTypeFor(Types.typeForClass(Identifiable), type))»
- «val keyType = type.getKey»
- «IF isList && keyType !== null»
- «val keyProps = new ArrayList((keyType as GeneratedTransferObject).properties)»
- «Collections.sort(keyProps,
- [ p1, p2 |
- return p1.name.compareTo(p2.name)
- ])
- »
- «FOR field : keyProps»
- «removeProperty(allProps, field.name)»
- «ENDFOR»
- «removeProperty(allProps, "key")»
- if (base.getKey() == null) {
- this._key = new «keyType.importedName»(
- «FOR keyProp : keyProps SEPARATOR ", "»
- base.«keyProp.getterMethodName»()
- «ENDFOR»
- );
- «FOR field : keyProps»
- this.«field.fieldName» = base.«field.getterMethodName»();
- «ENDFOR»
- } else {
- this._key = base.getKey();
- «FOR field : keyProps»
- this.«field.fieldName» = _key.«field.getterMethodName»();
- «ENDFOR»
+ def private generateSetter(GeneratedProperty field) {
+ val returnType = field.returnType
+ if (returnType instanceof ParameterizedType) {
+ if (Types.isListType(returnType) || Types.isSetType(returnType)) {
+ val arguments = returnType.actualTypeArguments
+ if (arguments.isEmpty) {
+ return generateListSetter(field, Types.objectType)
}
- «ENDIF»
- «FOR field : allProps»
- this.«field.fieldName» = base.«field.getterMethodName»();
- «ENDFOR»
- «IF augmentField !== null»
- «IF impl»
- this.«augmentField.name» = «ImmutableMap.importedName».copyOf(base.«augmentField.name»);
- «ELSE»
- if (base instanceof «type.name»«IMPL») {
- «type.name»«IMPL» impl = («type.name»«IMPL») base;
- if (!impl.«augmentField.name».isEmpty()) {
- this.«augmentField.name» = new «HashMap.importedName»<>(impl.«augmentField.name»);
- }
- } else if (base instanceof «AugmentationHolder.importedName») {
- @SuppressWarnings("unchecked")
- «AugmentationHolder.importedName»<«type.importedName»> casted =(«AugmentationHolder.importedName»<«type.importedName»>) base;
- if (!casted.augmentations().isEmpty()) {
- this.«augmentField.name» = new «HashMap.importedName»<>(casted.augmentations());
- }
- }
- «ENDIF»
- «ENDIF»
- }
- '''
-
- private def boolean implementsIfc(GeneratedType type, Type impl) {
- for (Type ifc : type.implements) {
- if (ifc.equals(impl)) {
- return true;
+ return generateListSetter(field, arguments.get(0))
+ } else if (Types.isMapType(returnType)) {
+ return generateMapSetter(field, returnType.actualTypeArguments.get(1))
}
}
- return false;
+ return generateSimpleSetter(field, returnType)
}
- private def Type getKey(GeneratedType type) {
- for (m : type.methodDefinitions) {
- if ("getKey".equals(m.name)) {
- return m.returnType;
+ def private generateListSetter(GeneratedProperty field, Type actualType) '''
+ «val restrictions = restrictionsForSetter(actualType)»
+ «IF restrictions !== null»
+ «generateCheckers(field, restrictions, actualType)»
+ «ENDIF»
+ public «type.getName» set«field.getName.toFirstUpper»(final «field.returnType.importedName» values) {
+ «IF restrictions !== null»
+ if (values != null) {
+ for («actualType.importedName» value : values) {
+ «checkArgument(field, restrictions, actualType, "value")»
+ }
}
+ «ENDIF»
+ this.«field.fieldName» = values;
+ return this;
}
- return null;
- }
- private def void removeProperty(Collection<GeneratedProperty> props, String name) {
- var GeneratedProperty toRemove = null
- for (p : props) {
- if (p.name.equals(name)) {
- toRemove = p;
- }
- }
- if (toRemove !== null) {
- props.remove(toRemove);
- }
- }
+ '''
- /**
- * Template method which generate getter methods for IMPL class.
- *
- * @return string with getter methods
- */
- def private generateGetters(boolean addOverride) '''
- «IF !properties.empty»
- «FOR field : properties SEPARATOR '\n'»
- «IF addOverride»@Override«ENDIF»
- «field.getterMethod»
- «ENDFOR»
+ def private generateMapSetter(GeneratedProperty field, Type actualType) '''
+ «val restrictions = restrictionsForSetter(actualType)»
+ «IF restrictions !== null»
+ «generateCheckers(field, restrictions, actualType)»
«ENDIF»
- «IF augmentField !== null»
-
- @SuppressWarnings("unchecked")
- «IF addOverride»@Override«ENDIF»
- public <E extends «augmentField.returnType.importedName»> E get«augmentField.name.toFirstUpper»(«Class.importedName»<E> augmentationType) {
- return (E) «augmentField.name».get(«CodeHelpers.importedName».nonNullValue(augmentationType, "augmentationType"));
+ public «type.getName» set«field.name.toFirstUpper»(final «field.returnType.importedName» values) {
+ «IF restrictions !== null»
+ if (values != null) {
+ for («actualType.importedName» value : values.values()) {
+ «checkArgument(field, restrictions, actualType, "value")»
+ }
}
«ENDIF»
+ this.«field.fieldName» = values;
+ return this;
+ }
'''
- /**
- * Template method which generates the method <code>hashCode()</code>.
- *
- * @return string with the <code>hashCode()</code> method definition in JAVA format
- */
- def protected generateHashCode() '''
- «IF !properties.empty || augmentField !== null»
- private int hash = 0;
- private volatile boolean hashValid = false;
-
- @Override
- public int hashCode() {
- if (hashValid) {
- return hash;
- }
-
- final int prime = 31;
- int result = 1;
- «FOR property : properties»
- «IF property.returnType.name.contains("[")»
- result = prime * result + «Arrays.importedName».hashCode(«property.fieldName»);
- «ELSE»
- result = prime * result + «Objects.importedName».hashCode(«property.fieldName»);
- «ENDIF»
- «ENDFOR»
- «IF augmentField !== null»
- result = prime * result + «Objects.importedName».hashCode(«augmentField.name»);
- «ENDIF»
+ def private generateSimpleSetter(GeneratedProperty field, Type actualType) '''
+ «val restrictions = restrictionsForSetter(actualType)»
+ «IF restrictions !== null»
- hash = result;
- hashValid = true;
- return result;
- }
+ «generateCheckers(field, restrictions, actualType)»
«ENDIF»
+
+ «val setterName = "set" + field.getName.toFirstUpper»
+ public «type.getName» «setterName»(final «field.returnType.importedName» value) {
+ «IF restrictions !== null»
+ if (value != null) {
+ «checkArgument(field, restrictions, actualType, "value")»
+ }
+ «ENDIF»
+ this.«field.fieldName» = value;
+ return this;
+ }
'''
/**
- * Template method which generates the method <code>equals()</code>.
+ * Template method which generates setter methods
*
- * @return string with the <code>equals()</code> method definition in JAVA format
+ * @return string with the setter methods
*/
- def protected generateEquals() '''
- «IF !properties.empty || augmentField !== null»
- @Override
- public boolean equals(«Object.importedName» obj) {
- if (this == obj) {
- return true;
- }
- if (!(obj instanceof «DataObject.importedName»)) {
- return false;
- }
- if (!«type.importedName».class.equals(((«DataObject.importedName»)obj).getImplementedInterface())) {
- return false;
- }
- «type.importedName» other = («type.importedName»)obj;
- «FOR property : properties»
- «val fieldName = property.fieldName»
- «IF property.returnType.name.contains("[")»
- if (!«Arrays.importedName».equals(«fieldName», other.«property.getterMethodName»())) {
- «ELSE»
- if (!«Objects.importedName».equals(«fieldName», other.«property.getterMethodName»())) {
- «ENDIF»
- return false;
- }
- «ENDFOR»
- «IF augmentField !== null»
- if (getClass() == obj.getClass()) {
- // Simple case: we are comparing against self
- «type.name»«IMPL» otherImpl = («type.name»«IMPL») obj;
- «val fieldName = augmentField.name»
- if (!«Objects.importedName».equals(«fieldName», otherImpl.«fieldName»)) {
- return false;
- }
- } else {
- // Hard case: compare our augments with presence there...
- for («Map.importedName».Entry<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»> e : «augmentField.name».entrySet()) {
- if (!e.getValue().equals(other.getAugmentation(e.getKey()))) {
- return false;
- }
- }
- // .. and give the other one the chance to do the same
- if (!obj.equals(this)) {
- return false;
- }
- }
- «ENDIF»
- return true;
+ def private generateSetters() '''
+ «IF keyType !== null»
+ public «type.getName» withKey(final «keyType.importedName» key) {
+ this.key = key;
+ return this;
}
«ENDIF»
- '''
+ «FOR property : properties»
+ «generateSetter(property)»
+ «ENDFOR»
- def override generateToString(Collection<GeneratedProperty> properties) '''
- «IF properties !== null»
- @Override
- public «String.importedName» toString() {
- final «MoreObjects.importedName».ToStringHelper helper = «MoreObjects.importedName».toStringHelper("«type.name»");
- «FOR property : properties»
- «CodeHelpers.importedName».appendValue(helper, "«property.fieldName»", «property.fieldName»);
- «ENDFOR»
- «IF augmentField !== null»
- «CodeHelpers.importedName».appendValue(helper, "«augmentField.name»", «augmentField.name».values());
- «ENDIF»
- return helper.toString();
+ «IF augmentType !== null»
+ «val augmentTypeRef = augmentType.importedName»
+ «val hashMapRef = JU_HASHMAP.importedName»
+ /**
+ * Add an augmentation to this builder's product.
+ *
+ * @param augmentation augmentation to be added
+ * @return this builder
+ * @throws NullPointerException if {@code augmentation} is null
+ */
+ public «type.name» addAugmentation(«augmentTypeRef» augmentation) {
+ if (!(this.«AUGMENTATION_FIELD» instanceof «hashMapRef»)) {
+ this.«AUGMENTATION_FIELD» = new «hashMapRef»<>();
+ }
+
+ this.«AUGMENTATION_FIELD».put(augmentation.«BINDING_CONTRACT_IMPLEMENTED_INTERFACE_NAME»(), augmentation);
+ return this;
}
- «ENDIF»
- '''
- def implementedInterfaceGetter() '''
- @Override
- public «Class.importedName»<«type.importedName»> getImplementedInterface() {
- return «type.importedName».class;
- }
+ /**
+ * Remove an augmentation from this builder's product. If this builder does not track such an augmentation
+ * type, this method does nothing.
+ *
+ * @param augmentationType augmentation type to be removed
+ * @return this builder
+ */
+ public «type.name» removeAugmentation(«CLASS.importedName»<? extends «augmentTypeRef»> augmentationType) {
+ if (this.«AUGMENTATION_FIELD» instanceof «hashMapRef») {
+ this.«AUGMENTATION_FIELD».remove(augmentationType);
+ }
+ return this;
+ }
+ «ENDIF»
'''
- private def createDescription(GeneratedType type) {
+ private def createDescription(GeneratedType targetType) {
+ val target = targetType.importedName
return '''
- Class that builds {@link «type.importedName»} instances.
-
- @see «type.importedName»
+ Class that builds {@link «target»} instances. Overall design of the class is that of a
+ <a href="https://en.wikipedia.org/wiki/Fluent_interface">fluent interface</a>, where method chaining is used.
+
+ <p>
+ In general, this class is supposed to be used like this template:
+ <pre>
+ <code>
+ «target» create«target»(int fooXyzzy, int barBaz) {
+ return new «target»Builder()
+ .setFoo(new FooBuilder().setXyzzy(fooXyzzy).build())
+ .setBar(new BarBuilder().setBaz(barBaz).build())
+ .build();
+ }
+ </code>
+ </pre>
+
+ <p>
+ This pattern is supported by the immutable nature of «target», as instances can be freely passed around without
+ worrying about synchronization issues.
+
+ <p>
+ As a side note: method chaining results in:
+ <ul>
+ <li>very efficient Java bytecode, as the method invocation result, in this case the Builder reference, is
+ on the stack, so further method invocations just need to fill method arguments for the next method
+ invocation, which is terminated by {@link #build()}, which is then returned from the method</li>
+ <li>better understanding by humans, as the scope of mutable state (the builder) is kept to a minimum and is
+ very localized</li>
+ <li>better optimization opportunities, as the object scope is minimized in terms of invocation (rather than
+ method) stack, making <a href="https://en.wikipedia.org/wiki/Escape_analysis">escape analysis</a> a lot
+ easier. Given enough compiler (JIT/AOT) prowess, the cost of th builder object can be completely
+ eliminated</li>
+ </ul>
+
+ @see «target»
'''
}
- override def protected String formatDataForJavaDoc(GeneratedType type) {
+ override protected String formatDataForJavaDoc(GeneratedType type) {
val typeDescription = createDescription(type)
return '''
«ENDIF»
'''.toString
}
-}
+ private def generateAugmentation() '''
+ /**
+ * Return the specified augmentation, if it is present in this builder.
+ *
+ * @param <E$$> augmentation type
+ * @param augmentationType augmentation type class
+ * @return Augmentation object from this builder, or {@code null} if not present
+ * @throws «NPE.importedName» if {@code augmentType} is {@code null}
+ */
+ @«SUPPRESS_WARNINGS.importedName»({ "unchecked", "checkstyle:methodTypeParameterName"})
+ public <E$$ extends «augmentType.importedName»> E$$ «AUGMENTABLE_AUGMENTATION_NAME»(«CLASS.importedName»<E$$> augmentationType) {
+ return (E$$) «AUGMENTATION_FIELD».get(«JU_OBJECTS.importedName».requireNonNull(augmentationType));
+ }
+ '''
+
+ override protected generateCopyKeys(List<GeneratedProperty> keyProps) '''
+ this.key = base.«BindingMapping.IDENTIFIABLE_KEY_NAME»();
+ «FOR field : keyProps»
+ this.«field.fieldName» = base.«field.getterMethodName»();
+ «ENDFOR»
+ '''
+
+ override protected CharSequence generateCopyNonKeys(Collection<BuilderGeneratedProperty> props) '''
+ «FOR field : props»
+ this.«field.fieldName» = base.«field.getterName»();
+ «ENDFOR»
+ '''
+
+ override protected generateCopyAugmentation(Type implType) {
+ val hashMapRef = JU_HASHMAP.importedName
+ val augmentTypeRef = augmentType.importedName
+ return '''
+ «JU_MAP.importedName»<«CLASS.importedName»<? extends «augmentTypeRef»>, «augmentTypeRef»> aug = base.augmentations();
+ if (!aug.isEmpty()) {
+ this.«AUGMENTATION_FIELD» = new «hashMapRef»<>(aug);
+ }
+ '''
+ }
+}