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) return ''
171 txt = formatToParagraph(txt)
174 «wrapToDocumentation(txt)»
178 def static String wrapToDocumentation(String text) {
182 val StringBuilder sb = new StringBuilder().append("/**\n")
183 for (String t : NL_SPLITTER.split(text)) {
186 sb.append(SPACE).append(t)
195 def protected String formatDataForJavaDoc(GeneratedType type) {
196 val sb = new StringBuilder()
197 val comment = type.comment
198 if (comment !== null) {
199 sb.append(comment.javadoc)
202 appendSnippet(sb, type)
211 def static encodeJavadocSymbols(String description) {
212 if (description.nullOrEmpty) {
216 return TAIL_COMMENT_PATTERN.matcher(AMP_MATCHER.replaceFrom(description, "&")).replaceAll("*/")
219 def protected String formatDataForJavaDoc(GeneratedType type, String additionalComment) {
220 val comment = type.comment
221 if (comment === null) {
227 val sb = new StringBuilder().append(comment.javadoc)
228 appendSnippet(sb, type)
233 .append(additionalComment)
240 def private static void appendSnippet(StringBuilder sb, GeneratedType type) {
241 val optDef = type.yangSourceDefinition
242 if (optDef.present) {
246 if (def instanceof Single) {
249 .append("This class represents the following YANG schema fragment defined in module <b>")
250 .append(def.module.argument).append("</b>\n")
252 appendYangSnippet(sb, def.module, (node as EffectiveStatement<?, ?>).declared)
255 if (node instanceof SchemaNode) {
256 sb.append("The schema path to identify an instance is\n")
258 .append(formatSchemaPath(def.module.argument, node.path.pathFromRoot))
261 if (hasBuilderClass(node)) {
262 val builderName = type.name + "Builder";
264 sb.append("\n<p>To create instances of this class use {@link ").append(builderName)
266 .append("@see ").append(builderName).append('\n')
267 if (node instanceof ListSchemaNode) {
268 val keyDef = node.keyDefinition
269 if (keyDef !== null && !keyDef.empty) {
270 sb.append("@see ").append(type.name).append("Key")
276 } else if (def instanceof Multiple) {
278 for (SchemaNode node : def.nodes) {
279 appendYangSnippet(sb, def.module, (node as EffectiveStatement<?, ?>).declared)
281 sb.append("</pre>\n")
286 def private static void appendYangSnippet(StringBuilder sb, ModuleEffectiveStatement module,
287 DeclaredStatement<?> stmt) {
288 for (String str : YANG_FORMATTER.toYangTextSnippet(module, stmt)) {
289 sb.append(encodeAngleBrackets(encodeJavadocSymbols(str)))
293 def private static boolean hasBuilderClass(SchemaNode schemaNode) {
294 return schemaNode instanceof ContainerSchemaNode || schemaNode instanceof ListSchemaNode
295 || schemaNode instanceof RpcDefinition || schemaNode instanceof NotificationDefinition;
298 def private static String formatSchemaPath(String moduleName, Iterable<QName> schemaPath) {
299 val sb = new StringBuilder().append(moduleName);
301 var currentElement = Iterables.getFirst(schemaPath, null);
302 for (QName pathElement : schemaPath) {
304 if (!currentElement.namespace.equals(pathElement.namespace)) {
305 currentElement = pathElement
306 sb.append(pathElement)
308 sb.append(pathElement.getLocalName())
311 return sb.toString();
314 def protected static String formatDataForJavaDoc(TypeMember type, String additionalComment) {
315 val StringBuilder typeDescriptionBuilder = new StringBuilder();
316 if (!type.comment.nullOrEmpty) {
317 typeDescriptionBuilder.append(formatToParagraph(type.comment))
318 typeDescriptionBuilder.append(NEW_LINE)
319 typeDescriptionBuilder.append(NEW_LINE)
320 typeDescriptionBuilder.append(NEW_LINE)
322 typeDescriptionBuilder.append(additionalComment)
323 var typeDescription = wrapToDocumentation(typeDescriptionBuilder.toString)
329 def asCode(String text) {
330 return "<code>" + text + "</code>"
333 def asLink(String text) {
334 val StringBuilder sb = new StringBuilder()
336 var char lastChar = SPACE
337 var boolean badEnding = false
339 if (text.endsWith('.') || text.endsWith(':') || text.endsWith(',')) {
340 tempText = text.substring(0, text.length - 1)
341 lastChar = text.charAt(text.length - 1)
344 sb.append("<a href = \"")
356 protected static def formatToParagraph(String text) {
357 if(text === null || text.isEmpty)
360 var formattedText = text
361 val StringBuilder sb = new StringBuilder();
362 var StringBuilder lineBuilder = new StringBuilder();
363 var boolean isFirstElementOnNewLineEmptyChar = false;
365 formattedText = encodeJavadocSymbols(formattedText)
366 formattedText = WS_MATCHER.replaceFrom(formattedText, SPACE)
367 formattedText = SPACES_PATTERN.matcher(formattedText).replaceAll(" ")
369 val StringTokenizer tokenizer = new StringTokenizer(formattedText, " ", true);
371 while (tokenizer.hasMoreTokens) {
372 val nextElement = tokenizer.nextToken
374 if (lineBuilder.length != 0 && lineBuilder.length + nextElement.length > 80) {
375 // FIXME: what tricks are we playing here? Equality probably does not trigger ever
376 // and then the setLength()/append() combo does not work, either
377 if (lineBuilder.charAt(lineBuilder.length - 1) == ' ') {
378 lineBuilder.setLength(0)
379 lineBuilder.append(lineBuilder.substring(0, lineBuilder.length - 1))
381 if (lineBuilder.charAt(0) == ' ') {
382 lineBuilder.setLength(0)
383 lineBuilder.append(lineBuilder.substring(1))
386 sb.append(lineBuilder).append(NEW_LINE)
387 lineBuilder.setLength(0)
389 if (nextElement == " ") {
390 isFirstElementOnNewLineEmptyChar = !isFirstElementOnNewLineEmptyChar;
394 if (isFirstElementOnNewLineEmptyChar) {
395 isFirstElementOnNewLineEmptyChar = !isFirstElementOnNewLineEmptyChar
397 lineBuilder.append(nextElement)
401 return sb.append(lineBuilder).append(NEW_LINE).toString
404 def protected generateToString(Collection<GeneratedProperty> properties) '''
405 «IF !properties.empty»
406 @«Override.importedName»
407 public «String.importedName» toString() {
408 final «MoreObjects.importedName».ToStringHelper helper = «MoreObjects.importedName».toStringHelper(«type.importedName».class);
409 «FOR property : properties»
410 «CodeHelpers.importedName».appendValue(helper, "«property.fieldName»", «property.fieldName»);
412 return helper.toString();
418 * Template method which generates method parameters with their types from <code>parameters</code>.
421 * list of parameter instances which are transformed to the method parameters
422 * @return string with the list of the method parameters with their types in JAVA format
424 def protected generateParameters(List<MethodSignature.Parameter> parameters) '''«
425 IF !parameters.empty»«
426 FOR parameter : parameters SEPARATOR ", "»«
427 parameter.type.importedName» «parameter.name»«
432 def protected emitConstant(Constant c) '''
433 «IF BindingMapping.QNAME_STATIC_FIELD_NAME.equals(c.name)»
434 «val entry = c.value as Entry<JavaTypeName, String>»
435 public static final «c.type.importedNonNull» «c.name» = «entry.key.importedName».«BindingMapping.MODULE_INFO_QNAMEOF_METHOD_NAME»("«entry.value»");
437 public static final «c.type.importedName» «c.name» = «c.value»;
441 def protected generateCheckers(GeneratedProperty field, Restrictions restrictions, Type actualType) '''
442 «IF restrictions.rangeConstraint.present»
443 «AbstractRangeGenerator.forType(actualType).generateRangeChecker(field.name.toFirstUpper,
444 restrictions.rangeConstraint.get, this)»
446 «IF restrictions.lengthConstraint.present»
447 «LengthGenerator.generateLengthChecker(field.fieldName.toString, actualType, restrictions.lengthConstraint.get, this)»
451 def protected checkArgument(GeneratedProperty property, Restrictions restrictions, Type actualType, String value) '''
452 «IF restrictions.getRangeConstraint.isPresent»
453 «IF actualType instanceof ConcreteType»
454 «AbstractRangeGenerator.forType(actualType).generateRangeCheckerCall(property.getName.toFirstUpper, value)»
456 «AbstractRangeGenerator.forType(actualType).generateRangeCheckerCall(property.getName.toFirstUpper, value + ".getValue()")»
459 «IF restrictions.getLengthConstraint.isPresent»
460 «IF actualType instanceof ConcreteType»
461 «LengthGenerator.generateLengthCheckerCall(property.fieldName.toString, value)»
463 «LengthGenerator.generateLengthCheckerCall(property.fieldName.toString, value + ".getValue()")»
467 «val fieldUpperCase = property.fieldName.toString.toUpperCase(Locale.ENGLISH)»
468 «FOR currentConstant : type.getConstantDefinitions»
469 «IF currentConstant.getName.startsWith(TypeConstants.PATTERN_CONSTANT_NAME)
470 && fieldUpperCase.equals(currentConstant.getName.substring(TypeConstants.PATTERN_CONSTANT_NAME.length))»
471 «CodeHelpers.importedName».checkPattern(value, «Constants.MEMBER_PATTERN_LIST»«property.fieldName», «Constants.MEMBER_REGEX_LIST»«property.fieldName»);
476 def protected hashCodeResult(Collection<GeneratedProperty> properties) '''
477 final int prime = 31;
479 «FOR property : properties»
480 result = prime * result + «property.importedUtilClass».hashCode(«property.fieldName»);