Document builder constructors
[mdsal.git] / binding / mdsal-binding-java-api-generator / src / main / java / org / opendaylight / mdsal / binding / java / api / generator / BuilderTemplate.xtend
index d095d726be2b9dca6061c7272ad2f656e5e69847..2a23fe1a90a776e0d971fc4137cfdac66c99b50d 100644 (file)
@@ -8,12 +8,14 @@
 package org.opendaylight.mdsal.binding.java.api.generator
 
 import static extension org.apache.commons.text.StringEscapeUtils.escapeJava
-import static org.opendaylight.mdsal.binding.model.util.BindingTypes.DATA_OBJECT
+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.HashSet
@@ -25,33 +27,26 @@ 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
 
 /**
  * Template for generating JAVA builder classes.
  */
 class BuilderTemplate extends AbstractBuilderTemplate {
-    /**
-     * Constant used as suffix for builder name.
-     */
-    package static val BUILDER_STR = "Builder";
-
-    static val AUGMENTATION_FIELD_UPPER = AUGMENTATION_FIELD.toFirstUpper
-    static val BUILDER = JavaTypeName.create(Builder)
+    val BuilderImplTemplate implTemplate
 
     /**
      * Constructs new instance of this class.
      * @throws IllegalArgumentException if <code>genType</code> equals <code>null</code>
      */
-    new(GeneratedType genType, GeneratedType targetType, Set<GeneratedProperty> 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) {
@@ -67,7 +62,8 @@ class BuilderTemplate extends AbstractBuilderTemplate {
     override body() '''
         «wrapToDocumentation(formatDataForJavaDoc(targetType))»
         «targetType.annotations.generateDeprecatedAnnotation»
-        public class «type.name» implements «BUILDER.importedName»<«targetType.importedName»> {
+        «generatedAnnotation»
+        public class «type.name» {
 
             «generateFields(false)»
 
@@ -77,8 +73,21 @@ class BuilderTemplate extends AbstractBuilderTemplate {
                 «generateAugmentField()»
             «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()»
@@ -91,12 +100,16 @@ class BuilderTemplate extends AbstractBuilderTemplate {
 
             «generateSetters»
 
-            @«OVERRIDE.importedName»
-            public «targetType.name» build() {
+            /**
+             * A new {@link «targetTypeName»} instance.
+             *
+             * @return A new {@link «targetTypeName»} instance.
+             */
+            public «targetTypeName» build() {
                 return new «type.enclosedTypes.get(0).importedName»(this);
             }
 
-            «new BuilderImplTemplate(this, type.enclosedTypes.get(0)).body»
+            «implTemplate.body»
         }
     '''
 
@@ -112,10 +125,8 @@ class BuilderTemplate extends AbstractBuilderTemplate {
      * 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»
@@ -127,9 +138,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)»
@@ -142,15 +160,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<MethodSignature> alreadySetProperties) '''
+        «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
+            «val ifc = implementedIfc as GeneratedType»
+            «FOR getter : ifc.nonDefaultMethods»
+                «IF BindingMapping.isGetterMethodName(getter.name) && getterByName(alreadySetProperties, getter.name).isEmpty»
+                    «val propertyName = getter.propertyNameFromGetter»
+                    «printPropertySetter(getter, '''arg.«getter.name»()''', propertyName)»;
+                «ENDIF»
+            «ENDFOR»
+            «FOR descendant : ifc.implements»
+                «printConstructorPropertySetter(descendant, Sets.union(alreadySetProperties, getSpecifiedGetters(ifc)))»
+            «ENDFOR»
+        «ENDIF»
+    '''
+
+    def static Set<MethodSignature> getSpecifiedGetters(GeneratedType type) {
+        val ImmutableSet.Builder<MethodSignature> setBuilder = new ImmutableSet.Builder
+        for (MethodSignature method : type.getMethodDefinitions()) {
+            if (method.hasOverrideAnnotation) {
+                setBuilder.add(method)
+            }
+        }
+        return setBuilder.build()
+    }
+
     /**
      * Generate 'fieldsFrom' method to set builder properties based on type of given argument.
      */
@@ -175,12 +219,12 @@ class BuilderTemplate extends AbstractBuilderTemplate {
          * Set fields from given grouping argument. Valid argument is instance of one of following types:
          * <ul>
          «FOR impl : type.getAllIfcs»
-         * <li>«impl.fullyQualifiedName»</li>
+         *   <li>{@link «impl.importedName»}</li>
          «ENDFOR»
          * </ul>
          *
          * @param arg grouping object
-         * @throws IllegalArgumentException if given argument is none of valid types
+         * @throws IllegalArgumentException if given argument is none of valid types or has property with incompatible value
         */
     '''
 
@@ -200,7 +244,7 @@ class BuilderTemplate extends AbstractBuilderTemplate {
     def private generateIfCheck(Type impl, List<Type> 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;
             }
@@ -211,13 +255,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<Type> getBaseIfcs(GeneratedType type) {
         val List<Type> baseIfcs = new ArrayList();
         for (ifc : type.implements) {
@@ -245,7 +330,7 @@ class BuilderTemplate extends AbstractBuilderTemplate {
     private def List<String> toListOfNames(Collection<Type> types) {
         val List<String> names = new ArrayList
         for (type : types) {
-            names.add(type.fullyQualifiedName)
+            names.add(type.importedName)
         }
         return names
     }
@@ -275,16 +360,20 @@ class BuilderTemplate extends AbstractBuilderTemplate {
     def private generateSetter(GeneratedProperty field) {
         val returnType = field.returnType
         if (returnType instanceof ParameterizedType) {
-            if (Types.isListType(returnType)) {
-                return generateListSetter(field, returnType.actualTypeArguments.get(0), "")
+            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 generateListSetter(field, returnType.actualTypeArguments.get(1), ".values()")
+                return generateMapSetter(field, returnType.actualTypeArguments.get(1))
             }
         }
         return generateSimpleSetter(field, returnType)
     }
 
-    def private generateListSetter(GeneratedProperty field, Type actualType, String extractor) '''
+    def private generateListSetter(GeneratedProperty field, Type actualType) '''
         «val restrictions = restrictionsForSetter(actualType)»
         «IF restrictions !== null»
             «generateCheckers(field, restrictions, actualType)»
@@ -292,7 +381,7 @@ class BuilderTemplate extends AbstractBuilderTemplate {
         public «type.getName» set«field.getName.toFirstUpper»(final «field.returnType.importedName» values) {
         «IF restrictions !== null»
             if (values != null) {
-               for («actualType.importedName» value : values«extractor») {
+               for («actualType.importedName» value : values) {
                    «checkArgument(field, restrictions, actualType, "value")»
                }
             }
@@ -303,6 +392,24 @@ class BuilderTemplate extends AbstractBuilderTemplate {
 
     '''
 
+    def private generateMapSetter(GeneratedProperty field, Type actualType) '''
+        «val restrictions = restrictionsForSetter(actualType)»
+        «IF restrictions !== null»
+            «generateCheckers(field, restrictions, actualType)»
+        «ENDIF»
+        public «type.getName» set«field.name.toFirstUpper»(final «field.returnType.importedName» values) {
+        «IF restrictions !== null»
+            if (values != null) {
+               for («actualType.importedName» value : values.values()) {
+                   «checkArgument(field, restrictions, actualType, "value")»
+               }
+            }
+        «ENDIF»
+            this.«field.fieldName» = values;
+            return this;
+        }
+    '''
+
     def private generateSimpleSetter(GeneratedProperty field, Type actualType) '''
         «val restrictions = restrictionsForSetter(actualType)»
         «IF restrictions !== null»
@@ -342,24 +449,31 @@ class BuilderTemplate extends AbstractBuilderTemplate {
             «val augmentTypeRef = augmentType.importedName»
             «val jlClassRef = CLASS.importedName»
             «val hashMapRef = JU_HASHMAP.importedName»
-            public «type.name» add«AUGMENTATION_FIELD_UPPER»(«augmentTypeRef» augmentation) {
-                return add«AUGMENTATION_FIELD_UPPER»(augmentation.«DATA_CONTAINER_IMPLEMENTED_INTERFACE_NAME»(), augmentation);
-            }
-
-            public «type.name» add«AUGMENTATION_FIELD_UPPER»(«jlClassRef»<? extends «augmentTypeRef»> augmentationType, «augmentTypeRef» augmentationValue) {
-                if (augmentationValue == null) {
-                    return remove«AUGMENTATION_FIELD_UPPER»(augmentationType);
-                }
-
+            /**
+              * 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»<? extends «augmentTypeRef»> augmentationType = augmentation.«DATA_CONTAINER_IMPLEMENTED_INTERFACE_NAME»();
                 if (!(this.«AUGMENTATION_FIELD» instanceof «hashMapRef»)) {
                     this.«AUGMENTATION_FIELD» = new «hashMapRef»<>();
                 }
 
-                this.«AUGMENTATION_FIELD».put(augmentationType, augmentationValue);
+                this.«AUGMENTATION_FIELD».put(augmentationType, augmentation);
                 return this;
             }
 
-            public «type.name» remove«AUGMENTATION_FIELD_UPPER»(«jlClassRef»<? extends «augmentTypeRef»> augmentationType) {
+            /**
+              * 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»<? extends «augmentTypeRef»> augmentationType) {
                 if (this.«AUGMENTATION_FIELD» instanceof «hashMapRef») {
                     this.«AUGMENTATION_FIELD».remove(augmentationType);
                 }
@@ -369,7 +483,7 @@ class BuilderTemplate extends AbstractBuilderTemplate {
     '''
 
     private def createDescription(GeneratedType targetType) {
-        val target = type.importedName
+        val target = targetType.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.
@@ -378,7 +492,7 @@ class BuilderTemplate extends AbstractBuilderTemplate {
         In general, this class is supposed to be used like this template:
         <pre>
           <code>
-            «target» createTarget(int fooXyzzy, int barBaz) {
+            «target» create«target»(int fooXyzzy, int barBaz) {
                 return new «target»Builder()
                     .setFoo(new FooBuilder().setXyzzy(fooXyzzy).build())
                     .setBar(new BarBuilder().setBaz(barBaz).build())
@@ -399,14 +513,13 @@ class BuilderTemplate extends AbstractBuilderTemplate {
               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
+          <li>better optimization opportunities, as the object scope is minimized in terms of invocation (rather than
               method) stack, making <a href="https://en.wikipedia.org/wiki/Escape_analysis">escape analysis</a> a lot
               easier. Given enough compiler (JIT/AOT) prowess, the cost of th builder object can be completely
               eliminated</li>
         </ul>
 
         @see «target»
-        @see «BUILDER.importedName»
     '''
     }
 
@@ -429,30 +542,21 @@ class BuilderTemplate extends AbstractBuilderTemplate {
 
     override protected generateCopyKeys(List<GeneratedProperty> keyProps) '''
         this.key = base.«BindingMapping.IDENTIFIABLE_KEY_NAME»();
-        «generateCopyNonKeys(keyProps)»
+        «FOR field : keyProps»
+            this.«field.fieldName» = base.«field.getterMethodName»();
+        «ENDFOR»
     '''
 
-    override protected generateCopyAugmentation(Type implType) {
-        val augmentationHolderRef = AugmentationHolder.importedName
-        val typeRef = targetType.importedName
-        val hashMapRef = JU_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);
-                }
-            }
-        '''
-    }
-
-    private static def hasNonDefaultMethods(GeneratedType type) {
-        !type.methodDefinitions.isEmpty && type.methodDefinitions.exists([def | !def.isDefault])
-    }
+    override protected CharSequence generateCopyNonKeys(Collection<BuilderGeneratedProperty> 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);
+       }
+    '''
 }