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