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=c29f8aea4b59143f86d0be305e52a3f712545974;hb=c09dc4165cbea734b6815243019443419ae060d0;hp=66022bf6b96ac54b4743c832d16d445e7dcccd96;hpb=dcacf7468c8d742f4207b7bc5175413e2ab9eb45;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 66022bf6b9..c29f8aea4b 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 @@ -8,47 +8,45 @@ package org.opendaylight.mdsal.binding.java.api.generator 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.collect.ImmutableList +import com.google.common.collect.ImmutableSet +import com.google.common.collect.Sets import java.util.ArrayList import java.util.Collection -import java.util.HashMap import java.util.HashSet import java.util.List import java.util.Map import java.util.Set -import java.util.regex.Pattern +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.ParameterizedType import org.opendaylight.mdsal.binding.model.api.Type -import org.opendaylight.mdsal.binding.model.util.TypeConstants -import org.opendaylight.mdsal.binding.model.util.Types +import org.opendaylight.mdsal.binding.model.ri.TypeConstants +import org.opendaylight.mdsal.binding.model.ri.Types import org.opendaylight.mdsal.binding.spec.naming.BindingMapping -import org.opendaylight.yangtools.concepts.Builder -import org.opendaylight.yangtools.yang.binding.AugmentationHolder -import org.opendaylight.yangtools.yang.binding.CodeHelpers -import org.opendaylight.yangtools.yang.binding.DataObject /** * Template for generating JAVA builder classes. */ class BuilderTemplate extends AbstractBuilderTemplate { - /** - * Constant used as suffix for builder name. - */ - public static val BUILDER = "Builder"; + val BuilderImplTemplate implTemplate /** * Constructs new instance of this class. * @throws IllegalArgumentException if genType equals null */ - new(GeneratedType genType, GeneratedType targetType, Set properties, Type augmentType, - Type keyType) { - super(genType, targetType, properties, augmentType, keyType) + new(GeneratedType genType, GeneratedType targetType, Type keyType) { + super(genType, targetType, keyType) + implTemplate = new BuilderImplTemplate(this, type.enclosedTypes.get(0)) } override isLocalInnerClass(JavaTypeName name) { @@ -62,44 +60,75 @@ class BuilderTemplate extends AbstractBuilderTemplate { * @return string with JAVA source code */ override body() ''' - «wrapToDocumentation(formatDataForJavaDoc(type))» - public class «type.name» implements «Builder.importedName»<«targetType.importedName»> { + «wrapToDocumentation(formatDataForJavaDoc(targetType))» + «targetType.annotations.generateDeprecatedAnnotation» + «generatedAnnotation» + public class «type.name» { «generateFields(false)» «constantsDeclarations()» «IF augmentType !== null» - «generateAugmentField()» + «val augmentTypeRef = augmentType.importedName» + «val mapTypeRef = JU_MAP.importedName» + «mapTypeRef»<«CLASS.importedName», «augmentTypeRef»> «AUGMENTATION_FIELD» = «mapTypeRef».of(); «ENDIF» + /** + * Construct an empty builder. + */ + public «type.name»() { + // No-op + } + «generateConstructorsFromIfcs()» + «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))» «generateMethodFieldsFrom()» «generateGetters(false)» + «IF augmentType !== null» + + «generateAugmentation()» + «ENDIF» «generateSetters» - @«Override.importedName» - public «targetType.name» build() { + /** + * A new {@link «targetTypeName»} instance. + * + * @return A new {@link «targetTypeName»} instance. + */ + public «targetType.importedNonNull» build() { return new «type.enclosedTypes.get(0).importedName»(this); } - «new BuilderImplTemplate(this, type.enclosedTypes.get(0)).body» + «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() ''' - public «type.name»() { - } «IF (!(targetType instanceof GeneratedTransferObject))» - «FOR impl : targetType.implements» + «FOR impl : targetType.implements SEPARATOR "\n"» «generateConstructorFromIfc(impl)» «ENDFOR» «ENDIF» @@ -111,9 +140,16 @@ class BuilderTemplate extends AbstractBuilderTemplate { def private Object generateConstructorFromIfc(Type impl) ''' «IF (impl instanceof GeneratedType)» «IF impl.hasNonDefaultMethods» - public «type.name»(«impl.fullyQualifiedName» arg) { + «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)» @@ -126,15 +162,41 @@ class BuilderTemplate extends AbstractBuilderTemplate { «val ifc = implementedIfc as GeneratedType» «FOR getter : ifc.nonDefaultMethods» «IF BindingMapping.isGetterMethodName(getter.name)» - this._«getter.propertyNameFromGetter» = arg.«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. */ @@ -143,12 +205,12 @@ class BuilderTemplate extends AbstractBuilderTemplate { «IF targetType.hasImplementsFromUses» «val List done = targetType.getBaseIfcs» «generateMethodFieldsFromComment(targetType)» - public void fieldsFrom(«DataObject.importedName» arg) { + public void fieldsFrom(«DATA_OBJECT.importedName» arg) { boolean isValidArg = false; «FOR impl : targetType.getAllIfcs» «generateIfCheck(impl, done)» «ENDFOR» - «CodeHelpers.importedName».validValue(isValidArg, arg, "«targetType.getAllIfcs.toListOfNames»"); + «CODEHELPERS.importedName».validValue(isValidArg, arg, "«targetType.getAllIfcs.toListOfNames»"); } «ENDIF» «ENDIF» @@ -159,12 +221,12 @@ class BuilderTemplate extends AbstractBuilderTemplate { * 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 */ ''' @@ -184,7 +246,7 @@ class BuilderTemplate extends AbstractBuilderTemplate { def private generateIfCheck(Type impl, List done) ''' «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; } @@ -195,13 +257,54 @@ class BuilderTemplate extends AbstractBuilderTemplate { «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))» «val ifc = implementedIfc as GeneratedType» «FOR getter : ifc.nonDefaultMethods» - «IF BindingMapping.isGetterMethodName(getter.name)» - this._«getter.propertyNameFromGetter» = ((«implementedIfc.fullyQualifiedName»)arg).«getter.name»(); + «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) { @@ -229,7 +332,7 @@ class BuilderTemplate extends AbstractBuilderTemplate { private def List toListOfNames(Collection types) { val List names = new ArrayList for (type : types) { - names.add(type.fullyQualifiedName) + names.add(type.importedName) } return names } @@ -239,11 +342,13 @@ class BuilderTemplate extends AbstractBuilderTemplate { «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)» + «val jurPatternRef = JUR_PATTERN.importedName» «IF cValue.size == 1» - private static final «Pattern.importedName» «Constants.MEMBER_PATTERN_LIST»«fieldSuffix» = «Pattern.importedName».compile("«cValue.keySet.get(0).escapeJava»"); - 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(«ImmutableList.importedName».of( + 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» }; @@ -254,45 +359,100 @@ class BuilderTemplate extends AbstractBuilderTemplate { «ENDFOR» ''' - def private generateListSetter(GeneratedProperty field, Type actualType) ''' + def private generateSetter(BuilderGeneratedProperty 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 generateSimpleSetter(field, returnType) + } + + def private generateListSetter(BuilderGeneratedProperty field, Type actualType) ''' «val restrictions = restrictionsForSetter(actualType)» «IF restrictions !== null» «generateCheckers(field, restrictions, actualType)» «ENDIF» + + /** + * Set the property corresponding to {@link «targetType.importedName»#«field.getterName»()} to the specified + * value. + * + * @param values desired value + * @return this builder + */ public «type.getName» set«field.getName.toFirstUpper»(final «field.returnType.importedName» values) { «IF restrictions !== null» if (values != null) { - for («actualType.getFullyQualifiedName» value : values) { + 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(BuilderGeneratedProperty 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» value) { + /** + * Set the property corresponding to {@link «targetType.importedName»#«field.getterName»()} to the specified + * value. + * + * @param values desired value + * @return this builder + */ + public «type.getName» set«field.name.toFirstUpper»(final «field.returnType.importedName» values) { «IF restrictions !== null» - if (value != null) { - «checkArgument(field, restrictions, actualType, "value")» + 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) - } + def private generateSimpleSetter(BuilderGeneratedProperty field, Type actualType) ''' + «val restrictions = restrictionsForSetter(actualType)» + «IF restrictions !== null» + + «generateCheckers(field, restrictions, actualType)» + «ENDIF» + + /** + * Set the property corresponding to {@link «targetType.importedName»#«field.getterName»()} to the specified + * value. + * + * @param value desired value + * @return this builder + */ + «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 setter methods @@ -307,29 +467,37 @@ class BuilderTemplate extends AbstractBuilderTemplate { } «ENDIF» «FOR property : properties» - «IF property.returnType instanceof ParameterizedType && Types.isListType(property.returnType)» - «generateListSetter(property, getActualType(property.returnType as ParameterizedType))» - «ELSE» - «generateSetter(property, property.returnType)» - «ENDIF» + «generateSetter(property)» «ENDFOR» «IF augmentType !== null» - public «type.name» add«AUGMENTATION_FIELD.toFirstUpper»(«Class.importedName» augmentationType, «augmentType.importedName» augmentationValue) { - if (augmentationValue == null) { - return remove«AUGMENTATION_FIELD.toFirstUpper»(augmentationType); - } - - if (!(this.«AUGMENTATION_FIELD» instanceof «HashMap.importedName»)) { - this.«AUGMENTATION_FIELD» = new «HashMap.importedName»<>(); + «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(augmentationType, augmentationValue); + this.«AUGMENTATION_FIELD».put(augmentation.«DATA_CONTAINER_IMPLEMENTED_INTERFACE_NAME»(), augmentation); return this; } - public «type.name» remove«AUGMENTATION_FIELD.toFirstUpper»(«Class.importedName» augmentationType) { - if (this.«AUGMENTATION_FIELD» instanceof «HashMap.importedName») { + /** + * 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» augmentationType) { + if (this.«AUGMENTATION_FIELD» instanceof «hashMapRef») { this.«AUGMENTATION_FIELD».remove(augmentationType); } return this; @@ -337,11 +505,44 @@ class BuilderTemplate extends AbstractBuilderTemplate { «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 + 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» ''' } @@ -355,6 +556,21 @@ class BuilderTemplate extends AbstractBuilderTemplate { '''.toString } + private def generateAugmentation() ''' + /** + * Return the specified augmentation, if it is present in this builder. + * + * @param 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$$ «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» @@ -362,33 +578,16 @@ class BuilderTemplate extends AbstractBuilderTemplate { «ENDFOR» ''' - override protected generateCopyAugmentation(Type implType) { - val implTypeRef = implType.importedName - val augmentationHolderRef = AugmentationHolder.importedName - val typeRef = targetType.importedName - val hashMapRef = HashMap.importedName - val augmentTypeRef = augmentType.importedName - return ''' - if (base instanceof «implTypeRef») { - «implTypeRef» impl = («implTypeRef») base; - if (!impl.«AUGMENTATION_FIELD».isEmpty()) { - this.«AUGMENTATION_FIELD» = new «hashMapRef»<>(impl.«AUGMENTATION_FIELD»); - } - } else if (base instanceof «augmentationHolderRef») { - @SuppressWarnings("unchecked") - «Map.importedName»<«Class.importedName», «augmentTypeRef»> aug =((«augmentationHolderRef»<«typeRef»>) base).augmentations(); - if (!aug.isEmpty()) { - this.«AUGMENTATION_FIELD» = new «hashMapRef»<>(aug); - } - } - ''' - } - - private static def hasNonDefaultMethods(GeneratedType type) { - !type.methodDefinitions.isEmpty && type.methodDefinitions.exists([def | !def.isDefault]) - } + override protected CharSequence generateCopyNonKeys(Collection props) ''' + «FOR field : props» + this.«field.fieldName» = base.«field.getterName»(); + «ENDFOR» + ''' - private static def nonDefaultMethods(GeneratedType type) { - type.methodDefinitions.filter([def | !def.isDefault]) - } + override protected generateCopyAugmentation(Type implType) ''' + final var aug = base.augmentations(); + if (!aug.isEmpty()) { + this.«AUGMENTATION_FIELD» = new «JU_HASHMAP.importedName»<>(aug); + } + ''' }