Make sure builder-generated object do not emit null values
[yangtools.git] / code-generator / binding-java-api-generator / src / main / java / org / opendaylight / yangtools / sal / 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.yangtools.sal.java.api.generator
9
10 import java.util.Arrays;
11 import java.util.LinkedHashSet
12 import java.util.List
13 import java.util.Map
14 import java.util.Set
15 import org.opendaylight.yangtools.binding.generator.util.ReferencedTypeImpl
16 import org.opendaylight.yangtools.binding.generator.util.Types
17 import org.opendaylight.yangtools.binding.generator.util.generated.type.builder.GeneratedTOBuilderImpl
18 import org.opendaylight.yangtools.sal.binding.model.api.GeneratedProperty
19 import org.opendaylight.yangtools.sal.binding.model.api.GeneratedTransferObject
20 import org.opendaylight.yangtools.sal.binding.model.api.GeneratedType
21 import org.opendaylight.yangtools.sal.binding.model.api.MethodSignature
22 import org.opendaylight.yangtools.sal.binding.model.api.Type
23 import org.opendaylight.yangtools.yang.binding.Augmentable
24 import static org.opendaylight.yangtools.binding.generator.util.Types.*
25 import java.util.HashMap
26 import java.util.Collections
27 import org.opendaylight.yangtools.yang.binding.DataObject
28 import java.util.ArrayList
29 import java.util.HashSet
30 import java.util.Collection
31 import org.opendaylight.yangtools.yang.binding.Identifiable
32
33 /**
34  * Template for generating JAVA builder classes. 
35  */
36
37 class BuilderTemplate extends BaseTemplate {
38
39     /**
40      * Constant with the name of the concrete method.
41      */
42     val static GET_AUGMENTATION_METHOD_NAME = "getAugmentation"
43
44     /**
45      * Constant with the suffix for builder classes.
46      */
47     val static BUILDER = 'Builder'
48
49     /**
50      * Constant with suffix for the classes which are generated from the builder classes.
51      */
52     val static IMPL = 'Impl'
53
54     /**
55      * Generated property is set if among methods is found one with the name GET_AUGMENTATION_METHOD_NAME
56      */
57     var GeneratedProperty augmentField
58
59     /**
60      * Set of class attributes (fields) which are derived from the getter methods names
61      */
62     val Set<GeneratedProperty> properties
63
64     /**
65      * Constructs new instance of this class.
66      * @throws IllegalArgumentException if <code>genType</code> equals <code>null</code>
67      */
68     new(GeneratedType genType) {
69         super(genType)
70         this.properties = propertiesFromMethods(createMethods)
71     }
72
73     /**
74      * Returns set of method signature instances which contains all the methods of the <code>genType</code>
75      * and all the methods of the implemented interfaces.
76      * 
77      * @returns set of method signature instances
78      */
79     def private Set<MethodSignature> createMethods() {
80         val Set<MethodSignature> methods = new LinkedHashSet
81         methods.addAll(type.methodDefinitions)
82         collectImplementedMethods(methods, type.implements)
83         return methods
84     }
85
86     /**
87      * Adds to the <code>methods</code> set all the methods of the <code>implementedIfcs</code> 
88      * and recursivelly their implemented interfaces.
89      * 
90      * @param methods set of method signatures
91      * @param implementedIfcs list of implemented interfaces
92      */
93     def private void collectImplementedMethods(Set<MethodSignature> methods, List<Type> implementedIfcs) {
94         if (implementedIfcs == null || implementedIfcs.empty) {
95             return
96         }
97         for (implementedIfc : implementedIfcs) {
98             if ((implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))) {
99                 val ifc = implementedIfc as GeneratedType
100                 methods.addAll(ifc.methodDefinitions)
101                 collectImplementedMethods(methods, ifc.implements)
102             } else if (implementedIfc.fullyQualifiedName == Augmentable.name) {
103                 for (m : Augmentable.methods) {
104                     if (m.name == GET_AUGMENTATION_METHOD_NAME) {
105                         val fullyQualifiedName = m.returnType.name
106                         val pkg = fullyQualifiedName.package
107                         val name = fullyQualifiedName.name
108                         val tmpGenTO = new GeneratedTOBuilderImpl(pkg, name)
109                         val refType = new ReferencedTypeImpl(pkg, name)
110                         val generic = new ReferencedTypeImpl(type.packageName, type.name)
111                         val parametrizedReturnType = Types.parameterizedTypeFor(refType, generic)
112                         tmpGenTO.addMethod(m.name).setReturnType(parametrizedReturnType)
113                         augmentField = tmpGenTO.toInstance.methodDefinitions.first.propertyFromGetter
114                     }
115                 }
116             }
117         }
118     }
119
120     /**
121      * Returns the first element of the list <code>elements</code>.
122      * 
123      * @param list of elements
124      */
125     def private <E> first(List<E> elements) {
126         elements.get(0)
127     }
128
129     /**
130      * Returns the name of the package from <code>fullyQualifiedName</code>.
131      * 
132      * @param fullyQualifiedName string with fully qualified type name (package + type)
133      * @return string with the package name
134      */
135     def private String getPackage(String fullyQualifiedName) {
136         val lastDotIndex = fullyQualifiedName.lastIndexOf(Constants.DOT)
137         return if (lastDotIndex == -1) "" else fullyQualifiedName.substring(0, lastDotIndex)
138     }
139
140         /**
141          * Returns the name of tye type from <code>fullyQualifiedName</code>
142          * 
143          * @param fullyQualifiedName string with fully qualified type name (package + type)
144          * @return string with the name of the type
145          */
146     def private String getName(String fullyQualifiedName) {
147         val lastDotIndex = fullyQualifiedName.lastIndexOf(Constants.DOT)
148         return if (lastDotIndex == -1) fullyQualifiedName else fullyQualifiedName.substring(lastDotIndex + 1)
149     }
150
151     /**
152      * Creates set of generated property instances from getter <code>methods</code>.
153      * 
154      * @param set of method signature instances which should be transformed to list of properties 
155      * @return set of generated property instances which represents the getter <code>methods</code>
156      */
157     def private propertiesFromMethods(Set<MethodSignature> methods) {
158         if (methods == null || methods.isEmpty()) {
159             return Collections.emptySet
160         }
161         val Set<GeneratedProperty> result = new LinkedHashSet
162         for (m : methods) {
163             val createdField = m.propertyFromGetter
164             if (createdField != null) {
165                 result.add(createdField)
166             }
167         }
168         return result
169     }
170
171     /**
172      * Creates generated property instance from the getter <code>method</code> name and return type.
173      * 
174      * @param method method signature from which is the method name and return type obtained
175      * @return generated property instance for the getter <code>method</code>
176      * @throws IllegalArgumentException<ul>
177      *  <li>if the <code>method</code> equals <code>null</code></li>
178      *  <li>if the name of the <code>method</code> equals <code>null</code></li>
179      *  <li>if the name of the <code>method</code> is empty</li>
180      *  <li>if the return type of the <code>method</code> equals <code>null</code></li>
181      * </ul>
182      */
183     def private GeneratedProperty propertyFromGetter(MethodSignature method) {
184         if (method == null || method.name == null || method.name.empty || method.returnType == null) {
185             throw new IllegalArgumentException("Method, method name, method return type reference cannot be NULL or empty!")
186         }
187         var prefix = "get";
188         if(BOOLEAN.equals(method.returnType)) {
189             prefix = "is";
190         } 
191         if (method.name.startsWith(prefix)) {
192             val fieldName = method.getName().substring(prefix.length()).toFirstLower
193             val tmpGenTO = new GeneratedTOBuilderImpl("foo", "foo")
194             tmpGenTO.addProperty(fieldName).setReturnType(method.returnType)
195             return tmpGenTO.toInstance.properties.first
196         }
197     }
198
199     /**
200      * Template method which generates JAVA class body for builder class and for IMPL class. 
201      * 
202      * @return string with JAVA source code
203      */
204     override body() '''
205
206         public class «type.name»«BUILDER» {
207
208             «generateFields(false)»
209
210             «generateConstructorsFromIfcs(type)»
211
212             «generateMethodFieldsFrom(type)»
213
214             «generateGetters(false)»
215
216             «generateSetters»
217
218             public «type.name» build() {
219                 return new «type.name»«IMPL»(this);
220             }
221
222             private static final class «type.name»«IMPL» implements «type.name» {
223
224                 «implementedInterfaceGetter»
225
226                 «generateFields(true)»
227
228                 «generateConstructor»
229
230                 «generateGetters(true)»
231
232                 «generateHashCode()»
233
234                 «generateEquals()»
235                 
236                 «generateToString(properties)»
237             }
238
239         }
240     '''
241
242     /**
243      * Generate default constructor and constructor for every implemented interface from uses statements.
244      */
245     def private generateConstructorsFromIfcs(Type type) '''
246         public «type.name»«BUILDER»() {
247         } 
248         «IF (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject))»
249             «val ifc = type as GeneratedType»
250             «FOR impl : ifc.implements»
251                 «generateConstructorFromIfc(impl)»
252             «ENDFOR»
253         «ENDIF»
254     '''
255
256     /**
257      * Generate constructor with argument of given type.
258      */
259     def private generateConstructorFromIfc(Type impl) '''
260         «IF (impl instanceof GeneratedType)»
261             «val implType = impl as GeneratedType»
262
263             «IF !(implType.methodDefinitions.empty)»
264                 public «type.name»«BUILDER»(«implType.fullyQualifiedName» arg) {
265                     «printConstructorPropertySetter(implType)»
266                 }
267             «ENDIF»
268             «FOR implTypeImplement : implType.implements»
269                 «generateConstructorFromIfc(implTypeImplement)»
270             «ENDFOR»
271         «ENDIF»
272     '''
273
274     def private printConstructorPropertySetter(Type implementedIfc) '''
275         «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
276             «val ifc = implementedIfc as GeneratedType»
277             «FOR getter : ifc.methodDefinitions»
278                 this._«getter.propertyNameFromGetter» = arg.«getter.name»();
279             «ENDFOR»
280             «FOR impl : ifc.implements»
281                 «printConstructorPropertySetter(impl)»
282             «ENDFOR»
283         «ENDIF»
284     '''
285
286     /**
287      * Generate 'fieldsFrom' method to set builder properties based on type of given argument.
288      */
289     def private generateMethodFieldsFrom(Type type) '''
290         «IF (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject))»
291             «val ifc = type as GeneratedType»
292             «IF ifc.hasImplementsFromUses»
293                 «val List<Type> done = ifc.getBaseIfcs»
294                 «generateMethodFieldsFromComment(ifc)»
295                 public void fieldsFrom(«DataObject.importedName» arg) {
296                     boolean isValidArg = false;
297                     «FOR impl : ifc.getAllIfcs»
298                         «generateIfCheck(impl, done)»
299                     «ENDFOR»
300                     if (!isValidArg) {
301                         throw new IllegalArgumentException(
302                           "expected one of: «ifc.getAllIfcs.toListOfNames» \n" +
303                           "but was: " + arg
304                         );
305                     }
306                 }
307             «ENDIF»
308         «ENDIF»
309     '''
310
311     def private generateMethodFieldsFromComment(GeneratedType type) '''
312         /**
313          Set fields from given grouping argument. Valid argument is instance of one of following types:
314          * <ul>
315          «FOR impl : type.getAllIfcs»
316          * <li>«impl.fullyQualifiedName»</li>
317          «ENDFOR»
318          * </ul>
319          *
320          * @param arg grouping object
321          * @throws IllegalArgumentException if given argument is none of valid types
322         */
323     '''
324
325     /**
326      * Method is used to find out if given type implements any interface from uses.
327      */
328     def boolean hasImplementsFromUses(GeneratedType type) {
329         var i = 0
330         for (impl : type.getAllIfcs) {
331             if ((impl instanceof GeneratedType) &&  !((impl as GeneratedType).methodDefinitions.empty)) {
332                 i = i + 1
333             }
334         }
335         return i > 0
336     }
337
338     def private generateIfCheck(Type impl, List<Type> done) '''
339         «IF (impl instanceof GeneratedType) &&  !((impl as GeneratedType).methodDefinitions.empty)»
340             «val implType = impl as GeneratedType»
341             if (arg instanceof «implType.fullyQualifiedName») {
342                 «printPropertySetter(implType)»
343                 isValidArg = true;
344             }
345         «ENDIF»
346     '''
347
348     def private printPropertySetter(Type implementedIfc) '''
349         «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
350         «val ifc = implementedIfc as GeneratedType»
351         «FOR getter : ifc.methodDefinitions»
352             this._«getter.propertyNameFromGetter» = ((«implementedIfc.fullyQualifiedName»)arg).«getter.name»();
353         «ENDFOR»
354         «ENDIF»
355     '''
356
357     private def List<Type> getBaseIfcs(GeneratedType type) {
358         val List<Type> baseIfcs = new ArrayList();
359         for (ifc : type.implements) {
360             if (ifc instanceof GeneratedType && !(ifc as GeneratedType).methodDefinitions.empty) {
361                 baseIfcs.add(ifc)
362             }
363         }
364         return baseIfcs 
365     }
366
367     private def Set<Type> getAllIfcs(Type type) {
368         val Set<Type> baseIfcs = new HashSet()
369         if (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject)) {
370             val ifc = type as GeneratedType
371             for (impl : ifc.implements) {
372                 if (impl instanceof GeneratedType && !(impl as GeneratedType).methodDefinitions.empty) {
373                     baseIfcs.add(impl)
374                 }
375                 baseIfcs.addAll(impl.getAllIfcs)
376             }
377         }
378         return baseIfcs 
379     }
380
381     private def List<String> toListOfNames(Collection<Type> types) {
382         val List<String> names = new ArrayList
383         for (type : types) {
384             names.add(type.fullyQualifiedName)
385         }
386         return names
387     }
388
389         /**
390          * Template method which generates class attributes.
391          * 
392          * @param boolean value which specify whether field is|isn't final
393          * @return string with class attributes and their types
394          */
395     def private generateFields(boolean _final) '''
396         «IF !properties.empty»
397             «FOR f : properties»
398                 private«IF _final» final«ENDIF» «f.returnType.importedName» «f.fieldName»;
399             «ENDFOR»
400         «ENDIF»
401         «IF augmentField != null»
402             private «Map.importedName»<Class<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»> «augmentField.name» = new «HashMap.importedName»<>();
403         «ENDIF»
404     '''
405
406         /**
407          * Template method which generates setter methods
408          * 
409          * @return string with the setter methods 
410          */
411     def private generateSetters() '''
412         «FOR field : properties SEPARATOR '\n'»
413             public «type.name»«BUILDER» set«field.name.toFirstUpper»(«field.returnType.importedName» value) {
414                 «generateRestrictions(field, "value")»
415
416                 this.«field.fieldName» = value;
417                 return this;
418             }
419         «ENDFOR»
420         «IF augmentField != null»
421
422             public «type.name»«BUILDER» add«augmentField.name.toFirstUpper»(Class<? extends «augmentField.returnType.importedName»> augmentationType, «augmentField.returnType.importedName» augmentation) {
423                 this.«augmentField.name».put(augmentationType, augmentation);
424                 return this;
425             }
426         «ENDIF»
427     '''
428
429     /**
430      * Template method which generate constructor for IMPL class.
431      * 
432      * @return string with IMPL class constructor
433      */
434     def private generateConstructor() '''
435         private «type.name»«IMPL»(«type.name»«BUILDER» builder) {
436             «val allProps = new ArrayList(properties)»
437             «val isList = implementsIfc(type, Types.parameterizedTypeFor(Types.typeForClass(Identifiable), type))»
438             «val keyType = type.getKey»
439             «IF isList && keyType != null»
440                 «val keyProps = new ArrayList((keyType as GeneratedTransferObject).properties)»
441                 «Collections.sort(keyProps,
442                     [ p1, p2 |
443                         return p1.name.compareTo(p2.name)
444                     ])
445                 »
446                 «FOR field : keyProps»
447                     «removeProperty(allProps, field.name)»
448                 «ENDFOR»
449                 «removeProperty(allProps, "key")»
450                 if (builder.getKey() == null) {
451                     this._key = new «keyType.importedName»(
452                         «FOR keyProp : keyProps SEPARATOR ", "»
453                             builder.«keyProp.getterMethodName»()
454                         «ENDFOR»
455                     );
456                     «FOR field : keyProps»
457                         this.«field.fieldName» = builder.«field.getterMethodName»();
458                     «ENDFOR»
459                 } else {
460                     this._key = builder.getKey();
461                     «FOR field : keyProps»
462                            this.«field.fieldName» = _key.«field.getterMethodName»();
463                     «ENDFOR»
464                 }
465             «ENDIF»
466             «FOR field : allProps»
467                 this.«field.fieldName» = builder.«field.getterMethodName»();
468             «ENDFOR»
469             «IF augmentField != null»
470                 this.«augmentField.name».putAll(builder.«augmentField.name»);
471             «ENDIF»
472         }
473     '''
474
475     private def boolean implementsIfc(GeneratedType type, Type impl) {
476         for (Type ifc : type.implements) {
477             if (ifc.equals(impl)) {
478                 return true;
479             }
480         }
481         return false;
482     }
483
484     private def Type getKey(GeneratedType type) {
485         for (m : type.methodDefinitions) {
486             if ("getKey".equals(m.name)) {
487                 return m.returnType;
488             }
489         }
490         return null;
491     }
492
493     private def void removeProperty(Collection<GeneratedProperty> props, String name) {
494         var GeneratedProperty toRemove = null
495         for (p : props) {
496             if (p.name.equals(name)) {
497                 toRemove = p;
498             }
499         }
500         if (toRemove != null) {
501             props.remove(toRemove);
502         }
503     }
504
505     /**
506      * Template method which generate getter methods for IMPL class.
507      * 
508      * @return string with getter methods
509      */
510     def private generateGetters(boolean addOverride) '''
511         «IF !properties.empty»
512             «FOR field : properties SEPARATOR '\n'»
513                 «IF addOverride»@Override«ENDIF»
514                 «field.getterMethod»
515             «ENDFOR»
516         «ENDIF»
517         «IF augmentField != null»
518
519             @SuppressWarnings("unchecked")
520             «IF addOverride»@Override«ENDIF»
521             public <E extends «augmentField.returnType.importedName»> E get«augmentField.name.toFirstUpper»(Class<E> augmentationType) {
522                 if (augmentationType == null) {
523                     throw new IllegalArgumentException("Augmentation Type reference cannot be NULL!");
524                 }
525                 return (E) «augmentField.name».get(augmentationType);
526             }
527         «ENDIF»
528     '''
529
530     /**
531      * Template method which generates the method <code>hashCode()</code>.
532      * 
533      * @return string with the <code>hashCode()</code> method definition in JAVA format
534      */
535     def protected generateHashCode() '''
536         «IF !properties.empty || augmentField != null»
537             @Override
538             public int hashCode() {
539                 final int prime = 31;
540                 int result = 1;
541                 «FOR property : properties»
542                     «IF property.returnType.name.contains("[")»
543                     result = prime * result + ((«property.fieldName» == null) ? 0 : «Arrays.importedName».hashCode(«property.fieldName»));
544                     «ELSE»
545                     result = prime * result + ((«property.fieldName» == null) ? 0 : «property.fieldName».hashCode());
546                     «ENDIF»
547                 «ENDFOR»
548                 «IF augmentField != null»
549                     result = prime * result + ((«augmentField.name» == null) ? 0 : «augmentField.name».hashCode());
550                 «ENDIF»
551                 return result;
552             }
553         «ENDIF»
554     '''
555
556     /**
557      * Template method which generates the method <code>equals()</code>.
558      * 
559      * @return string with the <code>equals()</code> method definition in JAVA format     
560      */
561     def protected generateEquals() '''
562         «IF !properties.empty || augmentField != null»
563             @Override
564             public boolean equals(java.lang.Object obj) {
565                 if (this == obj) {
566                     return true;
567                 }
568                 if (obj == null) {
569                     return false;
570                 }
571                 if (getClass() != obj.getClass()) {
572                     return false;
573                 }
574                 «type.name»«IMPL» other = («type.name»«IMPL») obj;
575                 «FOR property : properties»
576                     «val fieldName = property.fieldName»
577                     if («fieldName» == null) {
578                         if (other.«fieldName» != null) {
579                             return false;
580                         }
581                     «IF property.returnType.name.contains("[")»
582                     } else if(!«Arrays.importedName».equals(«fieldName», other.«fieldName»)) {
583                     «ELSE»
584                     } else if(!«fieldName».equals(other.«fieldName»)) {
585                     «ENDIF»
586                         return false;
587                     }
588                 «ENDFOR»
589                 «IF augmentField != null»
590                     «val fieldName = augmentField.name»
591                     if («fieldName» == null) {
592                         if (other.«fieldName» != null) {
593                             return false;
594                         }
595                     } else if(!«fieldName».equals(other.«fieldName»)) {
596                         return false;
597                     }
598                 «ENDIF»
599                 return true;
600             }
601         «ENDIF»
602     '''
603
604     def override generateToString(Collection<GeneratedProperty> properties) '''
605         «IF !properties.empty»
606             @Override
607             public String toString() {
608                 StringBuilder builder = new StringBuilder("«type.name» [");
609                 boolean first = true;
610
611                 «FOR property : properties»
612                     if («property.fieldName» != null) {
613                         if (first) {
614                             first = false;
615                         } else {
616                             builder.append(", ");
617                         }
618                         builder.append("«property.fieldName»=");
619                         «IF property.returnType.name.contains("[")»
620                             builder.append(«Arrays.importedName».toString(«property.fieldName»));
621                         «ELSE»
622                             builder.append(«property.fieldName»);
623                         «ENDIF»
624                      }
625                 «ENDFOR»
626                 «IF augmentField != null»
627                     if (first) {
628                         first = false;
629                     } else {
630                         builder.append(", ");
631                     }
632                     builder.append("«augmentField.name»=");
633                     builder.append(«augmentField.name».values());
634                 «ENDIF»
635                 return builder.append(']').toString();
636             }
637         «ENDIF»
638     '''
639
640     override protected getFullyQualifiedName() {
641         '''«type.fullyQualifiedName»Builder'''.toString
642     }
643
644     def implementedInterfaceGetter() '''
645     public «Class.importedName»<«type.importedName»> getImplementedInterface() {
646         return «type.importedName».class;
647     }
648     '''
649
650 }
651