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