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.ConcreteType
23 import org.opendaylight.mdsal.binding.model.api.Constant
24 import org.opendaylight.mdsal.binding.model.api.GeneratedProperty
25 import org.opendaylight.mdsal.binding.model.api.GeneratedType
26 import org.opendaylight.mdsal.binding.model.api.JavaTypeName
27 import org.opendaylight.mdsal.binding.model.api.MethodSignature
28 import org.opendaylight.mdsal.binding.model.api.Restrictions
29 import org.opendaylight.mdsal.binding.model.api.Type
30 import org.opendaylight.mdsal.binding.model.api.TypeMember
31 import org.opendaylight.mdsal.binding.model.api.YangSourceDefinition.Single
32 import org.opendaylight.mdsal.binding.model.api.YangSourceDefinition.Multiple
33 import org.opendaylight.mdsal.binding.model.util.TypeConstants
34 import org.opendaylight.mdsal.binding.model.util.Types
35 import org.opendaylight.mdsal.binding.spec.naming.BindingMapping
36 import org.opendaylight.yangtools.yang.binding.CodeHelpers
37 import org.opendaylight.yangtools.yang.common.QName
38 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode
39 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode
40 import org.opendaylight.yangtools.yang.model.api.NotificationDefinition
41 import org.opendaylight.yangtools.yang.model.api.RpcDefinition
42 import org.opendaylight.yangtools.yang.model.api.SchemaNode
43 import org.opendaylight.yangtools.yang.model.api.YangStmtMapping
44 import org.opendaylight.yangtools.yang.model.api.meta.DeclaredStatement
45 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement
46 import org.opendaylight.yangtools.yang.model.api.stmt.ModuleEffectiveStatement
47 import org.opendaylight.yangtools.yang.model.export.DeclaredStatementFormatter
49 abstract class BaseTemplate extends JavaFileTemplate {
50 static final char NEW_LINE = '\n'
51 static final char SPACE = ' '
52 static val AMP_MATCHER = CharMatcher.is('&')
53 static val WS_MATCHER = CharMatcher.anyOf("\n\t")
54 static val SPACES_PATTERN = Pattern.compile(" +")
55 static val NL_SPLITTER = Splitter.on(NEW_LINE)
56 static val TAIL_COMMENT_PATTERN = Pattern.compile("*/", Pattern.LITERAL);
57 static val YANG_FORMATTER = DeclaredStatementFormatter.builder()
58 .addIgnoredStatement(YangStmtMapping.CONTACT)
59 .addIgnoredStatement(YangStmtMapping.DESCRIPTION)
60 .addIgnoredStatement(YangStmtMapping.REFERENCE)
61 .addIgnoredStatement(YangStmtMapping.ORGANIZATION)
64 new(GeneratedType type) {
68 new(AbstractJavaGeneratedType javaType, GeneratedType type) {
72 final def generate() {
75 package «type.packageName»;
82 protected abstract def CharSequence body();
85 final protected def fieldName(GeneratedProperty property) '''_«property.name»'''
87 final protected def propertyNameFromGetter(MethodSignature getter) {
89 if (getter.name.startsWith(BindingMapping.BOOLEAN_GETTER_PREFIX)) {
90 prefix = BindingMapping.BOOLEAN_GETTER_PREFIX
91 } else if (getter.name.startsWith(BindingMapping.GETTER_PREFIX)) {
92 prefix = BindingMapping.GETTER_PREFIX
93 } else if (getter.name.startsWith(BindingMapping.NONNULL_PREFIX)) {
94 prefix = BindingMapping.NONNULL_PREFIX
96 throw new IllegalArgumentException(getter + " is not a getter")
98 return getter.name.substring(prefix.length).toFirstLower;
102 * Template method which generates the getter method for <code>field</code>
105 * generated property with data about field which is generated as the getter method
106 * @return string with the getter method source code in JAVA format
108 protected def getterMethod(GeneratedProperty field) {
110 public «field.returnType.importedName» «field.getterMethodName»() {
111 «val fieldName = field.fieldName»
112 «IF field.returnType.name.endsWith("[]")»
113 return «fieldName» == null ? null : «fieldName».clone();
121 final protected def getterMethodName(GeneratedProperty field) {
122 val prefix = if(field.returnType.equals(Types.BOOLEAN)) "is" else "get"
123 return '''«prefix»«field.name.toFirstUpper»'''
127 * Template method which generates the setter method for <code>field</code>
130 * generated property with data about field which is generated as the setter method
131 * @return string with the setter method source code in JAVA format
133 final protected def setterMethod(GeneratedProperty field) '''
134 «val returnType = field.returnType.importedName»
135 public «type.name» set«field.name.toFirstUpper»(«returnType» value) {
136 this.«field.fieldName» = value;
142 * Template method which generates method parameters with their types from <code>parameters</code>.
145 * group of generated property instances which are transformed to the method parameters
146 * @return string with the list of the method parameters with their types in JAVA format
148 def final protected asArgumentsDeclaration(Iterable<GeneratedProperty> parameters) '''«IF !parameters.empty»«FOR parameter : parameters SEPARATOR ", "»«parameter.
149 returnType.importedName» «parameter.fieldName»«ENDFOR»«ENDIF»'''
152 * Template method which generates sequence of the names of the class attributes from <code>parameters</code>.
155 * group of generated property instances which are transformed to the sequence of parameter names
156 * @return string with the list of the parameter names of the <code>parameters</code>
158 def final protected asArguments(Collection<GeneratedProperty> parameters) '''«IF !parameters.empty»«FOR parameter : parameters SEPARATOR ", "»«parameter.
159 fieldName»«ENDFOR»«ENDIF»'''
162 * Template method which generates JAVA comments.
164 * @param comment string with the comment for whole JAVA class
165 * @return string with comment in JAVA format
167 def protected CharSequence asJavadoc(String comment) {
168 if (comment === null) {
172 «wrapToDocumentation(formatToParagraph(comment.trim))»
176 def static String wrapToDocumentation(String text) {
180 val StringBuilder sb = new StringBuilder().append("/**\n")
181 for (String t : NL_SPLITTER.split(text)) {
184 sb.append(SPACE).append(t)
193 def protected String formatDataForJavaDoc(GeneratedType type) {
194 val sb = new StringBuilder()
195 val comment = type.comment
196 if (comment !== null) {
197 sb.append(comment.javadoc)
200 appendSnippet(sb, type)
209 def static encodeJavadocSymbols(String description) {
210 if (description.nullOrEmpty) {
214 return TAIL_COMMENT_PATTERN.matcher(AMP_MATCHER.replaceFrom(description, "&")).replaceAll("*/")
217 def protected String formatDataForJavaDoc(GeneratedType type, String additionalComment) {
218 val comment = type.comment
219 if (comment === null) {
225 val sb = new StringBuilder().append(comment.javadoc)
226 appendSnippet(sb, type)
231 .append(additionalComment)
238 def private static void appendSnippet(StringBuilder sb, GeneratedType type) {
239 val optDef = type.yangSourceDefinition
240 if (optDef.present) {
244 if (def instanceof Single) {
247 .append("This class represents the following YANG schema fragment defined in module <b>")
248 .append(def.module.argument).append("</b>\n")
250 appendYangSnippet(sb, def.module, (node as EffectiveStatement<?, ?>).declared)
253 if (node instanceof SchemaNode) {
254 sb.append("The schema path to identify an instance is\n")
256 .append(formatSchemaPath(def.module.argument, node.path.pathFromRoot))
259 if (hasBuilderClass(node)) {
260 val builderName = type.name + "Builder";
262 sb.append("\n<p>To create instances of this class use {@link ").append(builderName)
264 .append("@see ").append(builderName).append('\n')
265 if (node instanceof ListSchemaNode) {
266 val keyDef = node.keyDefinition
267 if (keyDef !== null && !keyDef.empty) {
268 sb.append("@see ").append(type.name).append("Key")
274 } else if (def instanceof Multiple) {
276 for (SchemaNode node : def.nodes) {
277 appendYangSnippet(sb, def.module, (node as EffectiveStatement<?, ?>).declared)
279 sb.append("</pre>\n")
284 def private static void appendYangSnippet(StringBuilder sb, ModuleEffectiveStatement module,
285 DeclaredStatement<?> stmt) {
286 for (String str : YANG_FORMATTER.toYangTextSnippet(module, stmt)) {
287 sb.append(encodeAngleBrackets(encodeJavadocSymbols(str)))
291 def private static boolean hasBuilderClass(SchemaNode schemaNode) {
292 return schemaNode instanceof ContainerSchemaNode || schemaNode instanceof ListSchemaNode
293 || schemaNode instanceof RpcDefinition || schemaNode instanceof NotificationDefinition;
296 def private static String formatSchemaPath(String moduleName, Iterable<QName> schemaPath) {
297 val sb = new StringBuilder().append(moduleName);
299 var currentElement = Iterables.getFirst(schemaPath, null);
300 for (QName pathElement : schemaPath) {
302 if (!currentElement.namespace.equals(pathElement.namespace)) {
303 currentElement = pathElement
304 sb.append(pathElement)
306 sb.append(pathElement.getLocalName())
309 return sb.toString();
312 def protected static String formatDataForJavaDoc(TypeMember type, String additionalComment) {
313 val StringBuilder typeDescriptionBuilder = new StringBuilder();
314 if (!type.comment.nullOrEmpty) {
315 typeDescriptionBuilder.append(formatToParagraph(type.comment))
316 typeDescriptionBuilder.append(NEW_LINE)
317 typeDescriptionBuilder.append(NEW_LINE)
318 typeDescriptionBuilder.append(NEW_LINE)
320 typeDescriptionBuilder.append(additionalComment)
321 var typeDescription = wrapToDocumentation(typeDescriptionBuilder.toString)
327 def asCode(String text) {
328 return "<code>" + text + "</code>"
331 def asLink(String text) {
332 val StringBuilder sb = new StringBuilder()
334 var char lastChar = SPACE
335 var boolean badEnding = false
337 if (text.endsWith('.') || text.endsWith(':') || text.endsWith(',')) {
338 tempText = text.substring(0, text.length - 1)
339 lastChar = text.charAt(text.length - 1)
342 sb.append("<a href = \"")
354 protected static def formatToParagraph(String text) {
355 if(text === null || text.isEmpty)
358 var formattedText = text
359 val StringBuilder sb = new StringBuilder();
360 var StringBuilder lineBuilder = new StringBuilder();
361 var boolean isFirstElementOnNewLineEmptyChar = false;
363 formattedText = encodeJavadocSymbols(formattedText)
364 formattedText = WS_MATCHER.replaceFrom(formattedText, SPACE)
365 formattedText = SPACES_PATTERN.matcher(formattedText).replaceAll(" ")
367 val StringTokenizer tokenizer = new StringTokenizer(formattedText, " ", true);
369 while (tokenizer.hasMoreTokens) {
370 val nextElement = tokenizer.nextToken
372 if (lineBuilder.length != 0 && lineBuilder.length + nextElement.length > 80) {
373 // FIXME: what tricks are we playing here? Equality probably does not trigger ever
374 // and then the setLength()/append() combo does not work, either
375 if (lineBuilder.charAt(lineBuilder.length - 1) == ' ') {
376 lineBuilder.setLength(0)
377 lineBuilder.append(lineBuilder.substring(0, lineBuilder.length - 1))
379 if (lineBuilder.charAt(0) == ' ') {
380 lineBuilder.setLength(0)
381 lineBuilder.append(lineBuilder.substring(1))
384 sb.append(lineBuilder).append(NEW_LINE)
385 lineBuilder.setLength(0)
387 if (nextElement == " ") {
388 isFirstElementOnNewLineEmptyChar = !isFirstElementOnNewLineEmptyChar;
392 if (isFirstElementOnNewLineEmptyChar) {
393 isFirstElementOnNewLineEmptyChar = !isFirstElementOnNewLineEmptyChar
395 lineBuilder.append(nextElement)
399 return sb.append(lineBuilder).append(NEW_LINE).toString
402 def protected generateToString(Collection<GeneratedProperty> properties) '''
403 «IF !properties.empty»
404 @«Override.importedName»
405 public «String.importedName» toString() {
406 final «MoreObjects.importedName».ToStringHelper helper = «MoreObjects.importedName».toStringHelper(«type.importedName».class);
407 «FOR property : properties»
408 «CodeHelpers.importedName».appendValue(helper, "«property.fieldName»", «property.fieldName»);
410 return helper.toString();
416 * Template method which generates method parameters with their types from <code>parameters</code>.
419 * list of parameter instances which are transformed to the method parameters
420 * @return string with the list of the method parameters with their types in JAVA format
422 def protected generateParameters(List<MethodSignature.Parameter> parameters) '''«
423 IF !parameters.empty»«
424 FOR parameter : parameters SEPARATOR ", "»«
425 parameter.type.importedName» «parameter.name»«
430 def protected emitConstant(Constant c) '''
431 «IF BindingMapping.QNAME_STATIC_FIELD_NAME.equals(c.name)»
432 «val entry = c.value as Entry<JavaTypeName, String>»
433 public static final «c.type.importedNonNull» «c.name» = «entry.key.importedName».«BindingMapping.MODULE_INFO_QNAMEOF_METHOD_NAME»("«entry.value»");
435 public static final «c.type.importedName» «c.name» = «c.value»;
439 def protected generateCheckers(GeneratedProperty field, Restrictions restrictions, Type actualType) '''
440 «IF restrictions.rangeConstraint.present»
441 «AbstractRangeGenerator.forType(actualType).generateRangeChecker(field.name.toFirstUpper,
442 restrictions.rangeConstraint.get, this)»
444 «IF restrictions.lengthConstraint.present»
445 «LengthGenerator.generateLengthChecker(field.fieldName.toString, actualType, restrictions.lengthConstraint.get, this)»
449 def protected checkArgument(GeneratedProperty property, Restrictions restrictions, Type actualType, String value) '''
450 «IF restrictions.getRangeConstraint.isPresent»
451 «IF actualType instanceof ConcreteType»
452 «AbstractRangeGenerator.forType(actualType).generateRangeCheckerCall(property.getName.toFirstUpper, value)»
454 «AbstractRangeGenerator.forType(actualType).generateRangeCheckerCall(property.getName.toFirstUpper, value + ".getValue()")»
457 «val fieldName = property.fieldName»
458 «IF restrictions.getLengthConstraint.isPresent»
459 «IF actualType instanceof ConcreteType»
460 «LengthGenerator.generateLengthCheckerCall(fieldName.toString, value)»
462 «LengthGenerator.generateLengthCheckerCall(fieldName.toString, value + ".getValue()")»
466 «val fieldUpperCase = fieldName.toString.toUpperCase(Locale.ENGLISH)»
467 «FOR currentConstant : type.getConstantDefinitions»
468 «IF currentConstant.getName.startsWith(TypeConstants.PATTERN_CONSTANT_NAME)
469 && fieldUpperCase.equals(currentConstant.getName.substring(TypeConstants.PATTERN_CONSTANT_NAME.length))»
470 «CodeHelpers.importedName».checkPattern(value, «Constants.MEMBER_PATTERN_LIST»«fieldName», «Constants.MEMBER_REGEX_LIST»«fieldName»);
475 def protected hashCodeResult(Collection<GeneratedProperty> properties) '''
476 final int prime = 31;
478 «FOR property : properties»
479 result = prime * result + «property.importedUtilClass».hashCode(«property.fieldName»);