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