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