Merge "Added export of augmentation schemas to Binding Context"
[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 \r
229         }\r
230     '''\r
231 \r
232     /**\r
233      * Generate default constructor and constructor for every implemented interface from uses statements.\r
234      */\r
235     def private generateConstructorsFromIfcs(Type type) '''\r
236         public «type.name»«BUILDER»() {\r
237         } \r
238         «IF (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject))»\r
239             «val ifc = type as GeneratedType»\r
240             «FOR impl : ifc.implements»\r
241                 «generateConstructorFromIfc(impl)»\r
242             «ENDFOR»\r
243         «ENDIF»\r
244     '''\r
245 \r
246     /**\r
247      * Generate constructor with argument of given type.\r
248      */\r
249     def private generateConstructorFromIfc(Type impl) '''\r
250         «IF (impl instanceof GeneratedType) &&  !((impl as GeneratedType).methodDefinitions.empty)»\r
251             «val implType = impl as GeneratedType»\r
252 \r
253             public «type.name»«BUILDER»(«implType.fullyQualifiedName» arg) {\r
254                 «printConstructorPropertySetter(implType)»\r
255             }\r
256             «FOR implTypeImplement : implType.implements»\r
257                 «generateConstructorFromIfc(implTypeImplement)»\r
258             «ENDFOR»\r
259         «ENDIF»\r
260     '''\r
261 \r
262     def private printConstructorPropertySetter(Type implementedIfc) '''\r
263         «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»\r
264             «val ifc = implementedIfc as GeneratedType»\r
265             «FOR getter : ifc.methodDefinitions»\r
266                 this._«getter.propertyNameFromGetter» = arg.«getter.name»();\r
267             «ENDFOR»\r
268             «FOR impl : ifc.implements»\r
269                 «printConstructorPropertySetter(impl)»\r
270             «ENDFOR»\r
271         «ENDIF»\r
272     '''\r
273 \r
274     /**\r
275      * Generate 'fieldsFrom' method to set builder properties based on type of given argument.\r
276      */\r
277     def private generateMethodFieldsFrom(Type type) '''\r
278         «IF (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject))»\r
279             «val ifc = type as GeneratedType»\r
280             «IF ifc.hasImplementsFromUses»\r
281                 «val List<Type> done = ifc.getBaseIfcs»\r
282                 «generateMethodFieldsFromComment(ifc)»\r
283                 public void fieldsFrom(«DataObject.importedName» arg) {\r
284                     boolean isValidArg = false;\r
285                     «FOR impl : ifc.getAllIfcs»\r
286                         «generateIfCheck(impl, done)»\r
287                     «ENDFOR»\r
288                     if (!isValidArg) {\r
289                         throw new IllegalArgumentException(\r
290                           "expected one of: «ifc.getAllIfcs.toListOfNames» \n" +\r
291                           "but was: " + arg\r
292                         );\r
293                     }\r
294                 }\r
295             «ENDIF»\r
296         «ENDIF»\r
297     '''\r
298 \r
299     def private generateMethodFieldsFromComment(GeneratedType type) '''\r
300         /**\r
301          Set fields from given grouping argument. Valid argument is instance of one of following types:\r
302          * <ul>\r
303          «FOR impl : type.getAllIfcs»\r
304          * <li>«impl.fullyQualifiedName»</li>\r
305          «ENDFOR»\r
306          * </ul>\r
307          *\r
308          * @param arg grouping object\r
309          * @throws IllegalArgumentException if given argument is none of valid types\r
310         */\r
311     '''\r
312 \r
313     /**\r
314      * Method is used to find out if given type implements any interface from uses.\r
315      */\r
316     def boolean hasImplementsFromUses(GeneratedType type) {\r
317         var i = 0\r
318         for (impl : type.getAllIfcs) {\r
319             if ((impl instanceof GeneratedType) &&  !((impl as GeneratedType).methodDefinitions.empty)) {\r
320                 i = i + 1\r
321             }\r
322         }\r
323         return i > 0\r
324     }\r
325 \r
326     def private generateIfCheck(Type impl, List<Type> done) '''\r
327         «IF (impl instanceof GeneratedType) &&  !((impl as GeneratedType).methodDefinitions.empty)»\r
328             «val implType = impl as GeneratedType»\r
329             if (arg instanceof «implType.fullyQualifiedName») {\r
330                 «printPropertySetter(implType)»\r
331                 isValidArg = true;\r
332             }\r
333         «ENDIF»\r
334     '''\r
335 \r
336     def private printPropertySetter(Type implementedIfc) '''\r
337         «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»\r
338         «val ifc = implementedIfc as GeneratedType»\r
339         «FOR getter : ifc.methodDefinitions»\r
340             this._«getter.propertyNameFromGetter» = ((«implementedIfc.fullyQualifiedName»)arg).«getter.name»();\r
341         «ENDFOR»\r
342         «ENDIF»\r
343     '''\r
344 \r
345     private def List<Type> getBaseIfcs(GeneratedType type) {\r
346         val List<Type> baseIfcs = new ArrayList();\r
347         for (ifc : type.implements) {\r
348             if (ifc instanceof GeneratedType && !(ifc as GeneratedType).methodDefinitions.empty) {\r
349                 baseIfcs.add(ifc)\r
350             }\r
351         }\r
352         return baseIfcs \r
353     }\r
354 \r
355     private def Set<Type> getAllIfcs(Type type) {\r
356         val Set<Type> baseIfcs = new HashSet()\r
357         if (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject)) {\r
358             val ifc = type as GeneratedType\r
359             for (impl : ifc.implements) {\r
360                 if (impl instanceof GeneratedType && !(impl as GeneratedType).methodDefinitions.empty) {\r
361                     baseIfcs.add(impl)\r
362                 }\r
363                 baseIfcs.addAll(impl.getAllIfcs)\r
364             }\r
365         }\r
366         return baseIfcs \r
367     }\r
368 \r
369     private def List<String> toListOfNames(Collection<Type> types) {\r
370         val List<String> names = new ArrayList\r
371         for (type : types) {\r
372             names.add(type.fullyQualifiedName)\r
373         }\r
374         return names\r
375     }\r
376 \r
377         /**\r
378          * Template method which generates class attributes.\r
379          * \r
380          * @param boolean value which specify whether field is|isn't final\r
381          * @return string with class attributes and their types\r
382          */\r
383     def private generateFields(boolean _final) '''\r
384         «IF !properties.empty»\r
385             «FOR f : properties»\r
386                 private«IF _final» final«ENDIF» «f.returnType.importedName» «f.fieldName»;\r
387             «ENDFOR»\r
388         «ENDIF»\r
389         «IF augmentField != null»\r
390             private «Map.importedName»<Class<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»> «augmentField.name» = new «HashMap.importedName»<>();\r
391         «ENDIF»\r
392     '''\r
393 \r
394         /**\r
395          * Template method which generates setter methods\r
396          * \r
397          * @return string with the setter methods \r
398          */\r
399     def private generateSetters() '''\r
400         «FOR field : properties SEPARATOR '\n'»\r
401             public «type.name»«BUILDER» set«field.name.toFirstUpper»(«field.returnType.importedName» value) {\r
402                 «generateRestrictions(field, "value")»\r
403 \r
404                 this.«field.fieldName» = value;\r
405                 return this;\r
406             }\r
407         «ENDFOR»\r
408         «IF augmentField != null»\r
409 \r
410             public «type.name»«BUILDER» add«augmentField.name.toFirstUpper»(Class<? extends «augmentField.returnType.importedName»> augmentationType, «augmentField.returnType.importedName» augmentation) {\r
411                 this.«augmentField.name».put(augmentationType, augmentation);\r
412                 return this;\r
413             }\r
414         «ENDIF»\r
415     '''\r
416 \r
417     /**\r
418      * Template method which generate constructor for IMPL class.\r
419      * \r
420      * @return string with IMPL class constructor\r
421      */\r
422     def private generateConstructor() '''\r
423         private «type.name»«IMPL»(«type.name»«BUILDER» builder) {\r
424             «val allProps = new ArrayList(properties)»\r
425             «val isList = implementsIfc(type, Types.parameterizedTypeFor(Types.typeForClass(Identifiable), type))»\r
426             «val keyType = type.getKey»\r
427             «IF isList && keyType != null»\r
428                 «val keyProps = new ArrayList((keyType as GeneratedTransferObject).properties)»\r
429                 «Collections.sort(keyProps,\r
430                     [ p1, p2 |\r
431                         return p1.name.compareTo(p2.name)\r
432                     ])\r
433                 »\r
434                 «FOR field : keyProps»\r
435                     «removeProperty(allProps, field.name)»\r
436                 «ENDFOR»\r
437                 «removeProperty(allProps, "key")»\r
438                 if (builder.getKey() == null) {\r
439                     this._key = new «keyType.importedName»(\r
440                         «FOR keyProp : keyProps SEPARATOR ", "»\r
441                             builder.«keyProp.getterMethodName»()\r
442                         «ENDFOR»\r
443                     );\r
444                     «FOR field : keyProps»\r
445                         this.«field.fieldName» = builder.«field.getterMethodName»();\r
446                     «ENDFOR»\r
447                 } else {\r
448                     this._key = builder.getKey();\r
449                     «FOR field : keyProps»\r
450                            this.«field.fieldName» = _key.«field.getterMethodName»();\r
451                     «ENDFOR»\r
452                 }\r
453             «ENDIF»\r
454             «FOR field : allProps»\r
455                 this.«field.fieldName» = builder.«field.getterMethodName»();\r
456             «ENDFOR»\r
457             «IF augmentField != null»\r
458                 this.«augmentField.name».putAll(builder.«augmentField.name»);\r
459             «ENDIF»\r
460         }\r
461     '''\r
462 \r
463     private def boolean implementsIfc(GeneratedType type, Type impl) {\r
464         for (Type ifc : type.implements) {\r
465             if (ifc.equals(impl)) {\r
466                 return true;\r
467             }\r
468         }\r
469         return false;\r
470     }\r
471 \r
472     private def Type getKey(GeneratedType type) {\r
473         for (m : type.methodDefinitions) {\r
474             if ("getKey".equals(m.name)) {\r
475                 return m.returnType;\r
476             }\r
477         }\r
478         return null;\r
479     }\r
480 \r
481     private def void removeProperty(Collection<GeneratedProperty> props, String name) {\r
482         var GeneratedProperty toRemove = null\r
483         for (p : props) {\r
484             if (p.name.equals(name)) {\r
485                 toRemove = p;\r
486             }\r
487         }\r
488         if (toRemove != null) {\r
489             props.remove(toRemove);\r
490         }\r
491     }\r
492 \r
493     /**\r
494      * Template method which generate getter methods for IMPL class.\r
495      * \r
496      * @return string with getter methods\r
497      */\r
498     def private generateGetters(boolean addOverride) '''\r
499         «IF !properties.empty»\r
500             «FOR field : properties SEPARATOR '\n'»\r
501                 «IF addOverride»@Override«ENDIF»\r
502                 «field.getterMethod»\r
503             «ENDFOR»\r
504         «ENDIF»\r
505         «IF augmentField != null»\r
506 \r
507             @SuppressWarnings("unchecked")\r
508             «IF addOverride»@Override«ENDIF»\r
509             public <E extends «augmentField.returnType.importedName»> E get«augmentField.name.toFirstUpper»(Class<E> augmentationType) {\r
510                 if (augmentationType == null) {\r
511                     throw new IllegalArgumentException("Augmentation Type reference cannot be NULL!");\r
512                 }\r
513                 return (E) «augmentField.name».get(augmentationType);\r
514             }\r
515         «ENDIF»\r
516     '''\r
517 \r
518     /**\r
519      * Template method which generates the method <code>hashCode()</code>.\r
520      * \r
521      * @return string with the <code>hashCode()</code> method definition in JAVA format\r
522      */\r
523     def protected generateHashCode() '''\r
524         «IF !properties.empty || augmentField != null»\r
525             @Override\r
526             public int hashCode() {\r
527                 final int prime = 31;\r
528                 int result = 1;\r
529                 «FOR property : properties»\r
530                     «IF property.returnType.name.contains("[")»\r
531                     result = prime * result + ((«property.fieldName» == null) ? 0 : «Arrays.importedName».hashCode(«property.fieldName»));\r
532                     «ELSE»\r
533                     result = prime * result + ((«property.fieldName» == null) ? 0 : «property.fieldName».hashCode());\r
534                     «ENDIF»\r
535                 «ENDFOR»\r
536                 «IF augmentField != null»\r
537                     result = prime * result + ((«augmentField.name» == null) ? 0 : «augmentField.name».hashCode());\r
538                 «ENDIF»\r
539                 return result;\r
540             }\r
541         «ENDIF»\r
542     '''\r
543 \r
544     /**\r
545      * Template method which generates the method <code>equals()</code>.\r
546      * \r
547      * @return string with the <code>equals()</code> method definition in JAVA format     \r
548      */\r
549     def protected generateEquals() '''\r
550         «IF !properties.empty || augmentField != null»\r
551             @Override\r
552             public boolean equals(java.lang.Object obj) {\r
553                 if (this == obj) {\r
554                     return true;\r
555                 }\r
556                 if (obj == null) {\r
557                     return false;\r
558                 }\r
559                 if (getClass() != obj.getClass()) {\r
560                     return false;\r
561                 }\r
562                 «type.name»«IMPL» other = («type.name»«IMPL») obj;\r
563                 «FOR property : properties»\r
564                     «val fieldName = property.fieldName»\r
565                     if («fieldName» == null) {\r
566                         if (other.«fieldName» != null) {\r
567                             return false;\r
568                         }\r
569                     «IF property.returnType.name.contains("[")»\r
570                     } else if(!«Arrays.importedName».equals(«fieldName», other.«fieldName»)) {\r
571                     «ELSE»\r
572                     } else if(!«fieldName».equals(other.«fieldName»)) {\r
573                     «ENDIF»\r
574                         return false;\r
575                     }\r
576                 «ENDFOR»\r
577                 «IF augmentField != null»\r
578                     «val fieldName = augmentField.name»\r
579                     if («fieldName» == null) {\r
580                         if (other.«fieldName» != null) {\r
581                             return false;\r
582                         }\r
583                     } else if(!«fieldName».equals(other.«fieldName»)) {\r
584                         return false;\r
585                     }\r
586                 «ENDIF»\r
587                 return true;\r
588             }\r
589         «ENDIF»\r
590     '''\r
591 \r
592     override protected getFullyQualifiedName() {\r
593         '''«type.fullyQualifiedName»Builder'''.toString\r
594     }\r
595     \r
596     def implementedInterfaceGetter() '''\r
597     public «Class.importedName»<«type.importedName»> getImplementedInterface() {\r
598         return «type.importedName».class;\r
599     }\r
600     '''\r
601 \r
602 }\r
603 \r