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