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