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