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
11 import static org.opendaylight.mdsal.binding.model.util.Types.STRING;
13 import com.google.common.base.CharMatcher
14 import com.google.common.base.MoreObjects
15 import com.google.common.base.Splitter
16 import com.google.common.collect.ImmutableMap
17 import com.google.common.collect.Iterables
18 import java.math.BigInteger
19 import java.util.Collection
21 import java.util.Locale
22 import java.util.Map.Entry
23 import java.util.StringTokenizer
24 import java.util.regex.Pattern
25 import org.gaul.modernizer_maven_annotations.SuppressModernizer
26 import org.opendaylight.mdsal.binding.model.api.AnnotationType
27 import org.opendaylight.mdsal.binding.model.api.ConcreteType
28 import org.opendaylight.mdsal.binding.model.api.Constant
29 import org.opendaylight.mdsal.binding.model.api.GeneratedProperty
30 import org.opendaylight.mdsal.binding.model.api.GeneratedType
31 import org.opendaylight.mdsal.binding.model.api.JavaTypeName
32 import org.opendaylight.mdsal.binding.model.api.MethodSignature
33 import org.opendaylight.mdsal.binding.model.api.Restrictions
34 import org.opendaylight.mdsal.binding.model.api.Type
35 import org.opendaylight.mdsal.binding.model.api.TypeMember
36 import org.opendaylight.mdsal.binding.model.api.YangSourceDefinition.Single
37 import org.opendaylight.mdsal.binding.model.api.YangSourceDefinition.Multiple
38 import org.opendaylight.mdsal.binding.model.util.TypeConstants
39 import org.opendaylight.mdsal.binding.model.util.Types
40 import org.opendaylight.mdsal.binding.spec.naming.BindingMapping
41 import org.opendaylight.yangtools.yang.common.QName
42 import org.opendaylight.yangtools.yang.common.Uint8
43 import org.opendaylight.yangtools.yang.common.Uint16
44 import org.opendaylight.yangtools.yang.common.Uint32
45 import org.opendaylight.yangtools.yang.common.Uint64
46 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode
47 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode
48 import org.opendaylight.yangtools.yang.model.api.NotificationDefinition
49 import org.opendaylight.yangtools.yang.model.api.RpcDefinition
50 import org.opendaylight.yangtools.yang.model.api.SchemaNode
51 import org.opendaylight.yangtools.yang.model.api.YangStmtMapping
52 import org.opendaylight.yangtools.yang.model.api.meta.DeclaredStatement
53 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement
54 import org.opendaylight.yangtools.yang.model.api.stmt.ModuleEffectiveStatement
55 import org.opendaylight.yangtools.yang.model.export.DeclaredStatementFormatter
58 abstract class BaseTemplate extends JavaFileTemplate {
59 static final char NEW_LINE = '\n'
60 static final char SPACE = ' '
61 static val AMP_MATCHER = CharMatcher.is('&')
62 static val WS_MATCHER = CharMatcher.anyOf("\n\t")
63 static val SPACES_PATTERN = Pattern.compile(" +")
64 static val NL_SPLITTER = Splitter.on(NEW_LINE)
65 static val TAIL_COMMENT_PATTERN = Pattern.compile("*/", Pattern.LITERAL);
66 static val YANG_FORMATTER = DeclaredStatementFormatter.builder()
67 .addIgnoredStatement(YangStmtMapping.CONTACT)
68 .addIgnoredStatement(YangStmtMapping.DESCRIPTION)
69 .addIgnoredStatement(YangStmtMapping.REFERENCE)
70 .addIgnoredStatement(YangStmtMapping.ORGANIZATION)
73 protected static val UINT_TYPES = ImmutableMap.of(
74 Types.typeForClass(Uint8), Types.typeForClass(Short),
75 Types.typeForClass(Uint16), Types.typeForClass(Integer),
76 Types.typeForClass(Uint32), Types.typeForClass(Long),
77 Types.typeForClass(Uint64), Types.typeForClass(BigInteger)
80 new(GeneratedType type) {
84 new(AbstractJavaGeneratedType javaType, GeneratedType type) {
88 final def generate() {
91 package «type.packageName»;
98 protected abstract def CharSequence body();
101 final protected def fieldName(GeneratedProperty property) {
105 final protected def propertyNameFromGetter(MethodSignature getter) {
107 if (getter.name.startsWith(BindingMapping.BOOLEAN_GETTER_PREFIX)) {
108 prefix = BindingMapping.BOOLEAN_GETTER_PREFIX
109 } else if (getter.name.startsWith(BindingMapping.GETTER_PREFIX)) {
110 prefix = BindingMapping.GETTER_PREFIX
111 } else if (getter.name.startsWith(BindingMapping.NONNULL_PREFIX)) {
112 prefix = BindingMapping.NONNULL_PREFIX
114 throw new IllegalArgumentException(getter + " is not a getter")
116 return getter.name.substring(prefix.length).toFirstLower;
120 * Template method which generates the getter method for <code>field</code>
123 * generated property with data about field which is generated as the getter method
124 * @return string with the getter method source code in JAVA format
126 protected def getterMethod(GeneratedProperty field) {
128 public «field.returnType.importedName» «field.getterMethodName»() {
129 «val fieldName = field.fieldName»
130 «IF field.returnType.name.endsWith("[]")»
131 return «fieldName» == null ? null : «fieldName».clone();
139 final protected def getterMethodName(GeneratedProperty field) {
140 val prefix = if(field.returnType.equals(Types.BOOLEAN)) "is" else "get"
141 return '''«prefix»«field.name.toFirstUpper»'''
145 * Template method which generates the setter method for <code>field</code>
148 * generated property with data about field which is generated as the setter method
149 * @return string with the setter method source code in JAVA format
151 final protected def setterMethod(GeneratedProperty field) '''
152 «val returnType = field.returnType.importedName»
153 public «type.name» set«field.name.toFirstUpper»(«returnType» value) {
154 this.«field.fieldName» = value;
160 * Template method which generates method parameters with their types from <code>parameters</code>.
163 * group of generated property instances which are transformed to the method parameters
164 * @return string with the list of the method parameters with their types in JAVA format
166 def final protected asArgumentsDeclaration(Iterable<GeneratedProperty> parameters) '''«IF !parameters.empty»«FOR parameter : parameters SEPARATOR ", "»«parameter.
167 returnType.importedName» «parameter.fieldName»«ENDFOR»«ENDIF»'''
170 * Template method which generates sequence of the names of the class attributes from <code>parameters</code>.
173 * group of generated property instances which are transformed to the sequence of parameter names
174 * @return string with the list of the parameter names of the <code>parameters</code>
176 def final protected asArguments(Collection<GeneratedProperty> parameters) '''«IF !parameters.empty»«FOR parameter : parameters SEPARATOR ", "»«parameter.
177 fieldName»«ENDFOR»«ENDIF»'''
180 * Template method which generates JAVA comments.
182 * @param comment string with the comment for whole JAVA class
183 * @return string with comment in JAVA format
185 def protected CharSequence asJavadoc(String comment) {
186 if (comment === null) {
190 «wrapToDocumentation(formatToParagraph(comment.trim))»
194 def static String wrapToDocumentation(String text) {
198 val StringBuilder sb = new StringBuilder().append("/**\n")
199 for (String t : NL_SPLITTER.split(text)) {
202 sb.append(SPACE).append(t)
211 def protected String formatDataForJavaDoc(GeneratedType type) {
212 val sb = new StringBuilder()
213 val comment = type.comment
214 if (comment !== null) {
215 sb.append(comment.javadoc)
218 appendSnippet(sb, type)
227 def static encodeJavadocSymbols(String description) {
228 if (description.nullOrEmpty) {
232 return TAIL_COMMENT_PATTERN.matcher(AMP_MATCHER.replaceFrom(description, "&")).replaceAll("*/")
235 def protected String formatDataForJavaDoc(GeneratedType type, String additionalComment) {
236 val comment = type.comment
237 if (comment === null) {
243 val sb = new StringBuilder().append(comment.javadoc)
244 appendSnippet(sb, type)
249 .append(additionalComment)
256 def private static void appendSnippet(StringBuilder sb, GeneratedType type) {
257 val optDef = type.yangSourceDefinition
258 if (optDef.present) {
262 if (def instanceof Single) {
265 .append("This class represents the following YANG schema fragment defined in module <b>")
266 .append(def.module.argument).append("</b>\n")
268 appendYangSnippet(sb, def.module, (node as EffectiveStatement<?, ?>).declared)
271 if (node instanceof SchemaNode) {
272 sb.append("The schema path to identify an instance is\n")
274 .append(formatSchemaPath(def.module.argument, node.path.pathFromRoot))
277 if (hasBuilderClass(node)) {
278 val builderName = type.name + "Builder";
280 sb.append("\n<p>To create instances of this class use {@link ").append(builderName)
282 .append("@see ").append(builderName).append('\n')
283 if (node instanceof ListSchemaNode) {
284 val keyDef = node.keyDefinition
285 if (keyDef !== null && !keyDef.empty) {
286 sb.append("@see ").append(type.name).append("Key")
292 } else if (def instanceof Multiple) {
294 for (SchemaNode node : def.nodes) {
295 appendYangSnippet(sb, def.module, (node as EffectiveStatement<?, ?>).declared)
297 sb.append("</pre>\n")
302 def private static void appendYangSnippet(StringBuilder sb, ModuleEffectiveStatement module,
303 DeclaredStatement<?> stmt) {
304 for (String str : YANG_FORMATTER.toYangTextSnippet(module, stmt)) {
305 sb.append(encodeAngleBrackets(encodeJavadocSymbols(str)))
309 def private static boolean hasBuilderClass(SchemaNode schemaNode) {
310 return schemaNode instanceof ContainerSchemaNode || schemaNode instanceof ListSchemaNode
311 || schemaNode instanceof RpcDefinition || schemaNode instanceof NotificationDefinition;
314 def private static String formatSchemaPath(String moduleName, Iterable<QName> schemaPath) {
315 val sb = new StringBuilder().append(moduleName);
317 var currentElement = Iterables.getFirst(schemaPath, null);
318 for (QName pathElement : schemaPath) {
320 if (!currentElement.namespace.equals(pathElement.namespace)) {
321 currentElement = pathElement
322 sb.append(pathElement)
324 sb.append(pathElement.getLocalName())
327 return sb.toString();
330 def protected static String formatDataForJavaDoc(TypeMember type, String additionalComment) {
331 val StringBuilder typeDescriptionBuilder = new StringBuilder();
332 if (!type.comment.nullOrEmpty) {
333 typeDescriptionBuilder.append(formatToParagraph(type.comment))
334 typeDescriptionBuilder.append(NEW_LINE)
335 typeDescriptionBuilder.append(NEW_LINE)
336 typeDescriptionBuilder.append(NEW_LINE)
338 typeDescriptionBuilder.append(additionalComment)
339 var typeDescription = wrapToDocumentation(typeDescriptionBuilder.toString)
345 def asCode(String text) {
346 return "<code>" + text + "</code>"
349 def asLink(String text) {
350 val StringBuilder sb = new StringBuilder()
352 var char lastChar = SPACE
353 var boolean badEnding = false
355 if (text.endsWith('.') || text.endsWith(':') || text.endsWith(',')) {
356 tempText = text.substring(0, text.length - 1)
357 lastChar = text.charAt(text.length - 1)
360 sb.append("<a href = \"")
372 protected static def formatToParagraph(String text) {
373 if(text === null || text.isEmpty)
376 var formattedText = text
377 val StringBuilder sb = new StringBuilder();
378 var StringBuilder lineBuilder = new StringBuilder();
379 var boolean isFirstElementOnNewLineEmptyChar = false;
381 formattedText = encodeJavadocSymbols(formattedText)
382 formattedText = WS_MATCHER.replaceFrom(formattedText, SPACE)
383 formattedText = SPACES_PATTERN.matcher(formattedText).replaceAll(" ")
385 val StringTokenizer tokenizer = new StringTokenizer(formattedText, " ", true);
387 while (tokenizer.hasMoreTokens) {
388 val nextElement = tokenizer.nextToken
390 if (lineBuilder.length != 0 && lineBuilder.length + nextElement.length > 80) {
391 if (lineBuilder.charAt(lineBuilder.length - 1) == SPACE) {
392 lineBuilder.setLength(lineBuilder.length - 1)
394 if (lineBuilder.length != 0 && lineBuilder.charAt(0) == SPACE) {
395 lineBuilder.deleteCharAt(0)
398 sb.append(lineBuilder).append(NEW_LINE)
399 lineBuilder.setLength(0)
401 if (nextElement == " ") {
402 isFirstElementOnNewLineEmptyChar = !isFirstElementOnNewLineEmptyChar;
406 if (isFirstElementOnNewLineEmptyChar) {
407 isFirstElementOnNewLineEmptyChar = !isFirstElementOnNewLineEmptyChar
409 lineBuilder.append(nextElement)
413 return sb.append(lineBuilder).append(NEW_LINE).toString
416 def protected generateToString(Collection<GeneratedProperty> properties) '''
417 «IF !properties.empty»
418 @«OVERRIDE.importedName»
419 public «STRING.importedName» toString() {
420 final «MoreObjects.importedName».ToStringHelper helper = «MoreObjects.importedName».toStringHelper(«type.importedName».class);
421 «FOR property : properties»
422 «CODEHELPERS.importedName».appendValue(helper, "«property.fieldName»", «property.fieldName»);
424 return helper.toString();
430 * Template method which generates method parameters with their types from <code>parameters</code>.
433 * list of parameter instances which are transformed to the method parameters
434 * @return string with the list of the method parameters with their types in JAVA format
436 def protected generateParameters(List<MethodSignature.Parameter> parameters) '''«
437 IF !parameters.empty»«
438 FOR parameter : parameters SEPARATOR ", "»«
439 parameter.type.importedName» «parameter.name»«
444 def protected emitConstant(Constant c) '''
445 «IF BindingMapping.QNAME_STATIC_FIELD_NAME.equals(c.name)»
446 «val entry = c.value as Entry<JavaTypeName, String>»
447 public static final «c.type.importedNonNull» «c.name» = «entry.key.importedName».«BindingMapping.MODULE_INFO_QNAMEOF_METHOD_NAME»("«entry.value»");
449 public static final «c.type.importedName» «c.name» = «c.value»;
453 def protected generateCheckers(GeneratedProperty field, Restrictions restrictions, Type actualType) '''
454 «IF restrictions.rangeConstraint.present»
455 «AbstractRangeGenerator.forType(actualType).generateRangeChecker(field.name.toFirstUpper,
456 restrictions.rangeConstraint.get, this)»
458 «IF restrictions.lengthConstraint.present»
459 «LengthGenerator.generateLengthChecker(field.fieldName, actualType, restrictions.lengthConstraint.get, this)»
463 def protected checkArgument(GeneratedProperty property, Restrictions restrictions, Type actualType, String value) '''
464 «IF restrictions.getRangeConstraint.isPresent»
465 «IF actualType instanceof ConcreteType»
466 «AbstractRangeGenerator.forType(actualType).generateRangeCheckerCall(property.getName.toFirstUpper, value)»
468 «AbstractRangeGenerator.forType(actualType).generateRangeCheckerCall(property.getName.toFirstUpper, value + ".getValue()")»
471 «val fieldName = property.fieldName»
472 «IF restrictions.getLengthConstraint.isPresent»
473 «IF actualType instanceof ConcreteType»
474 «LengthGenerator.generateLengthCheckerCall(fieldName, value)»
476 «LengthGenerator.generateLengthCheckerCall(fieldName, value + ".getValue()")»
480 «val fieldUpperCase = fieldName.toUpperCase(Locale.ENGLISH)»
481 «FOR currentConstant : type.getConstantDefinitions»
482 «IF currentConstant.getName.startsWith(TypeConstants.PATTERN_CONSTANT_NAME)
483 && fieldUpperCase.equals(currentConstant.getName.substring(TypeConstants.PATTERN_CONSTANT_NAME.length))»
484 «CODEHELPERS.importedName».checkPattern(value, «Constants.MEMBER_PATTERN_LIST»«fieldName», «Constants.MEMBER_REGEX_LIST»«fieldName»);
489 def protected hashCodeResult(Collection<GeneratedProperty> properties) '''
490 final int prime = 31;
492 «FOR property : properties»
493 result = prime * result + «property.importedUtilClass».hashCode(«property.fieldName»);
497 def protected final generateAnnotation(AnnotationType annotation) '''
498 @«annotation.importedName»
499 «IF annotation.parameters !== null && !annotation.parameters.empty»
501 «FOR param : annotation.parameters SEPARATOR ","»
502 «param.name»=«param.value»