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