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