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