Fix use of StringTokenizer methods
[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) return ''
168         var txt = comment
169
170         txt = comment.trim
171         txt = formatToParagraph(txt)
172
173         return '''
174             «wrapToDocumentation(txt)»
175         '''
176     }
177
178     def static String wrapToDocumentation(String text) {
179         if (text.empty)
180             return ""
181
182         val StringBuilder sb = new StringBuilder().append("/**\n")
183         for (String t : NL_SPLITTER.split(text)) {
184             sb.append(" *")
185             if (!t.isEmpty()) {
186                 sb.append(SPACE).append(t)
187             }
188             sb.append(NEW_LINE)
189         }
190         sb.append(" */")
191
192         return sb.toString
193     }
194
195     def protected String formatDataForJavaDoc(GeneratedType type) {
196         val sb = new StringBuilder()
197         val comment = type.comment
198         if (comment !== null) {
199             sb.append(comment.javadoc)
200         }
201
202         appendSnippet(sb, type)
203
204         return '''
205             «IF sb.length != 0»
206             «sb.toString»
207             «ENDIF»
208         '''.toString
209     }
210
211     def static encodeJavadocSymbols(String description) {
212         if (description.nullOrEmpty) {
213             return description;
214         }
215
216         return TAIL_COMMENT_PATTERN.matcher(AMP_MATCHER.replaceFrom(description, "&amp;")).replaceAll("&#42;&#47;")
217     }
218
219     def protected String formatDataForJavaDoc(GeneratedType type, String additionalComment) {
220         val comment = type.comment
221         if (comment === null) {
222             return '''
223                 «additionalComment»
224             '''
225         }
226
227         val sb = new StringBuilder().append(comment.javadoc)
228         appendSnippet(sb, type)
229
230         sb.append(NEW_LINE)
231         .append(NEW_LINE)
232         .append(NEW_LINE)
233         .append(additionalComment)
234
235         return '''
236             «sb.toString»
237         '''
238     }
239
240     def private static void appendSnippet(StringBuilder sb, GeneratedType type) {
241         val optDef = type.yangSourceDefinition
242         if (optDef.present) {
243             val def = optDef.get
244             sb.append(NEW_LINE)
245
246             if (def instanceof Single) {
247                 val node = def.node
248                 sb.append("<p>\n")
249                 .append("This class represents the following YANG schema fragment defined in module <b>")
250                 .append(def.module.argument).append("</b>\n")
251                 .append("<pre>\n")
252                 appendYangSnippet(sb, def.module, (node as EffectiveStatement<?, ?>).declared)
253                 sb.append("</pre>")
254
255                 if (node instanceof SchemaNode) {
256                     sb.append("The schema path to identify an instance is\n")
257                     .append("<i>")
258                     .append(formatSchemaPath(def.module.argument, node.path.pathFromRoot))
259                     .append("</i>\n")
260
261                     if (hasBuilderClass(node)) {
262                         val builderName = type.name + "Builder";
263
264                         sb.append("\n<p>To create instances of this class use {@link ").append(builderName)
265                         .append("}.\n")
266                         .append("@see ").append(builderName).append('\n')
267                         if (node instanceof ListSchemaNode) {
268                             val keyDef = node.keyDefinition
269                             if (keyDef !== null && !keyDef.empty) {
270                                 sb.append("@see ").append(type.name).append("Key")
271                             }
272                             sb.append('\n');
273                         }
274                     }
275                 }
276             } else if (def instanceof Multiple) {
277                 sb.append("<pre>\n")
278                 for (SchemaNode node : def.nodes) {
279                     appendYangSnippet(sb, def.module, (node as EffectiveStatement<?, ?>).declared)
280                 }
281                 sb.append("</pre>\n")
282             }
283         }
284     }
285
286     def private static void appendYangSnippet(StringBuilder sb, ModuleEffectiveStatement module,
287             DeclaredStatement<?> stmt) {
288         for (String str : YANG_FORMATTER.toYangTextSnippet(module, stmt)) {
289             sb.append(encodeAngleBrackets(encodeJavadocSymbols(str)))
290         }
291     }
292
293     def private static boolean hasBuilderClass(SchemaNode schemaNode) {
294         return schemaNode instanceof ContainerSchemaNode || schemaNode instanceof ListSchemaNode
295                 || schemaNode instanceof RpcDefinition || schemaNode instanceof NotificationDefinition;
296     }
297
298     def private static String formatSchemaPath(String moduleName, Iterable<QName> schemaPath) {
299         val sb = new StringBuilder().append(moduleName);
300
301         var currentElement = Iterables.getFirst(schemaPath, null);
302         for (QName pathElement : schemaPath) {
303             sb.append('/')
304             if (!currentElement.namespace.equals(pathElement.namespace)) {
305                 currentElement = pathElement
306                 sb.append(pathElement)
307             } else {
308                 sb.append(pathElement.getLocalName())
309             }
310         }
311         return sb.toString();
312     }
313
314     def protected static String formatDataForJavaDoc(TypeMember type, String additionalComment) {
315         val StringBuilder typeDescriptionBuilder = new StringBuilder();
316         if (!type.comment.nullOrEmpty) {
317             typeDescriptionBuilder.append(formatToParagraph(type.comment))
318             typeDescriptionBuilder.append(NEW_LINE)
319             typeDescriptionBuilder.append(NEW_LINE)
320             typeDescriptionBuilder.append(NEW_LINE)
321         }
322         typeDescriptionBuilder.append(additionalComment)
323         var typeDescription = wrapToDocumentation(typeDescriptionBuilder.toString)
324         return '''
325             «typeDescription»
326         '''.toString
327     }
328
329     def asCode(String text) {
330         return "<code>" + text + "</code>"
331     }
332
333     def asLink(String text) {
334         val StringBuilder sb = new StringBuilder()
335         var tempText = text
336         var char lastChar = SPACE
337         var boolean badEnding = false
338
339         if (text.endsWith('.') || text.endsWith(':') || text.endsWith(',')) {
340             tempText = text.substring(0, text.length - 1)
341             lastChar = text.charAt(text.length - 1)
342             badEnding = true
343         }
344         sb.append("<a href = \"")
345         sb.append(tempText)
346         sb.append("\">")
347         sb.append(tempText)
348         sb.append("</a>")
349
350         if(badEnding)
351             sb.append(lastChar)
352
353         return sb.toString
354     }
355
356     protected static def formatToParagraph(String text) {
357         if(text === null || text.isEmpty)
358             return text
359
360         var formattedText = text
361         val StringBuilder sb = new StringBuilder();
362         var StringBuilder lineBuilder = new StringBuilder();
363         var boolean isFirstElementOnNewLineEmptyChar = false;
364
365         formattedText = encodeJavadocSymbols(formattedText)
366         formattedText = WS_MATCHER.replaceFrom(formattedText, SPACE)
367         formattedText = SPACES_PATTERN.matcher(formattedText).replaceAll(" ")
368
369         val StringTokenizer tokenizer = new StringTokenizer(formattedText, " ", true);
370
371         while (tokenizer.hasMoreTokens) {
372             val nextElement = tokenizer.nextToken
373
374             if (lineBuilder.length != 0 && lineBuilder.length + nextElement.length > 80) {
375                 // FIXME: what tricks are we playing here? Equality probably does not trigger ever
376                 //        and then the setLength()/append() combo does not work, either
377                 if (lineBuilder.charAt(lineBuilder.length - 1) == ' ') {
378                     lineBuilder.setLength(0)
379                     lineBuilder.append(lineBuilder.substring(0, lineBuilder.length - 1))
380                 }
381                 if (lineBuilder.charAt(0) == ' ') {
382                     lineBuilder.setLength(0)
383                     lineBuilder.append(lineBuilder.substring(1))
384                 }
385
386                 sb.append(lineBuilder).append(NEW_LINE)
387                 lineBuilder.setLength(0)
388
389                 if (nextElement == " ") {
390                     isFirstElementOnNewLineEmptyChar = !isFirstElementOnNewLineEmptyChar;
391                 }
392             }
393
394             if (isFirstElementOnNewLineEmptyChar) {
395                 isFirstElementOnNewLineEmptyChar = !isFirstElementOnNewLineEmptyChar
396             } else {
397                 lineBuilder.append(nextElement)
398             }
399         }
400
401         return sb.append(lineBuilder).append(NEW_LINE).toString
402     }
403
404     def protected generateToString(Collection<GeneratedProperty> properties) '''
405         «IF !properties.empty»
406             @«Override.importedName»
407             public «String.importedName» toString() {
408                 final «MoreObjects.importedName».ToStringHelper helper = «MoreObjects.importedName».toStringHelper(«type.importedName».class);
409                 «FOR property : properties»
410                     «CodeHelpers.importedName».appendValue(helper, "«property.fieldName»", «property.fieldName»);
411                 «ENDFOR»
412                 return helper.toString();
413             }
414         «ENDIF»
415     '''
416
417     /**
418      * Template method which generates method parameters with their types from <code>parameters</code>.
419      *
420      * @param parameters
421      * list of parameter instances which are transformed to the method parameters
422      * @return string with the list of the method parameters with their types in JAVA format
423      */
424     def protected generateParameters(List<MethodSignature.Parameter> parameters) '''«
425         IF !parameters.empty»«
426             FOR parameter : parameters SEPARATOR ", "»«
427                 parameter.type.importedName» «parameter.name»«
428             ENDFOR»«
429         ENDIF
430     »'''
431
432     def protected emitConstant(Constant c) '''
433         «IF BindingMapping.QNAME_STATIC_FIELD_NAME.equals(c.name)»
434             «val entry = c.value as Entry<JavaTypeName, String>»
435             public static final «c.type.importedNonNull» «c.name» = «entry.key.importedName».«BindingMapping.MODULE_INFO_QNAMEOF_METHOD_NAME»("«entry.value»");
436         «ELSE»
437             public static final «c.type.importedName» «c.name» = «c.value»;
438         «ENDIF»
439     '''
440
441     def protected generateCheckers(GeneratedProperty field, Restrictions restrictions, Type actualType) '''
442        «IF restrictions.rangeConstraint.present»
443            «AbstractRangeGenerator.forType(actualType).generateRangeChecker(field.name.toFirstUpper,
444                restrictions.rangeConstraint.get, this)»
445        «ENDIF»
446        «IF restrictions.lengthConstraint.present»
447            «LengthGenerator.generateLengthChecker(field.fieldName.toString, actualType, restrictions.lengthConstraint.get, this)»
448        «ENDIF»
449     '''
450
451     def protected checkArgument(GeneratedProperty property, Restrictions restrictions, Type actualType, String value) '''
452        «IF restrictions.getRangeConstraint.isPresent»
453            «IF actualType instanceof ConcreteType»
454                «AbstractRangeGenerator.forType(actualType).generateRangeCheckerCall(property.getName.toFirstUpper, value)»
455            «ELSE»
456                «AbstractRangeGenerator.forType(actualType).generateRangeCheckerCall(property.getName.toFirstUpper, value + ".getValue()")»
457            «ENDIF»
458        «ENDIF»
459        «IF restrictions.getLengthConstraint.isPresent»
460            «IF actualType instanceof ConcreteType»
461                «LengthGenerator.generateLengthCheckerCall(property.fieldName.toString, value)»
462            «ELSE»
463                «LengthGenerator.generateLengthCheckerCall(property.fieldName.toString, value + ".getValue()")»
464            «ENDIF»
465        «ENDIF»
466
467        «val fieldUpperCase = property.fieldName.toString.toUpperCase(Locale.ENGLISH)»
468        «FOR currentConstant : type.getConstantDefinitions»
469            «IF currentConstant.getName.startsWith(TypeConstants.PATTERN_CONSTANT_NAME)
470                && fieldUpperCase.equals(currentConstant.getName.substring(TypeConstants.PATTERN_CONSTANT_NAME.length))»
471            «CodeHelpers.importedName».checkPattern(value, «Constants.MEMBER_PATTERN_LIST»«property.fieldName», «Constants.MEMBER_REGEX_LIST»«property.fieldName»);
472            «ENDIF»
473        «ENDFOR»
474     '''
475
476     def protected hashCodeResult(Collection<GeneratedProperty> properties) '''
477         final int prime = 31;
478         int result = 1;
479         «FOR property : properties»
480             result = prime * result + «property.importedUtilClass».hashCode(«property.fieldName»);
481         «ENDFOR»
482     '''
483 }