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