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