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