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