Import types being checked
[mdsal.git] / binding / mdsal-binding-java-api-generator / src / main / java / org / opendaylight / mdsal / binding / java / api / generator / BuilderTemplate.xtend
index 877f26542b5485c59f08deba77bb29a85fcc89bf..2390ffbdca8273a7539635f3ee35ea9e89197aaf 100644 (file)
  */
 package org.opendaylight.mdsal.binding.java.api.generator
 
-import com.google.common.collect.ImmutableSortedSet
+import static extension org.apache.commons.text.StringEscapeUtils.escapeJava
+import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.AUGMENTABLE_AUGMENTATION_NAME
+import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.AUGMENTATION_FIELD
+
+import com.google.common.collect.ImmutableList
 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.ParameterizedType
 import org.opendaylight.mdsal.binding.model.api.Type
-import org.opendaylight.mdsal.binding.model.util.ReferencedTypeImpl
+import org.opendaylight.mdsal.binding.model.util.TypeConstants
 import org.opendaylight.mdsal.binding.model.util.Types
-import org.opendaylight.mdsal.binding.model.util.generated.type.builder.GeneratedTOBuilderImpl
+import org.opendaylight.mdsal.binding.spec.naming.BindingMapping
 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.DataObject
-import org.opendaylight.yangtools.yang.binding.Identifiable
 
 /**
  * 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;
-
+class BuilderTemplate extends AbstractBuilderTemplate {
     /**
-     * Constant with suffix for the classes which are generated from the builder classes.
+     * Constant used as suffix for builder name.
      */
-    val static IMPL = 'Impl'
+    public static val BUILDER = "Builder";
 
-    /**
-     * 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>();
+    static val AUGMENTATION_FIELD_UPPER = AUGMENTATION_FIELD.toFirstUpper
 
     /**
      * 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)
-        importMap.put(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 GeneratedTOBuilderImpl(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.toInstance.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)
+    new(GeneratedType genType, GeneratedType targetType, Set<GeneratedProperty> properties, Type augmentType,
+            Type keyType) {
+        super(genType, targetType, properties, augmentType, keyType)
     }
 
-    /**
-     * 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 GeneratedTOBuilderImpl("foo", "foo")
-            tmpGenTO.addProperty(fieldName).setReturnType(method.returnType)
-            return tmpGenTO.toInstance.properties.first
-        }
-    }
-
-    override isLocalInnerClass(String importedTypePackageName) {
+    override isLocalInnerClass(JavaTypeName name) {
         // Builders do not have inner types
         return false;
     }
@@ -221,59 +64,57 @@ 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»
+        public class «type.name» implements «Builder.importedName»<«targetType.importedName»> {
 
             «generateFields(false)»
 
-            «generateAugmentField(false)»
-
-            «generateConstructorsFromIfcs(type)»
-
-            «generateCopyConstructor(false)»
-
-            «generateMethodFieldsFrom(type)»
-
-            «generateGetters(false)»
-
-            «generateSetters»
-
-            @Override
-            public «type.name» build() {
-                return new «type.name»«IMPL»(this);
-            }
-
-            private static final class «type.name»«IMPL» implements «type.name» {
+            «constantsDeclarations()»
 
-                «implementedInterfaceGetter»
+            «IF augmentType !== null»
+                «generateAugmentField()»
+            «ENDIF»
 
-                «generateFields(true
+            «generateConstructorsFromIfcs(
 
-                «generateAugmentField(true
+            public «generateCopyConstructor(targetType, type.enclosedTypes.get(0)
 
-                «generateCopyConstructor(true
+            «generateMethodFieldsFrom(
 
-                «generateGetters(true)»
+            «generateGetters(false)»
+            «IF augmentType !== null»
 
-                «generateHashCode()»
+                «generateAugmentation()»
+            «ENDIF»
 
-                «generateEquals()»
+            «generateSetters»
 
-                «generateToString(properties)»
+            @«OVERRIDE.importedName»
+            public «targetType.name» build() {
+                return new «type.enclosedTypes.get(0).importedName»(this);
             }
 
+            «new BuilderImplTemplate(this, type.enclosedTypes.get(0)).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»() {
+    def private generateConstructorsFromIfcs() '''
+        public «type.name»() {
         }
-        «IF (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject))»
-            «val ifc = type as GeneratedType»
-            «FOR impl : ifc.implements»
+        «IF (!(targetType instanceof GeneratedTransferObject))»
+            «FOR impl : targetType.implements»
                 «generateConstructorFromIfc(impl)»
             «ENDFOR»
         «ENDIF»
@@ -284,8 +125,8 @@ 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»
+                public «type.name»(«impl.fullyQualifiedName» arg) {
                     «printConstructorPropertySetter(impl)»
                 }
             «ENDIF»
@@ -298,8 +139,10 @@ 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)»
+                    this._«getter.propertyNameFromGetter» = arg.«getter.name»();
+                «ENDIF»
             «ENDFOR»
             «FOR impl : ifc.implements»
                 «printConstructorPropertySetter(impl)»
@@ -310,23 +153,17 @@ class BuilderTemplate extends BaseTemplate {
     /**
      * 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)»
+    def private generateMethodFieldsFrom() '''
+        «IF (!(targetType instanceof GeneratedTransferObject))»
+            «IF targetType.hasImplementsFromUses»
+                «val List<Type> done = targetType.getBaseIfcs»
+                «generateMethodFieldsFromComment(targetType)»
                 public void fieldsFrom(«DataObject.importedName» arg) {
                     boolean isValidArg = false;
-                    «FOR impl : ifc.getAllIfcs»
+                    «FOR impl : targetType.getAllIfcs»
                         «generateIfCheck(impl, done)»
                     «ENDFOR»
-                    if (!isValidArg) {
-                        throw new IllegalArgumentException(
-                          "expected one of: «ifc.getAllIfcs.toListOfNames» \n" +
-                          "but was: " + arg
-                        );
-                    }
+                    «CODEHELPERS.importedName».validValue(isValidArg, arg, "«targetType.getAllIfcs.toListOfNames»");
                 }
             «ENDIF»
         «ENDIF»
@@ -334,7 +171,7 @@ class BuilderTemplate extends BaseTemplate {
 
     def private generateMethodFieldsFromComment(GeneratedType type) '''
         /**
-         *Set fields from given grouping argument. Valid argument is instance of one of following types:
+         * Set fields from given grouping argument. Valid argument is instance of one of following types:
          * <ul>
          «FOR impl : type.getAllIfcs»
          * <li>«impl.fullyQualifiedName»</li>
@@ -352,7 +189,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
             }
         }
@@ -360,7 +197,7 @@ class BuilderTemplate extends BaseTemplate {
     }
 
     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») {
                 «printPropertySetter(implType)»
@@ -372,8 +209,10 @@ 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)»
+                this._«getter.propertyNameFromGetter» = ((«implementedIfc.fullyQualifiedName»)arg).«getter.name»();
+            «ENDIF»
         «ENDFOR»
         «ENDIF»
     '''
@@ -381,7 +220,7 @@ class BuilderTemplate extends BaseTemplate {
     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)
             }
         }
@@ -393,7 +232,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)
@@ -410,23 +249,88 @@ class BuilderTemplate extends BaseTemplate {
         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»
+    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»
+        «ENDFOR»
+    '''
+
+    def private generateSetter(GeneratedProperty field) {
+        val returnType = field.returnType
+        if (returnType instanceof ParameterizedType) {
+            if (Types.isListType(returnType)) {
+                return generateListSetter(field, returnType.actualTypeArguments.get(0))
+            }
+        }
+        return generateSimpleSetter(field, 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;
+        }
+
     '''
 
-    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();
+    def private generateSimpleSetter(GeneratedProperty field, Type actualType) '''
+        «val restrictions = restrictionsForSetter(actualType)»
+        «IF restrictions !== null»
+
+            «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;
+        }
+        «val uintType = UINT_TYPES.get(field.returnType)»
+        «IF uintType !== null»
+
+            /**
+             * Utility migration setter.
+             *
+             * @param value field value in legacy type
+             * @return this builder
+             * @deprecated Use {#link «setterName»(«field.returnType.importedJavadocName»)} instead.
+             */
+            @Deprecated(forRemoval = true)
+            public «type.getName» «setterName»(final «uintType.importedName» value) {
+                return «setterName»(«CODEHELPERS.importedName».compatUint(value));
+            }
         «ENDIF»
     '''
 
@@ -436,343 +340,128 @@ class BuilderTemplate extends BaseTemplate {
      * @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)»
-
-                    «ENDIF»
-                    «IF restrictions.lengthConstraint.present»
-                    «LengthGenerator.generateLengthChecker(field.fieldName.toString, field.returnType, restrictions.lengthConstraint.get)»
-
-                    «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»
-                }
-                «ENDIF»
-            «ENDIF»
-                this.«field.fieldName» = value;
+        «IF keyType !== null»
+            public «type.getName» withKey(final «keyType.importedName» key) {
+                this.key = key;
                 return this;
             }
+        «ENDIF»
+        «FOR property : properties»
+            «generateSetter(property)»
         «ENDFOR»
-        «IF augmentField !== null»
 
-            public «type.name»«BUILDER» add«augmentField.name.toFirstUpper»(«Class.importedName»<? extends «augmentField.returnType.importedName»> augmentationType, «augmentField.returnType.importedName» augmentationValue) {
+        «IF augmentType !== null»
+            «val augmentTypeRef = augmentType.importedName»
+            «val jlClassRef = CLASS.importedName»
+            public «type.name» add«AUGMENTATION_FIELD_UPPER»(«jlClassRef»<? extends «augmentTypeRef»> augmentationType, «augmentTypeRef» augmentationValue) {
                 if (augmentationValue == null) {
-                    return remove«augmentField.name.toFirstUpper»(augmentationType);
+                    return remove«AUGMENTATION_FIELD_UPPER»(augmentationType);
                 }
 
-                if (!(this.«augmentField.name» instanceof «HashMap.importedName»)) {
-                    this.«augmentField.name» = new «HashMap.importedName»<>();
+                if (!(this.«AUGMENTATION_FIELD» instanceof «HashMap.importedName»)) {
+                    this.«AUGMENTATION_FIELD» = new «HashMap.importedName»<>();
                 }
 
-                this.«augmentField.name».put(augmentationType, augmentationValue);
+                this.«AUGMENTATION_FIELD».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);
+            public «type.name» remove«AUGMENTATION_FIELD_UPPER»(«jlClassRef»<? extends «augmentTypeRef»> augmentationType) {
+                if (this.«AUGMENTATION_FIELD» instanceof «HashMap.importedName») {
+                    this.«AUGMENTATION_FIELD».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»
-                }
-            «ENDIF»
-            «FOR field : allProps»
-                this.«field.fieldName» = base.«field.getterMethodName»();
-            «ENDFOR»
-            «IF augmentField !== null»
-                «IF impl»
-                    switch (base.«augmentField.name».size()) {
-                    case 0:
-                        this.«augmentField.name» = «Collections.importedName».emptyMap();
-                        break;
-                    case 1:
-                        final «Map.importedName».Entry<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»> e = base.«augmentField.name».entrySet().iterator().next();
-                        this.«augmentField.name» = «Collections.importedName».<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»>singletonMap(e.getKey(), e.getValue());
-                        break;
-                    default :
-                        this.«augmentField.name» = new «HashMap.importedName»<>(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;
+    private def createDescription(GeneratedType targetType) {
+        val target = type.importedName
+        return '''
+        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» createTarget(int fooXyzzy, int barBaz) {
+                return new «target»Builder()
+                    .setFoo(new FooBuilder().setXyzzy(fooXyzzy).build())
+                    .setBar(new BarBuilder().setBaz(barBaz).build())
+                    .build();
             }
-        }
-        return false;
+          </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 oportunities, 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»
+        @see «Builder.importedName»
+    '''
     }
 
-    private def Type getKey(GeneratedType type) {
-        for (m : type.methodDefinitions) {
-            if ("getKey".equals(m.name)) {
-                return m.returnType;
-            }
-        }
-        return null;
-    }
+    override protected String formatDataForJavaDoc(GeneratedType type) {
+        val typeDescription = createDescription(type)
 
-    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);
-        }
+        return '''
+            «IF !typeDescription.nullOrEmpty»
+            «typeDescription»
+            «ENDIF»
+        '''.toString
     }
 
-    /**
-     * 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»
-        «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) {
-                if (augmentationType == null) {
-                    throw new IllegalArgumentException("Augmentation Type reference cannot be NULL!");
-                }
-                return (E) «augmentField.name».get(augmentationType);
-            }
-        «ENDIF»
+    private def generateAugmentation() '''
+        @«SUPPRESS_WARNINGS.importedName»({ "unchecked", "checkstyle:methodTypeParameterName"})
+        public <E$$ extends «augmentType.importedName»> E$$ «AUGMENTABLE_AUGMENTATION_NAME»(«CLASS.importedName»<E$$> augmentationType) {
+            return (E$$) «AUGMENTATION_FIELD».get(«CODEHELPERS.importedName».nonNullValue(augmentationType, "augmentationType"));
+        }
     '''
 
-    /**
-     * 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»
-
-                hash = result;
-                hashValid = true;
-                return result;
-            }
-        «ENDIF»
+    override protected generateCopyKeys(List<GeneratedProperty> keyProps) '''
+        this.key = base.«BindingMapping.IDENTIFIABLE_KEY_NAME»();
+        «FOR field : keyProps»
+            this.«field.fieldName» = base.«field.getterMethodName»();
+        «ENDFOR»
     '''
 
-    /**
-     * Template method which generates the method <code>equals()</code>.
-     *
-     * @return string with the <code>equals()</code> 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;
-                }
-                if (!«type.importedName».class.equals(((«DataObject.importedName»)obj).getImplementedInterface())) {
-                    return false;
+    override protected generateCopyAugmentation(Type implType) {
+        val augmentationHolderRef = AugmentationHolder.importedName
+        val typeRef = targetType.importedName
+        val hashMapRef = HashMap.importedName
+        val augmentTypeRef = augmentType.importedName
+        return '''
+            if (base instanceof «augmentationHolderRef») {
+                @SuppressWarnings("unchecked")
+                «JU_MAP.importedName»<«CLASS.importedName»<? extends «augmentTypeRef»>, «augmentTypeRef»> aug =((«augmentationHolderRef»<«typeRef»>) base).augmentations();
+                if (!aug.isEmpty()) {
+                    this.«AUGMENTATION_FIELD» = new «hashMapRef»<>(aug);
                 }
-                «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;
-            }
-        «ENDIF»
-    '''
-
-    def override generateToString(Collection<GeneratedProperty> properties) '''
-        «IF !(properties === null)»
-            @Override
-            public «String.importedName» toString() {
-                «String.importedName» name = "«type.name» [";
-                «StringBuilder.importedName» builder = new «StringBuilder.importedName» (name);
-                «FOR property : properties SEPARATOR "\n    builder.append(\", \");\n}" AFTER "    }\n"»
-                    if («property.fieldName» != null) {
-                        builder.append("«property.fieldName»=");
-                        «IF property.returnType.name.contains("[")»
-                            builder.append(«Arrays.importedName».toString(«property.fieldName»));
-                        «ELSE»
-                            builder.append(«property.fieldName»);
-                        «ENDIF»
-                «ENDFOR»
-                «IF augmentField !== null»
-                    «IF !properties.empty»
-                «««Append comma separator only if it's not there already from previous operation»»»
-final int builderLength = builder.length();
-                    final int builderAdditionalLength = builder.substring(name.length(), builderLength).length();
-                    if (builderAdditionalLength > 2 && !builder.substring(builderLength - 2, builderLength).equals(", ")) {
-                        builder.append(", ");
-                    }
-                    «ENDIF»
-                    builder.append("«augmentField.name»=");
-                    builder.append(«augmentField.name».values());«"\n"»
-                    return builder.append(']').toString();
-                «ELSE»
-                    «IF properties.empty»
-                    return builder.append(']').toString();
-                    «ELSE»
-            return builder.append(']').toString();
-                    «ENDIF»
-                «ENDIF»
             }
-        «ENDIF»
-    '''
-
-    def implementedInterfaceGetter() '''
-    @Override
-    public «Class.importedName»<«type.importedName»> getImplementedInterface() {
-        return «type.importedName».class;
+        '''
     }
-    '''
 
-    private def createDescription(GeneratedType type) {
-        return '''
-        Class that builds {@link «type.importedName»} instances.
-
-        @see «type.importedName»
-    '''
+    private static def hasNonDefaultMethods(GeneratedType type) {
+        !type.methodDefinitions.isEmpty && type.methodDefinitions.exists([def | !def.isDefault])
     }
 
-    override def protected String formatDataForJavaDoc(GeneratedType type) {
-        val typeDescription = createDescription(type)
-
-        return '''
-            «IF !typeDescription.nullOrEmpty»
-            «typeDescription»
-            «ENDIF»
-        '''.toString
+    private static def nonDefaultMethods(GeneratedType type) {
+        type.methodDefinitions.filter([def | !def.isDefault])
     }
 }
-