Add Types.isListType(ParameterizedType)
[mdsal.git] / binding / mdsal-binding-java-api-generator / src / main / java / org / opendaylight / mdsal / binding / java / api / generator / BuilderTemplate.xtend
index 66022bf6b96ac54b4743c832d16d445e7dcccd96..14b77a06fdef05b464a54ae4310ac8b26cbc51bc 100644 (file)
@@ -8,6 +8,7 @@
 package org.opendaylight.mdsal.binding.java.api.generator
 
 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
@@ -18,7 +19,7 @@ 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
@@ -30,7 +31,6 @@ import org.opendaylight.mdsal.binding.model.util.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
 
 /**
@@ -42,6 +42,8 @@ class BuilderTemplate extends AbstractBuilderTemplate {
      */
     public static val BUILDER = "Builder";
 
+    static val AUGMENTATION_FIELD_UPPER = AUGMENTATION_FIELD.toFirstUpper
+
     /**
      * Constructs new instance of this class.
      * @throws IllegalArgumentException if <code>genType</code> equals <code>null</code>
@@ -62,7 +64,8 @@ class BuilderTemplate extends AbstractBuilderTemplate {
      * @return string with JAVA source code
      */
     override body() '''
-        «wrapToDocumentation(formatDataForJavaDoc(type))»
+        «wrapToDocumentation(formatDataForJavaDoc(targetType))»
+        «targetType.annotations.generateDeprecatedAnnotation»
         public class «type.name» implements «Builder.importedName»<«targetType.importedName»> {
 
             «generateFields(false)»
@@ -80,10 +83,14 @@ class BuilderTemplate extends AbstractBuilderTemplate {
             «generateMethodFieldsFrom()»
 
             «generateGetters(false)»
+            «IF augmentType !== null»
+
+                «generateAugmentation()»
+            «ENDIF»
 
             «generateSetters»
 
-            @«Override.importedName»
+            @«OVERRIDE.importedName»
             public «targetType.name» build() {
                 return new «type.enclosedTypes.get(0).importedName»(this);
             }
@@ -92,6 +99,14 @@ class BuilderTemplate extends AbstractBuilderTemplate {
         }
     '''
 
+    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.
      */
@@ -148,7 +163,7 @@ class BuilderTemplate extends AbstractBuilderTemplate {
                     «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»
@@ -239,11 +254,13 @@ class BuilderTemplate extends AbstractBuilderTemplate {
             «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»
-                   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,6 +271,16 @@ class BuilderTemplate extends AbstractBuilderTemplate {
         «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»
@@ -267,33 +294,46 @@ class BuilderTemplate extends AbstractBuilderTemplate {
                }
             }
         «ENDIF»
-            this.«field.fieldName.toString» = values;
+            this.«field.fieldName» = values;
             return this;
         }
 
     '''
 
-    def private generateSetter(GeneratedProperty field, Type actualType) '''
+    def private generateSimpleSetter(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» value) {
-        «IF restrictions !== null»
-            if (value != null) {
-                «checkArgument(field, restrictions, actualType, "value")»
-            }
-        «ENDIF»
-            this.«field.fieldName.toString» = value;
+        «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»
     '''
 
-    private def Type getActualType(ParameterizedType ptype) {
-        return ptype.getActualTypeArguments.get(0)
-    }
-
     /**
      * Template method which generates setter methods
      *
@@ -307,17 +347,15 @@ 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»<? extends «augmentType.importedName»> augmentationType, «augmentType.importedName» augmentationValue) {
+            «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«AUGMENTATION_FIELD.toFirstUpper»(augmentationType);
+                    return remove«AUGMENTATION_FIELD_UPPER»(augmentationType);
                 }
 
                 if (!(this.«AUGMENTATION_FIELD» instanceof «HashMap.importedName»)) {
@@ -328,7 +366,7 @@ class BuilderTemplate extends AbstractBuilderTemplate {
                 return this;
             }
 
-            public «type.name» remove«AUGMENTATION_FIELD.toFirstUpper»(«Class.importedName»<? extends «augmentType.importedName»> augmentationType) {
+            public «type.name» remove«AUGMENTATION_FIELD_UPPER»(«jlClassRef»<? extends «augmentTypeRef»> augmentationType) {
                 if (this.«AUGMENTATION_FIELD» instanceof «HashMap.importedName») {
                     this.«AUGMENTATION_FIELD».remove(augmentationType);
                 }
@@ -337,11 +375,45 @@ class BuilderTemplate extends AbstractBuilderTemplate {
         «ENDIF»
     '''
 
-    private def createDescription(GeneratedType type) {
+    private def createDescription(GeneratedType targetType) {
+        val target = type.importedName
         return '''
-        Class that builds {@link «type.importedName»} instances.
-
-        @see «type.importedName»
+        Class that builds {@link «target»} instances. Overall design of the class is that of a
+        <a href="https://en.wikipedia.org/wiki/Fluent_interface">fluent interface</a>, where method chaining is used.
+
+        <p>
+        In general, this class is supposed to be used like this template:
+        <pre>
+          <code>
+            «target» createTarget(int fooXyzzy, int barBaz) {
+                return new «target»Builder()
+                    .setFoo(new FooBuilder().setXyzzy(fooXyzzy).build())
+                    .setBar(new BarBuilder().setBaz(barBaz).build())
+                    .build();
+            }
+          </code>
+        </pre>
+
+        <p>
+        This pattern is supported by the immutable nature of «target», as instances can be freely passed around without
+        worrying about synchronization issues.
+
+        <p>
+        As a side note: method chaining results in:
+        <ul>
+          <li>very efficient Java bytecode, as the method invocation result, in this case the Builder reference, is
+              on the stack, so further method invocations just need to fill method arguments for the next method
+              invocation, which is terminated by {@link #build()}, which is then returned from the method</li>
+          <li>better understanding by humans, as the scope of mutable state (the builder) is kept to a minimum and is
+              very localized</li>
+          <li>better optimization 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»
     '''
     }
 
@@ -355,6 +427,13 @@ class BuilderTemplate extends AbstractBuilderTemplate {
         '''.toString
     }
 
+    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"));
+        }
+    '''
+
     override protected generateCopyKeys(List<GeneratedProperty> keyProps) '''
         this.key = base.«BindingMapping.IDENTIFIABLE_KEY_NAME»();
         «FOR field : keyProps»
@@ -363,20 +442,14 @@ class BuilderTemplate extends AbstractBuilderTemplate {
     '''
 
     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») {
+            if (base instanceof «augmentationHolderRef») {
                 @SuppressWarnings("unchecked")
-                «Map.importedName»<«Class.importedName»<? extends «augmentTypeRef»>, «augmentTypeRef»> aug =((«augmentationHolderRef»<«typeRef»>) base).augmentations();
+                «JU_MAP.importedName»<«CLASS.importedName»<? extends «augmentTypeRef»>, «augmentTypeRef»> aug =((«augmentationHolderRef»<«typeRef»>) base).augmentations();
                 if (!aug.isEmpty()) {
                     this.«AUGMENTATION_FIELD» = new «hashMapRef»<>(aug);
                 }