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