Merge "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, 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, String paramName, boolean isNestedType) '''
203         if («paramName» != null) {
204             «printRangeConstraint(returnType, paramName, isNestedType)»
205             boolean isValidRange = false;
206             for («Range.importedName»<«returnType.importedNumber»> r : «IF isNestedType»«returnType.importedName».«ENDIF»range()) {
207                 if (r.contains(_constraint)) {
208                     isValidRange = true;
209                 }
210             }
211             if (!isValidRange) {
212                 throw new IllegalArgumentException(String.format("Invalid range: %s, expected: %s.", «paramName», «IF isNestedType»«returnType.importedName».«ENDIF»range()));
213             }
214         }
215     '''
216
217     /**
218      * Print length constraint.
219      * This should always be a BigInteger (only string and binary can have length restriction)
220      */
221     def printLengthConstraint(Type returnType, Class<? extends Number> clazz, String paramName, boolean isNestedType, boolean isArray) '''
222         «clazz.importedNumber» _constraint = «clazz.importedNumber».valueOf(«paramName»«IF isNestedType».getValue()«ENDIF».length«IF !isArray»()«ENDIF»);
223     '''
224
225     def printRangeConstraint(Type returnType, String paramName, boolean isNestedType) '''
226         «IF BigDecimal.canonicalName.equals(returnType.fullyQualifiedName)»
227             «BigDecimal.importedName» _constraint = new «BigDecimal.importedName»(«paramName»«IF isNestedType».getValue()«ENDIF».toString());
228         «ELSE»
229             «IF isNestedType»
230                 «val propReturnType = findProperty(returnType as GeneratedTransferObject, "value").returnType»
231                 «IF propReturnType.fullyQualifiedName.equals(BigInteger.canonicalName)»
232                     «BigInteger.importedName» _constraint = «paramName».getValue();
233                 «ELSE»
234                     «BigInteger.importedName» _constraint = «BigInteger.importedName».valueOf(«paramName».getValue());
235                 «ENDIF»
236             «ELSE»
237                 «IF returnType.fullyQualifiedName.equals(BigInteger.canonicalName)»
238                     «BigInteger.importedName» _constraint = «paramName»;
239                 «ELSE»
240                     «BigInteger.importedName» _constraint = «BigInteger.importedName».valueOf(«paramName»);
241                 «ENDIF»
242             «ENDIF»
243         «ENDIF»
244     '''
245
246     def protected generateToString(Collection<GeneratedProperty> properties) '''
247         «IF !properties.empty»
248             @Override
249             public «String.importedName» toString() {
250                 «StringBuilder.importedName» builder = new «StringBuilder.importedName»("«type.name» [");
251                 boolean first = true;
252
253                 «FOR property : properties»
254                     if («property.fieldName» != null) {
255                         if (first) {
256                             first = false;
257                         } else {
258                             builder.append(", ");
259                         }
260                         builder.append("«property.fieldName»=");
261                         «IF property.returnType.name.contains("[")»
262                             builder.append(«Arrays.importedName».toString(«property.fieldName»));
263                         «ELSE»
264                             builder.append(«property.fieldName»);
265                         «ENDIF»
266                      }
267                 «ENDFOR»
268                 return builder.append(']').toString();
269             }
270         «ENDIF»
271     '''
272
273     def getRestrictions(Type type) {
274         var Restrictions restrictions = null
275         if (type instanceof ConcreteType) {
276             restrictions = (type as ConcreteType).restrictions
277         } else if (type instanceof GeneratedTransferObject) {
278             restrictions = (type as GeneratedTransferObject).restrictions
279         }
280         return restrictions
281     }
282
283     def boolean isArrayType(GeneratedTransferObject type) {
284         var isArray = false
285         val GeneratedProperty value = findProperty(type, "value")
286         if (value != null && value.returnType.name.contains("[")) {
287             isArray = true
288         }
289         return isArray
290     }
291
292     def String toQuote(Object obj) {
293         return "\"" + obj.toString + "\"";
294     }
295
296     /**
297      * Template method which generates method parameters with their types from <code>parameters</code>.
298      * 
299      * @param parameters
300      * list of parameter instances which are transformed to the method parameters
301      * @return string with the list of the method parameters with their types in JAVA format
302      */
303     def protected generateParameters(List<MethodSignature.Parameter> parameters) '''«
304         IF !parameters.empty»«
305             FOR parameter : parameters SEPARATOR ", "»«
306                 parameter.type.importedName» «parameter.name»«
307             ENDFOR»«
308         ENDIF
309     »'''
310
311     def protected generateLengthMethod(String methodName, Type type, String className, String varName) '''
312         «val Restrictions restrictions = type.restrictions»
313         «IF restrictions != null && !(restrictions.lengthConstraints.empty)»
314             «val numberClass = restrictions.lengthConstraints.iterator.next.min.class»
315             public static «List.importedName»<«Range.importedName»<«numberClass.importedNumber»>> «methodName»() {
316                 «IF numberClass.equals(typeof(BigDecimal))»
317                     «lengthMethodBody(restrictions, numberClass, className, varName)»
318                 «ELSE»
319                     «lengthMethodBody(restrictions, typeof(BigInteger), className, varName)»
320                 «ENDIF»
321             }
322         «ENDIF»
323     '''
324
325     def private lengthMethodBody(Restrictions restrictions, Class<? extends Number> numberClass, String className, String varName) '''
326         if («varName» == null) {
327             synchronized («className».class) {
328                 if («varName» == null) {
329                     «ImmutableList.importedName».Builder<«Range.importedName»<«numberClass.importedName»>> builder = «ImmutableList.importedName».builder();
330                     «FOR r : restrictions.lengthConstraints»
331                         builder.add(«Range.importedName».closed(«numericValue(numberClass, r.min)», «numericValue(numberClass, r.max)»));
332                     «ENDFOR»
333                     «varName» = builder.build();
334                 }
335             }
336         }
337         return «varName»;
338     '''
339
340     def protected generateRangeMethod(String methodName, Restrictions restrictions, Type returnType, String className, String varName) '''
341         «IF restrictions != null && !(restrictions.rangeConstraints.empty)»
342             «val number = returnType.importedNumber»
343             public static «List.importedName»<«Range.importedName»<«number»>> «methodName»() {
344                 «IF returnType.fullyQualifiedName.equals(BigDecimal.canonicalName)»
345                     «rangeMethodBody(restrictions, BigDecimal, className, varName)»
346                 «ELSE»
347                     «rangeMethodBody(restrictions, BigInteger, className, varName)»
348                 «ENDIF»
349             }
350         «ENDIF»
351     '''
352
353     def protected generateRangeMethod(String methodName, Restrictions restrictions, String className, String varName, Iterable<GeneratedProperty> properties) '''
354         «IF restrictions != null && !(restrictions.rangeConstraints.empty)»
355             «val returnType = properties.iterator.next.returnType»
356             public static «List.importedName»<«Range.importedName»<«returnType.importedNumber»>> «methodName»() {
357                 «IF returnType.fullyQualifiedName.equals(BigDecimal.canonicalName)»
358                     «rangeMethodBody(restrictions, BigDecimal, className, varName)»
359                 «ELSE»
360                     «rangeMethodBody(restrictions, BigInteger, className, varName)»
361                 «ENDIF»
362             }
363         «ENDIF»
364     '''
365
366     def private rangeMethodBody(Restrictions restrictions, Class<? extends Number> numberClass, String className, String varName) '''
367         if («varName» == null) {
368             synchronized («className».class) {
369                 if («varName» == null) {
370                     «ImmutableList.importedName».Builder<«Range.importedName»<«numberClass.importedName»>> builder = «ImmutableList.importedName».builder();
371                     «FOR r : restrictions.rangeConstraints»
372                         builder.add(«Range.importedName».closed(«numericValue(numberClass, r.min)», «numericValue(numberClass, r.max)»));
373                     «ENDFOR»
374                     «varName» = builder.build();
375                 }
376             }
377         }
378         return «varName»;
379     '''
380
381     def protected String importedNumber(Class<? extends Number> clazz) {
382         if (clazz.equals(typeof(BigDecimal))) {
383             return BigDecimal.importedName
384         }
385         return BigInteger.importedName
386     }
387
388     def protected String importedNumber(Type clazz) {
389         if (clazz.fullyQualifiedName.equals(BigDecimal.canonicalName)) {
390             return BigDecimal.importedName
391         }
392         return BigInteger.importedName
393     }
394
395     def private String numericValue(Class<? extends Number> clazz, Object numberValue) {
396         val number = clazz.importedName;
397         val value = numberValue.toString
398         if (clazz.equals(typeof(BigInteger)) || clazz.equals(typeof(BigDecimal))) {
399             if (value.equals("0")) {
400                 return number + ".ZERO"
401             } else if (value.equals("1")) {
402                 return number + ".ONE"
403             } else if (value.equals("10")) {
404                 return number + ".TEN"
405             } else {
406                 try {
407                     val Long longVal = Long.valueOf(value)
408                     return number + ".valueOf(" + longVal + "L)"
409                 } catch (NumberFormatException e) {
410                     if (clazz.equals(typeof(BigDecimal))) {
411                         try {
412                             val Double doubleVal = Double.valueOf(value);
413                             return number + ".valueOf(" + doubleVal + ")"
414                         } catch (NumberFormatException e2) {
415                         }
416                     }
417                 }
418             }
419         }
420         return "new " + number + "(\"" + value + "\")"
421     }
422
423     def private GeneratedProperty findProperty(GeneratedTransferObject gto, String name) {
424         val props = gto.properties
425         for (prop : props) {
426             if (prop.name.equals(name)) {
427                 return prop
428             }
429         }
430         val GeneratedTransferObject parent = gto.superType
431         if (parent != null) {
432             return findProperty(parent, name)
433         }
434         return null
435     }
436
437 }