Merge "Bug 1372 - toString methods in generated classes"
[mdsal.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 com.google.common.collect.ImmutableList
11 import com.google.common.collect.Range
12 import java.math.BigDecimal
13 import java.math.BigInteger
14 import java.util.Arrays
15 import java.util.Collection
16 import java.util.HashMap
17 import java.util.List
18 import java.util.Map
19 import java.util.StringTokenizer
20 import org.opendaylight.yangtools.binding.generator.util.Types
21 import org.opendaylight.yangtools.sal.binding.model.api.ConcreteType
22 import org.opendaylight.yangtools.sal.binding.model.api.GeneratedProperty
23 import org.opendaylight.yangtools.sal.binding.model.api.GeneratedTransferObject
24 import org.opendaylight.yangtools.sal.binding.model.api.GeneratedType
25 import org.opendaylight.yangtools.sal.binding.model.api.MethodSignature
26 import org.opendaylight.yangtools.sal.binding.model.api.Restrictions
27 import org.opendaylight.yangtools.sal.binding.model.api.Type
28 import org.opendaylight.yangtools.yang.common.QName
29
30 abstract class BaseTemplate {
31     protected val GeneratedType type;
32     protected val Map<String, String> importMap;
33
34     private static final String NEW_LINE = '\n'
35
36     new(GeneratedType _type) {
37         if (_type == null) {
38             throw new IllegalArgumentException("Generated type reference cannot be NULL!")
39         }
40         this.type = _type;
41         this.importMap = new HashMap<String,String>()
42     }
43
44     def packageDefinition() '''package «type.packageName»;'''
45
46     protected def getFullyQualifiedName() {
47         return type.fullyQualifiedName
48     }
49
50     final public def generate() {
51         val _body = body()
52         '''
53             «packageDefinition»
54             «imports»
55
56             «_body»
57         '''.toString
58     }
59
60     protected def imports() ''' 
61         «IF !importMap.empty»
62             «FOR entry : importMap.entrySet»
63                 «IF entry.value != fullyQualifiedName»
64                     import «entry.value».«entry.key»;
65                 «ENDIF»
66             «ENDFOR»
67         «ENDIF»
68
69     '''
70
71     protected abstract def CharSequence body();
72
73     // Helper patterns
74     final protected def fieldName(GeneratedProperty property) '''_«property.name»'''
75
76     final protected def propertyNameFromGetter(MethodSignature getter) {
77         var int prefix;
78         if (getter.name.startsWith("is")) {
79             prefix = 2
80         } else if (getter.name.startsWith("get")) {
81             prefix = 3
82         } else {
83             throw new IllegalArgumentException("Not a getter")
84         }
85         return getter.name.substring(prefix).toFirstLower;
86     }
87
88     /**
89      * Template method which generates the getter method for <code>field</code>
90      * 
91      * @param field 
92      * generated property with data about field which is generated as the getter method
93      * @return string with the getter method source code in JAVA format 
94      */
95     final protected def getterMethod(GeneratedProperty field) {
96         '''
97             public «field.returnType.importedName» «field.getterMethodName»() {
98                 return «field.fieldName»;
99             }
100         '''
101     }
102
103     final protected def getterMethodName(GeneratedProperty field) {
104         val prefix = if(field.returnType.equals(Types.BOOLEAN)) "is" else "get"
105         return '''«prefix»«field.name.toFirstUpper»'''
106     }
107
108     /**
109      * Template method which generates the setter method for <code>field</code>
110      * 
111      * @param field 
112      * generated property with data about field which is generated as the setter method
113      * @return string with the setter method source code in JAVA format 
114      */
115     final protected def setterMethod(GeneratedProperty field) '''
116         «val returnType = field.returnType.importedName»
117         public «type.name» set«field.name.toFirstUpper»(«returnType» value) {
118             this.«field.fieldName» = value;
119             return this;
120         }
121     '''
122
123     final protected def importedName(Type intype) {
124         GeneratorUtil.putTypeIntoImports(type, intype, importMap);
125         GeneratorUtil.getExplicitType(type, intype, importMap)
126     }
127
128     final protected def importedName(Class<?> cls) {
129         importedName(Types.typeForClass(cls))
130     }
131
132     /**
133      * Template method which generates method parameters with their types from <code>parameters</code>.
134      * 
135      * @param parameters
136      * group of generated property instances which are transformed to the method parameters
137      * @return string with the list of the method parameters with their types in JAVA format
138      */
139     def final protected asArgumentsDeclaration(Iterable<GeneratedProperty> parameters) '''«IF !parameters.empty»«FOR parameter : parameters SEPARATOR ", "»«parameter.
140         returnType.importedName» «parameter.fieldName»«ENDFOR»«ENDIF»'''
141
142     /**
143      * Template method which generates sequence of the names of the class attributes from <code>parameters</code>.
144      * 
145      * @param parameters 
146      * group of generated property instances which are transformed to the sequence of parameter names
147      * @return string with the list of the parameter names of the <code>parameters</code> 
148      */
149     def final protected asArguments(Iterable<GeneratedProperty> parameters) '''«IF !parameters.empty»«FOR parameter : parameters SEPARATOR ", "»«parameter.
150         fieldName»«ENDFOR»«ENDIF»'''
151
152     /**
153      * Template method which generates JAVA comments.
154      * 
155      * @param comment string with the comment for whole JAVA class
156      * @return string with comment in JAVA format
157      */
158     def protected CharSequence asJavadoc(String comment) {
159         if(comment == null) return ''
160         var txt = comment
161         if (txt.contains("*/")) {
162             txt = txt.replace("*/", "&#42;&#47;")
163         }
164         txt = comment.trim
165         txt = formatToParagraph(txt)
166
167         return '''
168             «wrapToDocumentation(txt)»
169         '''
170     }
171
172     def String wrapToDocumentation(String text) {
173         val StringTokenizer tokenizer = new StringTokenizer(text, "\n", false)
174         val StringBuilder sb = new StringBuilder()
175
176         if(text.empty)
177             return ""
178
179         sb.append("/**")
180         sb.append(NEW_LINE)
181
182         while(tokenizer.hasMoreTokens) {
183             sb.append(" * ")
184             sb.append(tokenizer.nextToken)
185             sb.append(NEW_LINE)
186         }
187         sb.append(" */")
188
189         return sb.toString
190     }
191
192     def protected String formatDataForJavaDoc(GeneratedType type) {
193         val typeDescription = type.getDescription();
194
195         return '''
196             «IF !typeDescription.nullOrEmpty»
197             «typeDescription»
198             «ENDIF»
199         '''.toString
200     }
201
202     def asLink(String text) {
203         val StringBuilder sb = new StringBuilder()
204         var tempText = text
205         var char lastChar = ' '
206         var boolean badEnding = false
207
208         if(text.endsWith(".") || text.endsWith(":") || text.endsWith(",")) {
209             tempText = text.substring(0, text.length - 1)
210             lastChar = text.charAt(text.length - 1)
211             badEnding = true
212         }
213         sb.append("<a href = \"")
214         sb.append(tempText)
215         sb.append("\">")
216         sb.append(tempText)
217         sb.append("</a>")
218
219         if(badEnding)
220             sb.append(lastChar)
221
222         return sb.toString
223     }
224
225     protected def formatToParagraph(String text) {
226         if(text == null || text.isEmpty)
227             return text
228
229         var formattedText = text
230         val StringBuilder sb = new StringBuilder();
231         var StringBuilder lineBuilder = new StringBuilder();
232         var boolean isFirstElementOnNewLineEmptyChar = false;
233
234         formattedText = formattedText.replace("*/", "&#42;&#47;")
235         formattedText = formattedText.replace(NEW_LINE, "")
236         formattedText = formattedText.replace("\t", "")
237         formattedText = formattedText.replaceAll(" +", " ");
238
239         val StringTokenizer tokenizer = new StringTokenizer(formattedText, " ", true);
240
241         while(tokenizer.hasMoreElements) {
242             val nextElement = tokenizer.nextElement.toString
243
244             if(lineBuilder.length + nextElement.length > 80) {
245                 if (lineBuilder.charAt(lineBuilder.length - 1) == ' ') {
246                     lineBuilder.setLength(0)
247                     lineBuilder.append(lineBuilder.substring(0, lineBuilder.length - 1))
248                 }
249                 if (lineBuilder.charAt(0) == ' ') {
250                     lineBuilder.setLength(0)
251                     lineBuilder.append(lineBuilder.substring(1))
252                 }
253
254                 sb.append(lineBuilder);
255                 lineBuilder.setLength(0)
256                 sb.append(NEW_LINE)
257
258                 if(nextElement.toString == ' ')
259                     isFirstElementOnNewLineEmptyChar = !isFirstElementOnNewLineEmptyChar;
260             }
261
262             if(isFirstElementOnNewLineEmptyChar) {
263                 isFirstElementOnNewLineEmptyChar = !isFirstElementOnNewLineEmptyChar
264             }
265
266             else {
267                 lineBuilder.append(nextElement)
268             }
269         }
270         sb.append(lineBuilder)
271         sb.append(NEW_LINE)
272
273         return sb.toString
274     }
275
276     def isDocumentationParametersNullOrEmtpy(GeneratedType type) {
277         val boolean isTypeDescriptionNullOrEmpty = type.description.nullOrEmpty
278         val boolean isTypeReferenceNullOrEmpty = type.reference.nullOrEmpty
279         val boolean isTypeModuleNameNullOrEmpty = type.moduleName.nullOrEmpty
280         val boolean isTypeSchemaPathNullOrEmpty = type.schemaPath.nullOrEmpty
281
282         if (isTypeDescriptionNullOrEmpty && isTypeReferenceNullOrEmpty && isTypeModuleNameNullOrEmpty
283             && isTypeSchemaPathNullOrEmpty) {
284             return true
285         }
286         return false
287     }
288
289     def generateRestrictions(Type type, String paramName, Type returnType) '''
290         «val restrictions = type.getRestrictions»
291         «IF restrictions !== null»
292             «val boolean isNestedType = !(returnType instanceof ConcreteType)»
293             «IF !restrictions.lengthConstraints.empty»
294                 «generateLengthRestriction(returnType, restrictions, paramName, isNestedType)»
295             «ENDIF»
296             «IF !restrictions.rangeConstraints.empty»
297                 «generateRangeRestriction(returnType, paramName, isNestedType)»
298             «ENDIF»
299         «ENDIF»
300     '''
301
302     def private generateLengthRestriction(Type returnType, Restrictions restrictions, String paramName, boolean isNestedType) '''
303         «val clazz = restrictions.lengthConstraints.iterator.next.min.class»
304         if («paramName» != null) {
305             «printLengthConstraint(returnType, clazz, paramName, isNestedType, returnType.name.contains("["))»
306             boolean isValidLength = false;
307             for («Range.importedName»<«clazz.importedNumber»> r : «IF isNestedType»«returnType.importedName».«ENDIF»length()) {
308                 if (r.contains(_constraint)) {
309                     isValidLength = true;
310                 }
311             }
312             if (!isValidLength) {
313                 throw new IllegalArgumentException(String.format("Invalid length: %s, expected: %s.", «paramName», «IF isNestedType»«returnType.importedName».«ENDIF»length()));
314             }
315         }
316     '''
317
318     def private generateRangeRestriction(Type returnType, String paramName, boolean isNestedType) '''
319         if («paramName» != null) {
320             «printRangeConstraint(returnType, paramName, isNestedType)»
321             boolean isValidRange = false;
322             for («Range.importedName»<«returnType.importedNumber»> r : «IF isNestedType»«returnType.importedName».«ENDIF»range()) {
323                 if (r.contains(_constraint)) {
324                     isValidRange = true;
325                 }
326             }
327             if (!isValidRange) {
328                 throw new IllegalArgumentException(String.format("Invalid range: %s, expected: %s.", «paramName», «IF isNestedType»«returnType.importedName».«ENDIF»range()));
329             }
330         }
331     '''
332
333     /**
334      * Print length constraint.
335      * This should always be a BigInteger (only string and binary can have length restriction)
336      */
337     def printLengthConstraint(Type returnType, Class<? extends Number> clazz, String paramName, boolean isNestedType, boolean isArray) '''
338         «clazz.importedNumber» _constraint = «clazz.importedNumber».valueOf(«paramName»«IF isNestedType».getValue()«ENDIF».length«IF !isArray»()«ENDIF»);
339     '''
340
341     def printRangeConstraint(Type returnType, String paramName, boolean isNestedType) '''
342         «IF BigDecimal.canonicalName.equals(returnType.fullyQualifiedName)»
343             «BigDecimal.importedName» _constraint = new «BigDecimal.importedName»(«paramName»«IF isNestedType».getValue()«ENDIF».toString());
344         «ELSE»
345             «IF isNestedType»
346                 «val propReturnType = findProperty(returnType as GeneratedTransferObject, "value").returnType»
347                 «IF propReturnType.fullyQualifiedName.equals(BigInteger.canonicalName)»
348                     «BigInteger.importedName» _constraint = «paramName».getValue();
349                 «ELSE»
350                     «BigInteger.importedName» _constraint = «BigInteger.importedName».valueOf(«paramName».getValue());
351                 «ENDIF»
352             «ELSE»
353                 «IF returnType.fullyQualifiedName.equals(BigInteger.canonicalName)»
354                     «BigInteger.importedName» _constraint = «paramName»;
355                 «ELSE»
356                     «BigInteger.importedName» _constraint = «BigInteger.importedName».valueOf(«paramName»);
357                 «ENDIF»
358             «ENDIF»
359         «ENDIF»
360     '''
361
362     def protected generateToString(Collection<GeneratedProperty> properties) '''
363         «IF !properties.empty»
364             @Override
365             public «String.importedName» toString() {
366                 «StringBuilder.importedName» builder = new «StringBuilder.importedName»(«type.importedName».class.getSimpleName()).append(" [");
367                 boolean first = true;
368
369                 «FOR property : properties»
370                     if («property.fieldName» != null) {
371                         if (first) {
372                             first = false;
373                         } else {
374                             builder.append(", ");
375                         }
376                         builder.append("«property.fieldName»=");
377                         «IF property.returnType.name.contains("[")»
378                             builder.append(«Arrays.importedName».toString(«property.fieldName»));
379                         «ELSE»
380                             builder.append(«property.fieldName»);
381                         «ENDIF»
382                      }
383                 «ENDFOR»
384                 return builder.append(']').toString();
385             }
386         «ENDIF»
387     '''
388
389     def getRestrictions(Type type) {
390         var Restrictions restrictions = null
391         if (type instanceof ConcreteType) {
392             restrictions = (type as ConcreteType).restrictions
393         } else if (type instanceof GeneratedTransferObject) {
394             restrictions = (type as GeneratedTransferObject).restrictions
395         }
396         return restrictions
397     }
398
399     def boolean isArrayType(GeneratedTransferObject type) {
400         var isArray = false
401         val GeneratedProperty value = findProperty(type, "value")
402         if (value != null && value.returnType.name.contains("[")) {
403             isArray = true
404         }
405         return isArray
406     }
407
408     def String toQuote(Object obj) {
409         return "\"" + obj.toString + "\"";
410     }
411
412     /**
413      * Template method which generates method parameters with their types from <code>parameters</code>.
414      * 
415      * @param parameters
416      * list of parameter instances which are transformed to the method parameters
417      * @return string with the list of the method parameters with their types in JAVA format
418      */
419     def protected generateParameters(List<MethodSignature.Parameter> parameters) '''«
420         IF !parameters.empty»«
421             FOR parameter : parameters SEPARATOR ", "»«
422                 parameter.type.importedName» «parameter.name»«
423             ENDFOR»«
424         ENDIF
425     »'''
426
427     def protected generateLengthMethod(String methodName, Type type, String className, String varName) '''
428         «val Restrictions restrictions = type.restrictions»
429         «IF restrictions != null && !(restrictions.lengthConstraints.empty)»
430             «val numberClass = restrictions.lengthConstraints.iterator.next.min.class»
431             public static «List.importedName»<«Range.importedName»<«numberClass.importedNumber»>> «methodName»() {
432                 «IF numberClass.equals(typeof(BigDecimal))»
433                     «lengthBody(restrictions, numberClass, className, varName)»
434                 «ELSE»
435                     «lengthBody(restrictions, typeof(BigInteger), className, varName)»
436                 «ENDIF»
437             }
438         «ENDIF»
439     '''
440
441     def private lengthBody(Restrictions restrictions, Class<? extends Number> numberClass, String className, String varName) '''
442         if («varName» == null) {
443             synchronized («className».class) {
444                 if («varName» == null) {
445                     «ImmutableList.importedName».Builder<«Range.importedName»<«numberClass.importedName»>> builder = «ImmutableList.importedName».builder();
446                     «FOR r : restrictions.lengthConstraints»
447                         builder.add(«Range.importedName».closed(«numericValue(numberClass, r.min)», «numericValue(numberClass, r.max)»));
448                     «ENDFOR»
449                     «varName» = builder.build();
450                 }
451             }
452         }
453         return «varName»;
454     '''
455
456     def protected generateRangeMethod(String methodName, Restrictions restrictions, Type returnType, String className, String varName) '''
457         «IF restrictions != null && !(restrictions.rangeConstraints.empty)»
458             «val number = returnType.importedNumber»
459             public static «List.importedName»<«Range.importedName»<«number»>> «methodName»() {
460                 «IF returnType.fullyQualifiedName.equals(BigDecimal.canonicalName)»
461                     «rangeBody(restrictions, BigDecimal, className, varName)»
462                 «ELSE»
463                     «rangeBody(restrictions, BigInteger, className, varName)»
464                 «ENDIF»
465             }
466         «ENDIF»
467     '''
468
469     def protected generateRangeMethod(String methodName, Restrictions restrictions, String className, String varName, Iterable<GeneratedProperty> properties) '''
470         «IF restrictions != null && !(restrictions.rangeConstraints.empty)»
471             «val returnType = properties.iterator.next.returnType»
472             public static «List.importedName»<«Range.importedName»<«returnType.importedNumber»>> «methodName»() {
473                 «IF returnType.fullyQualifiedName.equals(BigDecimal.canonicalName)»
474                     «rangeBody(restrictions, BigDecimal, className, varName)»
475                 «ELSE»
476                     «rangeBody(restrictions, BigInteger, className, varName)»
477                 «ENDIF»
478             }
479         «ENDIF»
480     '''
481
482     def private rangeBody(Restrictions restrictions, Class<? extends Number> numberClass, String className, String varName) '''
483         if («varName» == null) {
484             synchronized («className».class) {
485                 if («varName» == null) {
486                     «ImmutableList.importedName».Builder<«Range.importedName»<«numberClass.importedName»>> builder = «ImmutableList.importedName».builder();
487                     «FOR r : restrictions.rangeConstraints»
488                         builder.add(«Range.importedName».closed(«numericValue(numberClass, r.min)», «numericValue(numberClass, r.max)»));
489                     «ENDFOR»
490                     «varName» = builder.build();
491                 }
492             }
493         }
494         return «varName»;
495     '''
496
497     def protected String importedNumber(Class<? extends Number> clazz) {
498         if (clazz.equals(typeof(BigDecimal))) {
499             return BigDecimal.importedName
500         }
501         return BigInteger.importedName
502     }
503
504     def protected String importedNumber(Type clazz) {
505         if (clazz.fullyQualifiedName.equals(BigDecimal.canonicalName)) {
506             return BigDecimal.importedName
507         }
508         return BigInteger.importedName
509     }
510
511     def protected String numericValue(Class<? extends Number> clazz, Object numberValue) {
512         val number = clazz.importedName;
513         val value = numberValue.toString
514         if (clazz.equals(typeof(BigInteger)) || clazz.equals(typeof(BigDecimal))) {
515             if (value.equals("0")) {
516                 return number + ".ZERO"
517             } else if (value.equals("1")) {
518                 return number + ".ONE"
519             } else if (value.equals("10")) {
520                 return number + ".TEN"
521             } else {
522                 try {
523                     val Long longVal = Long.valueOf(value)
524                     return number + ".valueOf(" + longVal + "L)"
525                 } catch (NumberFormatException e) {
526                     if (clazz.equals(typeof(BigDecimal))) {
527                         try {
528                             val Double doubleVal = Double.valueOf(value);
529                             return number + ".valueOf(" + doubleVal + ")"
530                         } catch (NumberFormatException e2) {
531                         }
532                     }
533                 }
534             }
535         }
536         return "new " + number + "(\"" + value + "\")"
537     }
538
539     def private GeneratedProperty findProperty(GeneratedTransferObject gto, String name) {
540         val props = gto.properties
541         for (prop : props) {
542             if (prop.name.equals(name)) {
543                 return prop
544             }
545         }
546         val GeneratedTransferObject parent = gto.superType
547         if (parent != null) {
548             return findProperty(parent, name)
549         }
550         return null
551     }
552
553 }