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