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.Iterables
16 import java.util.Collection
18 import java.util.Locale
19 import java.util.Map.Entry
20 import java.util.StringTokenizer
21 import java.util.regex.Pattern
22 import org.opendaylight.mdsal.binding.model.api.AnnotationType
23 import org.opendaylight.mdsal.binding.model.api.ConcreteType
24 import org.opendaylight.mdsal.binding.model.api.Constant
25 import org.opendaylight.mdsal.binding.model.api.GeneratedProperty
26 import org.opendaylight.mdsal.binding.model.api.GeneratedType
27 import org.opendaylight.mdsal.binding.model.api.JavaTypeName
28 import org.opendaylight.mdsal.binding.model.api.MethodSignature
29 import org.opendaylight.mdsal.binding.model.api.Restrictions
30 import org.opendaylight.mdsal.binding.model.api.Type
31 import org.opendaylight.mdsal.binding.model.api.TypeMember
32 import org.opendaylight.mdsal.binding.model.api.YangSourceDefinition.Single
33 import org.opendaylight.mdsal.binding.model.api.YangSourceDefinition.Multiple
34 import org.opendaylight.mdsal.binding.model.util.TypeConstants
35 import org.opendaylight.mdsal.binding.model.util.Types
36 import org.opendaylight.mdsal.binding.spec.naming.BindingMapping
37 import org.opendaylight.yangtools.yang.binding.CodeHelpers
38 import org.opendaylight.yangtools.yang.common.QName
39 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode
40 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode
41 import org.opendaylight.yangtools.yang.model.api.NotificationDefinition
42 import org.opendaylight.yangtools.yang.model.api.RpcDefinition
43 import org.opendaylight.yangtools.yang.model.api.SchemaNode
44 import org.opendaylight.yangtools.yang.model.api.YangStmtMapping
45 import org.opendaylight.yangtools.yang.model.api.meta.DeclaredStatement
46 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement
47 import org.opendaylight.yangtools.yang.model.api.stmt.ModuleEffectiveStatement
48 import org.opendaylight.yangtools.yang.model.export.DeclaredStatementFormatter
50 abstract class BaseTemplate extends JavaFileTemplate {
51 static final char NEW_LINE = '\n'
52 static final char SPACE = ' '
53 static val AMP_MATCHER = CharMatcher.is('&')
54 static val WS_MATCHER = CharMatcher.anyOf("\n\t")
55 static val SPACES_PATTERN = Pattern.compile(" +")
56 static val NL_SPLITTER = Splitter.on(NEW_LINE)
57 static val TAIL_COMMENT_PATTERN = Pattern.compile("*/", Pattern.LITERAL);
58 static val YANG_FORMATTER = DeclaredStatementFormatter.builder()
59 .addIgnoredStatement(YangStmtMapping.CONTACT)
60 .addIgnoredStatement(YangStmtMapping.DESCRIPTION)
61 .addIgnoredStatement(YangStmtMapping.REFERENCE)
62 .addIgnoredStatement(YangStmtMapping.ORGANIZATION)
65 new(GeneratedType type) {
69 new(AbstractJavaGeneratedType javaType, GeneratedType type) {
73 final def generate() {
76 package «type.packageName»;
83 protected abstract def CharSequence body();
86 final protected def fieldName(GeneratedProperty property) '''_«property.name»'''
88 final protected def propertyNameFromGetter(MethodSignature getter) {
90 if (getter.name.startsWith(BindingMapping.BOOLEAN_GETTER_PREFIX)) {
91 prefix = BindingMapping.BOOLEAN_GETTER_PREFIX
92 } else if (getter.name.startsWith(BindingMapping.GETTER_PREFIX)) {
93 prefix = BindingMapping.GETTER_PREFIX
94 } else if (getter.name.startsWith(BindingMapping.NONNULL_PREFIX)) {
95 prefix = BindingMapping.NONNULL_PREFIX
97 throw new IllegalArgumentException(getter + " is not a getter")
99 return getter.name.substring(prefix.length).toFirstLower;
103 * Template method which generates the getter method for <code>field</code>
106 * generated property with data about field which is generated as the getter method
107 * @return string with the getter method source code in JAVA format
109 protected def getterMethod(GeneratedProperty field) {
111 public «field.returnType.importedName» «field.getterMethodName»() {
112 «val fieldName = field.fieldName»
113 «IF field.returnType.name.endsWith("[]")»
114 return «fieldName» == null ? null : «fieldName».clone();
122 final protected def getterMethodName(GeneratedProperty field) {
123 val prefix = if(field.returnType.equals(Types.BOOLEAN)) "is" else "get"
124 return '''«prefix»«field.name.toFirstUpper»'''
128 * Template method which generates the setter method for <code>field</code>
131 * generated property with data about field which is generated as the setter method
132 * @return string with the setter method source code in JAVA format
134 final protected def setterMethod(GeneratedProperty field) '''
135 «val returnType = field.returnType.importedName»
136 public «type.name» set«field.name.toFirstUpper»(«returnType» value) {
137 this.«field.fieldName» = value;
143 * Template method which generates method parameters with their types from <code>parameters</code>.
146 * group of generated property instances which are transformed to the method parameters
147 * @return string with the list of the method parameters with their types in JAVA format
149 def final protected asArgumentsDeclaration(Iterable<GeneratedProperty> parameters) '''«IF !parameters.empty»«FOR parameter : parameters SEPARATOR ", "»«parameter.
150 returnType.importedName» «parameter.fieldName»«ENDFOR»«ENDIF»'''
153 * Template method which generates sequence of the names of the class attributes from <code>parameters</code>.
156 * group of generated property instances which are transformed to the sequence of parameter names
157 * @return string with the list of the parameter names of the <code>parameters</code>
159 def final protected asArguments(Collection<GeneratedProperty> parameters) '''«IF !parameters.empty»«FOR parameter : parameters SEPARATOR ", "»«parameter.
160 fieldName»«ENDFOR»«ENDIF»'''
163 * Template method which generates JAVA comments.
165 * @param comment string with the comment for whole JAVA class
166 * @return string with comment in JAVA format
168 def protected CharSequence asJavadoc(String comment) {
169 if (comment === null) {
173 «wrapToDocumentation(formatToParagraph(comment.trim))»
177 def static String wrapToDocumentation(String text) {
181 val StringBuilder sb = new StringBuilder().append("/**\n")
182 for (String t : NL_SPLITTER.split(text)) {
185 sb.append(SPACE).append(t)
194 def protected String formatDataForJavaDoc(GeneratedType type) {
195 val sb = new StringBuilder()
196 val comment = type.comment
197 if (comment !== null) {
198 sb.append(comment.javadoc)
201 appendSnippet(sb, type)
210 def static encodeJavadocSymbols(String description) {
211 if (description.nullOrEmpty) {
215 return TAIL_COMMENT_PATTERN.matcher(AMP_MATCHER.replaceFrom(description, "&")).replaceAll("*/")
218 def protected String formatDataForJavaDoc(GeneratedType type, String additionalComment) {
219 val comment = type.comment
220 if (comment === null) {
226 val sb = new StringBuilder().append(comment.javadoc)
227 appendSnippet(sb, type)
232 .append(additionalComment)
239 def private static void appendSnippet(StringBuilder sb, GeneratedType type) {
240 val optDef = type.yangSourceDefinition
241 if (optDef.present) {
245 if (def instanceof Single) {
248 .append("This class represents the following YANG schema fragment defined in module <b>")
249 .append(def.module.argument).append("</b>\n")
251 appendYangSnippet(sb, def.module, (node as EffectiveStatement<?, ?>).declared)
254 if (node instanceof SchemaNode) {
255 sb.append("The schema path to identify an instance is\n")
257 .append(formatSchemaPath(def.module.argument, node.path.pathFromRoot))
260 if (hasBuilderClass(node)) {
261 val builderName = type.name + "Builder";
263 sb.append("\n<p>To create instances of this class use {@link ").append(builderName)
265 .append("@see ").append(builderName).append('\n')
266 if (node instanceof ListSchemaNode) {
267 val keyDef = node.keyDefinition
268 if (keyDef !== null && !keyDef.empty) {
269 sb.append("@see ").append(type.name).append("Key")
275 } else if (def instanceof Multiple) {
277 for (SchemaNode node : def.nodes) {
278 appendYangSnippet(sb, def.module, (node as EffectiveStatement<?, ?>).declared)
280 sb.append("</pre>\n")
285 def private static void appendYangSnippet(StringBuilder sb, ModuleEffectiveStatement module,
286 DeclaredStatement<?> stmt) {
287 for (String str : YANG_FORMATTER.toYangTextSnippet(module, stmt)) {
288 sb.append(encodeAngleBrackets(encodeJavadocSymbols(str)))
292 def private static boolean hasBuilderClass(SchemaNode schemaNode) {
293 return schemaNode instanceof ContainerSchemaNode || schemaNode instanceof ListSchemaNode
294 || schemaNode instanceof RpcDefinition || schemaNode instanceof NotificationDefinition;
297 def private static String formatSchemaPath(String moduleName, Iterable<QName> schemaPath) {
298 val sb = new StringBuilder().append(moduleName);
300 var currentElement = Iterables.getFirst(schemaPath, null);
301 for (QName pathElement : schemaPath) {
303 if (!currentElement.namespace.equals(pathElement.namespace)) {
304 currentElement = pathElement
305 sb.append(pathElement)
307 sb.append(pathElement.getLocalName())
310 return sb.toString();
313 def protected static String formatDataForJavaDoc(TypeMember type, String additionalComment) {
314 val StringBuilder typeDescriptionBuilder = new StringBuilder();
315 if (!type.comment.nullOrEmpty) {
316 typeDescriptionBuilder.append(formatToParagraph(type.comment))
317 typeDescriptionBuilder.append(NEW_LINE)
318 typeDescriptionBuilder.append(NEW_LINE)
319 typeDescriptionBuilder.append(NEW_LINE)
321 typeDescriptionBuilder.append(additionalComment)
322 var typeDescription = wrapToDocumentation(typeDescriptionBuilder.toString)
328 def asCode(String text) {
329 return "<code>" + text + "</code>"
332 def asLink(String text) {
333 val StringBuilder sb = new StringBuilder()
335 var char lastChar = SPACE
336 var boolean badEnding = false
338 if (text.endsWith('.') || text.endsWith(':') || text.endsWith(',')) {
339 tempText = text.substring(0, text.length - 1)
340 lastChar = text.charAt(text.length - 1)
343 sb.append("<a href = \"")
355 protected static def formatToParagraph(String text) {
356 if(text === null || text.isEmpty)
359 var formattedText = text
360 val StringBuilder sb = new StringBuilder();
361 var StringBuilder lineBuilder = new StringBuilder();
362 var boolean isFirstElementOnNewLineEmptyChar = false;
364 formattedText = encodeJavadocSymbols(formattedText)
365 formattedText = WS_MATCHER.replaceFrom(formattedText, SPACE)
366 formattedText = SPACES_PATTERN.matcher(formattedText).replaceAll(" ")
368 val StringTokenizer tokenizer = new StringTokenizer(formattedText, " ", true);
370 while (tokenizer.hasMoreTokens) {
371 val nextElement = tokenizer.nextToken
373 if (lineBuilder.length != 0 && lineBuilder.length + nextElement.length > 80) {
374 if (lineBuilder.charAt(lineBuilder.length - 1) == SPACE) {
375 lineBuilder.setLength(lineBuilder.length - 1)
377 if (lineBuilder.length != 0 && lineBuilder.charAt(0) == SPACE) {
378 lineBuilder.deleteCharAt(0)
381 sb.append(lineBuilder).append(NEW_LINE)
382 lineBuilder.setLength(0)
384 if (nextElement == " ") {
385 isFirstElementOnNewLineEmptyChar = !isFirstElementOnNewLineEmptyChar;
389 if (isFirstElementOnNewLineEmptyChar) {
390 isFirstElementOnNewLineEmptyChar = !isFirstElementOnNewLineEmptyChar
392 lineBuilder.append(nextElement)
396 return sb.append(lineBuilder).append(NEW_LINE).toString
399 def protected generateToString(Collection<GeneratedProperty> properties) '''
400 «IF !properties.empty»
401 @«Override.importedName»
402 public «String.importedName» toString() {
403 final «MoreObjects.importedName».ToStringHelper helper = «MoreObjects.importedName».toStringHelper(«type.importedName».class);
404 «FOR property : properties»
405 «CodeHelpers.importedName».appendValue(helper, "«property.fieldName»", «property.fieldName»);
407 return helper.toString();
413 * Template method which generates method parameters with their types from <code>parameters</code>.
416 * list of parameter instances which are transformed to the method parameters
417 * @return string with the list of the method parameters with their types in JAVA format
419 def protected generateParameters(List<MethodSignature.Parameter> parameters) '''«
420 IF !parameters.empty»«
421 FOR parameter : parameters SEPARATOR ", "»«
422 parameter.type.importedName» «parameter.name»«
427 def protected emitConstant(Constant c) '''
428 «IF BindingMapping.QNAME_STATIC_FIELD_NAME.equals(c.name)»
429 «val entry = c.value as Entry<JavaTypeName, String>»
430 public static final «c.type.importedNonNull» «c.name» = «entry.key.importedName».«BindingMapping.MODULE_INFO_QNAMEOF_METHOD_NAME»("«entry.value»");
432 public static final «c.type.importedName» «c.name» = «c.value»;
436 def protected generateCheckers(GeneratedProperty field, Restrictions restrictions, Type actualType) '''
437 «IF restrictions.rangeConstraint.present»
438 «AbstractRangeGenerator.forType(actualType).generateRangeChecker(field.name.toFirstUpper,
439 restrictions.rangeConstraint.get, this)»
441 «IF restrictions.lengthConstraint.present»
442 «LengthGenerator.generateLengthChecker(field.fieldName.toString, actualType, restrictions.lengthConstraint.get, this)»
446 def protected checkArgument(GeneratedProperty property, Restrictions restrictions, Type actualType, String value) '''
447 «IF restrictions.getRangeConstraint.isPresent»
448 «IF actualType instanceof ConcreteType»
449 «AbstractRangeGenerator.forType(actualType).generateRangeCheckerCall(property.getName.toFirstUpper, value)»
451 «AbstractRangeGenerator.forType(actualType).generateRangeCheckerCall(property.getName.toFirstUpper, value + ".getValue()")»
454 «val fieldName = property.fieldName»
455 «IF restrictions.getLengthConstraint.isPresent»
456 «IF actualType instanceof ConcreteType»
457 «LengthGenerator.generateLengthCheckerCall(fieldName.toString, value)»
459 «LengthGenerator.generateLengthCheckerCall(fieldName.toString, value + ".getValue()")»
463 «val fieldUpperCase = fieldName.toString.toUpperCase(Locale.ENGLISH)»
464 «FOR currentConstant : type.getConstantDefinitions»
465 «IF currentConstant.getName.startsWith(TypeConstants.PATTERN_CONSTANT_NAME)
466 && fieldUpperCase.equals(currentConstant.getName.substring(TypeConstants.PATTERN_CONSTANT_NAME.length))»
467 «CodeHelpers.importedName».checkPattern(value, «Constants.MEMBER_PATTERN_LIST»«fieldName», «Constants.MEMBER_REGEX_LIST»«fieldName»);
472 def protected hashCodeResult(Collection<GeneratedProperty> properties) '''
473 final int prime = 31;
475 «FOR property : properties»
476 result = prime * result + «property.importedUtilClass».hashCode(«property.fieldName»);
480 def protected final generateAnnotation(AnnotationType annotation) '''
481 @«annotation.importedName»
482 «IF annotation.parameters !== null && !annotation.parameters.empty»
484 «FOR param : annotation.parameters SEPARATOR ","»
485 «param.name»=«param.value»