Merge "BUG-865: get rid of InstanceIdentifier-related warnings"
[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 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.description
194         val typeReference = type.reference
195         val typeModuleName = type.moduleName
196         val typeSchemaPath = type.schemaPath
197
198         return '''
199             «IF !type.isDocumentationParametersNullOrEmtpy»
200                «IF typeDescription != null && !typeDescription.empty»
201                 «formatToParagraph(typeDescription)»
202                «ENDIF»
203                «IF typeReference != null && !typeReference.empty»
204                 Reference:
205                     «formatReference(typeReference)»
206                «ENDIF»
207                «IF typeModuleName != null && !typeModuleName.empty»
208                 Module name:
209                     «typeModuleName»
210                «ENDIF»
211                «IF typeSchemaPath != null && !typeSchemaPath.empty»
212                 Schema path:
213                     «formatPath(typeSchemaPath)»
214                «ENDIF»
215             «ENDIF»
216         '''.toString
217     }
218
219     def formatPath(Iterable<QName> schemaPath) {
220         var currentElement = schemaPath.head
221         val StringBuilder sb = new StringBuilder()
222         sb.append('[')
223         sb.append(currentElement)
224
225         for(pathElement : schemaPath) {
226             if(!currentElement.namespace.equals(pathElement.namespace)) {
227                 currentElement = pathElement
228                 sb.append('/')
229                 sb.append(pathElement)
230             }
231             else {
232                 sb.append('/')
233                 sb.append(pathElement.localName)
234             }
235         }
236         sb.append(']')
237         return sb.toString
238     }
239
240     def formatReference(String reference) {
241         if(reference == null || reference.isEmpty)
242             return reference
243
244         val StringTokenizer tokenizer = new StringTokenizer(reference, " ", true)
245         val StringBuilder sb = new StringBuilder();
246
247         while(tokenizer.hasMoreTokens) {
248             var String oneElement = tokenizer.nextToken
249             if (oneElement.contains("http://")) {
250                 oneElement = asLink(oneElement)
251             }
252             sb.append(oneElement)
253         }
254         return sb.toString
255     }
256
257     def asLink(String text) {
258         val StringBuilder sb = new StringBuilder()
259         var tempText = text
260         var char lastChar = ' '
261         var boolean badEnding = false
262
263         if(text.endsWith(".") || text.endsWith(":") || text.endsWith(",")) {
264             tempText = text.substring(0, text.length - 1)
265             lastChar = text.charAt(text.length - 1)
266             badEnding = true
267         }
268         sb.append("<a href = \"")
269         sb.append(tempText)
270         sb.append("\">")
271         sb.append(tempText)
272         sb.append("</a>")
273
274         if(badEnding)
275             sb.append(lastChar)
276
277         return sb.toString
278     }
279
280     protected def formatToParagraph(String text) {
281         if(text == null || text.isEmpty)
282             return text
283
284         var formattedText = text
285         val StringBuilder sb = new StringBuilder();
286         var StringBuilder lineBuilder = new StringBuilder();
287         var boolean isFirstElementOnNewLineEmptyChar = false;
288
289         formattedText = formattedText.replace("*/", "&#42;&#47;")
290         formattedText = formattedText.replace(NEW_LINE, "")
291         formattedText = formattedText.replace("\t", "")
292         formattedText = formattedText.replaceAll(" +", " ");
293
294         val StringTokenizer tokenizer = new StringTokenizer(formattedText, " ", true);
295
296         while(tokenizer.hasMoreElements) {
297             val nextElement = tokenizer.nextElement.toString
298
299             if(lineBuilder.length + nextElement.length > 80) {
300                 if (lineBuilder.charAt(lineBuilder.length - 1) == ' ') {
301                     lineBuilder.setLength(0)
302                     lineBuilder.append(lineBuilder.substring(0, lineBuilder.length - 1))
303                 }
304                 if (lineBuilder.charAt(0) == ' ') {
305                     lineBuilder.setLength(0)
306                     lineBuilder.append(lineBuilder.substring(1))
307                 }
308
309                 sb.append(lineBuilder);
310                 lineBuilder.setLength(0)
311                 sb.append(NEW_LINE)
312
313                 if(nextElement.toString == ' ')
314                     isFirstElementOnNewLineEmptyChar = !isFirstElementOnNewLineEmptyChar;
315             }
316
317             if(isFirstElementOnNewLineEmptyChar) {
318                 isFirstElementOnNewLineEmptyChar = !isFirstElementOnNewLineEmptyChar
319             }
320
321             else {
322                 lineBuilder.append(nextElement)
323             }
324         }
325         sb.append(lineBuilder)
326         sb.append(NEW_LINE)
327
328         return sb.toString
329     }
330
331     def isDocumentationParametersNullOrEmtpy(GeneratedType type) {
332         var boolean isNullOrEmpty = true
333         val String typeDescription = type.description
334         val String typeReference = type.reference
335         val String typeModuleName = type.moduleName
336         val Iterable<QName> typeSchemaPath = type.schemaPath
337
338         if(typeDescription != null && !typeDescription.empty) {
339             isNullOrEmpty = false
340             return isNullOrEmpty
341         }
342         if(typeReference != null && !typeReference.empty) {
343             isNullOrEmpty = false
344             return isNullOrEmpty
345         }
346         if(typeModuleName != null && !typeModuleName.empty) {
347             isNullOrEmpty = false
348             return isNullOrEmpty
349         }
350         if(typeSchemaPath != null && !typeSchemaPath.empty) {
351             isNullOrEmpty = false
352             return isNullOrEmpty
353         }
354         return isNullOrEmpty
355     }
356
357     def generateRestrictions(Type type, String paramName, Type returnType) '''
358         «val restrictions = type.getRestrictions»
359         «IF restrictions !== null»
360             «val boolean isNestedType = !(returnType instanceof ConcreteType)»
361             «IF !restrictions.lengthConstraints.empty»
362                 «generateLengthRestriction(returnType, restrictions, paramName, isNestedType)»
363             «ENDIF»
364             «IF !restrictions.rangeConstraints.empty»
365                 «generateRangeRestriction(returnType, paramName, isNestedType)»
366             «ENDIF»
367         «ENDIF»
368     '''
369
370     def private generateLengthRestriction(Type returnType, Restrictions restrictions, String paramName, boolean isNestedType) '''
371         «val clazz = restrictions.lengthConstraints.iterator.next.min.class»
372         if («paramName» != null) {
373             «printLengthConstraint(returnType, clazz, paramName, isNestedType, returnType.name.contains("["))»
374             boolean isValidLength = false;
375             for («Range.importedName»<«clazz.importedNumber»> r : «IF isNestedType»«returnType.importedName».«ENDIF»length()) {
376                 if (r.contains(_constraint)) {
377                     isValidLength = true;
378                 }
379             }
380             if (!isValidLength) {
381                 throw new IllegalArgumentException(String.format("Invalid length: %s, expected: %s.", «paramName», «IF isNestedType»«returnType.importedName».«ENDIF»length()));
382             }
383         }
384     '''
385
386     def private generateRangeRestriction(Type returnType, String paramName, boolean isNestedType) '''
387         if («paramName» != null) {
388             «printRangeConstraint(returnType, paramName, isNestedType)»
389             boolean isValidRange = false;
390             for («Range.importedName»<«returnType.importedNumber»> r : «IF isNestedType»«returnType.importedName».«ENDIF»range()) {
391                 if (r.contains(_constraint)) {
392                     isValidRange = true;
393                 }
394             }
395             if (!isValidRange) {
396                 throw new IllegalArgumentException(String.format("Invalid range: %s, expected: %s.", «paramName», «IF isNestedType»«returnType.importedName».«ENDIF»range()));
397             }
398         }
399     '''
400
401     /**
402      * Print length constraint.
403      * This should always be a BigInteger (only string and binary can have length restriction)
404      */
405     def printLengthConstraint(Type returnType, Class<? extends Number> clazz, String paramName, boolean isNestedType, boolean isArray) '''
406         «clazz.importedNumber» _constraint = «clazz.importedNumber».valueOf(«paramName»«IF isNestedType».getValue()«ENDIF».length«IF !isArray»()«ENDIF»);
407     '''
408
409     def printRangeConstraint(Type returnType, String paramName, boolean isNestedType) '''
410         «IF BigDecimal.canonicalName.equals(returnType.fullyQualifiedName)»
411             «BigDecimal.importedName» _constraint = new «BigDecimal.importedName»(«paramName»«IF isNestedType».getValue()«ENDIF».toString());
412         «ELSE»
413             «IF isNestedType»
414                 «val propReturnType = findProperty(returnType as GeneratedTransferObject, "value").returnType»
415                 «IF propReturnType.fullyQualifiedName.equals(BigInteger.canonicalName)»
416                     «BigInteger.importedName» _constraint = «paramName».getValue();
417                 «ELSE»
418                     «BigInteger.importedName» _constraint = «BigInteger.importedName».valueOf(«paramName».getValue());
419                 «ENDIF»
420             «ELSE»
421                 «IF returnType.fullyQualifiedName.equals(BigInteger.canonicalName)»
422                     «BigInteger.importedName» _constraint = «paramName»;
423                 «ELSE»
424                     «BigInteger.importedName» _constraint = «BigInteger.importedName».valueOf(«paramName»);
425                 «ENDIF»
426             «ENDIF»
427         «ENDIF»
428     '''
429
430     def protected generateToString(Collection<GeneratedProperty> properties) '''
431         «IF !properties.empty»
432             @Override
433             public «String.importedName» toString() {
434                 «StringBuilder.importedName» builder = new «StringBuilder.importedName»("«type.name» [");
435                 boolean first = true;
436
437                 «FOR property : properties»
438                     if («property.fieldName» != null) {
439                         if (first) {
440                             first = false;
441                         } else {
442                             builder.append(", ");
443                         }
444                         builder.append("«property.fieldName»=");
445                         «IF property.returnType.name.contains("[")»
446                             builder.append(«Arrays.importedName».toString(«property.fieldName»));
447                         «ELSE»
448                             builder.append(«property.fieldName»);
449                         «ENDIF»
450                      }
451                 «ENDFOR»
452                 return builder.append(']').toString();
453             }
454         «ENDIF»
455     '''
456
457     def getRestrictions(Type type) {
458         var Restrictions restrictions = null
459         if (type instanceof ConcreteType) {
460             restrictions = (type as ConcreteType).restrictions
461         } else if (type instanceof GeneratedTransferObject) {
462             restrictions = (type as GeneratedTransferObject).restrictions
463         }
464         return restrictions
465     }
466
467     def boolean isArrayType(GeneratedTransferObject type) {
468         var isArray = false
469         val GeneratedProperty value = findProperty(type, "value")
470         if (value != null && value.returnType.name.contains("[")) {
471             isArray = true
472         }
473         return isArray
474     }
475
476     def String toQuote(Object obj) {
477         return "\"" + obj.toString + "\"";
478     }
479
480     /**
481      * Template method which generates method parameters with their types from <code>parameters</code>.
482      * 
483      * @param parameters
484      * list of parameter instances which are transformed to the method parameters
485      * @return string with the list of the method parameters with their types in JAVA format
486      */
487     def protected generateParameters(List<MethodSignature.Parameter> parameters) '''«
488         IF !parameters.empty»«
489             FOR parameter : parameters SEPARATOR ", "»«
490                 parameter.type.importedName» «parameter.name»«
491             ENDFOR»«
492         ENDIF
493     »'''
494
495     def protected generateLengthMethod(String methodName, Type type, String className, String varName) '''
496         «val Restrictions restrictions = type.restrictions»
497         «IF restrictions != null && !(restrictions.lengthConstraints.empty)»
498             «val numberClass = restrictions.lengthConstraints.iterator.next.min.class»
499             public static «List.importedName»<«Range.importedName»<«numberClass.importedNumber»>> «methodName»() {
500                 «IF numberClass.equals(typeof(BigDecimal))»
501                     «lengthMethodBody(restrictions, numberClass, className, varName)»
502                 «ELSE»
503                     «lengthMethodBody(restrictions, typeof(BigInteger), className, varName)»
504                 «ENDIF»
505             }
506         «ENDIF»
507     '''
508
509     def private lengthMethodBody(Restrictions restrictions, Class<? extends Number> numberClass, String className, String varName) '''
510         if («varName» == null) {
511             synchronized («className».class) {
512                 if («varName» == null) {
513                     «ImmutableList.importedName».Builder<«Range.importedName»<«numberClass.importedName»>> builder = «ImmutableList.importedName».builder();
514                     «FOR r : restrictions.lengthConstraints»
515                         builder.add(«Range.importedName».closed(«numericValue(numberClass, r.min)», «numericValue(numberClass, r.max)»));
516                     «ENDFOR»
517                     «varName» = builder.build();
518                 }
519             }
520         }
521         return «varName»;
522     '''
523
524     def protected generateRangeMethod(String methodName, Restrictions restrictions, Type returnType, String className, String varName) '''
525         «IF restrictions != null && !(restrictions.rangeConstraints.empty)»
526             «val number = returnType.importedNumber»
527             public static «List.importedName»<«Range.importedName»<«number»>> «methodName»() {
528                 «IF returnType.fullyQualifiedName.equals(BigDecimal.canonicalName)»
529                     «rangeMethodBody(restrictions, BigDecimal, className, varName)»
530                 «ELSE»
531                     «rangeMethodBody(restrictions, BigInteger, className, varName)»
532                 «ENDIF»
533             }
534         «ENDIF»
535     '''
536
537     def protected generateRangeMethod(String methodName, Restrictions restrictions, String className, String varName, Iterable<GeneratedProperty> properties) '''
538         «IF restrictions != null && !(restrictions.rangeConstraints.empty)»
539             «val returnType = properties.iterator.next.returnType»
540             public static «List.importedName»<«Range.importedName»<«returnType.importedNumber»>> «methodName»() {
541                 «IF returnType.fullyQualifiedName.equals(BigDecimal.canonicalName)»
542                     «rangeMethodBody(restrictions, BigDecimal, className, varName)»
543                 «ELSE»
544                     «rangeMethodBody(restrictions, BigInteger, className, varName)»
545                 «ENDIF»
546             }
547         «ENDIF»
548     '''
549
550     def private rangeMethodBody(Restrictions restrictions, Class<? extends Number> numberClass, String className, String varName) '''
551         if («varName» == null) {
552             synchronized («className».class) {
553                 if («varName» == null) {
554                     «ImmutableList.importedName».Builder<«Range.importedName»<«numberClass.importedName»>> builder = «ImmutableList.importedName».builder();
555                     «FOR r : restrictions.rangeConstraints»
556                         builder.add(«Range.importedName».closed(«numericValue(numberClass, r.min)», «numericValue(numberClass, r.max)»));
557                     «ENDFOR»
558                     «varName» = builder.build();
559                 }
560             }
561         }
562         return «varName»;
563     '''
564
565     def protected String importedNumber(Class<? extends Number> clazz) {
566         if (clazz.equals(typeof(BigDecimal))) {
567             return BigDecimal.importedName
568         }
569         return BigInteger.importedName
570     }
571
572     def protected String importedNumber(Type clazz) {
573         if (clazz.fullyQualifiedName.equals(BigDecimal.canonicalName)) {
574             return BigDecimal.importedName
575         }
576         return BigInteger.importedName
577     }
578
579     def private String numericValue(Class<? extends Number> clazz, Object numberValue) {
580         val number = clazz.importedName;
581         val value = numberValue.toString
582         if (clazz.equals(typeof(BigInteger)) || clazz.equals(typeof(BigDecimal))) {
583             if (value.equals("0")) {
584                 return number + ".ZERO"
585             } else if (value.equals("1")) {
586                 return number + ".ONE"
587             } else if (value.equals("10")) {
588                 return number + ".TEN"
589             } else {
590                 try {
591                     val Long longVal = Long.valueOf(value)
592                     return number + ".valueOf(" + longVal + "L)"
593                 } catch (NumberFormatException e) {
594                     if (clazz.equals(typeof(BigDecimal))) {
595                         try {
596                             val Double doubleVal = Double.valueOf(value);
597                             return number + ".valueOf(" + doubleVal + ")"
598                         } catch (NumberFormatException e2) {
599                         }
600                     }
601                 }
602             }
603         }
604         return "new " + number + "(\"" + value + "\")"
605     }
606
607     def private GeneratedProperty findProperty(GeneratedTransferObject gto, String name) {
608         val props = gto.properties
609         for (prop : props) {
610             if (prop.name.equals(name)) {
611                 return prop
612             }
613         }
614         val GeneratedTransferObject parent = gto.superType
615         if (parent != null) {
616             return findProperty(parent, name)
617         }
618         return null
619     }
620
621 }