Revert "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.ImmutableMap
17 import com.google.common.collect.Iterables
18 import java.math.BigInteger
19 import java.util.Collection
20 import java.util.List
21 import java.util.Locale
22 import java.util.Map.Entry
23 import java.util.StringTokenizer
24 import java.util.regex.Pattern
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.YangSourceDefinition.Single
37 import org.opendaylight.mdsal.binding.model.api.YangSourceDefinition.Multiple
38 import org.opendaylight.mdsal.binding.model.util.BindingGeneratorUtil
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 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 sequence of the names of the class attributes from <code>parameters</code>.
172      *
173      * @param parameters
174      * group of generated property instances which are transformed to the sequence of parameter names
175      * @return string with the list of the parameter names of the <code>parameters</code>
176      */
177     def final protected asArguments(Collection<GeneratedProperty> parameters) '''«IF !parameters.empty»«FOR parameter : parameters SEPARATOR ", "»«parameter.
178         fieldName»«ENDFOR»«ENDIF»'''
179
180     /**
181      * Template method which generates JAVA comments.
182      *
183      * @param comment string with the comment for whole JAVA class
184      * @return string with comment in JAVA format
185      */
186     def protected CharSequence asJavadoc(String comment) {
187         if (comment === null) {
188             return ''
189         }
190         return '''
191             «wrapToDocumentation(formatToParagraph(comment.trim))»
192         '''
193     }
194
195     def static String wrapToDocumentation(String text) {
196         if (text.empty)
197             return ""
198
199         val StringBuilder sb = new StringBuilder().append("/**\n")
200         for (String t : NL_SPLITTER.split(text)) {
201             sb.append(" *")
202             if (!t.isEmpty()) {
203                 sb.append(SPACE).append(t)
204             }
205             sb.append(NEW_LINE)
206         }
207         sb.append(" */")
208
209         return sb.toString
210     }
211
212     def protected String formatDataForJavaDoc(GeneratedType type) {
213         val sb = new StringBuilder()
214         val comment = type.comment
215         if (comment !== null) {
216             sb.append(comment.javadoc)
217         }
218
219         appendSnippet(sb, type)
220
221         return '''
222             «IF sb.length != 0»
223             «sb.toString»
224             «ENDIF»
225         '''.toString
226     }
227
228     def static encodeJavadocSymbols(String description) {
229         if (description.nullOrEmpty) {
230             return description;
231         }
232
233         return TAIL_COMMENT_PATTERN.matcher(AMP_MATCHER.replaceFrom(description, "&amp;")).replaceAll("&#42;&#47;")
234     }
235
236     def protected String formatDataForJavaDoc(GeneratedType type, String additionalComment) {
237         val comment = type.comment
238         if (comment === null) {
239             return '''
240                 «additionalComment»
241             '''
242         }
243
244         val sb = new StringBuilder().append(comment.javadoc)
245         appendSnippet(sb, type)
246
247         sb.append(NEW_LINE)
248         .append(NEW_LINE)
249         .append(NEW_LINE)
250         .append(additionalComment)
251
252         return '''
253             «sb.toString»
254         '''
255     }
256
257     def private static void appendSnippet(StringBuilder sb, GeneratedType type) {
258         val optDef = type.yangSourceDefinition
259         if (optDef.present) {
260             val def = optDef.get
261             sb.append(NEW_LINE)
262
263             if (def instanceof Single) {
264                 val node = def.node
265                 sb.append("<p>\n")
266                 .append("This class represents the following YANG schema fragment defined in module <b>")
267                 .append(def.module.argument).append("</b>\n")
268                 .append("<pre>\n")
269                 appendYangSnippet(sb, def.module, (node as EffectiveStatement<?, ?>).declared)
270                 sb.append("</pre>")
271
272                 if (node instanceof SchemaNode) {
273                     sb.append("The schema path to identify an instance is\n")
274                     .append("<i>")
275                     .append(formatSchemaPath(def.module.argument, node.path.pathFromRoot))
276                     .append("</i>\n")
277
278                     if (hasBuilderClass(node)) {
279                         val builderName = type.name + "Builder";
280
281                         sb.append("\n<p>To create instances of this class use {@link ").append(builderName)
282                         .append("}.\n")
283                         .append("@see ").append(builderName).append('\n')
284                         if (node instanceof ListSchemaNode) {
285                             val keyDef = node.keyDefinition
286                             if (keyDef !== null && !keyDef.empty) {
287                                 sb.append("@see ").append(type.name).append("Key")
288                             }
289                             sb.append('\n');
290                         }
291                     }
292                 }
293             } else if (def instanceof Multiple) {
294                 sb.append("<pre>\n")
295                 for (SchemaNode node : def.nodes) {
296                     appendYangSnippet(sb, def.module, (node as EffectiveStatement<?, ?>).declared)
297                 }
298                 sb.append("</pre>\n")
299             }
300         }
301     }
302
303     def private static void appendYangSnippet(StringBuilder sb, ModuleEffectiveStatement module,
304             DeclaredStatement<?> stmt) {
305         for (String str : YANG_FORMATTER.toYangTextSnippet(module, stmt)) {
306             sb.append(BindingGeneratorUtil.replaceAllIllegalChars(encodeAngleBrackets(encodeJavadocSymbols(str))))
307         }
308     }
309
310     def private static boolean hasBuilderClass(SchemaNode schemaNode) {
311         return schemaNode instanceof ContainerSchemaNode || schemaNode instanceof ListSchemaNode
312                 || schemaNode instanceof RpcDefinition || schemaNode instanceof NotificationDefinition;
313     }
314
315     def private static String formatSchemaPath(String moduleName, Iterable<QName> schemaPath) {
316         val sb = new StringBuilder().append(moduleName);
317
318         var currentElement = Iterables.getFirst(schemaPath, null);
319         for (QName pathElement : schemaPath) {
320             sb.append('/')
321             if (!currentElement.namespace.equals(pathElement.namespace)) {
322                 currentElement = pathElement
323                 sb.append(pathElement)
324             } else {
325                 sb.append(pathElement.getLocalName())
326             }
327         }
328         return sb.toString();
329     }
330
331     def protected static String formatDataForJavaDoc(TypeMember type, String additionalComment) {
332         val StringBuilder typeDescriptionBuilder = new StringBuilder();
333         if (!type.comment.nullOrEmpty) {
334             typeDescriptionBuilder.append(formatToParagraph(type.comment))
335             typeDescriptionBuilder.append(NEW_LINE)
336             typeDescriptionBuilder.append(NEW_LINE)
337             typeDescriptionBuilder.append(NEW_LINE)
338         }
339         typeDescriptionBuilder.append(additionalComment)
340         var typeDescription = wrapToDocumentation(typeDescriptionBuilder.toString)
341         return '''
342             «typeDescription»
343         '''.toString
344     }
345
346     def asCode(String text) {
347         return "<code>" + text + "</code>"
348     }
349
350     def asLink(String text) {
351         val StringBuilder sb = new StringBuilder()
352         var tempText = text
353         var char lastChar = SPACE
354         var boolean badEnding = false
355
356         if (text.endsWith('.') || text.endsWith(':') || text.endsWith(',')) {
357             tempText = text.substring(0, text.length - 1)
358             lastChar = text.charAt(text.length - 1)
359             badEnding = true
360         }
361         sb.append("<a href = \"")
362         sb.append(tempText)
363         sb.append("\">")
364         sb.append(tempText)
365         sb.append("</a>")
366
367         if(badEnding)
368             sb.append(lastChar)
369
370         return sb.toString
371     }
372
373     protected static def formatToParagraph(String text) {
374         if(text === null || text.isEmpty)
375             return text
376
377         var formattedText = text
378         val StringBuilder sb = new StringBuilder();
379         var StringBuilder lineBuilder = new StringBuilder();
380         var boolean isFirstElementOnNewLineEmptyChar = false;
381
382         formattedText = encodeJavadocSymbols(formattedText)
383         formattedText = WS_MATCHER.replaceFrom(formattedText, SPACE)
384         formattedText = SPACES_PATTERN.matcher(formattedText).replaceAll(" ")
385
386         val StringTokenizer tokenizer = new StringTokenizer(formattedText, " ", true);
387
388         while (tokenizer.hasMoreTokens) {
389             val nextElement = tokenizer.nextToken
390
391             if (lineBuilder.length != 0 && lineBuilder.length + nextElement.length > 80) {
392                 if (lineBuilder.charAt(lineBuilder.length - 1) == SPACE) {
393                     lineBuilder.setLength(lineBuilder.length - 1)
394                 }
395                 if (lineBuilder.length != 0 && lineBuilder.charAt(0) == SPACE) {
396                     lineBuilder.deleteCharAt(0)
397                 }
398
399                 sb.append(lineBuilder).append(NEW_LINE)
400                 lineBuilder.setLength(0)
401
402                 if (nextElement == " ") {
403                     isFirstElementOnNewLineEmptyChar = !isFirstElementOnNewLineEmptyChar;
404                 }
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     def protected generateToString(Collection<? extends GeneratedProperty> properties) '''
418         «IF !properties.empty»
419             @«OVERRIDE.importedName»
420             public «STRING.importedName» toString() {
421                 final «MoreObjects.importedName».ToStringHelper helper = «MoreObjects.importedName».toStringHelper(«type.importedName».class);
422                 «FOR property : properties»
423                     «CODEHELPERS.importedName».appendValue(helper, "«property.fieldName»", «property.fieldName»);
424                 «ENDFOR»
425                 return helper.toString();
426             }
427         «ENDIF»
428     '''
429
430     /**
431      * Template method which generates method parameters with their types from <code>parameters</code>.
432      *
433      * @param parameters
434      * list of parameter instances which are transformed to the method parameters
435      * @return string with the list of the method parameters with their types in JAVA format
436      */
437     def protected generateParameters(List<MethodSignature.Parameter> parameters) '''«
438         IF !parameters.empty»«
439             FOR parameter : parameters SEPARATOR ", "»«
440                 parameter.type.importedName» «parameter.name»«
441             ENDFOR»«
442         ENDIF
443     »'''
444
445     def protected emitConstant(Constant c) '''
446         «IF BindingMapping.QNAME_STATIC_FIELD_NAME.equals(c.name)»
447             «val entry = c.value as Entry<JavaTypeName, String>»
448             public static final «c.type.importedNonNull» «c.name» = «entry.key.importedName».«BindingMapping.MODULE_INFO_QNAMEOF_METHOD_NAME»("«entry.value»");
449         «ELSE»
450             public static final «c.type.importedName» «c.name» = «c.value»;
451         «ENDIF»
452     '''
453
454     def protected generateCheckers(GeneratedProperty field, Restrictions restrictions, Type actualType) '''
455        «IF restrictions.rangeConstraint.present»
456            «AbstractRangeGenerator.forType(actualType).generateRangeChecker(field.name.toFirstUpper,
457                restrictions.rangeConstraint.get, this)»
458        «ENDIF»
459        «IF restrictions.lengthConstraint.present»
460            «LengthGenerator.generateLengthChecker(field.fieldName, actualType, restrictions.lengthConstraint.get, this)»
461        «ENDIF»
462     '''
463
464     def protected checkArgument(GeneratedProperty property, Restrictions restrictions, Type actualType, String value) '''
465        «IF restrictions.getRangeConstraint.isPresent»
466            «IF actualType instanceof ConcreteType»
467                «AbstractRangeGenerator.forType(actualType).generateRangeCheckerCall(property.getName.toFirstUpper, value)»
468            «ELSE»
469                «AbstractRangeGenerator.forType(actualType).generateRangeCheckerCall(property.getName.toFirstUpper, value + ".getValue()")»
470            «ENDIF»
471        «ENDIF»
472        «val fieldName = property.fieldName»
473        «IF restrictions.getLengthConstraint.isPresent»
474            «IF actualType instanceof ConcreteType»
475                «LengthGenerator.generateLengthCheckerCall(fieldName, value)»
476            «ELSE»
477                «LengthGenerator.generateLengthCheckerCall(fieldName, value + ".getValue()")»
478            «ENDIF»
479        «ENDIF»
480
481        «val fieldUpperCase = fieldName.toUpperCase(Locale.ENGLISH)»
482        «FOR currentConstant : type.getConstantDefinitions»
483            «IF currentConstant.getName.startsWith(TypeConstants.PATTERN_CONSTANT_NAME)
484                && fieldUpperCase.equals(currentConstant.getName.substring(TypeConstants.PATTERN_CONSTANT_NAME.length))»
485            «CODEHELPERS.importedName».checkPattern(value, «Constants.MEMBER_PATTERN_LIST»«fieldName», «Constants.MEMBER_REGEX_LIST»«fieldName»);
486            «ENDIF»
487        «ENDFOR»
488     '''
489
490     def protected hashCodeResult(Collection<? extends GeneratedProperty> properties) '''
491         final int prime = 31;
492         int result = 1;
493         «FOR property : properties»
494             result = prime * result + «property.importedUtilClass».hashCode(«property.fieldName»);
495         «ENDFOR»
496     '''
497
498     def protected final generateAnnotation(AnnotationType annotation) '''
499         @«annotation.importedName»
500         «IF annotation.parameters !== null && !annotation.parameters.empty»
501         (
502         «FOR param : annotation.parameters SEPARATOR ","»
503             «param.name»=«param.value»
504         «ENDFOR»
505         )
506         «ENDIF»
507     '''
508 }