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