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