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