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