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