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