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