70f4563316541cb55d264e0b322b90900c5f6289
[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             «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                 val importedClass = importedClass(itemType)
258                 if (importedClass !== null) {
259                     return printPropertySetter(retrieveProperty, propertyName, "checkListFieldCastIdentity", importedClass)
260                 }
261                 return printPropertySetter(retrieveProperty, propertyName, "checkListFieldCast", itemType.importedName)
262             }
263             if (Types.isSetType(ownGetterType)) {
264                 val importedClass = importedClass(itemType)
265                 if (importedClass !== null) {
266                     return printPropertySetter(retrieveProperty, propertyName, "checkSetFieldCastIdentity", importedClass)
267                 }
268                 return printPropertySetter(retrieveProperty, propertyName, "checkSetFieldCast", itemType.importedName)
269             }
270             if (Types.CLASS.equals(ownGetterType)) {
271                 return printPropertySetter(retrieveProperty, propertyName, "checkFieldCastIdentity", itemType.identifier.importedName)
272             }
273         }
274         return printPropertySetter(retrieveProperty, propertyName, "checkFieldCast", ownGetterType.importedName)
275     }
276
277     def private printPropertySetter(String retrieveProperty, String propertyName, String checkerName, String className) '''
278             this._«propertyName» = «CODEHELPERS.importedName».«checkerName»(«className».class, "«propertyName»", «retrieveProperty»)'''
279
280     private def importedClass(Type type) {
281         if (type instanceof ParameterizedType) {
282             if (Types.CLASS.equals(type.rawType)) {
283                 return type.actualTypeArguments.get(0).identifier.importedName
284             }
285         }
286         return null
287     }
288
289     private def List<Type> getBaseIfcs(GeneratedType type) {
290         val List<Type> baseIfcs = new ArrayList();
291         for (ifc : type.implements) {
292             if (ifc instanceof GeneratedType && (ifc as GeneratedType).hasNonDefaultMethods) {
293                 baseIfcs.add(ifc)
294             }
295         }
296         return baseIfcs
297     }
298
299     private def Set<Type> getAllIfcs(Type type) {
300         val Set<Type> baseIfcs = new HashSet()
301         if (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject)) {
302             val ifc = type as GeneratedType
303             for (impl : ifc.implements) {
304                 if (impl instanceof GeneratedType && (impl as GeneratedType).hasNonDefaultMethods) {
305                     baseIfcs.add(impl)
306                 }
307                 baseIfcs.addAll(impl.getAllIfcs)
308             }
309         }
310         return baseIfcs
311     }
312
313     private def List<String> toListOfNames(Collection<Type> types) {
314         val List<String> names = new ArrayList
315         for (type : types) {
316             names.add(type.importedName)
317         }
318         return names
319     }
320
321     def private constantsDeclarations() '''
322         «FOR c : type.getConstantDefinitions»
323             «IF c.getName.startsWith(TypeConstants.PATTERN_CONSTANT_NAME)»
324                 «val cValue = c.value as Map<String, String>»
325                 «val String fieldSuffix = c.getName.substring(TypeConstants.PATTERN_CONSTANT_NAME.length)»
326                 «val jurPatternRef = JUR_PATTERN.importedName»
327                 «IF cValue.size == 1»
328                    «val firstEntry = cValue.entrySet.iterator.next»
329                    private static final «jurPatternRef» «Constants.MEMBER_PATTERN_LIST»«fieldSuffix» = «jurPatternRef».compile("«firstEntry.key.escapeJava»");
330                    private static final String «Constants.MEMBER_REGEX_LIST»«fieldSuffix» = "«firstEntry.value.escapeJava»";
331                 «ELSE»
332                    private static final «jurPatternRef»[] «Constants.MEMBER_PATTERN_LIST»«fieldSuffix» = «CODEHELPERS.importedName».compilePatterns(«ImmutableList.importedName».of(
333                    «FOR v : cValue.keySet SEPARATOR ", "»"«v.escapeJava»"«ENDFOR»));
334                    private static final String[] «Constants.MEMBER_REGEX_LIST»«fieldSuffix» = { «
335                    FOR v : cValue.values SEPARATOR ", "»"«v.escapeJava»"«ENDFOR» };
336                 «ENDIF»
337             «ELSE»
338                 «emitConstant(c)»
339             «ENDIF»
340         «ENDFOR»
341     '''
342
343     def private generateSetter(GeneratedProperty field) {
344         val returnType = field.returnType
345         if (returnType instanceof ParameterizedType) {
346             if (Types.isListType(returnType) || Types.isSetType(returnType)) {
347                 val arguments = returnType.actualTypeArguments
348                 if (arguments.isEmpty) {
349                     return generateListSetter(field, Types.objectType)
350                 }
351                 return generateListSetter(field, arguments.get(0))
352             } else if (Types.isMapType(returnType)) {
353                 return generateMapSetter(field, returnType.actualTypeArguments.get(1))
354             }
355         }
356         return generateSimpleSetter(field, returnType)
357     }
358
359     def private generateListSetter(GeneratedProperty field, Type actualType) '''
360         «val restrictions = restrictionsForSetter(actualType)»
361         «IF restrictions !== null»
362             «generateCheckers(field, restrictions, actualType)»
363         «ENDIF»
364         public «type.getName» set«field.getName.toFirstUpper»(final «field.returnType.importedName» values) {
365         «IF restrictions !== null»
366             if (values != null) {
367                for («actualType.importedName» value : values) {
368                    «checkArgument(field, restrictions, actualType, "value")»
369                }
370             }
371         «ENDIF»
372             this.«field.fieldName» = values;
373             return this;
374         }
375
376     '''
377
378     def private generateMapSetter(GeneratedProperty field, Type actualType) '''
379         «val restrictions = restrictionsForSetter(actualType)»
380         «IF restrictions !== null»
381             «generateCheckers(field, restrictions, actualType)»
382         «ENDIF»
383         public «type.getName» set«field.name.toFirstUpper»(final «field.returnType.importedName» values) {
384         «IF restrictions !== null»
385             if (values != null) {
386                for («actualType.importedName» value : values.values()) {
387                    «checkArgument(field, restrictions, actualType, "value")»
388                }
389             }
390         «ENDIF»
391             this.«field.fieldName» = values;
392             return this;
393         }
394     '''
395
396     def private generateSimpleSetter(GeneratedProperty field, Type actualType) '''
397         «val restrictions = restrictionsForSetter(actualType)»
398         «IF restrictions !== null»
399
400             «generateCheckers(field, restrictions, actualType)»
401         «ENDIF»
402
403         «val setterName = "set" + field.getName.toFirstUpper»
404         public «type.getName» «setterName»(final «field.returnType.importedName» value) {
405             «IF restrictions !== null»
406                 if (value != null) {
407                     «checkArgument(field, restrictions, actualType, "value")»
408                 }
409             «ENDIF»
410             this.«field.fieldName» = value;
411             return this;
412         }
413     '''
414
415     /**
416      * Template method which generates setter methods
417      *
418      * @return string with the setter methods
419      */
420     def private generateSetters() '''
421         «IF keyType !== null»
422             public «type.getName» withKey(final «keyType.importedName» key) {
423                 this.key = key;
424                 return this;
425             }
426         «ENDIF»
427         «FOR property : properties»
428             «generateSetter(property)»
429         «ENDFOR»
430
431         «IF augmentType !== null»
432             «val augmentTypeRef = augmentType.importedName»
433             «val jlClassRef = CLASS.importedName»
434             «val hashMapRef = JU_HASHMAP.importedName»
435             /**
436               * Add an augmentation to this builder's product.
437               *
438               * @param augmentation augmentation to be added
439               * @return this builder
440               * @throws NullPointerException if {@code augmentation} is null
441               */
442             public «type.name» addAugmentation(«augmentTypeRef» augmentation) {
443                 «jlClassRef»<? extends «augmentTypeRef»> augmentationType = augmentation.«DATA_CONTAINER_IMPLEMENTED_INTERFACE_NAME»();
444                 if (!(this.«AUGMENTATION_FIELD» instanceof «hashMapRef»)) {
445                     this.«AUGMENTATION_FIELD» = new «hashMapRef»<>();
446                 }
447
448                 this.«AUGMENTATION_FIELD».put(augmentationType, augmentation);
449                 return this;
450             }
451
452             /**
453               * Remove an augmentation from this builder's product. If this builder does not track such an augmentation
454               * type, this method does nothing.
455               *
456               * @param augmentationType augmentation type to be removed
457               * @return this builder
458               */
459             public «type.name» removeAugmentation(«jlClassRef»<? extends «augmentTypeRef»> augmentationType) {
460                 if (this.«AUGMENTATION_FIELD» instanceof «hashMapRef») {
461                     this.«AUGMENTATION_FIELD».remove(augmentationType);
462                 }
463                 return this;
464             }
465         «ENDIF»
466     '''
467
468     private def createDescription(GeneratedType targetType) {
469         val target = targetType.importedName
470         return '''
471         Class that builds {@link «target»} instances. Overall design of the class is that of a
472         <a href="https://en.wikipedia.org/wiki/Fluent_interface">fluent interface</a>, where method chaining is used.
473
474         <p>
475         In general, this class is supposed to be used like this template:
476         <pre>
477           <code>
478             «target» create«target»(int fooXyzzy, int barBaz) {
479                 return new «target»Builder()
480                     .setFoo(new FooBuilder().setXyzzy(fooXyzzy).build())
481                     .setBar(new BarBuilder().setBaz(barBaz).build())
482                     .build();
483             }
484           </code>
485         </pre>
486
487         <p>
488         This pattern is supported by the immutable nature of «target», as instances can be freely passed around without
489         worrying about synchronization issues.
490
491         <p>
492         As a side note: method chaining results in:
493         <ul>
494           <li>very efficient Java bytecode, as the method invocation result, in this case the Builder reference, is
495               on the stack, so further method invocations just need to fill method arguments for the next method
496               invocation, which is terminated by {@link #build()}, which is then returned from the method</li>
497           <li>better understanding by humans, as the scope of mutable state (the builder) is kept to a minimum and is
498               very localized</li>
499           <li>better optimization opportunities, as the object scope is minimized in terms of invocation (rather than
500               method) stack, making <a href="https://en.wikipedia.org/wiki/Escape_analysis">escape analysis</a> a lot
501               easier. Given enough compiler (JIT/AOT) prowess, the cost of th builder object can be completely
502               eliminated</li>
503         </ul>
504
505         @see «target»
506     '''
507     }
508
509     override protected String formatDataForJavaDoc(GeneratedType type) {
510         val typeDescription = createDescription(type)
511
512         return '''
513             «IF !typeDescription.nullOrEmpty»
514             «typeDescription»
515             «ENDIF»
516         '''.toString
517     }
518
519     private def generateAugmentation() '''
520         @«SUPPRESS_WARNINGS.importedName»({ "unchecked", "checkstyle:methodTypeParameterName"})
521         public <E$$ extends «augmentType.importedName»> E$$ «AUGMENTABLE_AUGMENTATION_NAME»(«CLASS.importedName»<E$$> augmentationType) {
522             return (E$$) «AUGMENTATION_FIELD».get(«JU_OBJECTS.importedName».requireNonNull(augmentationType));
523         }
524     '''
525
526     override protected generateCopyKeys(List<GeneratedProperty> keyProps) '''
527         this.key = base.«BindingMapping.IDENTIFIABLE_KEY_NAME»();
528         «FOR field : keyProps»
529             this.«field.fieldName» = base.«field.getterMethodName»();
530         «ENDFOR»
531     '''
532
533     override protected CharSequence generateCopyNonKeys(Collection<BuilderGeneratedProperty> props) '''
534         «FOR field : props»
535             this.«field.fieldName» = base.«field.getterName»();
536         «ENDFOR»
537     '''
538
539     override protected generateCopyAugmentation(Type implType) '''
540        final var aug = base.augmentations();
541        if (!aug.isEmpty()) {
542            this.«AUGMENTATION_FIELD» = new «JU_HASHMAP.importedName»<>(aug);
543        }
544     '''
545 }