2 * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.mdsal.binding.java.api.generator
10 import static extension org.opendaylight.mdsal.binding.model.util.BindingGeneratorUtil.encodeAngleBrackets
11 import static extension org.opendaylight.mdsal.binding.model.util.BindingGeneratorUtil.replaceAllIllegalChars
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
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
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)
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)
80 new(GeneratedType type) {
84 new(AbstractJavaGeneratedType javaType, GeneratedType type) {
88 final def generate() {
91 package «type.packageName»;
98 protected abstract def CharSequence body();
101 final protected def fieldName(GeneratedProperty property) {
105 final protected static def propertyNameFromGetter(MethodSignature getter) {
107 if (getter.name.startsWith(BindingMapping.BOOLEAN_GETTER_PREFIX)) {
108 prefix = BindingMapping.BOOLEAN_GETTER_PREFIX
109 } else if (getter.name.startsWith(BindingMapping.GETTER_PREFIX)) {
110 prefix = BindingMapping.GETTER_PREFIX
111 } else if (getter.name.startsWith(BindingMapping.NONNULL_PREFIX)) {
112 prefix = BindingMapping.NONNULL_PREFIX
114 throw new IllegalArgumentException(getter + " is not a getter")
116 return getter.name.substring(prefix.length).toFirstLower;
120 * Template method which generates the getter method for <code>field</code>
123 * generated property with data about field which is generated as the getter method
124 * @return string with the getter method source code in JAVA format
126 protected def getterMethod(GeneratedProperty field) '''
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();
137 final protected def getterMethodName(GeneratedProperty field) {
138 val prefix = if(field.returnType.equals(Types.BOOLEAN)) "is" else "get"
139 return '''«prefix»«field.name.toFirstUpper»'''
143 * Template method which generates the setter method for <code>field</code>
146 * generated property with data about field which is generated as the setter method
147 * @return string with the setter method source code in JAVA format
149 final protected def setterMethod(GeneratedProperty field) '''
150 «val returnType = field.returnType.importedName»
151 public «type.name» set«field.name.toFirstUpper»(«returnType» value) {
152 this.«field.fieldName» = value;
158 * Template method which generates method parameters with their types from <code>parameters</code>.
161 * group of generated property instances which are transformed to the method parameters
162 * @return string with the list of the method parameters with their types in JAVA format
164 def final protected asArgumentsDeclaration(Iterable<GeneratedProperty> parameters) '''«IF !parameters.empty»«FOR parameter : parameters SEPARATOR ", "»«parameter.
165 returnType.importedName» «parameter.fieldName»«ENDFOR»«ENDIF»'''
168 * Template method which generates method parameters with their types from <code>parameters</code>, annotating them
169 * with {@link NonNull}.
171 * @param parameters group of generated property instances which are transformed to the method parameters
172 * @return string with the list of the method parameters with their types in JAVA format
174 def final protected asNonNullArgumentsDeclaration(Iterable<GeneratedProperty> parameters) '''«IF !parameters.empty»
175 «FOR parameter : parameters SEPARATOR ", "»«parameter.returnType.importedNonNull» «parameter
176 .fieldName»«ENDFOR»«ENDIF»'''
179 * Template method which generates sequence of the names of the class attributes from <code>parameters</code>.
182 * group of generated property instances which are transformed to the sequence of parameter names
183 * @return string with the list of the parameter names of the <code>parameters</code>
185 def final protected asArguments(Collection<GeneratedProperty> parameters) '''«IF !parameters.empty»«FOR parameter : parameters SEPARATOR ", "»«parameter.
186 fieldName»«ENDFOR»«ENDIF»'''
189 * Template method which generates JAVA comments.
191 * @param comment string with the comment for whole JAVA class
192 * @return string with comment in JAVA format
194 def final protected asJavadoc(TypeMemberComment comment) {
195 if (comment === null) {
198 return wrapToDocumentation('''
199 «comment.contractDescription»
201 «comment.referenceDescription.formatReference»
203 «comment.typeSignature»
207 def static String wrapToDocumentation(String text) {
211 val StringBuilder sb = new StringBuilder().append("/**\n")
212 for (String t : NL_SPLITTER.split(text)) {
215 sb.append(SPACE).append(t)
224 def protected String formatDataForJavaDoc(GeneratedType type) {
225 val sb = new StringBuilder()
226 val comment = type.comment
227 if (comment !== null) {
228 sb.append(comment.javadoc)
231 appendSnippet(sb, type)
240 def static encodeJavadocSymbols(String description) {
241 if (description.nullOrEmpty) {
245 return TAIL_COMMENT_PATTERN.matcher(AMP_MATCHER.replaceFrom(description, "&")).replaceAll("*/")
248 def protected String formatDataForJavaDoc(GeneratedType type, String additionalComment) {
249 val comment = type.comment
250 if (comment === null) {
256 val sb = new StringBuilder().append(comment.javadoc)
257 appendSnippet(sb, type)
262 .append(additionalComment)
269 def private static void appendSnippet(StringBuilder sb, GeneratedType type) {
270 val optDef = type.yangSourceDefinition
271 if (optDef.present) {
275 if (def instanceof Single) {
278 .append("This class represents the following YANG schema fragment defined in module <b>")
279 .append(def.module.argument).append("</b>\n")
281 appendYangSnippet(sb, def.module, (node as EffectiveStatement<?, ?>).declared)
284 if (node instanceof SchemaNode) {
285 sb.append("The schema path to identify an instance is\n")
287 .append(formatSchemaPath(def.module.argument.localName, node.path.pathFromRoot))
290 if (hasBuilderClass(node)) {
291 val builderName = type.name + "Builder";
293 sb.append("\n<p>To create instances of this class use {@link ").append(builderName)
295 .append("@see ").append(builderName).append('\n')
296 if (node instanceof ListSchemaNode) {
297 val keyDef = node.keyDefinition
298 if (keyDef !== null && !keyDef.empty) {
299 sb.append("@see ").append(type.name).append("Key")
305 } else if (def instanceof Multiple) {
307 for (SchemaNode node : def.nodes) {
308 appendYangSnippet(sb, def.module, (node as EffectiveStatement<?, ?>).declared)
310 sb.append("</pre>\n")
315 def private static void appendYangSnippet(StringBuilder sb, ModuleEffectiveStatement module,
316 DeclaredStatement<?> stmt) {
317 for (String str : YANG_FORMATTER.toYangTextSnippet(module, stmt)) {
318 sb.append(str.encodeJavadocSymbols.encodeAngleBrackets.replaceAllIllegalChars)
322 def private static boolean hasBuilderClass(SchemaNode schemaNode) {
323 return schemaNode instanceof ContainerSchemaNode || schemaNode instanceof ListSchemaNode
324 || schemaNode instanceof RpcDefinition || schemaNode instanceof NotificationDefinition;
327 def private static String formatSchemaPath(String moduleName, Iterable<QName> schemaPath) {
328 val sb = new StringBuilder().append(moduleName);
330 var currentElement = Iterables.getFirst(schemaPath, null);
331 for (QName pathElement : schemaPath) {
333 if (!currentElement.namespace.equals(pathElement.namespace)) {
334 currentElement = pathElement
335 sb.append(pathElement)
337 sb.append(pathElement.getLocalName())
340 return sb.toString();
343 def static formatReference(String reference) '''
344 «IF reference !== null»
347 «reference.encodeAngleBrackets.formatToParagraph»
354 def asLink(String text) {
355 val StringBuilder sb = new StringBuilder()
357 var char lastChar = SPACE
358 var boolean badEnding = false
360 if (text.endsWith('.') || text.endsWith(':') || text.endsWith(',')) {
361 tempText = text.substring(0, text.length - 1)
362 lastChar = text.charAt(text.length - 1)
365 sb.append("<a href = \"")
377 protected static def formatToParagraph(String inputText) {
378 val StringBuilder sb = new StringBuilder();
379 var StringBuilder lineBuilder = new StringBuilder();
380 var boolean isFirstElementOnNewLineEmptyChar = false;
382 var formattedText = WS_MATCHER.replaceFrom(inputText.encodeJavadocSymbols, SPACE)
383 formattedText = SPACES_PATTERN.matcher(formattedText).replaceAll(" ")
385 val StringTokenizer tokenizer = new StringTokenizer(formattedText, " ", true)
386 while (tokenizer.hasMoreTokens) {
387 val nextElement = tokenizer.nextToken
389 if (lineBuilder.length != 0 && lineBuilder.length + nextElement.length > 80) {
390 if (lineBuilder.charAt(lineBuilder.length - 1) == SPACE) {
391 lineBuilder.setLength(lineBuilder.length - 1)
393 if (lineBuilder.length != 0 && lineBuilder.charAt(0) == SPACE) {
394 lineBuilder.deleteCharAt(0)
397 sb.append(lineBuilder).append(NEW_LINE)
398 lineBuilder.setLength(0)
400 if (nextElement == " ") {
401 isFirstElementOnNewLineEmptyChar = !isFirstElementOnNewLineEmptyChar;
404 if (isFirstElementOnNewLineEmptyChar) {
405 isFirstElementOnNewLineEmptyChar = !isFirstElementOnNewLineEmptyChar
407 lineBuilder.append(nextElement)
411 return sb.append(lineBuilder).append(NEW_LINE).toString
415 * Template method which generates method parameters with their types from <code>parameters</code>.
418 * list of parameter instances which are transformed to the method parameters
419 * @return string with the list of the method parameters with their types in JAVA format
421 def protected generateParameters(List<MethodSignature.Parameter> parameters) '''«
422 IF !parameters.empty»«
423 FOR parameter : parameters SEPARATOR ", "»«
424 parameter.type.importedName» «parameter.name»«
429 def protected emitConstant(Constant c) '''
430 «IF BindingMapping.QNAME_STATIC_FIELD_NAME.equals(c.name)»
431 «val entry = c.value as Entry<JavaTypeName, String>»
432 public static final «c.type.importedNonNull» «c.name» = «entry.key.importedName».«BindingMapping.MODULE_INFO_QNAMEOF_METHOD_NAME»("«entry.value»");
434 public static final «c.type.importedName» «c.name» = «c.value»;
438 def protected generateCheckers(GeneratedProperty field, Restrictions restrictions, Type actualType) '''
439 «IF restrictions.rangeConstraint.present»
440 «AbstractRangeGenerator.forType(actualType).generateRangeChecker(field.name.toFirstUpper,
441 restrictions.rangeConstraint.get, this)»
443 «IF restrictions.lengthConstraint.present»
444 «LengthGenerator.generateLengthChecker(field.fieldName, actualType, restrictions.lengthConstraint.get, this)»
448 def protected checkArgument(GeneratedProperty property, Restrictions restrictions, Type actualType, String value) '''
449 «IF restrictions.getRangeConstraint.isPresent»
450 «IF actualType instanceof ConcreteType»
451 «AbstractRangeGenerator.forType(actualType).generateRangeCheckerCall(property.getName.toFirstUpper, value)»
453 «AbstractRangeGenerator.forType(actualType).generateRangeCheckerCall(property.getName.toFirstUpper, value + ".getValue()")»
456 «val fieldName = property.fieldName»
457 «IF restrictions.getLengthConstraint.isPresent»
458 «IF actualType instanceof ConcreteType»
459 «LengthGenerator.generateLengthCheckerCall(fieldName, value)»
461 «LengthGenerator.generateLengthCheckerCall(fieldName, value + ".getValue()")»
465 «val fieldUpperCase = fieldName.toUpperCase(Locale.ENGLISH)»
466 «FOR currentConstant : type.getConstantDefinitions»
467 «IF currentConstant.getName.startsWith(TypeConstants.PATTERN_CONSTANT_NAME)
468 && fieldUpperCase.equals(currentConstant.getName.substring(TypeConstants.PATTERN_CONSTANT_NAME.length))»
469 «CODEHELPERS.importedName».checkPattern(value, «Constants.MEMBER_PATTERN_LIST»«fieldName», «Constants.MEMBER_REGEX_LIST»«fieldName»);
474 def protected hashCodeResult(Collection<? extends GeneratedProperty> properties) '''
475 final int prime = 31;
477 «FOR property : properties»
478 result = prime * result + «property.importedUtilClass».hashCode(«property.fieldName»);
482 def protected final generateAnnotation(AnnotationType annotation) '''
483 @«annotation.importedName»
484 «IF annotation.parameters !== null && !annotation.parameters.empty»
486 «FOR param : annotation.parameters SEPARATOR ","»
487 «param.name»=«param.value»