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