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