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