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