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