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 org.opendaylight.mdsal.binding.model.util.BindingGeneratorUtil.encodeAngleBrackets
12 import com.google.common.base.CharMatcher
13 import com.google.common.base.Splitter
14 import com.google.common.collect.ImmutableMap
15 import com.google.common.collect.Iterables
16 import java.math.BigInteger
17 import java.util.Collection
19 import java.util.Locale
20 import java.util.Map.Entry
21 import java.util.StringTokenizer
22 import java.util.regex.Pattern
23 import org.eclipse.jdt.annotation.NonNull;
24 import org.gaul.modernizer_maven_annotations.SuppressModernizer
25 import org.opendaylight.mdsal.binding.model.api.AnnotationType
26 import org.opendaylight.mdsal.binding.model.api.ConcreteType
27 import org.opendaylight.mdsal.binding.model.api.Constant
28 import org.opendaylight.mdsal.binding.model.api.GeneratedProperty
29 import org.opendaylight.mdsal.binding.model.api.GeneratedType
30 import org.opendaylight.mdsal.binding.model.api.JavaTypeName
31 import org.opendaylight.mdsal.binding.model.api.MethodSignature
32 import org.opendaylight.mdsal.binding.model.api.Restrictions
33 import org.opendaylight.mdsal.binding.model.api.Type
34 import org.opendaylight.mdsal.binding.model.api.TypeMember
35 import org.opendaylight.mdsal.binding.model.api.YangSourceDefinition.Single
36 import org.opendaylight.mdsal.binding.model.api.YangSourceDefinition.Multiple
37 import org.opendaylight.mdsal.binding.model.util.BindingGeneratorUtil
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 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) {
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();
139 final protected def getterMethodName(GeneratedProperty field) {
140 val prefix = if(field.returnType.equals(Types.BOOLEAN)) "is" else "get"
141 return '''«prefix»«field.name.toFirstUpper»'''
145 * Template method which generates the setter method for <code>field</code>
148 * generated property with data about field which is generated as the setter method
149 * @return string with the setter method source code in JAVA format
151 final protected def setterMethod(GeneratedProperty field) '''
152 «val returnType = field.returnType.importedName»
153 public «type.name» set«field.name.toFirstUpper»(«returnType» value) {
154 this.«field.fieldName» = value;
160 * Template method which generates method parameters with their types from <code>parameters</code>.
163 * group of generated property instances which are transformed to the method parameters
164 * @return string with the list of the method parameters with their types in JAVA format
166 def final protected asArgumentsDeclaration(Iterable<GeneratedProperty> parameters) '''«IF !parameters.empty»«FOR parameter : parameters SEPARATOR ", "»«parameter.
167 returnType.importedName» «parameter.fieldName»«ENDFOR»«ENDIF»'''
170 * Template method which generates method parameters with their types from <code>parameters</code>, annotating them
171 * with {@link NonNull}.
173 * @param parameters group of generated property instances which are transformed to the method parameters
174 * @return string with the list of the method parameters with their types in JAVA format
176 def final protected asNonNullArgumentsDeclaration(Iterable<GeneratedProperty> parameters) '''«IF !parameters.empty»
177 «FOR parameter : parameters SEPARATOR ", "»«parameter.returnType.importedNonNull» «parameter
178 .fieldName»«ENDFOR»«ENDIF»'''
181 * Template method which generates sequence of the names of the class attributes from <code>parameters</code>.
184 * group of generated property instances which are transformed to the sequence of parameter names
185 * @return string with the list of the parameter names of the <code>parameters</code>
187 def final protected asArguments(Collection<GeneratedProperty> parameters) '''«IF !parameters.empty»«FOR parameter : parameters SEPARATOR ", "»«parameter.
188 fieldName»«ENDFOR»«ENDIF»'''
191 * Template method which generates JAVA comments.
193 * @param comment string with the comment for whole JAVA class
194 * @return string with comment in JAVA format
196 def protected CharSequence asJavadoc(String comment) {
197 if (comment === null) {
201 «wrapToDocumentation(formatToParagraph(comment.trim))»
205 def static String wrapToDocumentation(String text) {
209 val StringBuilder sb = new StringBuilder().append("/**\n")
210 for (String t : NL_SPLITTER.split(text)) {
213 sb.append(SPACE).append(t)
222 def protected String formatDataForJavaDoc(GeneratedType type) {
223 val sb = new StringBuilder()
224 val comment = type.comment
225 if (comment !== null) {
226 sb.append(comment.javadoc)
229 appendSnippet(sb, type)
238 def static encodeJavadocSymbols(String description) {
239 if (description.nullOrEmpty) {
243 return TAIL_COMMENT_PATTERN.matcher(AMP_MATCHER.replaceFrom(description, "&")).replaceAll("*/")
246 def protected String formatDataForJavaDoc(GeneratedType type, String additionalComment) {
247 val comment = type.comment
248 if (comment === null) {
254 val sb = new StringBuilder().append(comment.javadoc)
255 appendSnippet(sb, type)
260 .append(additionalComment)
267 def private static void appendSnippet(StringBuilder sb, GeneratedType type) {
268 val optDef = type.yangSourceDefinition
269 if (optDef.present) {
273 if (def instanceof Single) {
276 .append("This class represents the following YANG schema fragment defined in module <b>")
277 .append(def.module.argument).append("</b>\n")
279 appendYangSnippet(sb, def.module, (node as EffectiveStatement<?, ?>).declared)
282 if (node instanceof SchemaNode) {
283 sb.append("The schema path to identify an instance is\n")
285 .append(formatSchemaPath(def.module.argument, node.path.pathFromRoot))
288 if (hasBuilderClass(node)) {
289 val builderName = type.name + "Builder";
291 sb.append("\n<p>To create instances of this class use {@link ").append(builderName)
293 .append("@see ").append(builderName).append('\n')
294 if (node instanceof ListSchemaNode) {
295 val keyDef = node.keyDefinition
296 if (keyDef !== null && !keyDef.empty) {
297 sb.append("@see ").append(type.name).append("Key")
303 } else if (def instanceof Multiple) {
305 for (SchemaNode node : def.nodes) {
306 appendYangSnippet(sb, def.module, (node as EffectiveStatement<?, ?>).declared)
308 sb.append("</pre>\n")
313 def private static void appendYangSnippet(StringBuilder sb, ModuleEffectiveStatement module,
314 DeclaredStatement<?> stmt) {
315 for (String str : YANG_FORMATTER.toYangTextSnippet(module, stmt)) {
316 sb.append(BindingGeneratorUtil.replaceAllIllegalChars(encodeAngleBrackets(encodeJavadocSymbols(str))))
320 def private static boolean hasBuilderClass(SchemaNode schemaNode) {
321 return schemaNode instanceof ContainerSchemaNode || schemaNode instanceof ListSchemaNode
322 || schemaNode instanceof RpcDefinition || schemaNode instanceof NotificationDefinition;
325 def private static String formatSchemaPath(String moduleName, Iterable<QName> schemaPath) {
326 val sb = new StringBuilder().append(moduleName);
328 var currentElement = Iterables.getFirst(schemaPath, null);
329 for (QName pathElement : schemaPath) {
331 if (!currentElement.namespace.equals(pathElement.namespace)) {
332 currentElement = pathElement
333 sb.append(pathElement)
335 sb.append(pathElement.getLocalName())
338 return sb.toString();
341 def protected static String formatDataForJavaDoc(TypeMember type, String additionalComment) {
342 val StringBuilder typeDescriptionBuilder = new StringBuilder();
343 if (!type.comment.nullOrEmpty) {
344 typeDescriptionBuilder.append(formatToParagraph(type.comment))
345 typeDescriptionBuilder.append(NEW_LINE)
346 typeDescriptionBuilder.append(NEW_LINE)
347 typeDescriptionBuilder.append(NEW_LINE)
349 typeDescriptionBuilder.append(additionalComment)
350 var typeDescription = wrapToDocumentation(typeDescriptionBuilder.toString)
356 def asCode(String text) {
357 return "<code>" + text + "</code>"
360 def asLink(String text) {
361 val StringBuilder sb = new StringBuilder()
363 var char lastChar = SPACE
364 var boolean badEnding = false
366 if (text.endsWith('.') || text.endsWith(':') || text.endsWith(',')) {
367 tempText = text.substring(0, text.length - 1)
368 lastChar = text.charAt(text.length - 1)
371 sb.append("<a href = \"")
383 protected static def formatToParagraph(String text) {
384 if(text === null || text.isEmpty)
387 var formattedText = text
388 val StringBuilder sb = new StringBuilder();
389 var StringBuilder lineBuilder = new StringBuilder();
390 var boolean isFirstElementOnNewLineEmptyChar = false;
392 formattedText = encodeJavadocSymbols(formattedText)
393 formattedText = WS_MATCHER.replaceFrom(formattedText, SPACE)
394 formattedText = SPACES_PATTERN.matcher(formattedText).replaceAll(" ")
396 val StringTokenizer tokenizer = new StringTokenizer(formattedText, " ", true);
398 while (tokenizer.hasMoreTokens) {
399 val nextElement = tokenizer.nextToken
401 if (lineBuilder.length != 0 && lineBuilder.length + nextElement.length > 80) {
402 if (lineBuilder.charAt(lineBuilder.length - 1) == SPACE) {
403 lineBuilder.setLength(lineBuilder.length - 1)
405 if (lineBuilder.length != 0 && lineBuilder.charAt(0) == SPACE) {
406 lineBuilder.deleteCharAt(0)
409 sb.append(lineBuilder).append(NEW_LINE)
410 lineBuilder.setLength(0)
412 if (nextElement == " ") {
413 isFirstElementOnNewLineEmptyChar = !isFirstElementOnNewLineEmptyChar;
417 if (isFirstElementOnNewLineEmptyChar) {
418 isFirstElementOnNewLineEmptyChar = !isFirstElementOnNewLineEmptyChar
420 lineBuilder.append(nextElement)
424 return sb.append(lineBuilder).append(NEW_LINE).toString
428 * Template method which generates method parameters with their types from <code>parameters</code>.
431 * list of parameter instances which are transformed to the method parameters
432 * @return string with the list of the method parameters with their types in JAVA format
434 def protected generateParameters(List<MethodSignature.Parameter> parameters) '''«
435 IF !parameters.empty»«
436 FOR parameter : parameters SEPARATOR ", "»«
437 parameter.type.importedName» «parameter.name»«
442 def protected emitConstant(Constant c) '''
443 «IF BindingMapping.QNAME_STATIC_FIELD_NAME.equals(c.name)»
444 «val entry = c.value as Entry<JavaTypeName, String>»
445 public static final «c.type.importedNonNull» «c.name» = «entry.key.importedName».«BindingMapping.MODULE_INFO_QNAMEOF_METHOD_NAME»("«entry.value»");
447 public static final «c.type.importedName» «c.name» = «c.value»;
451 def protected generateCheckers(GeneratedProperty field, Restrictions restrictions, Type actualType) '''
452 «IF restrictions.rangeConstraint.present»
453 «AbstractRangeGenerator.forType(actualType).generateRangeChecker(field.name.toFirstUpper,
454 restrictions.rangeConstraint.get, this)»
456 «IF restrictions.lengthConstraint.present»
457 «LengthGenerator.generateLengthChecker(field.fieldName, actualType, restrictions.lengthConstraint.get, this)»
461 def protected checkArgument(GeneratedProperty property, Restrictions restrictions, Type actualType, String value) '''
462 «IF restrictions.getRangeConstraint.isPresent»
463 «IF actualType instanceof ConcreteType»
464 «AbstractRangeGenerator.forType(actualType).generateRangeCheckerCall(property.getName.toFirstUpper, value)»
466 «AbstractRangeGenerator.forType(actualType).generateRangeCheckerCall(property.getName.toFirstUpper, value + ".getValue()")»
469 «val fieldName = property.fieldName»
470 «IF restrictions.getLengthConstraint.isPresent»
471 «IF actualType instanceof ConcreteType»
472 «LengthGenerator.generateLengthCheckerCall(fieldName, value)»
474 «LengthGenerator.generateLengthCheckerCall(fieldName, value + ".getValue()")»
478 «val fieldUpperCase = fieldName.toUpperCase(Locale.ENGLISH)»
479 «FOR currentConstant : type.getConstantDefinitions»
480 «IF currentConstant.getName.startsWith(TypeConstants.PATTERN_CONSTANT_NAME)
481 && fieldUpperCase.equals(currentConstant.getName.substring(TypeConstants.PATTERN_CONSTANT_NAME.length))»
482 «CODEHELPERS.importedName».checkPattern(value, «Constants.MEMBER_PATTERN_LIST»«fieldName», «Constants.MEMBER_REGEX_LIST»«fieldName»);
487 def protected hashCodeResult(Collection<? extends GeneratedProperty> properties) '''
488 final int prime = 31;
490 «FOR property : properties»
491 result = prime * result + «property.importedUtilClass».hashCode(«property.fieldName»);
495 def protected final generateAnnotation(AnnotationType annotation) '''
496 @«annotation.importedName»
497 «IF annotation.parameters !== null && !annotation.parameters.empty»
499 «FOR param : annotation.parameters SEPARATOR ","»
500 «param.name»=«param.value»