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