eb2d2f973801c5e05fdf94425394897fe28b5543
[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 generateListSetter(GeneratedProperty field, Type actualType) '''
277         «val restrictions = restrictionsForSetter(actualType)»
278         «IF restrictions !== null»
279             «generateCheckers(field, restrictions, actualType)»
280         «ENDIF»
281         public «type.getName» set«field.getName.toFirstUpper»(final «field.returnType.importedName» values) {
282         «IF restrictions !== null»
283             if (values != null) {
284                for («actualType.getFullyQualifiedName» value : values) {
285                    «checkArgument(field, restrictions, actualType, "value")»
286                }
287             }
288         «ENDIF»
289             this.«field.fieldName.toString» = values;
290             return this;
291         }
292
293     '''
294
295     def private generateSetter(GeneratedProperty field, Type actualType) '''
296         «val restrictions = restrictionsForSetter(actualType)»
297         «IF restrictions !== null»
298             «generateCheckers(field, restrictions, actualType)»
299         «ENDIF»
300
301         public «type.getName» set«field.getName.toFirstUpper»(final «field.returnType.importedName» value) {
302         «IF restrictions !== null»
303             if (value != null) {
304                 «checkArgument(field, restrictions, actualType, "value")»
305             }
306         «ENDIF»
307             this.«field.fieldName.toString» = value;
308             return this;
309         }
310     '''
311
312     private def Type getActualType(ParameterizedType ptype) {
313         return ptype.getActualTypeArguments.get(0)
314     }
315
316     /**
317      * Template method which generates setter methods
318      *
319      * @return string with the setter methods
320      */
321     def private generateSetters() '''
322         «IF keyType !== null»
323             public «type.getName» withKey(final «keyType.importedName» key) {
324                 this.key = key;
325                 return this;
326             }
327         «ENDIF»
328         «FOR property : properties»
329             «IF property.returnType instanceof ParameterizedType && Types.isListType(property.returnType)»
330                 «generateListSetter(property, getActualType(property.returnType as ParameterizedType))»
331             «ELSE»
332                 «generateSetter(property, property.returnType)»
333             «ENDIF»
334         «ENDFOR»
335
336         «IF augmentType !== null»
337             «val augmentTypeRef = augmentType.importedName»
338             public «type.name» add«AUGMENTATION_FIELD_UPPER»(«Class.importedName»<? extends «augmentTypeRef»> augmentationType, «augmentTypeRef» augmentationValue) {
339                 if (augmentationValue == null) {
340                     return remove«AUGMENTATION_FIELD_UPPER»(augmentationType);
341                 }
342
343                 if (!(this.«AUGMENTATION_FIELD» instanceof «HashMap.importedName»)) {
344                     this.«AUGMENTATION_FIELD» = new «HashMap.importedName»<>();
345                 }
346
347                 this.«AUGMENTATION_FIELD».put(augmentationType, augmentationValue);
348                 return this;
349             }
350
351             public «type.name» remove«AUGMENTATION_FIELD_UPPER»(«Class.importedName»<? extends «augmentTypeRef»> augmentationType) {
352                 if (this.«AUGMENTATION_FIELD» instanceof «HashMap.importedName») {
353                     this.«AUGMENTATION_FIELD».remove(augmentationType);
354                 }
355                 return this;
356             }
357         «ENDIF»
358     '''
359
360     private def createDescription(GeneratedType targetType) {
361         val target = type.importedName
362         return '''
363         Class that builds {@link «target»} instances. Overall design of the class is that of a
364         <a href="https://en.wikipedia.org/wiki/Fluent_interface">fluent interface</a>, where method chaining is used.
365
366         <p>
367         In general, this class is supposed to be used like this template:
368         <pre>
369           <code>
370             «target» createTarget(int fooXyzzy, int barBaz) {
371                 return new «target»Builder()
372                     .setFoo(new FooBuilder().setXyzzy(fooXyzzy).build())
373                     .setBar(new BarBuilder().setBaz(barBaz).build())
374                     .build();
375             }
376           </code>
377         </pre>
378
379         <p>
380         This pattern is supported by the immutable nature of «target», as instances can be freely passed around without
381         worrying about synchronization issues.
382
383         <p>
384         As a side note: method chaining results in:
385         <ul>
386           <li>very efficient Java bytecode, as the method invocation result, in this case the Builder reference, is
387               on the stack, so further method invocations just need to fill method arguments for the next method
388               invocation, which is terminated by {@link #build()}, which is then returned from the method</li>
389           <li>better understanding by humans, as the scope of mutable state (the builder) is kept to a minimum and is
390               very localized</li>
391           <li>better optimization oportunities, as the object scope is minimized in terms of invocation (rather than
392               method) stack, making <a href="https://en.wikipedia.org/wiki/Escape_analysis">escape analysis</a> a lot
393               easier. Given enough compiler (JIT/AOT) prowess, the cost of th builder object can be completely
394               eliminated</li>
395         </ul>
396
397         @see «target»
398         @see «Builder.importedName»
399     '''
400     }
401
402     override protected String formatDataForJavaDoc(GeneratedType type) {
403         val typeDescription = createDescription(type)
404
405         return '''
406             «IF !typeDescription.nullOrEmpty»
407             «typeDescription»
408             «ENDIF»
409         '''.toString
410     }
411
412     private def generateAugmentation() '''
413         @«SUPPRESS_WARNINGS.importedName»({ "unchecked", "checkstyle:methodTypeParameterName"})
414         public <E$$ extends «augmentType.importedName»> E$$ «AUGMENTABLE_AUGMENTATION_NAME»(«Class.importedName»<E$$> augmentationType) {
415             return (E$$) «AUGMENTATION_FIELD».get(«CodeHelpers.importedName».nonNullValue(augmentationType, "augmentationType"));
416         }
417     '''
418
419     override protected generateCopyKeys(List<GeneratedProperty> keyProps) '''
420         this.key = base.«BindingMapping.IDENTIFIABLE_KEY_NAME»();
421         «FOR field : keyProps»
422             this.«field.fieldName» = base.«field.getterMethodName»();
423         «ENDFOR»
424     '''
425
426     override protected generateCopyAugmentation(Type implType) {
427         val augmentationHolderRef = AugmentationHolder.importedName
428         val typeRef = targetType.importedName
429         val hashMapRef = HashMap.importedName
430         val augmentTypeRef = augmentType.importedName
431         return '''
432             if (base instanceof «augmentationHolderRef») {
433                 @SuppressWarnings("unchecked")
434                 «Map.importedName»<«Class.importedName»<? extends «augmentTypeRef»>, «augmentTypeRef»> aug =((«augmentationHolderRef»<«typeRef»>) base).augmentations();
435                 if (!aug.isEmpty()) {
436                     this.«AUGMENTATION_FIELD» = new «hashMapRef»<>(aug);
437                 }
438             }
439         '''
440     }
441
442     private static def hasNonDefaultMethods(GeneratedType type) {
443         !type.methodDefinitions.isEmpty && type.methodDefinitions.exists([def | !def.isDefault])
444     }
445
446     private static def nonDefaultMethods(GeneratedType type) {
447         type.methodDefinitions.filter([def | !def.isDefault])
448     }
449 }