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