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