BUG-1276: fixed generated union constructor
[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.List
19 import org.opendaylight.yangtools.sal.binding.model.api.ConcreteType
20 import org.opendaylight.yangtools.sal.binding.model.api.Restrictions
21 import org.opendaylight.yangtools.sal.binding.model.api.GeneratedTransferObject
22 import java.util.Collection
23 import java.util.Arrays
24 import java.util.HashMap
25 import com.google.common.collect.ImmutableList
26 import java.math.BigInteger
27 import java.math.BigDecimal
28
29 abstract class BaseTemplate {
30     protected val GeneratedType type;
31     protected val Map<String, String> importMap;
32     static val paragraphSplitter = Splitter.on("\n\n").omitEmptyStrings();
33
34     new(GeneratedType _type) {
35         if (_type == null) {
36             throw new IllegalArgumentException("Generated type reference cannot be NULL!")
37         }
38         this.type = _type;
39         this.importMap = new HashMap<String,String>()
40     }
41
42     def packageDefinition() '''package «type.packageName»;'''
43
44     protected def getFullyQualifiedName() {
45         return type.fullyQualifiedName
46     }
47
48     final public def generate() {
49         val _body = body()
50         '''
51             «packageDefinition»
52             «imports»
53             
54             «_body»
55         '''.toString
56     }
57
58     protected def imports() ''' 
59         «IF !importMap.empty»
60             «FOR entry : importMap.entrySet»
61                 «IF entry.value != fullyQualifiedName»
62                     import «entry.value».«entry.key»;
63                 «ENDIF»
64             «ENDFOR»
65         «ENDIF»
66         
67     '''
68
69     protected abstract def CharSequence body();
70
71     // Helper patterns
72     final protected def fieldName(GeneratedProperty property) '''_«property.name»'''
73
74     final protected def propertyNameFromGetter(MethodSignature getter) {
75         var int prefix;
76         if (getter.name.startsWith("is")) {
77             prefix = 2
78         } else if (getter.name.startsWith("get")) {
79             prefix = 3
80         } else {
81             throw new IllegalArgumentException("Not a getter")
82         }
83         return getter.name.substring(prefix).toFirstLower;
84     }
85
86     /**
87      * Template method which generates the getter method for <code>field</code>
88      * 
89      * @param field 
90      * generated property with data about field which is generated as the getter method
91      * @return string with the getter method source code in JAVA format 
92      */
93     final protected def getterMethod(GeneratedProperty field) {
94         '''
95             public «field.returnType.importedName» «field.getterMethodName»() {
96                 return «field.fieldName»;
97             }
98         '''
99     }
100
101     final protected def getterMethodName(GeneratedProperty field) {
102         val prefix = if(field.returnType.equals(Types.BOOLEAN)) "is" else "get"
103         return '''«prefix»«field.name.toFirstUpper»'''
104     }
105
106     /**
107      * Template method which generates the setter method for <code>field</code>
108      * 
109      * @param field 
110      * generated property with data about field which is generated as the setter method
111      * @return string with the setter method source code in JAVA format 
112      */
113     final protected def setterMethod(GeneratedProperty field) '''
114         «val returnType = field.returnType.importedName»
115         public «type.name» set«field.name.toFirstUpper»(«returnType» value) {
116             this.«field.fieldName» = value;
117             return this;
118         }
119     '''
120
121     final protected def importedName(Type intype) {
122         GeneratorUtil.putTypeIntoImports(type, intype, importMap);
123         GeneratorUtil.getExplicitType(type, intype, importMap)
124     }
125
126     final protected def importedName(Class<?> cls) {
127         importedName(Types.typeForClass(cls))
128     }
129
130     /**
131      * Template method which generates method parameters with their types from <code>parameters</code>.
132      * 
133      * @param parameters
134      * group of generated property instances which are transformed to the method parameters
135      * @return string with the list of the method parameters with their types in JAVA format
136      */
137     def final protected asArgumentsDeclaration(Iterable<GeneratedProperty> parameters) '''«IF !parameters.empty»«FOR parameter : parameters SEPARATOR ", "»«parameter.
138         returnType.importedName» «parameter.fieldName»«ENDFOR»«ENDIF»'''
139
140     /**
141      * Template method which generates sequence of the names of the class attributes from <code>parameters</code>.
142      * 
143      * @param parameters 
144      * group of generated property instances which are transformed to the sequence of parameter names
145      * @return string with the list of the parameter names of the <code>parameters</code> 
146      */
147     def final protected asArguments(Iterable<GeneratedProperty> parameters) '''«IF !parameters.empty»«FOR parameter : parameters SEPARATOR ", "»«parameter.
148         fieldName»«ENDFOR»«ENDIF»'''
149
150     /**
151      * Template method which generates JAVA comments.
152      * 
153      * @param comment string with the comment for whole JAVA class
154      * @return string with comment in JAVA format
155      */
156     def protected CharSequence asJavadoc(String comment) {
157         if(comment == null) return '';
158         var txt = comment
159         if (txt.contains("*/")) {
160             txt = txt.replace("*/", "&#42;&#47;")
161         }
162         val paragraphs = paragraphSplitter.split(txt)
163
164         return '''
165             /**
166               «FOR p : paragraphs SEPARATOR "<p>"»
167                   «p»
168               «ENDFOR»
169             **/
170         '''
171     }
172
173     def generateRestrictions(Type type, String paramName, Type returnType) '''
174         «val restrictions = type.getRestrictions»
175         «IF restrictions !== null»
176             «val boolean isNestedType = !(returnType instanceof ConcreteType)»
177             «IF !restrictions.lengthConstraints.empty»
178                 «generateLengthRestriction(returnType, restrictions, paramName, isNestedType)»
179             «ENDIF»
180             «IF !restrictions.rangeConstraints.empty»
181                 «generateRangeRestriction(returnType, restrictions, paramName, isNestedType)»
182             «ENDIF»
183         «ENDIF»
184     '''
185
186     def private generateLengthRestriction(Type returnType, Restrictions restrictions, String paramName, boolean isNestedType) '''
187         «val clazz = restrictions.lengthConstraints.iterator.next.min.class»
188         if («paramName» != null) {
189             «printLengthConstraint(returnType, clazz, paramName, isNestedType, returnType.name.contains("["))»
190             boolean isValidLength = false;
191             for («Range.importedName»<«clazz.importedNumber»> r : «IF isNestedType»«returnType.importedName».«ENDIF»length()) {
192                 if (r.contains(_constraint)) {
193                     isValidLength = true;
194                 }
195             }
196             if (!isValidLength) {
197                 throw new IllegalArgumentException(String.format("Invalid length: %s, expected: %s.", «paramName», «IF isNestedType»«returnType.importedName».«ENDIF»length()));
198             }
199         }
200     '''
201
202     def private generateRangeRestriction(Type returnType, Restrictions restrictions, String paramName, boolean isNestedType) '''
203         «val clazz = restrictions.rangeConstraints.iterator.next.min.class»
204         if («paramName» != null) {
205             «printRangeConstraint(returnType, clazz, paramName, isNestedType)»
206             boolean isValidRange = false;
207             for («Range.importedName»<«clazz.importedNumber»> r : «IF isNestedType»«returnType.importedName».«ENDIF»range()) {
208                 if (r.contains(_constraint)) {
209                     isValidRange = true;
210                 }
211             }
212             if (!isValidRange) {
213                 throw new IllegalArgumentException(String.format("Invalid range: %s, expected: %s.", «paramName», «IF isNestedType»«returnType.importedName».«ENDIF»range()));
214             }
215         }
216     '''
217
218     /**
219      * Print length constraint.
220      * This should always be a BigInteger (only string and binary can have length restriction)
221      */
222     def printLengthConstraint(Type returnType, Class<? extends Number> clazz, String paramName, boolean isNestedType, boolean isArray) '''
223         «clazz.importedNumber» _constraint = «clazz.importedNumber».valueOf(«paramName»«IF isNestedType».getValue()«ENDIF».length«IF !isArray»()«ENDIF»);
224     '''
225
226     def printRangeConstraint(Type returnType, Class<? extends Number> clazz, String paramName, boolean isNestedType) '''
227         «IF clazz.canonicalName.equals(BigDecimal.canonicalName)»
228             «clazz.importedNumber» _constraint = new «clazz.importedNumber»(«paramName»«IF isNestedType».getValue()«ENDIF».toString());
229         «ELSE»
230             «IF isNestedType»
231                 «val propReturnType = findProperty(returnType as GeneratedTransferObject, "value").returnType»
232                 «IF propReturnType.fullyQualifiedName.equals(BigInteger.canonicalName)»
233                     «clazz.importedNumber» _constraint = «paramName».getValue();
234                 «ELSE»
235                     «clazz.importedNumber» _constraint = «clazz.importedNumber».valueOf(«paramName».getValue());
236                 «ENDIF»
237             «ELSE»
238                 «IF returnType.fullyQualifiedName.equals(BigInteger.canonicalName)»
239                     «clazz.importedNumber» _constraint = «paramName»;
240                 «ELSE»
241                     «clazz.importedNumber» _constraint = «clazz.importedNumber».valueOf(«paramName»);
242                 «ENDIF»
243             «ENDIF»
244         «ENDIF»
245     '''
246
247     def protected generateToString(Collection<GeneratedProperty> properties) '''
248         «IF !properties.empty»
249             @Override
250             public «String.importedName» toString() {
251                 «StringBuilder.importedName» builder = new «StringBuilder.importedName»("«type.name» [");
252                 boolean first = true;
253
254                 «FOR property : properties»
255                     if («property.fieldName» != null) {
256                         if (first) {
257                             first = false;
258                         } else {
259                             builder.append(", ");
260                         }
261                         builder.append("«property.fieldName»=");
262                         «IF property.returnType.name.contains("[")»
263                             builder.append(«Arrays.importedName».toString(«property.fieldName»));
264                         «ELSE»
265                             builder.append(«property.fieldName»);
266                         «ENDIF»
267                      }
268                 «ENDFOR»
269                 return builder.append(']').toString();
270             }
271         «ENDIF»
272     '''
273
274     def GeneratedProperty getPropByName(GeneratedType gt, String name) {
275         for (GeneratedProperty prop : gt.properties) {
276             if (prop.name.equals(name)) {
277                 return prop;
278             }
279         }
280         return null;
281     }
282
283     def getRestrictions(Type type) {
284         var Restrictions restrictions = null
285         if (type instanceof ConcreteType) {
286             restrictions = (type as ConcreteType).restrictions
287         } else if (type instanceof GeneratedTransferObject) {
288             restrictions = (type as GeneratedTransferObject).restrictions
289         }
290         return restrictions
291     }
292
293     def boolean isArrayType(GeneratedTransferObject type) {
294         var isArray = false
295         val GeneratedProperty value = findProperty(type, "value")
296         if (value != null && value.returnType.name.contains("[")) {
297             isArray = true
298         }
299         return isArray
300     }
301
302     def String toQuote(Object obj) {
303         return "\"" + obj.toString + "\"";
304     }
305
306     /**
307      * Template method which generates method parameters with their types from <code>parameters</code>.
308      * 
309      * @param parameters
310      * list of parameter instances which are transformed to the method parameters
311      * @return string with the list of the method parameters with their types in JAVA format
312      */
313     def protected generateParameters(List<MethodSignature.Parameter> parameters) '''«
314         IF !parameters.empty»«
315             FOR parameter : parameters SEPARATOR ", "»«
316                 parameter.type.importedName» «parameter.name»«
317             ENDFOR»«
318         ENDIF
319     »'''
320
321     def protected generateLengthMethod(String methodName, Type type, String className, String varName) '''
322         «val Restrictions restrictions = type.restrictions»
323         «IF restrictions != null && !(restrictions.lengthConstraints.empty)»
324             «val numberClass = restrictions.lengthConstraints.iterator.next.min.class»
325             public static «List.importedName»<«Range.importedName»<«numberClass.importedNumber»>> «methodName»() {
326                 «IF numberClass.equals(typeof(BigDecimal))»
327                     «lengthMethodBody(restrictions, numberClass, className, varName)»
328                 «ELSE»
329                     «lengthMethodBody(restrictions, typeof(BigInteger), className, varName)»
330                 «ENDIF»
331             }
332         «ENDIF»
333     '''
334
335     def private lengthMethodBody(Restrictions restrictions, Class<? extends Number> numberClass, String className, String varName) '''
336         if («varName» == null) {
337             synchronized («className».class) {
338                 if («varName» == null) {
339                     «ImmutableList.importedName».Builder<«Range.importedName»<«numberClass.importedName»>> builder = «ImmutableList.importedName».builder();
340                     «FOR r : restrictions.lengthConstraints»
341                         builder.add(«Range.importedName».closed(«numericValue(numberClass, r.min)», «numericValue(numberClass, r.max)»));
342                     «ENDFOR»
343                     «varName» = builder.build();
344                 }
345             }
346         }
347         return «varName»;
348     '''
349
350     def protected generateRangeMethod(String methodName, Type type, String className, String varName) '''
351         «val Restrictions restrictions = type.restrictions»
352         «IF restrictions != null && !(restrictions.rangeConstraints.empty)»
353             «val numberClass = restrictions.rangeConstraints.iterator.next.min.class»
354             public static «List.importedName»<«Range.importedName»<«numberClass.importedNumber»>> «methodName»() {
355                 «IF numberClass.equals(typeof(BigDecimal))»
356                     «rangeMethodBody(restrictions, numberClass, className, varName)»
357                 «ELSE»
358                     «rangeMethodBody(restrictions, typeof(BigInteger), className, varName)»
359                 «ENDIF»
360             }
361         «ENDIF»
362     '''
363
364     def private rangeMethodBody(Restrictions restrictions, Class<? extends Number> numberClass, String className, String varName) '''
365         if («varName» == null) {
366             synchronized («className».class) {
367                 if («varName» == null) {
368                     «ImmutableList.importedName».Builder<«Range.importedName»<«numberClass.importedName»>> builder = «ImmutableList.importedName».builder();
369                     «FOR r : restrictions.rangeConstraints»
370                         builder.add(«Range.importedName».closed(«numericValue(numberClass, r.min)», «numericValue(numberClass, r.max)»));
371                     «ENDFOR»
372                     «varName» = builder.build();
373                 }
374             }
375         }
376         return «varName»;
377     '''
378
379     def protected String importedNumber(Class<? extends Number> clazz) {
380         if (clazz.equals(typeof(BigDecimal))) {
381             return BigDecimal.importedName
382         }
383         return BigInteger.importedName
384     }
385
386     def private String numericValue(Class<? extends Number> clazz, Object numberValue) {
387         val number = clazz.importedName;
388         val value = numberValue.toString
389         if (clazz.equals(typeof(BigInteger)) || clazz.equals(typeof(BigDecimal))) {
390             if (value.equals("0")) {
391                 return number + ".ZERO"
392             } else if (value.equals("1")) {
393                 return number + ".ONE"
394             } else if (value.equals("10")) {
395                 return number + ".TEN"
396             } else {
397                 try {
398                     val Long longVal = Long.valueOf(value)
399                     return number + ".valueOf(" + longVal + "L)"
400                 } catch (NumberFormatException e) {
401                     if (clazz.equals(typeof(BigDecimal))) {
402                         try {
403                             val Double doubleVal = Double.valueOf(value);
404                             return number + ".valueOf(" + doubleVal + ")"
405                         } catch (NumberFormatException e2) {
406                         }
407                     }
408                 }
409             }
410         }
411         return "new " + number + "(\"" + value + "\")"
412     }
413
414     def private GeneratedProperty findProperty(GeneratedTransferObject gto, String name) {
415         val props = gto.properties
416         for (prop : props) {
417             if (prop.name.equals(name)) {
418                 return prop
419             }
420         }
421         val GeneratedTransferObject parent = gto.superType
422         if (parent != null) {
423             return findProperty(parent, name)
424         }
425         return null
426     }
427
428 }