Eliminate use of String.replace/replaceAll()
[mdsal.git] / binding / mdsal-binding-java-api-generator / src / main / java / org / opendaylight / mdsal / binding / 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.mdsal.binding.java.api.generator
9
10 import com.google.common.base.CharMatcher
11 import com.google.common.base.Splitter
12 import java.util.Arrays
13 import java.util.Collection
14 import java.util.HashMap
15 import java.util.List
16 import java.util.Map
17 import java.util.StringTokenizer
18 import java.util.regex.Pattern
19 import org.opendaylight.mdsal.binding.model.api.ConcreteType
20 import org.opendaylight.mdsal.binding.model.api.Constant
21 import org.opendaylight.mdsal.binding.model.api.GeneratedProperty
22 import org.opendaylight.mdsal.binding.model.api.GeneratedTransferObject
23 import org.opendaylight.mdsal.binding.model.api.GeneratedType
24 import org.opendaylight.mdsal.binding.model.api.MethodSignature
25 import org.opendaylight.mdsal.binding.model.api.Restrictions
26 import org.opendaylight.mdsal.binding.model.api.Type
27 import org.opendaylight.mdsal.binding.model.api.TypeMember
28 import org.opendaylight.mdsal.binding.model.util.Types
29 import org.opendaylight.yangtools.yang.common.QName
30
31 abstract class BaseTemplate {
32     protected val GeneratedType type;
33     protected val Map<String, String> importMap;
34
35     private static final char NEW_LINE = '\n'
36     private static final CharMatcher NL_MATCHER = CharMatcher.is(NEW_LINE)
37     private static final CharMatcher TAB_MATCHER = CharMatcher.is('\t')
38     private static final Pattern SPACES_PATTERN = Pattern.compile(" +")
39     private static final Splitter NL_SPLITTER = Splitter.on(NL_MATCHER)
40     private static final Pattern TAIL_COMMENT_PATTERN = Pattern.compile("*/", Pattern.LITERAL);
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     final public def generate() {
53         val _body = body()
54         '''
55             «packageDefinition»
56             «imports»
57
58             «_body»
59         '''.toString
60     }
61
62     protected def imports() '''
63         «FOR entry : importMap.entrySet»
64             «IF !hasSamePackage(entry.value) && !isLocalInnerClass(entry.value)»
65                 import «entry.value».«entry.key»;
66             «ENDIF»
67         «ENDFOR»
68     '''
69
70     /**
71      * Checks if packages of generated type and imported type is the same
72      *
73      * @param importedTypePackageName the package name of imported type
74      * @return true if the packages are the same false otherwise
75      */
76     final private def boolean hasSamePackage(String importedTypePackageName) {
77         return type.packageName.equals(importedTypePackageName);
78     }
79
80     def isLocalInnerClass(String importedTypePackageName) {
81         return type.fullyQualifiedName.equals(importedTypePackageName);
82     }
83
84     protected abstract def CharSequence body();
85
86     // Helper patterns
87     final protected def fieldName(GeneratedProperty property) '''_«property.name»'''
88
89     final protected def propertyNameFromGetter(MethodSignature getter) {
90         var int prefix;
91         if (getter.name.startsWith("is")) {
92             prefix = 2
93         } else if (getter.name.startsWith("get")) {
94             prefix = 3
95         } else {
96             throw new IllegalArgumentException("Not a getter")
97         }
98         return getter.name.substring(prefix).toFirstLower;
99     }
100
101     final protected def isAccessor(MethodSignature maybeGetter) {
102         return maybeGetter.name.startsWith("is") || maybeGetter.name.startsWith("get");
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     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             if (!t.isEmpty()) {
201                 sb.append(' ');
202                 sb.append(t)
203             }
204             sb.append(NEW_LINE)
205         }
206         sb.append(" */")
207
208         return sb.toString
209     }
210
211     def protected String formatDataForJavaDoc(GeneratedType type) {
212         val typeDescription = type.getDescription().encodeJavadocSymbols;
213
214         return '''
215             «IF !typeDescription.nullOrEmpty»
216             «typeDescription»
217             «ENDIF»
218         '''.toString
219     }
220
221     private static final CharMatcher AMP_MATCHER = CharMatcher.is('&');
222
223     def encodeJavadocSymbols(String description) {
224         if (description.nullOrEmpty) {
225             return description;
226         }
227
228         return AMP_MATCHER.replaceFrom(TAIL_COMMENT_PATTERN.matcher(description).replaceAll("&#42;&#47;"), "&amp;");
229     }
230
231     def protected String formatDataForJavaDoc(GeneratedType type, String additionalComment) {
232         val StringBuilder typeDescription = new StringBuilder();
233         if (!type.description.nullOrEmpty) {
234             typeDescription.append(type.description)
235             typeDescription.append(NEW_LINE)
236             typeDescription.append(NEW_LINE)
237             typeDescription.append(NEW_LINE)
238             typeDescription.append(additionalComment)
239         } else {
240             typeDescription.append(additionalComment)
241         }
242
243         return '''
244             «typeDescription.toString»
245         '''.toString
246     }
247
248     def protected String formatDataForJavaDoc(TypeMember type, String additionalComment) {
249         val StringBuilder typeDescriptionBuilder = new StringBuilder();
250         if (!type.comment.nullOrEmpty) {
251             typeDescriptionBuilder.append(formatToParagraph(type.comment))
252             typeDescriptionBuilder.append(NEW_LINE)
253             typeDescriptionBuilder.append(NEW_LINE)
254             typeDescriptionBuilder.append(NEW_LINE)
255         }
256         typeDescriptionBuilder.append(additionalComment)
257         var typeDescription = wrapToDocumentation(typeDescriptionBuilder.toString)
258         return '''
259             «typeDescription»
260         '''.toString
261     }
262
263     def asCode(String text) {
264         return "<code>" + text + "</code>"
265     }
266
267     def asLink(String text) {
268         val StringBuilder sb = new StringBuilder()
269         var tempText = text
270         var char lastChar = ' '
271         var boolean badEnding = false
272
273         if (text.endsWith('.') || text.endsWith(':') || text.endsWith(',')) {
274             tempText = text.substring(0, text.length - 1)
275             lastChar = text.charAt(text.length - 1)
276             badEnding = true
277         }
278         sb.append("<a href = \"")
279         sb.append(tempText)
280         sb.append("\">")
281         sb.append(tempText)
282         sb.append("</a>")
283
284         if(badEnding)
285             sb.append(lastChar)
286
287         return sb.toString
288     }
289
290     protected def formatToParagraph(String text) {
291         if(text === null || text.isEmpty)
292             return text
293
294         var formattedText = text
295         val StringBuilder sb = new StringBuilder();
296         var StringBuilder lineBuilder = new StringBuilder();
297         var boolean isFirstElementOnNewLineEmptyChar = false;
298
299         formattedText = encodeJavadocSymbols(formattedText)
300         formattedText = NL_MATCHER.removeFrom(formattedText)
301         formattedText = TAB_MATCHER.removeFrom(formattedText)
302         formattedText = SPACES_PATTERN.matcher(formattedText).replaceAll(" ")
303
304         val StringTokenizer tokenizer = new StringTokenizer(formattedText, " ", true);
305
306         while (tokenizer.hasMoreElements) {
307             val nextElement = tokenizer.nextElement.toString
308
309             if (lineBuilder.length != 0 && lineBuilder.length + nextElement.length > 80) {
310                 if (lineBuilder.charAt(lineBuilder.length - 1) == ' ') {
311                     lineBuilder.setLength(0)
312                     lineBuilder.append(lineBuilder.substring(0, lineBuilder.length - 1))
313                 }
314                 if (lineBuilder.charAt(0) == ' ') {
315                     lineBuilder.setLength(0)
316                     lineBuilder.append(lineBuilder.substring(1))
317                 }
318
319                 sb.append(lineBuilder);
320                 lineBuilder.setLength(0)
321                 sb.append(NEW_LINE)
322
323                 if(nextElement.toString == ' ') {
324                     isFirstElementOnNewLineEmptyChar = !isFirstElementOnNewLineEmptyChar;
325                 }
326             }
327
328             if (isFirstElementOnNewLineEmptyChar) {
329                 isFirstElementOnNewLineEmptyChar = !isFirstElementOnNewLineEmptyChar
330             }
331
332             else {
333                 lineBuilder.append(nextElement)
334             }
335         }
336         sb.append(lineBuilder)
337         sb.append(NEW_LINE)
338
339         return sb.toString
340     }
341
342     def protected generateToString(Collection<GeneratedProperty> properties) '''
343         «IF !properties.empty»
344             @Override
345             public «String.importedName» toString() {
346                 «StringBuilder.importedName» builder = new «StringBuilder.importedName»(«type.importedName».class.getSimpleName()).append(" [");
347                 boolean first = true;
348
349                 «FOR property : properties»
350                     if («property.fieldName» != null) {
351                         if (first) {
352                             first = false;
353                         } else {
354                             builder.append(", ");
355                         }
356                         builder.append("«property.fieldName»=");
357                         «IF property.returnType.name.contains("[")»
358                             builder.append(«Arrays.importedName».toString(«property.fieldName»));
359                         «ELSE»
360                             builder.append(«property.fieldName»);
361                         «ENDIF»
362                     }
363                 «ENDFOR»
364                 return builder.append(']').toString();
365             }
366         «ENDIF»
367     '''
368
369     def getRestrictions(Type type) {
370         var Restrictions restrictions = null
371         if (type instanceof ConcreteType) {
372             restrictions = type.restrictions
373         } else if (type instanceof GeneratedTransferObject) {
374             restrictions = type.restrictions
375         }
376         return restrictions
377     }
378
379     /**
380      * Template method which generates method parameters with their types from <code>parameters</code>.
381      *
382      * @param parameters
383      * list of parameter instances which are transformed to the method parameters
384      * @return string with the list of the method parameters with their types in JAVA format
385      */
386     def protected generateParameters(List<MethodSignature.Parameter> parameters) '''«
387         IF !parameters.empty»«
388             FOR parameter : parameters SEPARATOR ", "»«
389                 parameter.type.importedName» «parameter.name»«
390             ENDFOR»«
391         ENDIF
392     »'''
393
394     def protected GeneratedProperty findProperty(GeneratedTransferObject gto, String name) {
395         val props = gto.properties
396         for (prop : props) {
397             if (prop.name.equals(name)) {
398                 return prop
399             }
400         }
401         val GeneratedTransferObject parent = gto.superType
402         if (parent !== null) {
403             return findProperty(parent, name)
404         }
405         return null
406     }
407
408     def protected emitConstant(Constant c) '''
409         «IF c.value instanceof QName»
410             «val qname = c.value as QName»
411             public static final «c.type.importedName» «c.name» = «QName.name».create("«qname.namespace.toString»",
412                 "«qname.formattedRevision»", "«qname.localName»").intern();
413         «ELSE»
414             public static final «c.type.importedName» «c.name» = «c.value»;
415         «ENDIF»
416     '''
417 }