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