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