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