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.Splitter
14 import com.google.common.collect.ImmutableMap
15 import com.google.common.collect.Iterables
16 import java.math.BigInteger
17 import java.util.Collection
19 import java.util.Locale
20 import java.util.Map.Entry
21 import java.util.StringTokenizer
22 import java.util.regex.Pattern
23 import org.gaul.modernizer_maven_annotations.SuppressModernizer
24 import org.opendaylight.mdsal.binding.model.api.AnnotationType
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.BindingGeneratorUtil
37 import org.opendaylight.mdsal.binding.model.util.TypeConstants
38 import org.opendaylight.mdsal.binding.model.util.Types
39 import org.opendaylight.mdsal.binding.spec.naming.BindingMapping
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) {
104 final protected def propertyNameFromGetter(MethodSignature getter) {
106 if (getter.name.startsWith(BindingMapping.BOOLEAN_GETTER_PREFIX)) {
107 prefix = BindingMapping.BOOLEAN_GETTER_PREFIX
108 } else if (getter.name.startsWith(BindingMapping.GETTER_PREFIX)) {
109 prefix = BindingMapping.GETTER_PREFIX
110 } else if (getter.name.startsWith(BindingMapping.NONNULL_PREFIX)) {
111 prefix = BindingMapping.NONNULL_PREFIX
113 throw new IllegalArgumentException(getter + " is not a getter")
115 return getter.name.substring(prefix.length).toFirstLower;
119 * Template method which generates the getter method for <code>field</code>
122 * generated property with data about field which is generated as the getter method
123 * @return string with the getter method source code in JAVA format
125 protected def getterMethod(GeneratedProperty field) {
127 public «field.returnType.importedName» «field.getterMethodName»() {
128 «val fieldName = field.fieldName»
129 «IF field.returnType.name.endsWith("[]")»
130 return «fieldName» == null ? null : «fieldName».clone();
138 final protected def getterMethodName(GeneratedProperty field) {
139 val prefix = if(field.returnType.equals(Types.BOOLEAN)) "is" else "get"
140 return '''«prefix»«field.name.toFirstUpper»'''
144 * Template method which generates the setter method for <code>field</code>
147 * generated property with data about field which is generated as the setter method
148 * @return string with the setter method source code in JAVA format
150 final protected def setterMethod(GeneratedProperty field) '''
151 «val returnType = field.returnType.importedName»
152 public «type.name» set«field.name.toFirstUpper»(«returnType» value) {
153 this.«field.fieldName» = value;
159 * Template method which generates method parameters with their types from <code>parameters</code>.
162 * group of generated property instances which are transformed to the method parameters
163 * @return string with the list of the method parameters with their types in JAVA format
165 def final protected asArgumentsDeclaration(Iterable<GeneratedProperty> parameters) '''«IF !parameters.empty»«FOR parameter : parameters SEPARATOR ", "»«parameter.
166 returnType.importedName» «parameter.fieldName»«ENDFOR»«ENDIF»'''
169 * Template method which generates sequence of the names of the class attributes from <code>parameters</code>.
172 * group of generated property instances which are transformed to the sequence of parameter names
173 * @return string with the list of the parameter names of the <code>parameters</code>
175 def final protected asArguments(Collection<GeneratedProperty> parameters) '''«IF !parameters.empty»«FOR parameter : parameters SEPARATOR ", "»«parameter.
176 fieldName»«ENDFOR»«ENDIF»'''
179 * Template method which generates JAVA comments.
181 * @param comment string with the comment for whole JAVA class
182 * @return string with comment in JAVA format
184 def protected CharSequence asJavadoc(String comment) {
185 if (comment === null) {
189 «wrapToDocumentation(formatToParagraph(comment.trim))»
193 def static String wrapToDocumentation(String text) {
197 val StringBuilder sb = new StringBuilder().append("/**\n")
198 for (String t : NL_SPLITTER.split(text)) {
201 sb.append(SPACE).append(t)
210 def protected String formatDataForJavaDoc(GeneratedType type) {
211 val sb = new StringBuilder()
212 val comment = type.comment
213 if (comment !== null) {
214 sb.append(comment.javadoc)
217 appendSnippet(sb, type)
226 def static encodeJavadocSymbols(String description) {
227 if (description.nullOrEmpty) {
231 return TAIL_COMMENT_PATTERN.matcher(AMP_MATCHER.replaceFrom(description, "&")).replaceAll("*/")
234 def protected String formatDataForJavaDoc(GeneratedType type, String additionalComment) {
235 val comment = type.comment
236 if (comment === null) {
242 val sb = new StringBuilder().append(comment.javadoc)
243 appendSnippet(sb, type)
248 .append(additionalComment)
255 def private static void appendSnippet(StringBuilder sb, GeneratedType type) {
256 val optDef = type.yangSourceDefinition
257 if (optDef.present) {
261 if (def instanceof Single) {
264 .append("This class represents the following YANG schema fragment defined in module <b>")
265 .append(def.module.argument).append("</b>\n")
267 appendYangSnippet(sb, def.module, (node as EffectiveStatement<?, ?>).declared)
270 if (node instanceof SchemaNode) {
271 sb.append("The schema path to identify an instance is\n")
273 .append(formatSchemaPath(def.module.argument, node.path.pathFromRoot))
276 if (hasBuilderClass(node)) {
277 val builderName = type.name + "Builder";
279 sb.append("\n<p>To create instances of this class use {@link ").append(builderName)
281 .append("@see ").append(builderName).append('\n')
282 if (node instanceof ListSchemaNode) {
283 val keyDef = node.keyDefinition
284 if (keyDef !== null && !keyDef.empty) {
285 sb.append("@see ").append(type.name).append("Key")
291 } else if (def instanceof Multiple) {
293 for (SchemaNode node : def.nodes) {
294 appendYangSnippet(sb, def.module, (node as EffectiveStatement<?, ?>).declared)
296 sb.append("</pre>\n")
301 def private static void appendYangSnippet(StringBuilder sb, ModuleEffectiveStatement module,
302 DeclaredStatement<?> stmt) {
303 for (String str : YANG_FORMATTER.toYangTextSnippet(module, stmt)) {
304 sb.append(BindingGeneratorUtil.replaceAllIllegalChars(encodeAngleBrackets(encodeJavadocSymbols(str))))
308 def private static boolean hasBuilderClass(SchemaNode schemaNode) {
309 return schemaNode instanceof ContainerSchemaNode || schemaNode instanceof ListSchemaNode
310 || schemaNode instanceof RpcDefinition || schemaNode instanceof NotificationDefinition;
313 def private static String formatSchemaPath(String moduleName, Iterable<QName> schemaPath) {
314 val sb = new StringBuilder().append(moduleName);
316 var currentElement = Iterables.getFirst(schemaPath, null);
317 for (QName pathElement : schemaPath) {
319 if (!currentElement.namespace.equals(pathElement.namespace)) {
320 currentElement = pathElement
321 sb.append(pathElement)
323 sb.append(pathElement.getLocalName())
326 return sb.toString();
329 def protected static String formatDataForJavaDoc(TypeMember type, String additionalComment) {
330 val StringBuilder typeDescriptionBuilder = new StringBuilder();
331 if (!type.comment.nullOrEmpty) {
332 typeDescriptionBuilder.append(formatToParagraph(type.comment))
333 typeDescriptionBuilder.append(NEW_LINE)
334 typeDescriptionBuilder.append(NEW_LINE)
335 typeDescriptionBuilder.append(NEW_LINE)
337 typeDescriptionBuilder.append(additionalComment)
338 var typeDescription = wrapToDocumentation(typeDescriptionBuilder.toString)
344 def asCode(String text) {
345 return "<code>" + text + "</code>"
348 def asLink(String text) {
349 val StringBuilder sb = new StringBuilder()
351 var char lastChar = SPACE
352 var boolean badEnding = false
354 if (text.endsWith('.') || text.endsWith(':') || text.endsWith(',')) {
355 tempText = text.substring(0, text.length - 1)
356 lastChar = text.charAt(text.length - 1)
359 sb.append("<a href = \"")
371 protected static def formatToParagraph(String text) {
372 if(text === null || text.isEmpty)
375 var formattedText = text
376 val StringBuilder sb = new StringBuilder();
377 var StringBuilder lineBuilder = new StringBuilder();
378 var boolean isFirstElementOnNewLineEmptyChar = false;
380 formattedText = encodeJavadocSymbols(formattedText)
381 formattedText = WS_MATCHER.replaceFrom(formattedText, SPACE)
382 formattedText = SPACES_PATTERN.matcher(formattedText).replaceAll(" ")
384 val StringTokenizer tokenizer = new StringTokenizer(formattedText, " ", true);
386 while (tokenizer.hasMoreTokens) {
387 val nextElement = tokenizer.nextToken
389 if (lineBuilder.length != 0 && lineBuilder.length + nextElement.length > 80) {
390 if (lineBuilder.charAt(lineBuilder.length - 1) == SPACE) {
391 lineBuilder.setLength(lineBuilder.length - 1)
393 if (lineBuilder.length != 0 && lineBuilder.charAt(0) == SPACE) {
394 lineBuilder.deleteCharAt(0)
397 sb.append(lineBuilder).append(NEW_LINE)
398 lineBuilder.setLength(0)
400 if (nextElement == " ") {
401 isFirstElementOnNewLineEmptyChar = !isFirstElementOnNewLineEmptyChar;
405 if (isFirstElementOnNewLineEmptyChar) {
406 isFirstElementOnNewLineEmptyChar = !isFirstElementOnNewLineEmptyChar
408 lineBuilder.append(nextElement)
412 return sb.append(lineBuilder).append(NEW_LINE).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, 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, value)»
462 «LengthGenerator.generateLengthCheckerCall(fieldName, value + ".getValue()")»
466 «val fieldUpperCase = fieldName.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<? extends GeneratedProperty> properties) '''
476 final int prime = 31;
478 «FOR property : properties»
479 result = prime * result + «property.importedUtilClass».hashCode(«property.fieldName»);
483 def protected final generateAnnotation(AnnotationType annotation) '''
484 @«annotation.importedName»
485 «IF annotation.parameters !== null && !annotation.parameters.empty»
487 «FOR param : annotation.parameters SEPARATOR ","»
488 «param.name»=«param.value»