Optimize generated toString()
[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 static org.opendaylight.mdsal.binding.model.util.BindingGeneratorUtil.encodeAngleBrackets
11
12 import com.google.common.base.CharMatcher
13 import com.google.common.base.MoreObjects
14 import com.google.common.base.Splitter
15 import com.google.common.collect.Iterables
16 import java.util.Collection
17 import java.util.List
18 import java.util.StringTokenizer
19 import java.util.regex.Pattern
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.GeneratedType
23 import org.opendaylight.mdsal.binding.model.api.MethodSignature
24 import org.opendaylight.mdsal.binding.model.api.TypeMember
25 import org.opendaylight.mdsal.binding.model.api.YangSourceDefinition.Single
26 import org.opendaylight.mdsal.binding.model.api.YangSourceDefinition.Multiple
27 import org.opendaylight.mdsal.binding.model.util.Types
28 import org.opendaylight.yangtools.yang.binding.CodeHelpers
29 import org.opendaylight.yangtools.yang.common.QName
30 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode
31 import org.opendaylight.yangtools.yang.model.api.DocumentedNode
32 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode
33 import org.opendaylight.yangtools.yang.model.api.NotificationDefinition
34 import org.opendaylight.yangtools.yang.model.api.RpcDefinition
35 import org.opendaylight.yangtools.yang.model.api.SchemaNode
36
37 abstract class BaseTemplate extends JavaFileTemplate {
38     private static final char NEW_LINE = '\n'
39     private static val AMP_MATCHER = CharMatcher.is('&')
40     private static val NL_MATCHER = CharMatcher.is(NEW_LINE)
41     private static val TAB_MATCHER = CharMatcher.is('\t')
42     private static val SPACES_PATTERN = Pattern.compile(" +")
43     private static val NL_SPLITTER = Splitter.on(NL_MATCHER)
44     private static val TAIL_COMMENT_PATTERN = Pattern.compile("*/", Pattern.LITERAL);
45
46     new(GeneratedType type) {
47         super(type)
48     }
49
50     final public def generate() {
51         val _body = body()
52         '''
53             package «type.packageName»;
54             «generateImportBlock»
55
56             «_body»
57         '''.toString
58     }
59
60     protected abstract def CharSequence body();
61
62     // Helper patterns
63     final protected def fieldName(GeneratedProperty property) '''_«property.name»'''
64
65     final protected def propertyNameFromGetter(MethodSignature getter) {
66         var int prefix;
67         if (getter.name.startsWith("is")) {
68             prefix = 2
69         } else if (getter.name.startsWith("get")) {
70             prefix = 3
71         } else {
72             throw new IllegalArgumentException("Not a getter")
73         }
74         return getter.name.substring(prefix).toFirstLower;
75     }
76
77     final protected def isAccessor(MethodSignature maybeGetter) {
78         return maybeGetter.name.startsWith("is") || maybeGetter.name.startsWith("get");
79     }
80
81     /**
82      * Template method which generates the getter method for <code>field</code>
83      *
84      * @param field
85      * generated property with data about field which is generated as the getter method
86      * @return string with the getter method source code in JAVA format
87      */
88     protected def getterMethod(GeneratedProperty field) {
89         '''
90             public «field.returnType.importedName» «field.getterMethodName»() {
91                 «IF field.returnType.importedName.contains("[]")»
92                 return «field.fieldName» == null ? null : «field.fieldName».clone();
93                 «ELSE»
94                 return «field.fieldName»;
95                 «ENDIF»
96             }
97         '''
98     }
99
100     final protected def getterMethodName(GeneratedProperty field) {
101         val prefix = if(field.returnType.equals(Types.BOOLEAN)) "is" else "get"
102         return '''«prefix»«field.name.toFirstUpper»'''
103     }
104
105     /**
106      * Template method which generates the setter method for <code>field</code>
107      *
108      * @param field
109      * generated property with data about field which is generated as the setter method
110      * @return string with the setter method source code in JAVA format
111      */
112     final protected def setterMethod(GeneratedProperty field) '''
113         «val returnType = field.returnType.importedName»
114         public «type.name» set«field.name.toFirstUpper»(«returnType» value) {
115             this.«field.fieldName» = value;
116             return this;
117         }
118     '''
119
120     /**
121      * Template method which generates method parameters with their types from <code>parameters</code>.
122      *
123      * @param parameters
124      * group of generated property instances which are transformed to the method parameters
125      * @return string with the list of the method parameters with their types in JAVA format
126      */
127     def final protected asArgumentsDeclaration(Iterable<GeneratedProperty> parameters) '''«IF !parameters.empty»«FOR parameter : parameters SEPARATOR ", "»«parameter.
128         returnType.importedName» «parameter.fieldName»«ENDFOR»«ENDIF»'''
129
130     /**
131      * Template method which generates sequence of the names of the class attributes from <code>parameters</code>.
132      *
133      * @param parameters
134      * group of generated property instances which are transformed to the sequence of parameter names
135      * @return string with the list of the parameter names of the <code>parameters</code>
136      */
137     def final protected asArguments(Iterable<GeneratedProperty> parameters) '''«IF !parameters.empty»«FOR parameter : parameters SEPARATOR ", "»«parameter.
138         fieldName»«ENDFOR»«ENDIF»'''
139
140     /**
141      * Template method which generates JAVA comments.
142      *
143      * @param comment string with the comment for whole JAVA class
144      * @return string with comment in JAVA format
145      */
146     def protected CharSequence asJavadoc(String comment) {
147         if(comment === null) return ''
148         var txt = comment
149
150         txt = comment.trim
151         txt = formatToParagraph(txt)
152
153         return '''
154             «wrapToDocumentation(txt)»
155         '''
156     }
157
158     def String wrapToDocumentation(String text) {
159         if (text.empty)
160             return ""
161
162         val StringBuilder sb = new StringBuilder().append("/**\n")
163         for (String t : NL_SPLITTER.split(text)) {
164             sb.append(" *")
165             if (!t.isEmpty()) {
166                 sb.append(' ');
167                 sb.append(t)
168             }
169             sb.append(NEW_LINE)
170         }
171         sb.append(" */")
172
173         return sb.toString
174     }
175
176     def protected String formatDataForJavaDoc(GeneratedType type) {
177         val sb = new StringBuilder()
178         val comment = type.comment
179         if (comment !== null) {
180             sb.append(comment.javadoc)
181         }
182
183         appendSnippet(sb, type)
184
185         return '''
186             «IF sb.length != 0»
187             «sb.toString»
188             «ENDIF»
189         '''.toString
190     }
191
192     def static encodeJavadocSymbols(String description) {
193         if (description.nullOrEmpty) {
194             return description;
195         }
196
197         return TAIL_COMMENT_PATTERN.matcher(AMP_MATCHER.replaceFrom(description, "&amp;")).replaceAll("&#42;&#47;")
198     }
199
200     def protected String formatDataForJavaDoc(GeneratedType type, String additionalComment) {
201         val comment = type.comment
202         if (comment === null) {
203             return '''
204                 «additionalComment»
205             '''
206         }
207
208         val sb = new StringBuilder().append(comment.javadoc)
209         appendSnippet(sb, type)
210
211         sb.append(NEW_LINE)
212         .append(NEW_LINE)
213         .append(NEW_LINE)
214         .append(additionalComment)
215
216         return '''
217             «sb.toString»
218         '''
219     }
220
221     def private static void appendSnippet(StringBuilder sb, GeneratedType type) {
222         val optDef = type.yangSourceDefinition
223         if (optDef.present) {
224             val def = optDef.get
225             sb.append(NEW_LINE)
226
227                 if (def instanceof Single) {
228                 val node = def.node
229                 sb.append("<p>\n")
230                 .append("This class represents the following YANG schema fragment defined in module <b>")
231                 .append(def.module.name).append("</b>\n")
232                 .append("<pre>\n")
233                 .append(encodeAngleBrackets(encodeJavadocSymbols(YangTemplate.generateYangSnippet(node))))
234                 .append("</pre>")
235
236                 if (node instanceof SchemaNode) {
237                     sb.append("The schema path to identify an instance is\n")
238                     .append("<i>")
239                     .append(formatSchemaPath(def.module.name, node.path.pathFromRoot))
240                     .append("</i>\n")
241
242                     if (hasBuilderClass(node)) {
243                         val builderName = type.name + "Builder";
244
245                         sb.append("\n<p>To create instances of this class use {@link ").append(builderName)
246                         .append("}.\n")
247                         .append("@see ").append(builderName).append('\n')
248                         if (node instanceof ListSchemaNode) {
249                             val keyDef = node.keyDefinition
250                             if (keyDef !== null && !keyDef.empty) {
251                                 sb.append("@see ").append(type.name).append("Key")
252                             }
253                             sb.append('\n');
254                         }
255                     }
256                 }
257                 } else if (def instanceof Multiple) {
258                 sb.append("<pre>\n")
259                 for (DocumentedNode schemaNode : def.nodes) {
260                     sb.append(encodeAngleBrackets(encodeJavadocSymbols(YangTemplate.generateYangSnippet(schemaNode))))
261                 }
262                 sb.append("</pre>\n")
263             }
264         }
265     }
266
267     def private static boolean hasBuilderClass(SchemaNode schemaNode) {
268         return schemaNode instanceof ContainerSchemaNode || schemaNode instanceof ListSchemaNode
269                 || schemaNode instanceof RpcDefinition || schemaNode instanceof NotificationDefinition;
270     }
271
272     def private static String formatSchemaPath(String moduleName, Iterable<QName> schemaPath) {
273         val sb = new StringBuilder().append(moduleName);
274
275         var currentElement = Iterables.getFirst(schemaPath, null);
276         for (QName pathElement : schemaPath) {
277             sb.append('/')
278             if (!currentElement.namespace.equals(pathElement.namespace)) {
279                 currentElement = pathElement
280                 sb.append(pathElement)
281             } else {
282                 sb.append(pathElement.getLocalName())
283             }
284         }
285         return sb.toString();
286     }
287
288     def protected String formatDataForJavaDoc(TypeMember type, String additionalComment) {
289         val StringBuilder typeDescriptionBuilder = new StringBuilder();
290         if (!type.comment.nullOrEmpty) {
291             typeDescriptionBuilder.append(formatToParagraph(type.comment))
292             typeDescriptionBuilder.append(NEW_LINE)
293             typeDescriptionBuilder.append(NEW_LINE)
294             typeDescriptionBuilder.append(NEW_LINE)
295         }
296         typeDescriptionBuilder.append(additionalComment)
297         var typeDescription = wrapToDocumentation(typeDescriptionBuilder.toString)
298         return '''
299             «typeDescription»
300         '''.toString
301     }
302
303     def asCode(String text) {
304         return "<code>" + text + "</code>"
305     }
306
307     def asLink(String text) {
308         val StringBuilder sb = new StringBuilder()
309         var tempText = text
310         var char lastChar = ' '
311         var boolean badEnding = false
312
313         if (text.endsWith('.') || text.endsWith(':') || text.endsWith(',')) {
314             tempText = text.substring(0, text.length - 1)
315             lastChar = text.charAt(text.length - 1)
316             badEnding = true
317         }
318         sb.append("<a href = \"")
319         sb.append(tempText)
320         sb.append("\">")
321         sb.append(tempText)
322         sb.append("</a>")
323
324         if(badEnding)
325             sb.append(lastChar)
326
327         return sb.toString
328     }
329
330     protected def formatToParagraph(String text) {
331         if(text === null || text.isEmpty)
332             return text
333
334         var formattedText = text
335         val StringBuilder sb = new StringBuilder();
336         var StringBuilder lineBuilder = new StringBuilder();
337         var boolean isFirstElementOnNewLineEmptyChar = false;
338
339         formattedText = encodeJavadocSymbols(formattedText)
340         formattedText = NL_MATCHER.removeFrom(formattedText)
341         formattedText = TAB_MATCHER.removeFrom(formattedText)
342         formattedText = SPACES_PATTERN.matcher(formattedText).replaceAll(" ")
343
344         val StringTokenizer tokenizer = new StringTokenizer(formattedText, " ", true);
345
346         while (tokenizer.hasMoreElements) {
347             val nextElement = tokenizer.nextElement.toString
348
349             if (lineBuilder.length != 0 && lineBuilder.length + nextElement.length > 80) {
350                 if (lineBuilder.charAt(lineBuilder.length - 1) == ' ') {
351                     lineBuilder.setLength(0)
352                     lineBuilder.append(lineBuilder.substring(0, lineBuilder.length - 1))
353                 }
354                 if (lineBuilder.charAt(0) == ' ') {
355                     lineBuilder.setLength(0)
356                     lineBuilder.append(lineBuilder.substring(1))
357                 }
358
359                 sb.append(lineBuilder);
360                 lineBuilder.setLength(0)
361                 sb.append(NEW_LINE)
362
363                 if(nextElement.toString == ' ') {
364                     isFirstElementOnNewLineEmptyChar = !isFirstElementOnNewLineEmptyChar;
365                 }
366             }
367
368             if (isFirstElementOnNewLineEmptyChar) {
369                 isFirstElementOnNewLineEmptyChar = !isFirstElementOnNewLineEmptyChar
370             }
371
372             else {
373                 lineBuilder.append(nextElement)
374             }
375         }
376         sb.append(lineBuilder)
377         sb.append(NEW_LINE)
378
379         return sb.toString
380     }
381
382     def protected generateToString(Collection<GeneratedProperty> properties) '''
383         «IF !properties.empty»
384             @Override
385             public «String.importedName» toString() {
386                 final «MoreObjects.importedName».ToStringHelper helper = «MoreObjects.importedName».toStringHelper(«type.importedName».class);
387                 «FOR property : properties»
388                     «CodeHelpers.importedName».appendValue(helper, "«property.fieldName»", «property.fieldName»);
389                 «ENDFOR»
390                 return helper.toString();
391             }
392         «ENDIF»
393     '''
394
395     /**
396      * Template method which generates method parameters with their types from <code>parameters</code>.
397      *
398      * @param parameters
399      * list of parameter instances which are transformed to the method parameters
400      * @return string with the list of the method parameters with their types in JAVA format
401      */
402     def protected generateParameters(List<MethodSignature.Parameter> parameters) '''«
403         IF !parameters.empty»«
404             FOR parameter : parameters SEPARATOR ", "»«
405                 parameter.type.importedName» «parameter.name»«
406             ENDFOR»«
407         ENDIF
408     »'''
409
410     def protected emitConstant(Constant c) '''
411         «IF c.value instanceof QName»
412             «val qname = c.value as QName»
413             «val rev = qname.revision»
414             public static final «c.type.importedName» «c.name» = «QName.name».create("«qname.namespace.toString»",
415                 «IF rev.isPresent»"«rev.get»", «ENDIF»"«qname.localName»").intern();
416         «ELSE»
417             public static final «c.type.importedName» «c.name» = «c.value»;
418         «ENDIF»
419     '''
420 }