X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;f=binding%2Fmdsal-binding-java-api-generator%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fmdsal%2Fbinding%2Fjava%2Fapi%2Fgenerator%2FBuilderTemplate.xtend;h=2a23fe1a90a776e0d971fc4137cfdac66c99b50d;hb=46d3e5401d9c182c12819351b79176ad4354a5c1;hp=2168064fd8cb0b2989c67acc890789281517e1e9;hpb=12b9a4a5099fea3f3dcc58370271311c015b3d7b;p=mdsal.git diff --git a/binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/BuilderTemplate.xtend b/binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/BuilderTemplate.xtend index 2168064fd8..2a23fe1a90 100644 --- a/binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/BuilderTemplate.xtend +++ b/binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/BuilderTemplate.xtend @@ -7,194 +7,46 @@ */ package org.opendaylight.mdsal.binding.java.api.generator -import static extension org.apache.commons.text.StringEscapeUtils.escapeJava; +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.DATA_CONTAINER_IMPLEMENTED_INTERFACE_NAME -import com.google.common.base.MoreObjects -import com.google.common.collect.ImmutableMap -import com.google.common.collect.ImmutableSortedSet 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 java.util.regex.Pattern -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.JavaTypeName -import org.opendaylight.mdsal.binding.model.api.MethodSignature -import org.opendaylight.mdsal.binding.model.api.Type +import org.opendaylight.mdsal.binding.model.api.MethodSignature; import org.opendaylight.mdsal.binding.model.api.ParameterizedType -import org.opendaylight.mdsal.binding.model.api.Restrictions -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.mdsal.binding.model.util.TypeConstants -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.api.Type +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 properties - - private static val METHOD_COMPARATOR = new AlphabeticallyTypeMemberComparator(); +class BuilderTemplate extends AbstractBuilderTemplate { + val BuilderImplTemplate implTemplate /** * Constructs new instance of this class. * @throws IllegalArgumentException if genType equals null */ - new(GeneratedType genType) { - super(genType) - this.properties = propertiesFromMethods(createMethods) - addImport(Builder) - } - - /** - * Returns set of method signature instances which contains all the methods of the genType - * and all the methods of the implemented interfaces. - * - * @returns set of method signature instances - */ - def private Set createMethods() { - val Set methods = new LinkedHashSet(); - methods.addAll(type.methodDefinitions) - collectImplementedMethods(methods, type.implements) - val Set sortedMethods = ImmutableSortedSet.orderedBy(METHOD_COMPARATOR).addAll(methods).build() - - return sortedMethods - } - - /** - * Adds to the methods set all the methods of the implementedIfcs - * and recursively their implemented interfaces. - * - * @param methods set of method signatures - * @param implementedIfcs list of implemented interfaces - */ - def private void collectImplementedMethods(Set methods, List 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 identifier = JavaTypeName.create(m.returnType) - val tmpGenTO = new CodegenGeneratedTOBuilder(identifier) - val refType = new ReferencedTypeImpl(identifier) - val generic = new ReferencedTypeImpl(type.identifier) - 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 elements. - * - * @param list of elements - */ - def private first(List elements) { - elements.get(0) - } - - /** - * Creates set of generated property instances from getter methods. - * - * @param set of method signature instances which should be transformed to list of properties - * @return set of generated property instances which represents the getter methods - */ - def private propertiesFromMethods(Collection methods) { - if (methods === null || methods.isEmpty()) { - return Collections.emptySet - } - val Set 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 method 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 method - * @throws IllegalArgumentException
    - *
  • if the method equals null
  • - *
  • if the name of the method equals null
  • - *
  • if the name of the method is empty
  • - *
  • if the return type of the method equals null
  • - *
- */ - 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(JavaTypeName.create("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(JavaTypeName name) { @@ -208,61 +60,73 @@ class BuilderTemplate extends BaseTemplate { * @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)» «constantsDeclarations()» - «generateAugmentField(false)» - - «generateConstructorsFromIfcs(type)» - - «generateCopyConstructor(false)» - - «generateMethodFieldsFrom(type)» - - «generateGetters(false)» - - «generateSetters» + «IF augmentType !== null» + «generateAugmentField()» + «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» { + «generateConstructorsFromIfcs()» - «implementedInterfaceGetter» + «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))» - «generateFields(true)» + «generateMethodFieldsFrom()» - «generateAugmentField(true)» - - «generateCopyConstructor(true)» - - «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 «targetTypeName» 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» @@ -273,10 +137,17 @@ class BuilderTemplate extends BaseTemplate { */ 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)» @@ -287,30 +158,57 @@ class BuilderTemplate extends BaseTemplate { 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 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 getSpecifiedGetters(GeneratedType type) { + val ImmutableSet.Builder 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 done = ifc.getBaseIfcs» - «generateMethodFieldsFromComment(ifc)» - public void fieldsFrom(«DataObject.importedName» arg) { + def private generateMethodFieldsFrom() ''' + «IF (!(targetType instanceof GeneratedTransferObject))» + «IF targetType.hasImplementsFromUses» + «val List 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» @@ -321,12 +219,12 @@ class BuilderTemplate extends BaseTemplate { * Set fields from given grouping argument. Valid argument is instance of one of following types: *
    «FOR impl : type.getAllIfcs» - *
  • «impl.fullyQualifiedName»
  • + *
  • {@link «impl.importedName»}
  • «ENDFOR» *
* * @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 */ ''' @@ -336,7 +234,7 @@ class BuilderTemplate extends BaseTemplate { 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 } } @@ -344,9 +242,9 @@ class BuilderTemplate extends BaseTemplate { } def private generateIfCheck(Type impl, List 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; } @@ -356,16 +254,59 @@ class BuilderTemplate extends BaseTemplate { 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)) { + val importedClass = importedClass(itemType) + if (importedClass !== null) { + return printPropertySetter(retrieveProperty, propertyName, "checkListFieldCastIdentity", importedClass) + } + return printPropertySetter(retrieveProperty, propertyName, "checkListFieldCast", itemType.importedName) + } + if (Types.isSetType(ownGetterType)) { + val importedClass = importedClass(itemType) + if (importedClass !== null) { + return printPropertySetter(retrieveProperty, propertyName, "checkSetFieldCastIdentity", importedClass) + } + return printPropertySetter(retrieveProperty, propertyName, "checkSetFieldCast", itemType.importedName) + } + if (Types.CLASS.equals(ownGetterType)) { + return printPropertySetter(retrieveProperty, propertyName, "checkFieldCastIdentity", itemType.identifier.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 importedClass(Type type) { + if (type instanceof ParameterizedType) { + if (Types.CLASS.equals(type.rawType)) { + return type.actualTypeArguments.get(0).identifier.importedName + } + } + return null + } + private def List getBaseIfcs(GeneratedType type) { val List 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) } } @@ -377,7 +318,7 @@ class BuilderTemplate extends BaseTemplate { 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) @@ -389,43 +330,24 @@ class BuilderTemplate extends BaseTemplate { private def List toListOfNames(Collection types) { val List 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», «augmentField.returnType.importedName»> «augmentField.name» = «Collections.importedName».emptyMap(); - «ENDIF» - ''' - def private constantsDeclarations() ''' «FOR c : type.getConstantDefinitions» «IF c.getName.startsWith(TypeConstants.PATTERN_CONSTANT_NAME)» «val cValue = c.value as Map» - «val String fieldSuffix = c.getName.substring(TypeConstants.PATTERN_CONSTANT_NAME.length).toLowerCase» - public static final «List.importedName» «c.getName» = «ImmutableList.importedName».of( - «FOR v : cValue.keySet SEPARATOR ", "»"«v.escapeJava»"«ENDFOR»); + «val String fieldSuffix = c.getName.substring(TypeConstants.PATTERN_CONSTANT_NAME.length)» + «val jurPatternRef = JUR_PATTERN.importedName» «IF cValue.size == 1» - private static final «Pattern.importedName» «Constants.MEMBER_PATTERN_LIST»«fieldSuffix» = «Pattern.importedName».compile(«c.getName».get(0)); - private static final String «Constants.MEMBER_REGEX_LIST»«fieldSuffix» = "«cValue.values.get(0).escapeJava»"; + «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 «Pattern.importedName»[] «Constants.MEMBER_PATTERN_LIST»«fieldSuffix» = «CodeHelpers.importedName».compilePatterns(«c.getName»); + 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» @@ -435,46 +357,20 @@ class BuilderTemplate extends BaseTemplate { «ENDFOR» ''' - def private generateCheckers(GeneratedProperty field, Restrictions restrictions, Type actualType) ''' - «IF restrictions.rangeConstraint.present» - «AbstractRangeGenerator.forType(actualType).generateRangeChecker(field.name.toFirstUpper, - restrictions.rangeConstraint.get, this)» - «ENDIF» - «IF restrictions.lengthConstraint.present» - «LengthGenerator.generateLengthChecker(field.fieldName.toString, actualType, restrictions.lengthConstraint.get, this)» - «ENDIF» - ''' - - def private checkArgument(GeneratedProperty property, Restrictions restrictions, Type actualType) ''' - «IF restrictions.getRangeConstraint.isPresent» - «IF actualType instanceof ConcreteType» - «AbstractRangeGenerator.forType(actualType).generateRangeCheckerCall(property.getName.toFirstUpper, "value")» - «ELSE» - «AbstractRangeGenerator.forType(actualType).generateRangeCheckerCall(property.getName.toFirstUpper, "value.getValue()")» - «ENDIF» - «ENDIF» - «IF restrictions.getLengthConstraint.isPresent» - «IF actualType instanceof ConcreteType» - «LengthGenerator.generateLengthCheckerCall(property.fieldName.toString, "value")» - «ELSE» - «LengthGenerator.generateLengthCheckerCall(property.fieldName.toString, "value.getValue()")» - «ENDIF» - «ENDIF» - - «val fieldUpperCase = property.fieldName.toString.toUpperCase()» - «FOR currentConstant : type.getConstantDefinitions» - «IF currentConstant.getName.startsWith(TypeConstants.PATTERN_CONSTANT_NAME) - && fieldUpperCase.equals(currentConstant.getName.substring(TypeConstants.PATTERN_CONSTANT_NAME.length))» - «CodeHelpers.importedName».checkPattern(value, «Constants.MEMBER_PATTERN_LIST»«property.fieldName», «Constants.MEMBER_REGEX_LIST»«property.fieldName»); - «ENDIF» - «ENDFOR» - ''' - - def private Restrictions restrictionsForSetter(Type actualType) { - if (actualType instanceof GeneratedType) { - return null; + 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) + } + return generateListSetter(field, arguments.get(0)) + } else if (Types.isMapType(returnType)) { + return generateMapSetter(field, returnType.actualTypeArguments.get(1)) + } } - return actualType.restrictions; + return generateSimpleSetter(field, returnType) } def private generateListSetter(GeneratedProperty field, Type actualType) ''' @@ -482,310 +378,152 @@ class BuilderTemplate extends BaseTemplate { «IF restrictions !== null» «generateCheckers(field, restrictions, actualType)» «ENDIF» - public «type.getName»Builder set«field.getName.toFirstUpper»(final «field.returnType.importedName» values) { + public «type.getName» set«field.getName.toFirstUpper»(final «field.returnType.importedName» values) { «IF restrictions !== null» if (values != null) { - for («actualType.getFullyQualifiedName» value : values) { - «checkArgument(field, restrictions, actualType)» + for («actualType.importedName» value : values) { + «checkArgument(field, restrictions, actualType, "value")» } } «ENDIF» - this.«field.fieldName.toString» = values; + this.«field.fieldName» = values; return this; } ''' - def private generateSetter(GeneratedProperty field, Type actualType) ''' + def private generateMapSetter(GeneratedProperty field, Type actualType) ''' «val restrictions = restrictionsForSetter(actualType)» «IF restrictions !== null» «generateCheckers(field, restrictions, actualType)» «ENDIF» - - public «type.getName»Builder set«field.getName.toFirstUpper»(final «field.returnType.importedName» value) { + public «type.getName» set«field.name.toFirstUpper»(final «field.returnType.importedName» values) { «IF restrictions !== null» - if (value != null) { - «checkArgument(field, restrictions, actualType)» + if (values != null) { + for («actualType.importedName» value : values.values()) { + «checkArgument(field, restrictions, actualType, "value")» + } } «ENDIF» - this.«field.fieldName.toString» = value; + this.«field.fieldName» = values; return this; } ''' - private def Type getActualType(ParameterizedType ptype) { - return ptype.getActualTypeArguments.get(0) - } - - /** - * Template method which generates setter methods - * - * @return string with the setter methods - */ - def private generateSetters() ''' - «FOR property : properties» - «IF property.returnType instanceof ParameterizedType - && (property.returnType as ParameterizedType).rawType.equals(Types.LIST_TYPE)» - «generateListSetter(property, getActualType(property.returnType as ParameterizedType))» - «ELSE» - «generateSetter(property, property.returnType)» - «ENDIF» - «ENDFOR» - - «IF augmentField !== null» - public «type.name»«BUILDER» add«augmentField.name.toFirstUpper»(«Class.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; - } + def private generateSimpleSetter(GeneratedProperty field, Type actualType) ''' + «val restrictions = restrictionsForSetter(actualType)» + «IF restrictions !== null» - public «type.name»«BUILDER» remove«augmentField.name.toFirstUpper»(«Class.importedName» augmentationType) { - if (this.«augmentField.name» instanceof «HashMap.importedName») { - this.«augmentField.name».remove(augmentationType); - } - return this; - } + «generateCheckers(field, restrictions, actualType)» «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» + «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» - «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» + this.«field.fieldName» = value; + return this; } ''' - private def boolean implementsIfc(GeneratedType type, Type impl) { - for (Type ifc : type.implements) { - if (ifc.equals(impl)) { - return true; - } - } - return false; - } - - private def Type getKey(GeneratedType type) { - for (m : type.methodDefinitions) { - if ("getKey".equals(m.name)) { - return m.returnType; - } - } - return null; - } - - private def void removeProperty(Collection 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. + * Template method which generates setter methods * - * @return string with getter methods + * @return string with the setter methods */ - def private generateGetters(boolean addOverride) ''' - «IF !properties.empty» - «FOR field : properties SEPARATOR '\n'» - «IF addOverride»@Override«ENDIF» - «field.getterMethod» - «ENDFOR» - «ENDIF» - «IF augmentField !== null» - - @SuppressWarnings("unchecked") - «IF addOverride»@Override«ENDIF» - public E get«augmentField.name.toFirstUpper»(«Class.importedName» augmentationType) { - return (E) «augmentField.name».get(«CodeHelpers.importedName».nonNullValue(augmentationType, "augmentationType")); + 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» - /** - * Template method which generates the method hashCode(). - * - * @return string with the hashCode() 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; + «IF augmentType !== null» + «val augmentTypeRef = augmentType.importedName» + «val jlClassRef = CLASS.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) { + «jlClassRef» augmentationType = augmentation.«DATA_CONTAINER_IMPLEMENTED_INTERFACE_NAME»(); + if (!(this.«AUGMENTATION_FIELD» instanceof «hashMapRef»)) { + this.«AUGMENTATION_FIELD» = new «hashMapRef»<>(); } - 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» - - hash = result; - hashValid = true; - return result; + this.«AUGMENTATION_FIELD».put(augmentationType, augmentation); + return this; } - «ENDIF» - ''' - /** - * Template method which generates the method equals(). - * - * @return string with the equals() method definition in JAVA format - */ - 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; + /** + * 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(«jlClassRef» augmentationType) { + if (this.«AUGMENTATION_FIELD» instanceof «hashMapRef») { + this.«AUGMENTATION_FIELD».remove(augmentationType); } - 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», «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; - } - «ENDIF» - ''' - - def override generateToString(Collection 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(); + return this; } «ENDIF» ''' - def implementedInterfaceGetter() ''' - @Override - public «Class.importedName»<«type.importedName»> getImplementedInterface() { - return «type.importedName».class; - } - ''' - - 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 + fluent interface, where method chaining is used. + +

+ In general, this class is supposed to be used like this template: +

+          
+            «target» create«target»(int fooXyzzy, int barBaz) {
+                return new «target»Builder()
+                    .setFoo(new FooBuilder().setXyzzy(fooXyzzy).build())
+                    .setBar(new BarBuilder().setBaz(barBaz).build())
+                    .build();
+            }
+          
+        
+ +

+ This pattern is supported by the immutable nature of «target», as instances can be freely passed around without + worrying about synchronization issues. + +

+ As a side note: method chaining results in: +

    +
  • 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
  • +
  • better understanding by humans, as the scope of mutable state (the builder) is kept to a minimum and is + very localized
  • +
  • better optimization opportunities, as the object scope is minimized in terms of invocation (rather than + method) stack, making escape analysis a lot + easier. Given enough compiler (JIT/AOT) prowess, the cost of th builder object can be completely + eliminated
  • +
+ + @see «target» ''' } - override def protected String formatDataForJavaDoc(GeneratedType type) { + override protected String formatDataForJavaDoc(GeneratedType type) { val typeDescription = createDescription(type) return ''' @@ -794,5 +532,31 @@ class BuilderTemplate extends BaseTemplate { «ENDIF» '''.toString } -} + private def generateAugmentation() ''' + @«SUPPRESS_WARNINGS.importedName»({ "unchecked", "checkstyle:methodTypeParameterName"}) + public E$$ «AUGMENTABLE_AUGMENTATION_NAME»(«CLASS.importedName» augmentationType) { + return (E$$) «AUGMENTATION_FIELD».get(«JU_OBJECTS.importedName».requireNonNull(augmentationType)); + } + ''' + + override protected generateCopyKeys(List keyProps) ''' + this.key = base.«BindingMapping.IDENTIFIABLE_KEY_NAME»(); + «FOR field : keyProps» + this.«field.fieldName» = base.«field.getterMethodName»(); + «ENDFOR» + ''' + + override protected CharSequence generateCopyNonKeys(Collection props) ''' + «FOR field : props» + this.«field.fieldName» = base.«field.getterName»(); + «ENDFOR» + ''' + + override protected generateCopyAugmentation(Type implType) ''' + final var aug = base.augmentations(); + if (!aug.isEmpty()) { + this.«AUGMENTATION_FIELD» = new «JU_HASHMAP.importedName»<>(aug); + } + ''' +}