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