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 «IF field.returnType.importedName.contains("[]")»
112 return «field.fieldName» == null ? null : «field.fieldName».clone();
114 return «field.fieldName»;
120 final protected def getterMethodName(GeneratedProperty field) {
121 val prefix = if(field.returnType.equals(Types.BOOLEAN)) "is" else "get"
122 return '''«prefix»«field.name.toFirstUpper»'''
126 * Template method which generates the setter method for <code>field</code>
129 * generated property with data about field which is generated as the setter method
130 * @return string with the setter method source code in JAVA format
132 final protected def setterMethod(GeneratedProperty field) '''
133 «val returnType = field.returnType.importedName»
134 public «type.name» set«field.name.toFirstUpper»(«returnType» value) {
135 this.«field.fieldName» = value;
141 * Template method which generates method parameters with their types from <code>parameters</code>.
144 * group of generated property instances which are transformed to the method parameters
145 * @return string with the list of the method parameters with their types in JAVA format
147 def final protected asArgumentsDeclaration(Iterable<GeneratedProperty> parameters) '''«IF !parameters.empty»«FOR parameter : parameters SEPARATOR ", "»«parameter.
148 returnType.importedName» «parameter.fieldName»«ENDFOR»«ENDIF»'''
151 * Template method which generates sequence of the names of the class attributes from <code>parameters</code>.
154 * group of generated property instances which are transformed to the sequence of parameter names
155 * @return string with the list of the parameter names of the <code>parameters</code>
157 def final protected asArguments(Iterable<GeneratedProperty> parameters) '''«IF !parameters.empty»«FOR parameter : parameters SEPARATOR ", "»«parameter.
158 fieldName»«ENDFOR»«ENDIF»'''
161 * Template method which generates JAVA comments.
163 * @param comment string with the comment for whole JAVA class
164 * @return string with comment in JAVA format
166 def protected CharSequence asJavadoc(String comment) {
167 if (comment === null) {
171 «wrapToDocumentation(formatToParagraph(comment.trim))»
175 def static String wrapToDocumentation(String text) {
179 val StringBuilder sb = new StringBuilder().append("/**\n")
180 for (String t : NL_SPLITTER.split(text)) {
183 sb.append(SPACE).append(t)
192 def protected String formatDataForJavaDoc(GeneratedType type) {
193 val sb = new StringBuilder()
194 val comment = type.comment
195 if (comment !== null) {
196 sb.append(comment.javadoc)
199 appendSnippet(sb, type)
208 def static encodeJavadocSymbols(String description) {
209 if (description.nullOrEmpty) {
213 return TAIL_COMMENT_PATTERN.matcher(AMP_MATCHER.replaceFrom(description, "&")).replaceAll("*/")
216 def protected String formatDataForJavaDoc(GeneratedType type, String additionalComment) {
217 val comment = type.comment
218 if (comment === null) {
224 val sb = new StringBuilder().append(comment.javadoc)
225 appendSnippet(sb, type)
230 .append(additionalComment)
237 def private static void appendSnippet(StringBuilder sb, GeneratedType type) {
238 val optDef = type.yangSourceDefinition
239 if (optDef.present) {
243 if (def instanceof Single) {
246 .append("This class represents the following YANG schema fragment defined in module <b>")
247 .append(def.module.argument).append("</b>\n")
249 appendYangSnippet(sb, def.module, (node as EffectiveStatement<?, ?>).declared)
252 if (node instanceof SchemaNode) {
253 sb.append("The schema path to identify an instance is\n")
255 .append(formatSchemaPath(def.module.argument, node.path.pathFromRoot))
258 if (hasBuilderClass(node)) {
259 val builderName = type.name + "Builder";
261 sb.append("\n<p>To create instances of this class use {@link ").append(builderName)
263 .append("@see ").append(builderName).append('\n')
264 if (node instanceof ListSchemaNode) {
265 val keyDef = node.keyDefinition
266 if (keyDef !== null && !keyDef.empty) {
267 sb.append("@see ").append(type.name).append("Key")
273 } else if (def instanceof Multiple) {
275 for (SchemaNode node : def.nodes) {
276 appendYangSnippet(sb, def.module, (node as EffectiveStatement<?, ?>).declared)
278 sb.append("</pre>\n")
283 def private static void appendYangSnippet(StringBuilder sb, ModuleEffectiveStatement module,
284 DeclaredStatement<?> stmt) {
285 for (String str : YANG_FORMATTER.toYangTextSnippet(module, stmt)) {
286 sb.append(encodeAngleBrackets(encodeJavadocSymbols(str)))
290 def private static boolean hasBuilderClass(SchemaNode schemaNode) {
291 return schemaNode instanceof ContainerSchemaNode || schemaNode instanceof ListSchemaNode
292 || schemaNode instanceof RpcDefinition || schemaNode instanceof NotificationDefinition;
295 def private static String formatSchemaPath(String moduleName, Iterable<QName> schemaPath) {
296 val sb = new StringBuilder().append(moduleName);
298 var currentElement = Iterables.getFirst(schemaPath, null);
299 for (QName pathElement : schemaPath) {
301 if (!currentElement.namespace.equals(pathElement.namespace)) {
302 currentElement = pathElement
303 sb.append(pathElement)
305 sb.append(pathElement.getLocalName())
308 return sb.toString();
311 def protected static String formatDataForJavaDoc(TypeMember type, String additionalComment) {
312 val StringBuilder typeDescriptionBuilder = new StringBuilder();
313 if (!type.comment.nullOrEmpty) {
314 typeDescriptionBuilder.append(formatToParagraph(type.comment))
315 typeDescriptionBuilder.append(NEW_LINE)
316 typeDescriptionBuilder.append(NEW_LINE)
317 typeDescriptionBuilder.append(NEW_LINE)
319 typeDescriptionBuilder.append(additionalComment)
320 var typeDescription = wrapToDocumentation(typeDescriptionBuilder.toString)
326 def asCode(String text) {
327 return "<code>" + text + "</code>"
330 def asLink(String text) {
331 val StringBuilder sb = new StringBuilder()
333 var char lastChar = SPACE
334 var boolean badEnding = false
336 if (text.endsWith('.') || text.endsWith(':') || text.endsWith(',')) {
337 tempText = text.substring(0, text.length - 1)
338 lastChar = text.charAt(text.length - 1)
341 sb.append("<a href = \"")
353 protected static def formatToParagraph(String text) {
354 if(text === null || text.isEmpty)
357 var formattedText = text
358 val StringBuilder sb = new StringBuilder();
359 var StringBuilder lineBuilder = new StringBuilder();
360 var boolean isFirstElementOnNewLineEmptyChar = false;
362 formattedText = encodeJavadocSymbols(formattedText)
363 formattedText = WS_MATCHER.replaceFrom(formattedText, SPACE)
364 formattedText = SPACES_PATTERN.matcher(formattedText).replaceAll(" ")
366 val StringTokenizer tokenizer = new StringTokenizer(formattedText, " ", true);
368 while (tokenizer.hasMoreTokens) {
369 val nextElement = tokenizer.nextToken
371 if (lineBuilder.length != 0 && lineBuilder.length + nextElement.length > 80) {
372 // FIXME: what tricks are we playing here? Equality probably does not trigger ever
373 // and then the setLength()/append() combo does not work, either
374 if (lineBuilder.charAt(lineBuilder.length - 1) == ' ') {
375 lineBuilder.setLength(0)
376 lineBuilder.append(lineBuilder.substring(0, lineBuilder.length - 1))
378 if (lineBuilder.charAt(0) == ' ') {
379 lineBuilder.setLength(0)
380 lineBuilder.append(lineBuilder.substring(1))
383 sb.append(lineBuilder).append(NEW_LINE)
384 lineBuilder.setLength(0)
386 if (nextElement == " ") {
387 isFirstElementOnNewLineEmptyChar = !isFirstElementOnNewLineEmptyChar;
391 if (isFirstElementOnNewLineEmptyChar) {
392 isFirstElementOnNewLineEmptyChar = !isFirstElementOnNewLineEmptyChar
394 lineBuilder.append(nextElement)
398 return sb.append(lineBuilder).append(NEW_LINE).toString
401 def protected generateToString(Collection<GeneratedProperty> properties) '''
402 «IF !properties.empty»
403 @«Override.importedName»
404 public «String.importedName» toString() {
405 final «MoreObjects.importedName».ToStringHelper helper = «MoreObjects.importedName».toStringHelper(«type.importedName».class);
406 «FOR property : properties»
407 «CodeHelpers.importedName».appendValue(helper, "«property.fieldName»", «property.fieldName»);
409 return helper.toString();
415 * Template method which generates method parameters with their types from <code>parameters</code>.
418 * list of parameter instances which are transformed to the method parameters
419 * @return string with the list of the method parameters with their types in JAVA format
421 def protected generateParameters(List<MethodSignature.Parameter> parameters) '''«
422 IF !parameters.empty»«
423 FOR parameter : parameters SEPARATOR ", "»«
424 parameter.type.importedName» «parameter.name»«
429 def protected emitConstant(Constant c) '''
430 «IF BindingMapping.QNAME_STATIC_FIELD_NAME.equals(c.name)»
431 «val entry = c.value as Entry<JavaTypeName, String>»
432 public static final «c.type.importedNonNull» «c.name» = «entry.key.importedName».«BindingMapping.MODULE_INFO_QNAMEOF_METHOD_NAME»("«entry.value»");
434 public static final «c.type.importedName» «c.name» = «c.value»;
438 def protected generateCheckers(GeneratedProperty field, Restrictions restrictions, Type actualType) '''
439 «IF restrictions.rangeConstraint.present»
440 «AbstractRangeGenerator.forType(actualType).generateRangeChecker(field.name.toFirstUpper,
441 restrictions.rangeConstraint.get, this)»
443 «IF restrictions.lengthConstraint.present»
444 «LengthGenerator.generateLengthChecker(field.fieldName.toString, actualType, restrictions.lengthConstraint.get, this)»
448 def protected checkArgument(GeneratedProperty property, Restrictions restrictions, Type actualType, String value) '''
449 «IF restrictions.getRangeConstraint.isPresent»
450 «IF actualType instanceof ConcreteType»
451 «AbstractRangeGenerator.forType(actualType).generateRangeCheckerCall(property.getName.toFirstUpper, value)»
453 «AbstractRangeGenerator.forType(actualType).generateRangeCheckerCall(property.getName.toFirstUpper, value + ".getValue()")»
456 «IF restrictions.getLengthConstraint.isPresent»
457 «IF actualType instanceof ConcreteType»
458 «LengthGenerator.generateLengthCheckerCall(property.fieldName.toString, value)»
460 «LengthGenerator.generateLengthCheckerCall(property.fieldName.toString, value + ".getValue()")»
464 «val fieldUpperCase = property.fieldName.toString.toUpperCase(Locale.ENGLISH)»
465 «FOR currentConstant : type.getConstantDefinitions»
466 «IF currentConstant.getName.startsWith(TypeConstants.PATTERN_CONSTANT_NAME)
467 && fieldUpperCase.equals(currentConstant.getName.substring(TypeConstants.PATTERN_CONSTANT_NAME.length))»
468 «CodeHelpers.importedName».checkPattern(value, «Constants.MEMBER_PATTERN_LIST»«property.fieldName», «Constants.MEMBER_REGEX_LIST»«property.fieldName»);
473 def protected hashCodeResult(Collection<GeneratedProperty> properties) '''
474 final int prime = 31;
476 «FOR property : properties»
477 result = prime * result + «property.importedUtilClass».hashCode(«property.fieldName»);