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