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