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.MoreObjects
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.gaul.modernizer_maven_annotations.SuppressModernizer
25 import org.opendaylight.mdsal.binding.model.api.ConcreteType
26 import org.opendaylight.mdsal.binding.model.api.Constant
27 import org.opendaylight.mdsal.binding.model.api.GeneratedProperty
28 import org.opendaylight.mdsal.binding.model.api.GeneratedType
29 import org.opendaylight.mdsal.binding.model.api.JavaTypeName
30 import org.opendaylight.mdsal.binding.model.api.MethodSignature
31 import org.opendaylight.mdsal.binding.model.api.Restrictions
32 import org.opendaylight.mdsal.binding.model.api.Type
33 import org.opendaylight.mdsal.binding.model.api.TypeMember
34 import org.opendaylight.mdsal.binding.model.api.YangSourceDefinition.Single
35 import org.opendaylight.mdsal.binding.model.api.YangSourceDefinition.Multiple
36 import org.opendaylight.mdsal.binding.model.util.TypeConstants
37 import org.opendaylight.mdsal.binding.model.util.Types
38 import org.opendaylight.mdsal.binding.spec.naming.BindingMapping
39 import org.opendaylight.yangtools.yang.binding.CodeHelpers
40 import org.opendaylight.yangtools.yang.common.QName
41 import org.opendaylight.yangtools.yang.common.Uint8
42 import org.opendaylight.yangtools.yang.common.Uint16
43 import org.opendaylight.yangtools.yang.common.Uint32
44 import org.opendaylight.yangtools.yang.common.Uint64
45 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode
46 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode
47 import org.opendaylight.yangtools.yang.model.api.NotificationDefinition
48 import org.opendaylight.yangtools.yang.model.api.RpcDefinition
49 import org.opendaylight.yangtools.yang.model.api.SchemaNode
50 import org.opendaylight.yangtools.yang.model.api.YangStmtMapping
51 import org.opendaylight.yangtools.yang.model.api.meta.DeclaredStatement
52 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement
53 import org.opendaylight.yangtools.yang.model.api.stmt.ModuleEffectiveStatement
54 import org.opendaylight.yangtools.yang.model.export.DeclaredStatementFormatter
57 abstract class BaseTemplate extends JavaFileTemplate {
58 static final char NEW_LINE = '\n'
59 static final char SPACE = ' '
60 static val AMP_MATCHER = CharMatcher.is('&')
61 static val WS_MATCHER = CharMatcher.anyOf("\n\t")
62 static val SPACES_PATTERN = Pattern.compile(" +")
63 static val NL_SPLITTER = Splitter.on(NEW_LINE)
64 static val TAIL_COMMENT_PATTERN = Pattern.compile("*/", Pattern.LITERAL);
65 static val YANG_FORMATTER = DeclaredStatementFormatter.builder()
66 .addIgnoredStatement(YangStmtMapping.CONTACT)
67 .addIgnoredStatement(YangStmtMapping.DESCRIPTION)
68 .addIgnoredStatement(YangStmtMapping.REFERENCE)
69 .addIgnoredStatement(YangStmtMapping.ORGANIZATION)
72 protected static val UINT_TYPES = ImmutableMap.of(
73 Types.typeForClass(Uint8), Types.typeForClass(Short),
74 Types.typeForClass(Uint16), Types.typeForClass(Integer),
75 Types.typeForClass(Uint32), Types.typeForClass(Long),
76 Types.typeForClass(Uint64), Types.typeForClass(BigInteger)
79 new(GeneratedType type) {
83 new(AbstractJavaGeneratedType javaType, GeneratedType type) {
87 final def generate() {
90 package «type.packageName»;
97 protected abstract def CharSequence body();
100 final protected def fieldName(GeneratedProperty property) '''_«property.name»'''
102 final protected def propertyNameFromGetter(MethodSignature getter) {
104 if (getter.name.startsWith(BindingMapping.BOOLEAN_GETTER_PREFIX)) {
105 prefix = BindingMapping.BOOLEAN_GETTER_PREFIX
106 } else if (getter.name.startsWith(BindingMapping.GETTER_PREFIX)) {
107 prefix = BindingMapping.GETTER_PREFIX
108 } else if (getter.name.startsWith(BindingMapping.NONNULL_PREFIX)) {
109 prefix = BindingMapping.NONNULL_PREFIX
111 throw new IllegalArgumentException(getter + " is not a getter")
113 return getter.name.substring(prefix.length).toFirstLower;
117 * Template method which generates the getter method for <code>field</code>
120 * generated property with data about field which is generated as the getter method
121 * @return string with the getter method source code in JAVA format
123 protected def getterMethod(GeneratedProperty field) {
125 public «field.returnType.importedName» «field.getterMethodName»() {
126 «val fieldName = field.fieldName»
127 «IF field.returnType.name.endsWith("[]")»
128 return «fieldName» == null ? null : «fieldName».clone();
136 final protected def getterMethodName(GeneratedProperty field) {
137 val prefix = if(field.returnType.equals(Types.BOOLEAN)) "is" else "get"
138 return '''«prefix»«field.name.toFirstUpper»'''
142 * Template method which generates the setter method for <code>field</code>
145 * generated property with data about field which is generated as the setter method
146 * @return string with the setter method source code in JAVA format
148 final protected def setterMethod(GeneratedProperty field) '''
149 «val returnType = field.returnType.importedName»
150 public «type.name» set«field.name.toFirstUpper»(«returnType» value) {
151 this.«field.fieldName» = value;
157 * Template method which generates method parameters with their types from <code>parameters</code>.
160 * group of generated property instances which are transformed to the method parameters
161 * @return string with the list of the method parameters with their types in JAVA format
163 def final protected asArgumentsDeclaration(Iterable<GeneratedProperty> parameters) '''«IF !parameters.empty»«FOR parameter : parameters SEPARATOR ", "»«parameter.
164 returnType.importedName» «parameter.fieldName»«ENDFOR»«ENDIF»'''
167 * Template method which generates sequence of the names of the class attributes from <code>parameters</code>.
170 * group of generated property instances which are transformed to the sequence of parameter names
171 * @return string with the list of the parameter names of the <code>parameters</code>
173 def final protected asArguments(Iterable<GeneratedProperty> parameters) '''«IF !parameters.empty»«FOR parameter : parameters SEPARATOR ", "»«parameter.
174 fieldName»«ENDFOR»«ENDIF»'''
177 * Template method which generates JAVA comments.
179 * @param comment string with the comment for whole JAVA class
180 * @return string with comment in JAVA format
182 def protected CharSequence asJavadoc(String comment) {
183 if (comment === null) {
187 «wrapToDocumentation(formatToParagraph(comment.trim))»
191 def static String wrapToDocumentation(String text) {
195 val StringBuilder sb = new StringBuilder().append("/**\n")
196 for (String t : NL_SPLITTER.split(text)) {
199 sb.append(SPACE).append(t)
208 def protected String formatDataForJavaDoc(GeneratedType type) {
209 val sb = new StringBuilder()
210 val comment = type.comment
211 if (comment !== null) {
212 sb.append(comment.javadoc)
215 appendSnippet(sb, type)
224 def static encodeJavadocSymbols(String description) {
225 if (description.nullOrEmpty) {
229 return TAIL_COMMENT_PATTERN.matcher(AMP_MATCHER.replaceFrom(description, "&")).replaceAll("*/")
232 def protected String formatDataForJavaDoc(GeneratedType type, String additionalComment) {
233 val comment = type.comment
234 if (comment === null) {
240 val sb = new StringBuilder().append(comment.javadoc)
241 appendSnippet(sb, type)
246 .append(additionalComment)
253 def private static void appendSnippet(StringBuilder sb, GeneratedType type) {
254 val optDef = type.yangSourceDefinition
255 if (optDef.present) {
259 if (def instanceof Single) {
262 .append("This class represents the following YANG schema fragment defined in module <b>")
263 .append(def.module.argument).append("</b>\n")
265 appendYangSnippet(sb, def.module, (node as EffectiveStatement<?, ?>).declared)
268 if (node instanceof SchemaNode) {
269 sb.append("The schema path to identify an instance is\n")
271 .append(formatSchemaPath(def.module.argument, node.path.pathFromRoot))
274 if (hasBuilderClass(node)) {
275 val builderName = type.name + "Builder";
277 sb.append("\n<p>To create instances of this class use {@link ").append(builderName)
279 .append("@see ").append(builderName).append('\n')
280 if (node instanceof ListSchemaNode) {
281 val keyDef = node.keyDefinition
282 if (keyDef !== null && !keyDef.empty) {
283 sb.append("@see ").append(type.name).append("Key")
289 } else if (def instanceof Multiple) {
291 for (SchemaNode node : def.nodes) {
292 appendYangSnippet(sb, def.module, (node as EffectiveStatement<?, ?>).declared)
294 sb.append("</pre>\n")
299 def private static void appendYangSnippet(StringBuilder sb, ModuleEffectiveStatement module,
300 DeclaredStatement<?> stmt) {
301 for (String str : YANG_FORMATTER.toYangTextSnippet(module, stmt)) {
302 sb.append(encodeAngleBrackets(encodeJavadocSymbols(str)))
306 def private static boolean hasBuilderClass(SchemaNode schemaNode) {
307 return schemaNode instanceof ContainerSchemaNode || schemaNode instanceof ListSchemaNode
308 || schemaNode instanceof RpcDefinition || schemaNode instanceof NotificationDefinition;
311 def private static String formatSchemaPath(String moduleName, Iterable<QName> schemaPath) {
312 val sb = new StringBuilder().append(moduleName);
314 var currentElement = Iterables.getFirst(schemaPath, null);
315 for (QName pathElement : schemaPath) {
317 if (!currentElement.namespace.equals(pathElement.namespace)) {
318 currentElement = pathElement
319 sb.append(pathElement)
321 sb.append(pathElement.getLocalName())
324 return sb.toString();
327 def protected static String formatDataForJavaDoc(TypeMember type, String additionalComment) {
328 val StringBuilder typeDescriptionBuilder = new StringBuilder();
329 if (!type.comment.nullOrEmpty) {
330 typeDescriptionBuilder.append(formatToParagraph(type.comment))
331 typeDescriptionBuilder.append(NEW_LINE)
332 typeDescriptionBuilder.append(NEW_LINE)
333 typeDescriptionBuilder.append(NEW_LINE)
335 typeDescriptionBuilder.append(additionalComment)
336 var typeDescription = wrapToDocumentation(typeDescriptionBuilder.toString)
342 def asCode(String text) {
343 return "<code>" + text + "</code>"
346 def asLink(String text) {
347 val StringBuilder sb = new StringBuilder()
349 var char lastChar = SPACE
350 var boolean badEnding = false
352 if (text.endsWith('.') || text.endsWith(':') || text.endsWith(',')) {
353 tempText = text.substring(0, text.length - 1)
354 lastChar = text.charAt(text.length - 1)
357 sb.append("<a href = \"")
369 protected static def formatToParagraph(String text) {
370 if(text === null || text.isEmpty)
373 var formattedText = text
374 val StringBuilder sb = new StringBuilder();
375 var StringBuilder lineBuilder = new StringBuilder();
376 var boolean isFirstElementOnNewLineEmptyChar = false;
378 formattedText = encodeJavadocSymbols(formattedText)
379 formattedText = WS_MATCHER.replaceFrom(formattedText, SPACE)
380 formattedText = SPACES_PATTERN.matcher(formattedText).replaceAll(" ")
382 val StringTokenizer tokenizer = new StringTokenizer(formattedText, " ", true);
384 while (tokenizer.hasMoreTokens) {
385 val nextElement = tokenizer.nextToken
387 if (lineBuilder.length != 0 && lineBuilder.length + nextElement.length > 80) {
388 // FIXME: what tricks are we playing here? Equality probably does not trigger ever
389 // and then the setLength()/append() combo does not work, either
390 if (lineBuilder.charAt(lineBuilder.length - 1) == ' ') {
391 lineBuilder.setLength(0)
392 lineBuilder.append(lineBuilder.substring(0, lineBuilder.length - 1))
394 if (lineBuilder.charAt(0) == ' ') {
395 lineBuilder.setLength(0)
396 lineBuilder.append(lineBuilder.substring(1))
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<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.toString, 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.toString, value)»
477 «LengthGenerator.generateLengthCheckerCall(fieldName.toString, value + ".getValue()")»
481 «val fieldUpperCase = fieldName.toString.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<GeneratedProperty> properties) '''
491 final int prime = 31;
493 «FOR property : properties»
494 result = prime * result + «property.importedUtilClass».hashCode(«property.fieldName»);