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