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