Optimize whitespace replacement
[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 final char SPACE = ' '
51     static val AMP_MATCHER = CharMatcher.is('&')
52     static val WS_MATCHER = CharMatcher.anyOf("\n\t")
53     static val SPACES_PATTERN = Pattern.compile(" +")
54     static val NL_SPLITTER = Splitter.on(NEW_LINE)
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(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.hasMoreElements) {
371             val nextElement = tokenizer.nextElement.toString
372
373             if (lineBuilder.length != 0 && lineBuilder.length + nextElement.length > 80) {
374                 // FIXME: what tricks are we playing here? Equality probably does not trigger ever
375                 //        and then the setLength()/append() combo does not work, either
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).append(NEW_LINE)
386                 lineBuilder.setLength(0)
387
388                 if (nextElement.toString == ' ') {
389                     isFirstElementOnNewLineEmptyChar = !isFirstElementOnNewLineEmptyChar;
390                 }
391             }
392
393             if (isFirstElementOnNewLineEmptyChar) {
394                 isFirstElementOnNewLineEmptyChar = !isFirstElementOnNewLineEmptyChar
395             } else {
396                 lineBuilder.append(nextElement)
397             }
398         }
399
400         return sb.append(lineBuilder).append(NEW_LINE).toString
401     }
402
403     def protected generateToString(Collection<GeneratedProperty> properties) '''
404         «IF !properties.empty»
405             @«Override.importedName»
406             public «String.importedName» toString() {
407                 final «MoreObjects.importedName».ToStringHelper helper = «MoreObjects.importedName».toStringHelper(«type.importedName».class);
408                 «FOR property : properties»
409                     «CodeHelpers.importedName».appendValue(helper, "«property.fieldName»", «property.fieldName»);
410                 «ENDFOR»
411                 return helper.toString();
412             }
413         «ENDIF»
414     '''
415
416     /**
417      * Template method which generates method parameters with their types from <code>parameters</code>.
418      *
419      * @param parameters
420      * list of parameter instances which are transformed to the method parameters
421      * @return string with the list of the method parameters with their types in JAVA format
422      */
423     def protected generateParameters(List<MethodSignature.Parameter> parameters) '''«
424         IF !parameters.empty»«
425             FOR parameter : parameters SEPARATOR ", "»«
426                 parameter.type.importedName» «parameter.name»«
427             ENDFOR»«
428         ENDIF
429     »'''
430
431     def protected emitConstant(Constant c) '''
432         «IF BindingMapping.QNAME_STATIC_FIELD_NAME.equals(c.name)»
433             «val entry = c.value as Entry<JavaTypeName, String>»
434             public static final «c.type.importedNonNull» «c.name» = «entry.key.importedName».«BindingMapping.MODULE_INFO_QNAMEOF_METHOD_NAME»("«entry.value»");
435         «ELSE»
436             public static final «c.type.importedName» «c.name» = «c.value»;
437         «ENDIF»
438     '''
439
440     def protected generateCheckers(GeneratedProperty field, Restrictions restrictions, Type actualType) '''
441        «IF restrictions.rangeConstraint.present»
442            «AbstractRangeGenerator.forType(actualType).generateRangeChecker(field.name.toFirstUpper,
443                restrictions.rangeConstraint.get, this)»
444        «ENDIF»
445        «IF restrictions.lengthConstraint.present»
446            «LengthGenerator.generateLengthChecker(field.fieldName.toString, actualType, restrictions.lengthConstraint.get, this)»
447        «ENDIF»
448     '''
449
450     def protected checkArgument(GeneratedProperty property, Restrictions restrictions, Type actualType, String value) '''
451        «IF restrictions.getRangeConstraint.isPresent»
452            «IF actualType instanceof ConcreteType»
453                «AbstractRangeGenerator.forType(actualType).generateRangeCheckerCall(property.getName.toFirstUpper, value)»
454            «ELSE»
455                «AbstractRangeGenerator.forType(actualType).generateRangeCheckerCall(property.getName.toFirstUpper, value + ".getValue()")»
456            «ENDIF»
457        «ENDIF»
458        «IF restrictions.getLengthConstraint.isPresent»
459            «IF actualType instanceof ConcreteType»
460                «LengthGenerator.generateLengthCheckerCall(property.fieldName.toString, value)»
461            «ELSE»
462                «LengthGenerator.generateLengthCheckerCall(property.fieldName.toString, value + ".getValue()")»
463            «ENDIF»
464        «ENDIF»
465
466        «val fieldUpperCase = property.fieldName.toString.toUpperCase()»
467        «FOR currentConstant : type.getConstantDefinitions»
468            «IF currentConstant.getName.startsWith(TypeConstants.PATTERN_CONSTANT_NAME)
469                && fieldUpperCase.equals(currentConstant.getName.substring(TypeConstants.PATTERN_CONSTANT_NAME.length))»
470            «CodeHelpers.importedName».checkPattern(value, «Constants.MEMBER_PATTERN_LIST»«property.fieldName», «Constants.MEMBER_REGEX_LIST»«property.fieldName»);
471            «ENDIF»
472        «ENDFOR»
473     '''
474
475     def protected hashCodeResult(Collection<GeneratedProperty> properties) '''
476         final int prime = 31;
477         int result = 1;
478         «FOR property : properties»
479             result = prime * result + «property.importedUtilClass».hashCode(«property.fieldName»);
480         «ENDFOR»
481     '''
482 }