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