Make sure builder-generated object do not emit null values
[yangtools.git] / code-generator / binding-java-api-generator / src / main / java / org / opendaylight / yangtools / sal / java / api / generator / BaseTemplate.xtend
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.yangtools.sal.java.api.generator
9
10 import org.opendaylight.yangtools.sal.binding.model.api.GeneratedProperty
11 import org.opendaylight.yangtools.sal.binding.model.api.GeneratedType
12 import java.util.Map
13 import org.opendaylight.yangtools.sal.binding.model.api.Type
14 import org.opendaylight.yangtools.binding.generator.util.Types
15 import com.google.common.base.Splitter
16 import org.opendaylight.yangtools.sal.binding.model.api.MethodSignature
17 import com.google.common.collect.Range
18 import java.util.ArrayList
19 import java.util.List
20 import org.opendaylight.yangtools.sal.binding.model.api.ConcreteType
21 import org.opendaylight.yangtools.sal.binding.model.api.Restrictions
22 import org.opendaylight.yangtools.sal.binding.model.api.GeneratedTransferObject
23 import java.util.Collection
24 import java.util.Arrays
25
26 abstract class BaseTemplate {
27
28     protected val GeneratedType type;
29     protected val Map<String, String> importMap;
30     static val paragraphSplitter = Splitter.on("\n\n").omitEmptyStrings();
31
32     new(GeneratedType _type) {
33         if (_type == null) {
34             throw new IllegalArgumentException("Generated type reference cannot be NULL!")
35         }
36         this.type = _type;
37         this.importMap = GeneratorUtil.createImports(type)
38     }
39
40     def packageDefinition() '''package «type.packageName»;'''
41
42     protected def getFullyQualifiedName() {
43         return type.fullyQualifiedName
44     }
45
46     final public def generate() {
47         val _body = body()
48         '''
49             «packageDefinition»
50             «imports»
51             
52             «_body»
53         '''.toString
54     }
55
56     protected def imports() ''' 
57         «IF !importMap.empty»
58             «FOR entry : importMap.entrySet»
59                 «IF entry.value != fullyQualifiedName»
60                     import «entry.value».«entry.key»;
61                 «ENDIF»
62             «ENDFOR»
63         «ENDIF»
64         
65     '''
66
67     protected abstract def CharSequence body();
68
69     // Helper patterns
70     final protected def fieldName(GeneratedProperty property) '''_«property.name»'''
71
72     final protected def propertyNameFromGetter(MethodSignature getter) {
73         var int prefix;
74         if (getter.name.startsWith("is")) {
75             prefix = 2
76         } else if (getter.name.startsWith("get")) {
77             prefix = 3
78         } else {
79             throw new IllegalArgumentException("Not a getter")
80         }
81         return getter.name.substring(prefix).toFirstLower;
82     }
83
84     /**
85      * Template method which generates the getter method for <code>field</code>
86      * 
87      * @param field 
88      * generated property with data about field which is generated as the getter method
89      * @return string with the getter method source code in JAVA format 
90      */
91     final protected def getterMethod(GeneratedProperty field) {
92         '''
93             public «field.returnType.importedName» «field.getterMethodName»() {
94                 return «field.fieldName»;
95             }
96         '''
97     }
98
99     final protected def getterMethodName(GeneratedProperty field) {
100         val prefix = if(field.returnType.equals(Types.BOOLEAN)) "is" else "get"
101         return '''«prefix»«field.name.toFirstUpper»'''
102     }
103
104     /**
105      * Template method which generates the setter method for <code>field</code>
106      * 
107      * @param field 
108      * generated property with data about field which is generated as the setter method
109      * @return string with the setter method source code in JAVA format 
110      */
111     final protected def setterMethod(GeneratedProperty field) '''
112         «val returnType = field.returnType.importedName»
113         public «type.name» set«field.name.toFirstUpper»(«returnType» value) {
114             this.«field.fieldName» = value;
115             return this;
116         }
117     '''
118
119     final protected def importedName(Type intype) {
120         GeneratorUtil.putTypeIntoImports(type, intype, importMap);
121         GeneratorUtil.getExplicitType(type, intype, importMap)
122     }
123
124     final protected def importedName(Class<?> cls) {
125         importedName(Types.typeForClass(cls))
126     }
127
128     /**
129      * Template method which generates method parameters with their types from <code>parameters</code>.
130      * 
131      * @param parameters
132      * group of generated property instances which are transformed to the method parameters
133      * @return string with the list of the method parameters with their types in JAVA format
134      */
135     def final protected asArgumentsDeclaration(Iterable<GeneratedProperty> parameters) '''«IF !parameters.empty»«FOR parameter : parameters SEPARATOR ", "»«parameter.
136         returnType.importedName» «parameter.fieldName»«ENDFOR»«ENDIF»'''
137
138     /**
139      * Template method which generates sequence of the names of the class attributes from <code>parameters</code>.
140      * 
141      * @param parameters 
142      * group of generated property instances which are transformed to the sequence of parameter names
143      * @return string with the list of the parameter names of the <code>parameters</code> 
144      */
145     def final protected asArguments(Iterable<GeneratedProperty> parameters) '''«IF !parameters.empty»«FOR parameter : parameters SEPARATOR ", "»«parameter.
146         fieldName»«ENDFOR»«ENDIF»'''
147
148     /**
149      * Template method which generates JAVA comments.
150      * 
151      * @param comment string with the comment for whole JAVA class
152      * @return string with comment in JAVA format
153      */
154     def protected CharSequence asJavadoc(String comment) {
155         if(comment == null) return '';
156         var txt = comment
157         if (txt.contains("*/")) {
158             txt = txt.replace("*/", "&#42;&#47;")
159         }
160         val paragraphs = paragraphSplitter.split(txt)
161
162         return '''
163             /**
164               «FOR p : paragraphs SEPARATOR "<p>"»
165                   «p»
166               «ENDFOR»
167             **/
168         '''
169     }
170
171     def generateRestrictions(Type type, String paramName, Type returnType) '''
172         «val boolean isArray = returnType.name.contains("[")»
173         «processRestrictions(type, paramName, returnType, isArray)»
174     '''
175
176     def generateRestrictions(GeneratedProperty field, String paramName) '''
177         «val Type type = field.returnType»
178         «IF type instanceof ConcreteType»
179             «processRestrictions(type, paramName, field.returnType, type.name.contains("["))»
180         «ELSEIF type instanceof GeneratedTransferObject»
181             «processRestrictions(type, paramName, field.returnType, isArrayType(type as GeneratedTransferObject))»
182         «ENDIF»
183     '''
184
185
186     private def processRestrictions(Type type, String paramName, Type returnType, boolean isArray) '''
187         «val restrictions = type.getRestrictions»
188         «IF restrictions !== null»
189             «IF !restrictions.lengthConstraints.empty»
190                 «generateLengthRestriction(type, restrictions, paramName, isArray,
191             !(returnType instanceof ConcreteType))»
192             «ENDIF»
193             «IF !restrictions.rangeConstraints.empty &&
194             ("java.lang".equals(returnType.packageName) || "java.math".equals(returnType.packageName))»
195                 «generateRangeRestriction(type, returnType, restrictions, paramName,
196             !(returnType instanceof ConcreteType))»
197             «ENDIF»
198         «ENDIF»
199     '''
200
201     def generateLengthRestriction(Type type, Restrictions restrictions, String paramName, boolean isArray,
202         boolean isNestedType) '''
203         if («paramName» != null) {
204             boolean isValidLength = false;
205             «List.importedName»<«Range.importedName»<«Integer.importedName»>> lengthConstraints = new «ArrayList.
206             importedName»<>(); 
207             «FOR r : restrictions.lengthConstraints»
208                 lengthConstraints.add(«Range.importedName».closed(«r.min», «r.max»));
209             «ENDFOR»
210             for («Range.importedName»<«Integer.importedName»> r : lengthConstraints) {
211                 «IF isArray»
212                     «IF isNestedType»
213                         if (r.contains(«paramName».getValue().length)) {
214                     «ELSE»
215                         if (r.contains(«paramName».length)) {
216                     «ENDIF»
217                 «ELSE»
218                     «IF isNestedType»
219                         if (r.contains(«paramName».getValue().length())) {
220                     «ELSE»
221                         if (r.contains(«paramName».length())) {
222                     «ENDIF»
223                 «ENDIF»
224                 isValidLength = true;
225                 }
226             }
227             if (!isValidLength) {
228                 throw new IllegalArgumentException(String.format("Invalid length: {}, expected: {}.", «paramName», lengthConstraints));
229             }
230         }
231     '''
232
233     def generateRangeRestriction(Type type, Type returnType, Restrictions restrictions, String paramName,
234         boolean isNestedType) '''
235         «val javaType = Class.forName(returnType.fullyQualifiedName)»
236         if («paramName» != null) {
237             boolean isValidRange = false;
238             «List.importedName»<«Range.importedName»<«javaType.importedName»>> rangeConstraints = new «ArrayList.
239             importedName»<>(); 
240             «FOR r : restrictions.rangeConstraints»
241                 rangeConstraints.add(«Range.importedName».closed(new «javaType.importedName»(«r.min.toQuote»), new «javaType.
242             importedName»(«r.max.toQuote»)));
243             «ENDFOR»
244             for («Range.importedName»<«javaType.importedName»> r : rangeConstraints) {
245                 «IF isNestedType»
246                     if (r.contains(«paramName».getValue())) {
247                 «ELSE»
248                     if (r.contains(«paramName»)) {
249                 «ENDIF»
250                 isValidRange = true;
251                 }
252             }
253             if (!isValidRange) {
254                 throw new IllegalArgumentException(String.format("Invalid range: %s, expected: %s.", «paramName», rangeConstraints));
255             }
256         }
257     '''
258
259     def protected generateToString(Collection<GeneratedProperty> properties) '''
260         «IF !properties.empty»
261             @Override
262             public String toString() {
263                 StringBuilder builder = new StringBuilder("«type.name» [");
264                 boolean first = true;
265
266                 «FOR property : properties»
267                     if («property.fieldName» != null) {
268                         if (first) {
269                             first = false;
270                         } else {
271                             builder.append(", ");
272                         }
273                         builder.append("«property.fieldName»=");
274                         «IF property.returnType.name.contains("[")»
275                             builder.append(«Arrays.importedName».toString(«property.fieldName»));
276                         «ELSE»
277                             builder.append(«property.fieldName»);
278                         «ENDIF»
279                      }
280                 «ENDFOR»
281                 return builder.append(']').toString();
282             }
283         «ENDIF»
284     '''
285
286     def GeneratedProperty getPropByName(GeneratedType gt, String name) {
287         for (GeneratedProperty prop : gt.properties) {
288             if (prop.name.equals(name)) {
289                 return prop;
290             }
291         }
292         return null;
293     }
294
295     def getRestrictions(Type type) {
296         var Restrictions restrictions = null
297         if (type instanceof ConcreteType) {
298             restrictions = (type as ConcreteType).restrictions
299         } else if (type instanceof GeneratedTransferObject) {
300             restrictions = (type as GeneratedTransferObject).restrictions
301         }
302         return restrictions
303     }
304
305     def boolean isArrayType(GeneratedTransferObject type) {
306         var isArray = false
307         val GeneratedTransferObject superType = type.findSuperType
308         val GeneratedProperty value = superType.getPropByName("value")
309         if (value != null && value.returnType.name.contains("[")) {
310             isArray = true
311         }
312         return isArray
313     }
314
315     def GeneratedTransferObject findSuperType(GeneratedTransferObject gto) {
316         var GeneratedTransferObject base = gto
317         var GeneratedTransferObject superType = base.superType
318         while (superType !== null) {
319             base = superType
320             superType = base.superType
321         }
322         return base;
323     }
324
325     def String toQuote(Object obj) {
326         return "\"" + obj.toString + "\"";
327     }
328
329     /**
330      * Template method which generates method parameters with their types from <code>parameters</code>.
331      * 
332      * @param parameters
333      * list of parameter instances which are transformed to the method parameters
334      * @return string with the list of the method parameters with their types in JAVA format
335      */
336     def protected generateParameters(List<MethodSignature.Parameter> parameters) '''«
337         IF !parameters.empty»«
338             FOR parameter : parameters SEPARATOR ", "»«
339                 parameter.type.importedName» «parameter.name»«
340             ENDFOR»«
341         ENDIF
342     »'''
343
344 }