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