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
11 import static org.opendaylight.mdsal.binding.model.util.Types.STRING;
13 import com.google.common.base.CharMatcher
14 import com.google.common.base.MoreObjects
15 import com.google.common.base.Splitter
16 import com.google.common.collect.ImmutableMap
17 import com.google.common.collect.Iterables
18 import java.math.BigInteger
19 import java.util.Collection
21 import java.util.Locale
22 import java.util.Map.Entry
23 import java.util.StringTokenizer
24 import java.util.regex.Pattern
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.YangSourceDefinition.Single
37 import org.opendaylight.mdsal.binding.model.api.YangSourceDefinition.Multiple
38 import org.opendaylight.mdsal.binding.model.util.BindingGeneratorUtil
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
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)
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)
81 new(GeneratedType type) {
85 new(AbstractJavaGeneratedType javaType, GeneratedType type) {
89 final def generate() {
92 package «type.packageName»;
99 protected abstract def CharSequence body();
102 final protected def fieldName(GeneratedProperty property) {
106 final protected def propertyNameFromGetter(MethodSignature getter) {
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
115 throw new IllegalArgumentException(getter + " is not a getter")
117 return getter.name.substring(prefix.length).toFirstLower;
121 * Template method which generates the getter method for <code>field</code>
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
127 protected def getterMethod(GeneratedProperty field) {
129 public «field.returnType.importedName» «field.getterMethodName»() {
130 «val fieldName = field.fieldName»
131 «IF field.returnType.name.endsWith("[]")»
132 return «fieldName» == null ? null : «fieldName».clone();
140 final protected def getterMethodName(GeneratedProperty field) {
141 val prefix = if(field.returnType.equals(Types.BOOLEAN)) "is" else "get"
142 return '''«prefix»«field.name.toFirstUpper»'''
146 * Template method which generates the setter method for <code>field</code>
149 * generated property with data about field which is generated as the setter method
150 * @return string with the setter method source code in JAVA format
152 final protected def setterMethod(GeneratedProperty field) '''
153 «val returnType = field.returnType.importedName»
154 public «type.name» set«field.name.toFirstUpper»(«returnType» value) {
155 this.«field.fieldName» = value;
161 * Template method which generates method parameters with their types from <code>parameters</code>.
164 * 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
167 def final protected asArgumentsDeclaration(Iterable<GeneratedProperty> parameters) '''«IF !parameters.empty»«FOR parameter : parameters SEPARATOR ", "»«parameter.
168 returnType.importedName» «parameter.fieldName»«ENDFOR»«ENDIF»'''
171 * Template method which generates sequence of the names of the class attributes from <code>parameters</code>.
174 * group of generated property instances which are transformed to the sequence of parameter names
175 * @return string with the list of the parameter names of the <code>parameters</code>
177 def final protected asArguments(Collection<GeneratedProperty> parameters) '''«IF !parameters.empty»«FOR parameter : parameters SEPARATOR ", "»«parameter.
178 fieldName»«ENDFOR»«ENDIF»'''
181 * Template method which generates JAVA comments.
183 * @param comment string with the comment for whole JAVA class
184 * @return string with comment in JAVA format
186 def protected CharSequence asJavadoc(String comment) {
187 if (comment === null) {
191 «wrapToDocumentation(formatToParagraph(comment.trim))»
195 def static String wrapToDocumentation(String text) {
199 val StringBuilder sb = new StringBuilder().append("/**\n")
200 for (String t : NL_SPLITTER.split(text)) {
203 sb.append(SPACE).append(t)
212 def protected String formatDataForJavaDoc(GeneratedType type) {
213 val sb = new StringBuilder()
214 val comment = type.comment
215 if (comment !== null) {
216 sb.append(comment.javadoc)
219 appendSnippet(sb, type)
228 def static encodeJavadocSymbols(String description) {
229 if (description.nullOrEmpty) {
233 return TAIL_COMMENT_PATTERN.matcher(AMP_MATCHER.replaceFrom(description, "&")).replaceAll("*/")
236 def protected String formatDataForJavaDoc(GeneratedType type, String additionalComment) {
237 val comment = type.comment
238 if (comment === null) {
244 val sb = new StringBuilder().append(comment.javadoc)
245 appendSnippet(sb, type)
250 .append(additionalComment)
257 def private static void appendSnippet(StringBuilder sb, GeneratedType type) {
258 val optDef = type.yangSourceDefinition
259 if (optDef.present) {
263 if (def instanceof Single) {
266 .append("This class represents the following YANG schema fragment defined in module <b>")
267 .append(def.module.argument).append("</b>\n")
269 appendYangSnippet(sb, def.module, (node as EffectiveStatement<?, ?>).declared)
272 if (node instanceof SchemaNode) {
273 sb.append("The schema path to identify an instance is\n")
275 .append(formatSchemaPath(def.module.argument, node.path.pathFromRoot))
278 if (hasBuilderClass(node)) {
279 val builderName = type.name + "Builder";
281 sb.append("\n<p>To create instances of this class use {@link ").append(builderName)
283 .append("@see ").append(builderName).append('\n')
284 if (node instanceof ListSchemaNode) {
285 val keyDef = node.keyDefinition
286 if (keyDef !== null && !keyDef.empty) {
287 sb.append("@see ").append(type.name).append("Key")
293 } else if (def instanceof Multiple) {
295 for (SchemaNode node : def.nodes) {
296 appendYangSnippet(sb, def.module, (node as EffectiveStatement<?, ?>).declared)
298 sb.append("</pre>\n")
303 def private static void appendYangSnippet(StringBuilder sb, ModuleEffectiveStatement module,
304 DeclaredStatement<?> stmt) {
305 for (String str : YANG_FORMATTER.toYangTextSnippet(module, stmt)) {
306 sb.append(BindingGeneratorUtil.replaceAllIllegalChars(encodeAngleBrackets(encodeJavadocSymbols(str))))
310 def private static boolean hasBuilderClass(SchemaNode schemaNode) {
311 return schemaNode instanceof ContainerSchemaNode || schemaNode instanceof ListSchemaNode
312 || schemaNode instanceof RpcDefinition || schemaNode instanceof NotificationDefinition;
315 def private static String formatSchemaPath(String moduleName, Iterable<QName> schemaPath) {
316 val sb = new StringBuilder().append(moduleName);
318 var currentElement = Iterables.getFirst(schemaPath, null);
319 for (QName pathElement : schemaPath) {
321 if (!currentElement.namespace.equals(pathElement.namespace)) {
322 currentElement = pathElement
323 sb.append(pathElement)
325 sb.append(pathElement.getLocalName())
328 return sb.toString();
331 def protected static String formatDataForJavaDoc(TypeMember type, String additionalComment) {
332 val StringBuilder typeDescriptionBuilder = new StringBuilder();
333 if (!type.comment.nullOrEmpty) {
334 typeDescriptionBuilder.append(formatToParagraph(type.comment))
335 typeDescriptionBuilder.append(NEW_LINE)
336 typeDescriptionBuilder.append(NEW_LINE)
337 typeDescriptionBuilder.append(NEW_LINE)
339 typeDescriptionBuilder.append(additionalComment)
340 var typeDescription = wrapToDocumentation(typeDescriptionBuilder.toString)
346 def asCode(String text) {
347 return "<code>" + text + "</code>"
350 def asLink(String text) {
351 val StringBuilder sb = new StringBuilder()
353 var char lastChar = SPACE
354 var boolean badEnding = false
356 if (text.endsWith('.') || text.endsWith(':') || text.endsWith(',')) {
357 tempText = text.substring(0, text.length - 1)
358 lastChar = text.charAt(text.length - 1)
361 sb.append("<a href = \"")
373 protected static def formatToParagraph(String text) {
374 if(text === null || text.isEmpty)
377 var formattedText = text
378 val StringBuilder sb = new StringBuilder();
379 var StringBuilder lineBuilder = new StringBuilder();
380 var boolean isFirstElementOnNewLineEmptyChar = false;
382 formattedText = encodeJavadocSymbols(formattedText)
383 formattedText = WS_MATCHER.replaceFrom(formattedText, SPACE)
384 formattedText = SPACES_PATTERN.matcher(formattedText).replaceAll(" ")
386 val StringTokenizer tokenizer = new StringTokenizer(formattedText, " ", true);
388 while (tokenizer.hasMoreTokens) {
389 val nextElement = tokenizer.nextToken
391 if (lineBuilder.length != 0 && lineBuilder.length + nextElement.length > 80) {
392 if (lineBuilder.charAt(lineBuilder.length - 1) == SPACE) {
393 lineBuilder.setLength(lineBuilder.length - 1)
395 if (lineBuilder.length != 0 && lineBuilder.charAt(0) == SPACE) {
396 lineBuilder.deleteCharAt(0)
399 sb.append(lineBuilder).append(NEW_LINE)
400 lineBuilder.setLength(0)
402 if (nextElement == " ") {
403 isFirstElementOnNewLineEmptyChar = !isFirstElementOnNewLineEmptyChar;
407 if (isFirstElementOnNewLineEmptyChar) {
408 isFirstElementOnNewLineEmptyChar = !isFirstElementOnNewLineEmptyChar
410 lineBuilder.append(nextElement)
414 return sb.append(lineBuilder).append(NEW_LINE).toString
417 def protected generateToString(Collection<? extends GeneratedProperty> properties) '''
418 «IF !properties.empty»
419 @«OVERRIDE.importedName»
420 public «STRING.importedName» toString() {
421 final «MoreObjects.importedName».ToStringHelper helper = «MoreObjects.importedName».toStringHelper(«type.importedName».class);
422 «FOR property : properties»
423 «CODEHELPERS.importedName».appendValue(helper, "«property.fieldName»", «property.fieldName»);
425 return helper.toString();
431 * Template method which generates method parameters with their types from <code>parameters</code>.
434 * list of parameter instances which are transformed to the method parameters
435 * @return string with the list of the method parameters with their types in JAVA format
437 def protected generateParameters(List<MethodSignature.Parameter> parameters) '''«
438 IF !parameters.empty»«
439 FOR parameter : parameters SEPARATOR ", "»«
440 parameter.type.importedName» «parameter.name»«
445 def protected emitConstant(Constant c) '''
446 «IF BindingMapping.QNAME_STATIC_FIELD_NAME.equals(c.name)»
447 «val entry = c.value as Entry<JavaTypeName, String>»
448 public static final «c.type.importedNonNull» «c.name» = «entry.key.importedName».«BindingMapping.MODULE_INFO_QNAMEOF_METHOD_NAME»("«entry.value»");
450 public static final «c.type.importedName» «c.name» = «c.value»;
454 def protected generateCheckers(GeneratedProperty field, Restrictions restrictions, Type actualType) '''
455 «IF restrictions.rangeConstraint.present»
456 «AbstractRangeGenerator.forType(actualType).generateRangeChecker(field.name.toFirstUpper,
457 restrictions.rangeConstraint.get, this)»
459 «IF restrictions.lengthConstraint.present»
460 «LengthGenerator.generateLengthChecker(field.fieldName, actualType, restrictions.lengthConstraint.get, this)»
464 def protected checkArgument(GeneratedProperty property, Restrictions restrictions, Type actualType, String value) '''
465 «IF restrictions.getRangeConstraint.isPresent»
466 «IF actualType instanceof ConcreteType»
467 «AbstractRangeGenerator.forType(actualType).generateRangeCheckerCall(property.getName.toFirstUpper, value)»
469 «AbstractRangeGenerator.forType(actualType).generateRangeCheckerCall(property.getName.toFirstUpper, value + ".getValue()")»
472 «val fieldName = property.fieldName»
473 «IF restrictions.getLengthConstraint.isPresent»
474 «IF actualType instanceof ConcreteType»
475 «LengthGenerator.generateLengthCheckerCall(fieldName, value)»
477 «LengthGenerator.generateLengthCheckerCall(fieldName, value + ".getValue()")»
481 «val fieldUpperCase = fieldName.toUpperCase(Locale.ENGLISH)»
482 «FOR currentConstant : type.getConstantDefinitions»
483 «IF currentConstant.getName.startsWith(TypeConstants.PATTERN_CONSTANT_NAME)
484 && fieldUpperCase.equals(currentConstant.getName.substring(TypeConstants.PATTERN_CONSTANT_NAME.length))»
485 «CODEHELPERS.importedName».checkPattern(value, «Constants.MEMBER_PATTERN_LIST»«fieldName», «Constants.MEMBER_REGEX_LIST»«fieldName»);
490 def protected hashCodeResult(Collection<? extends GeneratedProperty> properties) '''
491 final int prime = 31;
493 «FOR property : properties»
494 result = prime * result + «property.importedUtilClass».hashCode(«property.fieldName»);
498 def protected final generateAnnotation(AnnotationType annotation) '''
499 @«annotation.importedName»
500 «IF annotation.parameters !== null && !annotation.parameters.empty»
502 «FOR param : annotation.parameters SEPARATOR ","»
503 «param.name»=«param.value»