Document builder constructors
[mdsal.git] / binding / mdsal-binding-java-api-generator / src / main / java / org / opendaylight / mdsal / binding / java / api / generator / BuilderTemplate.xtend
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.mdsal.binding.java.api.generator
9
10 import static extension org.apache.commons.text.StringEscapeUtils.escapeJava
11 import static org.opendaylight.mdsal.binding.model.ri.BindingTypes.DATA_OBJECT
12 import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.AUGMENTABLE_AUGMENTATION_NAME
13 import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.AUGMENTATION_FIELD
14 import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.DATA_CONTAINER_IMPLEMENTED_INTERFACE_NAME
15
16 import com.google.common.collect.ImmutableList
17 import com.google.common.collect.ImmutableSet
18 import com.google.common.collect.Sets
19 import java.util.ArrayList
20 import java.util.Collection
21 import java.util.HashSet
22 import java.util.List
23 import java.util.Map
24 import java.util.Set
25 import org.opendaylight.mdsal.binding.model.api.AnnotationType
26 import org.opendaylight.mdsal.binding.model.api.GeneratedProperty
27 import org.opendaylight.mdsal.binding.model.api.GeneratedTransferObject
28 import org.opendaylight.mdsal.binding.model.api.GeneratedType
29 import org.opendaylight.mdsal.binding.model.api.JavaTypeName
30 import org.opendaylight.mdsal.binding.model.api.MethodSignature;
31 import org.opendaylight.mdsal.binding.model.api.ParameterizedType
32 import org.opendaylight.mdsal.binding.model.api.Type
33 import org.opendaylight.mdsal.binding.model.ri.TypeConstants
34 import org.opendaylight.mdsal.binding.model.ri.Types
35 import org.opendaylight.mdsal.binding.spec.naming.BindingMapping
36
37 /**
38  * Template for generating JAVA builder classes.
39  */
40 class BuilderTemplate extends AbstractBuilderTemplate {
41     val BuilderImplTemplate implTemplate
42
43     /**
44      * Constructs new instance of this class.
45      * @throws IllegalArgumentException if <code>genType</code> equals <code>null</code>
46      */
47     new(GeneratedType genType, GeneratedType targetType, Type keyType) {
48         super(genType, targetType, keyType)
49         implTemplate = new BuilderImplTemplate(this, type.enclosedTypes.get(0))
50     }
51
52     override isLocalInnerClass(JavaTypeName name) {
53         // Builders do not have inner types
54         return false;
55     }
56
57     /**
58      * Template method which generates JAVA class body for builder class and for IMPL class.
59      *
60      * @return string with JAVA source code
61      */
62     override body() '''
63         «wrapToDocumentation(formatDataForJavaDoc(targetType))»
64         «targetType.annotations.generateDeprecatedAnnotation»
65         «generatedAnnotation»
66         public class «type.name» {
67
68             «generateFields(false)»
69
70             «constantsDeclarations()»
71
72             «IF augmentType !== null»
73                 «generateAugmentField()»
74             «ENDIF»
75
76             /**
77              * Construct an empty builder.
78              */
79             public «type.name»() {
80                 // No-op
81             }
82
83             «generateConstructorsFromIfcs()»
84
85             «val targetTypeName = targetType.importedName»
86             /**
87              * Construct a builder initialized with state from specified {@link «targetTypeName»}.
88              *
89              * @param base «targetTypeName» from which the builder should be initialized
90              */
91             public «generateCopyConstructor(targetType, type.enclosedTypes.get(0))»
92
93             «generateMethodFieldsFrom()»
94
95             «generateGetters(false)»
96             «IF augmentType !== null»
97
98                 «generateAugmentation()»
99             «ENDIF»
100
101             «generateSetters»
102
103             /**
104              * A new {@link «targetTypeName»} instance.
105              *
106              * @return A new {@link «targetTypeName»} instance.
107              */
108             public «targetTypeName» build() {
109                 return new «type.enclosedTypes.get(0).importedName»(this);
110             }
111
112             «implTemplate.body»
113         }
114     '''
115
116     override generateDeprecatedAnnotation(AnnotationType ann) {
117         val forRemoval = ann.getParameter("forRemoval")
118         if (forRemoval !== null) {
119             return "@" + DEPRECATED.importedName + "(forRemoval = " + forRemoval.value + ")"
120         }
121         return "@" + SUPPRESS_WARNINGS.importedName + "(\"deprecation\")"
122     }
123
124     /**
125      * Generate default constructor and constructor for every implemented interface from uses statements.
126      */
127     def private generateConstructorsFromIfcs() '''
128         «IF (!(targetType instanceof GeneratedTransferObject))»
129             «FOR impl : targetType.implements SEPARATOR "\n"»
130                 «generateConstructorFromIfc(impl)»
131             «ENDFOR»
132         «ENDIF»
133     '''
134
135     /**
136      * Generate constructor with argument of given type.
137      */
138     def private Object generateConstructorFromIfc(Type impl) '''
139         «IF (impl instanceof GeneratedType)»
140             «IF impl.hasNonDefaultMethods»
141                 «val typeName = impl.importedName»
142                 /**
143                  * Construct a new builder initialized from specified {@link «typeName»}.
144                  *
145                  * @param arg «typeName» from which the builder should be initialized
146                  */
147                 public «type.name»(«typeName» arg) {
148                     «printConstructorPropertySetter(impl)»
149                 }
150
151             «ENDIF»
152             «FOR implTypeImplement : impl.implements»
153                 «generateConstructorFromIfc(implTypeImplement)»
154             «ENDFOR»
155         «ENDIF»
156     '''
157
158     def private Object printConstructorPropertySetter(Type implementedIfc) '''
159         «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
160             «val ifc = implementedIfc as GeneratedType»
161             «FOR getter : ifc.nonDefaultMethods»
162                 «IF BindingMapping.isGetterMethodName(getter.name)»
163                     «val propertyName = getter.propertyNameFromGetter»
164                     «printPropertySetter(getter, '''arg.«getter.name»()''', propertyName)»;
165                 «ENDIF»
166             «ENDFOR»
167             «FOR impl : ifc.implements»
168                 «printConstructorPropertySetter(impl, getSpecifiedGetters(ifc))»
169             «ENDFOR»
170         «ENDIF»
171     '''
172
173     def private Object printConstructorPropertySetter(Type implementedIfc, Set<MethodSignature> alreadySetProperties) '''
174         «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
175             «val ifc = implementedIfc as GeneratedType»
176             «FOR getter : ifc.nonDefaultMethods»
177                 «IF BindingMapping.isGetterMethodName(getter.name) && getterByName(alreadySetProperties, getter.name).isEmpty»
178                     «val propertyName = getter.propertyNameFromGetter»
179                     «printPropertySetter(getter, '''arg.«getter.name»()''', propertyName)»;
180                 «ENDIF»
181             «ENDFOR»
182             «FOR descendant : ifc.implements»
183                 «printConstructorPropertySetter(descendant, Sets.union(alreadySetProperties, getSpecifiedGetters(ifc)))»
184             «ENDFOR»
185         «ENDIF»
186     '''
187
188     def static Set<MethodSignature> getSpecifiedGetters(GeneratedType type) {
189         val ImmutableSet.Builder<MethodSignature> setBuilder = new ImmutableSet.Builder
190         for (MethodSignature method : type.getMethodDefinitions()) {
191             if (method.hasOverrideAnnotation) {
192                 setBuilder.add(method)
193             }
194         }
195         return setBuilder.build()
196     }
197
198     /**
199      * Generate 'fieldsFrom' method to set builder properties based on type of given argument.
200      */
201     def private generateMethodFieldsFrom() '''
202         «IF (!(targetType instanceof GeneratedTransferObject))»
203             «IF targetType.hasImplementsFromUses»
204                 «val List<Type> done = targetType.getBaseIfcs»
205                 «generateMethodFieldsFromComment(targetType)»
206                 public void fieldsFrom(«DATA_OBJECT.importedName» arg) {
207                     boolean isValidArg = false;
208                     «FOR impl : targetType.getAllIfcs»
209                         «generateIfCheck(impl, done)»
210                     «ENDFOR»
211                     «CODEHELPERS.importedName».validValue(isValidArg, arg, "«targetType.getAllIfcs.toListOfNames»");
212                 }
213             «ENDIF»
214         «ENDIF»
215     '''
216
217     def private generateMethodFieldsFromComment(GeneratedType type) '''
218         /**
219          * Set fields from given grouping argument. Valid argument is instance of one of following types:
220          * <ul>
221          «FOR impl : type.getAllIfcs»
222          *   <li>{@link «impl.importedName»}</li>
223          «ENDFOR»
224          * </ul>
225          *
226          * @param arg grouping object
227          * @throws IllegalArgumentException if given argument is none of valid types or has property with incompatible value
228         */
229     '''
230
231     /**
232      * Method is used to find out if given type implements any interface from uses.
233      */
234     def boolean hasImplementsFromUses(GeneratedType type) {
235         var i = 0
236         for (impl : type.getAllIfcs) {
237             if (impl instanceof GeneratedType && (impl as GeneratedType).hasNonDefaultMethods) {
238                 i = i + 1
239             }
240         }
241         return i > 0
242     }
243
244     def private generateIfCheck(Type impl, List<Type> done) '''
245         «IF (impl instanceof GeneratedType && (impl as GeneratedType).hasNonDefaultMethods)»
246             «val implType = impl as GeneratedType»
247             if (arg instanceof «implType.importedName») {
248                 «printPropertySetter(implType)»
249                 isValidArg = true;
250             }
251         «ENDIF»
252     '''
253
254     def private printPropertySetter(Type implementedIfc) '''
255         «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
256         «val ifc = implementedIfc as GeneratedType»
257         «FOR getter : ifc.nonDefaultMethods»
258             «IF BindingMapping.isGetterMethodName(getter.name) && !hasOverrideAnnotation(getter)»
259                 «printPropertySetter(getter, '''((«ifc.importedName»)arg).«getter.name»()''', getter.propertyNameFromGetter)»;
260             «ENDIF»
261         «ENDFOR»
262         «ENDIF»
263     '''
264
265     def private printPropertySetter(MethodSignature getter, String retrieveProperty, String propertyName) {
266         val ownGetter = implTemplate.findGetter(getter.name)
267         val ownGetterType = ownGetter.returnType
268         if (Types.strictTypeEquals(getter.returnType, ownGetterType)) {
269             return "this._" + propertyName + " = " + retrieveProperty
270         }
271         if (ownGetterType instanceof ParameterizedType) {
272             val itemType = ownGetterType.actualTypeArguments.get(0)
273             if (Types.isListType(ownGetterType)) {
274                 val importedClass = importedClass(itemType)
275                 if (importedClass !== null) {
276                     return printPropertySetter(retrieveProperty, propertyName, "checkListFieldCastIdentity", importedClass)
277                 }
278                 return printPropertySetter(retrieveProperty, propertyName, "checkListFieldCast", itemType.importedName)
279             }
280             if (Types.isSetType(ownGetterType)) {
281                 val importedClass = importedClass(itemType)
282                 if (importedClass !== null) {
283                     return printPropertySetter(retrieveProperty, propertyName, "checkSetFieldCastIdentity", importedClass)
284                 }
285                 return printPropertySetter(retrieveProperty, propertyName, "checkSetFieldCast", itemType.importedName)
286             }
287             if (Types.CLASS.equals(ownGetterType)) {
288                 return printPropertySetter(retrieveProperty, propertyName, "checkFieldCastIdentity", itemType.identifier.importedName)
289             }
290         }
291         return printPropertySetter(retrieveProperty, propertyName, "checkFieldCast", ownGetterType.importedName)
292     }
293
294     def private printPropertySetter(String retrieveProperty, String propertyName, String checkerName, String className) '''
295             this._«propertyName» = «CODEHELPERS.importedName».«checkerName»(«className».class, "«propertyName»", «retrieveProperty»)'''
296
297     private def importedClass(Type type) {
298         if (type instanceof ParameterizedType) {
299             if (Types.CLASS.equals(type.rawType)) {
300                 return type.actualTypeArguments.get(0).identifier.importedName
301             }
302         }
303         return null
304     }
305
306     private def List<Type> getBaseIfcs(GeneratedType type) {
307         val List<Type> baseIfcs = new ArrayList();
308         for (ifc : type.implements) {
309             if (ifc instanceof GeneratedType && (ifc as GeneratedType).hasNonDefaultMethods) {
310                 baseIfcs.add(ifc)
311             }
312         }
313         return baseIfcs
314     }
315
316     private def Set<Type> getAllIfcs(Type type) {
317         val Set<Type> baseIfcs = new HashSet()
318         if (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject)) {
319             val ifc = type as GeneratedType
320             for (impl : ifc.implements) {
321                 if (impl instanceof GeneratedType && (impl as GeneratedType).hasNonDefaultMethods) {
322                     baseIfcs.add(impl)
323                 }
324                 baseIfcs.addAll(impl.getAllIfcs)
325             }
326         }
327         return baseIfcs
328     }
329
330     private def List<String> toListOfNames(Collection<Type> types) {
331         val List<String> names = new ArrayList
332         for (type : types) {
333             names.add(type.importedName)
334         }
335         return names
336     }
337
338     def private constantsDeclarations() '''
339         «FOR c : type.getConstantDefinitions»
340             «IF c.getName.startsWith(TypeConstants.PATTERN_CONSTANT_NAME)»
341                 «val cValue = c.value as Map<String, String>»
342                 «val String fieldSuffix = c.getName.substring(TypeConstants.PATTERN_CONSTANT_NAME.length)»
343                 «val jurPatternRef = JUR_PATTERN.importedName»
344                 «IF cValue.size == 1»
345                    «val firstEntry = cValue.entrySet.iterator.next»
346                    private static final «jurPatternRef» «Constants.MEMBER_PATTERN_LIST»«fieldSuffix» = «jurPatternRef».compile("«firstEntry.key.escapeJava»");
347                    private static final String «Constants.MEMBER_REGEX_LIST»«fieldSuffix» = "«firstEntry.value.escapeJava»";
348                 «ELSE»
349                    private static final «jurPatternRef»[] «Constants.MEMBER_PATTERN_LIST»«fieldSuffix» = «CODEHELPERS.importedName».compilePatterns(«ImmutableList.importedName».of(
350                    «FOR v : cValue.keySet SEPARATOR ", "»"«v.escapeJava»"«ENDFOR»));
351                    private static final String[] «Constants.MEMBER_REGEX_LIST»«fieldSuffix» = { «
352                    FOR v : cValue.values SEPARATOR ", "»"«v.escapeJava»"«ENDFOR» };
353                 «ENDIF»
354             «ELSE»
355                 «emitConstant(c)»
356             «ENDIF»
357         «ENDFOR»
358     '''
359
360     def private generateSetter(GeneratedProperty field) {
361         val returnType = field.returnType
362         if (returnType instanceof ParameterizedType) {
363             if (Types.isListType(returnType) || Types.isSetType(returnType)) {
364                 val arguments = returnType.actualTypeArguments
365                 if (arguments.isEmpty) {
366                     return generateListSetter(field, Types.objectType)
367                 }
368                 return generateListSetter(field, arguments.get(0))
369             } else if (Types.isMapType(returnType)) {
370                 return generateMapSetter(field, returnType.actualTypeArguments.get(1))
371             }
372         }
373         return generateSimpleSetter(field, returnType)
374     }
375
376     def private generateListSetter(GeneratedProperty field, Type actualType) '''
377         «val restrictions = restrictionsForSetter(actualType)»
378         «IF restrictions !== null»
379             «generateCheckers(field, restrictions, actualType)»
380         «ENDIF»
381         public «type.getName» set«field.getName.toFirstUpper»(final «field.returnType.importedName» values) {
382         «IF restrictions !== null»
383             if (values != null) {
384                for («actualType.importedName» value : values) {
385                    «checkArgument(field, restrictions, actualType, "value")»
386                }
387             }
388         «ENDIF»
389             this.«field.fieldName» = values;
390             return this;
391         }
392
393     '''
394
395     def private generateMapSetter(GeneratedProperty field, Type actualType) '''
396         «val restrictions = restrictionsForSetter(actualType)»
397         «IF restrictions !== null»
398             «generateCheckers(field, restrictions, actualType)»
399         «ENDIF»
400         public «type.getName» set«field.name.toFirstUpper»(final «field.returnType.importedName» values) {
401         «IF restrictions !== null»
402             if (values != null) {
403                for («actualType.importedName» value : values.values()) {
404                    «checkArgument(field, restrictions, actualType, "value")»
405                }
406             }
407         «ENDIF»
408             this.«field.fieldName» = values;
409             return this;
410         }
411     '''
412
413     def private generateSimpleSetter(GeneratedProperty field, Type actualType) '''
414         «val restrictions = restrictionsForSetter(actualType)»
415         «IF restrictions !== null»
416
417             «generateCheckers(field, restrictions, actualType)»
418         «ENDIF»
419
420         «val setterName = "set" + field.getName.toFirstUpper»
421         public «type.getName» «setterName»(final «field.returnType.importedName» value) {
422             «IF restrictions !== null»
423                 if (value != null) {
424                     «checkArgument(field, restrictions, actualType, "value")»
425                 }
426             «ENDIF»
427             this.«field.fieldName» = value;
428             return this;
429         }
430     '''
431
432     /**
433      * Template method which generates setter methods
434      *
435      * @return string with the setter methods
436      */
437     def private generateSetters() '''
438         «IF keyType !== null»
439             public «type.getName» withKey(final «keyType.importedName» key) {
440                 this.key = key;
441                 return this;
442             }
443         «ENDIF»
444         «FOR property : properties»
445             «generateSetter(property)»
446         «ENDFOR»
447
448         «IF augmentType !== null»
449             «val augmentTypeRef = augmentType.importedName»
450             «val jlClassRef = CLASS.importedName»
451             «val hashMapRef = JU_HASHMAP.importedName»
452             /**
453               * Add an augmentation to this builder's product.
454               *
455               * @param augmentation augmentation to be added
456               * @return this builder
457               * @throws NullPointerException if {@code augmentation} is null
458               */
459             public «type.name» addAugmentation(«augmentTypeRef» augmentation) {
460                 «jlClassRef»<? extends «augmentTypeRef»> augmentationType = augmentation.«DATA_CONTAINER_IMPLEMENTED_INTERFACE_NAME»();
461                 if (!(this.«AUGMENTATION_FIELD» instanceof «hashMapRef»)) {
462                     this.«AUGMENTATION_FIELD» = new «hashMapRef»<>();
463                 }
464
465                 this.«AUGMENTATION_FIELD».put(augmentationType, augmentation);
466                 return this;
467             }
468
469             /**
470               * Remove an augmentation from this builder's product. If this builder does not track such an augmentation
471               * type, this method does nothing.
472               *
473               * @param augmentationType augmentation type to be removed
474               * @return this builder
475               */
476             public «type.name» removeAugmentation(«jlClassRef»<? extends «augmentTypeRef»> augmentationType) {
477                 if (this.«AUGMENTATION_FIELD» instanceof «hashMapRef») {
478                     this.«AUGMENTATION_FIELD».remove(augmentationType);
479                 }
480                 return this;
481             }
482         «ENDIF»
483     '''
484
485     private def createDescription(GeneratedType targetType) {
486         val target = targetType.importedName
487         return '''
488         Class that builds {@link «target»} instances. Overall design of the class is that of a
489         <a href="https://en.wikipedia.org/wiki/Fluent_interface">fluent interface</a>, where method chaining is used.
490
491         <p>
492         In general, this class is supposed to be used like this template:
493         <pre>
494           <code>
495             «target» create«target»(int fooXyzzy, int barBaz) {
496                 return new «target»Builder()
497                     .setFoo(new FooBuilder().setXyzzy(fooXyzzy).build())
498                     .setBar(new BarBuilder().setBaz(barBaz).build())
499                     .build();
500             }
501           </code>
502         </pre>
503
504         <p>
505         This pattern is supported by the immutable nature of «target», as instances can be freely passed around without
506         worrying about synchronization issues.
507
508         <p>
509         As a side note: method chaining results in:
510         <ul>
511           <li>very efficient Java bytecode, as the method invocation result, in this case the Builder reference, is
512               on the stack, so further method invocations just need to fill method arguments for the next method
513               invocation, which is terminated by {@link #build()}, which is then returned from the method</li>
514           <li>better understanding by humans, as the scope of mutable state (the builder) is kept to a minimum and is
515               very localized</li>
516           <li>better optimization opportunities, as the object scope is minimized in terms of invocation (rather than
517               method) stack, making <a href="https://en.wikipedia.org/wiki/Escape_analysis">escape analysis</a> a lot
518               easier. Given enough compiler (JIT/AOT) prowess, the cost of th builder object can be completely
519               eliminated</li>
520         </ul>
521
522         @see «target»
523     '''
524     }
525
526     override protected String formatDataForJavaDoc(GeneratedType type) {
527         val typeDescription = createDescription(type)
528
529         return '''
530             «IF !typeDescription.nullOrEmpty»
531             «typeDescription»
532             «ENDIF»
533         '''.toString
534     }
535
536     private def generateAugmentation() '''
537         @«SUPPRESS_WARNINGS.importedName»({ "unchecked", "checkstyle:methodTypeParameterName"})
538         public <E$$ extends «augmentType.importedName»> E$$ «AUGMENTABLE_AUGMENTATION_NAME»(«CLASS.importedName»<E$$> augmentationType) {
539             return (E$$) «AUGMENTATION_FIELD».get(«JU_OBJECTS.importedName».requireNonNull(augmentationType));
540         }
541     '''
542
543     override protected generateCopyKeys(List<GeneratedProperty> keyProps) '''
544         this.key = base.«BindingMapping.IDENTIFIABLE_KEY_NAME»();
545         «FOR field : keyProps»
546             this.«field.fieldName» = base.«field.getterMethodName»();
547         «ENDFOR»
548     '''
549
550     override protected CharSequence generateCopyNonKeys(Collection<BuilderGeneratedProperty> props) '''
551         «FOR field : props»
552             this.«field.fieldName» = base.«field.getterName»();
553         «ENDFOR»
554     '''
555
556     override protected generateCopyAugmentation(Type implType) '''
557        final var aug = base.augmentations();
558        if (!aug.isEmpty()) {
559            this.«AUGMENTATION_FIELD» = new «JU_HASHMAP.importedName»<>(aug);
560        }
561     '''
562 }