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