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