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