5f842a29ffe1ae661dec30eb4a381b125c994c6e
[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
2
3 import java.util.LinkedHashSet
4 import java.util.List
5 import java.util.Map
6 import java.util.Set
7 import org.opendaylight.yangtools.binding.generator.util.ReferencedTypeImpl
8 import org.opendaylight.yangtools.binding.generator.util.Types
9 import org.opendaylight.yangtools.binding.generator.util.generated.type.builder.GeneratedTOBuilderImpl
10 import org.opendaylight.yangtools.sal.binding.model.api.GeneratedProperty
11 import org.opendaylight.yangtools.sal.binding.model.api.GeneratedTransferObject
12 import org.opendaylight.yangtools.sal.binding.model.api.GeneratedType
13 import org.opendaylight.yangtools.sal.binding.model.api.MethodSignature
14 import org.opendaylight.yangtools.sal.binding.model.api.Type
15 import org.opendaylight.yangtools.yang.binding.Augmentable
16
17 /**
18  * Template for generating JAVA builder classes. 
19  */
20 class BuilderTemplate {
21
22         /**
23          * Constant with prefix for getter methods.
24          */
25     val static GET_PREFIX = "get"
26     
27     /**
28      * Constant with the name of the concrete package prefix. 
29      */
30     val static JAVA_UTIL = "java.util"
31     
32     /**
33      * Constant with the name of the concrete JAVA type
34      */
35     val static HASH_MAP = "HashMap"
36     
37     /**
38      * Constant with the name of the concrete JAVA interface.
39      */
40     val static MAP = "Map"
41     
42     /**
43      * Constant with the name of the concrete method.
44      */
45     val static GET_AUGMENTATION_METHOD_NAME = "getAugmentation"
46     
47     /**
48      * Constant with the suffix for builder classes.
49      */
50     val static BUILDER = 'Builder'
51     
52     /**
53      * Constant with suffix for the classes which are generated from the builder classes.
54      */
55     val static IMPL = 'Impl'
56     
57     /**
58      * Reference to type for which is generated builder class
59      */
60     val GeneratedType genType
61     
62     /**
63      * Map of imports. The keys are type names and the values are package names.
64      */
65     val Map<String, String> imports
66     
67     /**
68      * Generated property is set if among methods is found one with the name GET_AUGMENTATION_METHOD_NAME
69      */
70     var GeneratedProperty augmentField
71     
72     /**
73      * Set of class attributes (fields) which are derived from the getter methods names
74      */
75     val Set<GeneratedProperty> fields
76     
77     /**
78      * Constructs new instance of this class.
79      * @throws IllegalArgumentException if <code>genType</code> equals <code>null</code>
80      */
81     new(GeneratedType genType) {
82         if (genType == null) {
83             throw new IllegalArgumentException("Generated type reference cannot be NULL!")
84         }
85         
86         this.genType = genType
87         this.imports = GeneratorUtil.createChildImports(genType)
88         this.fields = createFieldsFromMethods(createMethods)
89     }
90     
91     /**
92      * Returns set of method signature instances which contains all the methods of the <code>genType</code>
93      * and all the methods of the implemented interfaces.
94      * 
95      * @returns set of method signature instances
96      */
97     def private Set<MethodSignature> createMethods() {
98         val Set<MethodSignature> methods = new LinkedHashSet
99         methods.addAll(genType.methodDefinitions)
100         storeMethodsOfImplementedIfcs(methods, genType.implements)
101         return methods
102     }
103     
104     /**
105      * Adds to the <code>methods</code> set all the methods of the <code>implementedIfcs</code> 
106      * and recursivelly their implemented interfaces.
107      * 
108      * @param methods set of method signatures
109      * @param implementedIfcs list of implemented interfaces
110      */
111     def private void storeMethodsOfImplementedIfcs(Set<MethodSignature> methods, List<Type> implementedIfcs) {
112         if (implementedIfcs == null || implementedIfcs.empty) {
113             return
114         }
115         for (implementedIfc : implementedIfcs) {
116             if ((implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))) {
117                 val ifc = implementedIfc as GeneratedType
118                 methods.addAll(ifc.methodDefinitions)
119                 storeMethodsOfImplementedIfcs(methods, ifc.implements)
120             } else if (implementedIfc.fullyQualifiedName == Augmentable.name) {
121                 for (m : Augmentable.methods) {
122                     if (m.name == GET_AUGMENTATION_METHOD_NAME) {
123                         addToImports(JAVA_UTIL, HASH_MAP)
124                         addToImports(JAVA_UTIL, MAP)
125                         val fullyQualifiedName = m.returnType.name
126                         val pkg = fullyQualifiedName.package
127                         val name = fullyQualifiedName.name
128                         addToImports(pkg, name)
129                         val tmpGenTO = new GeneratedTOBuilderImpl(pkg, name)
130                         val type = new ReferencedTypeImpl(pkg, name)
131                         val generic = new ReferencedTypeImpl(genType.packageName, genType.name)
132                         val parametrizedReturnType = Types.parameterizedTypeFor(type, generic)
133                         tmpGenTO.addMethod(m.name).setReturnType(parametrizedReturnType)
134                         augmentField = tmpGenTO.toInstance.methodDefinitions.first.createFieldFromGetter
135                     }
136                 }
137             }
138         }
139     }
140     
141     /**
142      * Adds to the <code>imports</code> map the package <code>typePackageName</code>.
143      * 
144      * @param typePackageName 
145      * string with the name of the package which is added to <code>imports</code> as a value
146      * @param typeName 
147      * string with the name of the package which is added to <code>imports</code> as a key
148      */
149     def private void addToImports(String typePackageName,String typeName) {
150         if (typePackageName.startsWith("java.lang") || typePackageName.isEmpty()) {
151             return
152         }
153         if (!imports.containsKey(typeName)) {
154             imports.put(typeName, typePackageName)
155         }
156     }
157     
158     /**
159      * Returns the first element of the list <code>elements</code>.
160      * 
161      * @param list of elements
162      */
163     def private <E> first(List<E> elements) {
164         elements.get(0)
165     }
166     
167     /**
168      * Returns the name of the package from <code>fullyQualifiedName</code>.
169      * 
170      * @param fullyQualifiedName string with fully qualified type name (package + type)
171      * @return string with the package name
172      */
173     def private String getPackage(String fullyQualifiedName) {
174         val lastDotIndex = fullyQualifiedName.lastIndexOf(Constants.DOT)
175         return if (lastDotIndex == -1) "" else fullyQualifiedName.substring(0, lastDotIndex)
176     }
177
178         /**
179          * Returns the name of tye type from <code>fullyQualifiedName</code>
180          * 
181          * @param fullyQualifiedName string with fully qualified type name (package + type)
182          * @return string with the name of the type
183          */
184     def private String getName(String fullyQualifiedName) {
185         val lastDotIndex = fullyQualifiedName.lastIndexOf(Constants.DOT)
186         return if (lastDotIndex == -1) fullyQualifiedName else fullyQualifiedName.substring(lastDotIndex + 1)
187     }
188     
189     /**
190      * Creates set of generated property instances from getter <code>methods</code>.
191      * 
192      * @param set of method signature instances which should be transformed to list of properties 
193      * @return set of generated property instances which represents the getter <code>methods</code>
194      */
195     def private createFieldsFromMethods(Set<MethodSignature> methods) {
196         val Set<GeneratedProperty> result = new LinkedHashSet
197
198         if (methods == null || methods.isEmpty()) {
199             return result
200         }
201
202         for (m : methods) {
203             val createdField = m.createFieldFromGetter
204             if (createdField != null) {
205                 result.add(createdField)
206             }
207         }
208         return result
209     }
210     
211     /**
212      * Creates generated property instance from the getter <code>method</code> name and return type.
213      * 
214      * @param method method signature from which is the method name and return type obtained
215      * @return generated property instance for the getter <code>method</code>
216      * @throws IllegalArgumentException<ul>
217      *  <li>if the <code>method</code> equals <code>null</code></li>
218      *  <li>if the name of the <code>method</code> equals <code>null</code></li>
219      *  <li>if the name of the <code>method</code> is empty</li>
220      *  <li>if the return type of the <code>method</code> equals <code>null</code></li>
221      * </ul>
222      */
223     def private GeneratedProperty createFieldFromGetter(MethodSignature method) {
224         if (method == null || method.name == null || method.name.empty || method.returnType == null) {
225             throw new IllegalArgumentException("Method, method name, method return type reference cannot be NULL or empty!")
226         }
227         if (method.name.startsWith(GET_PREFIX)) {
228             val fieldName = method.getName().substring(GET_PREFIX.length()).toFirstLower
229             val tmpGenTO = new GeneratedTOBuilderImpl("foo", "foo")
230             tmpGenTO.addProperty(fieldName).setReturnType(method.returnType)
231             return tmpGenTO.toInstance.properties.first
232         }
233     }
234
235         /**
236          * Builds string which contains JAVA source code.
237          * 
238          * @return string with JAVA source code
239          */
240     def String generate() {
241         val body = generateBody
242         val pkgAndImports = generatePkgAndImports
243         return pkgAndImports.toString + body.toString
244     }
245     
246     /**
247      * Template method which generates JAVA class body for builder class and for IMPL class. 
248      * 
249      * @return string with JAVA source code
250      */
251     def private generateBody() '''
252         public class «genType.name»«BUILDER» {
253         
254             «generateFields(false)»
255
256             «generateSetters»
257
258             public «genType.name» build() {
259                 return new «genType.name»«IMPL»();
260             }
261
262             private class «genType.name»«IMPL» implements «genType.name» {
263
264                 «generateFields(true)»
265
266                 «generateConstructor»
267
268                 «generateGetters»
269
270             }
271
272         }
273     '''
274
275         /**
276          * Template method which generates class attributes.
277          * 
278          * @param boolean value which specify whether field is|isn't final
279          * @return string with class attributes and their types
280          */
281     def private generateFields(boolean _final) '''
282         «IF !fields.empty»
283             «FOR f : fields»
284                 private  «IF _final»final«ENDIF»  «f.returnType.resolveName» «f.name»;
285             «ENDFOR»
286         «ENDIF»
287         «IF augmentField != null»
288             private Map<Class<? extends «augmentField.returnType.resolveName»>, «augmentField.returnType.resolveName»> «augmentField.name» = new HashMap<>();
289         «ENDIF»
290     '''
291
292         /**
293          * Template method which generates setter methods
294          * 
295          * @return string with the setter methods 
296          */
297     def private generateSetters() '''
298         «FOR field : fields SEPARATOR '\n'»
299             public «genType.name»«BUILDER» set«field.name.toFirstUpper»(«field.returnType.resolveName» «field.name») {
300                 this.«field.name» = «field.name»;
301                 return this;
302             }
303         «ENDFOR»
304         «IF augmentField != null»
305             
306             public «genType.name»«BUILDER» add«augmentField.name.toFirstUpper»(Class<? extends «augmentField.returnType.resolveName»> augmentationType, «augmentField.returnType.resolveName» augmentation) {
307                 this.«augmentField.name».put(augmentationType, augmentation);
308                 return this;
309             }
310         «ENDIF»
311     '''
312     
313     /**
314      * Template method which generate constructor for IMPL class.
315      * 
316      * @return string with IMPL class constructor
317      */
318     def private generateConstructor() '''
319         private «genType.name»«IMPL»() {
320             «IF !fields.empty»
321                 «FOR field : fields»
322                     this.«field.name» = «genType.name»«BUILDER».this.«field.name»;
323                 «ENDFOR»
324             «ENDIF»
325             «IF augmentField != null»
326                 this.«augmentField.name».putAll(«genType.name»«BUILDER».this.«augmentField.name»);
327             «ENDIF»
328         }
329     '''
330     
331     /**
332      * Template method which generate getter methods for IMPL class.
333      * 
334      * @return string with getter methods
335      */
336     def private generateGetters() '''
337         «IF !fields.empty»
338             «FOR field : fields SEPARATOR '\n'»
339                 @Override
340                 public «field.returnType.resolveName» get«field.name.toFirstUpper»() {
341                     return «field.name»;
342                 }
343             «ENDFOR»
344         «ENDIF»
345         «IF augmentField != null»
346
347             @SuppressWarnings("unchecked")
348             @Override
349             public <E extends «augmentField.returnType.resolveName»> E get«augmentField.name.toFirstUpper»(Class<E> augmentationType) {
350                 if (augmentationType == null) {
351                     throw new IllegalArgumentException("Augmentation Type reference cannot be NULL!");
352                 }
353                 return (E) «augmentField.name».get(augmentationType);
354             }
355         «ENDIF»
356     '''    
357     
358     /**
359      * Template method which generate package name line and import lines.
360      * 
361      * @result string with package and import lines in JAVA format
362      */
363     def private generatePkgAndImports() '''
364         package «genType.packageName»;
365         
366         
367         «IF !imports.empty»
368             «FOR entry : imports.entrySet»
369                 import «entry.value».«entry.key»;
370             «ENDFOR»
371         «ENDIF»
372         
373     '''
374     
375     /**
376      * Adds package to imports if it is necessary and returns necessary type name (with or without package name)
377      * 
378      * @param type JAVA <code>Type</code>
379      * @return string with the type name (with or without package name)
380      */
381     def private resolveName(Type type) {
382         GeneratorUtil.putTypeIntoImports(genType, type, imports);
383         GeneratorUtil.getExplicitType(genType, type, imports)
384     }
385     
386 }
387