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 extension org.opendaylight.mdsal.binding.model.util.BindingGeneratorUtil.encodeAngleBrackets
11 import static extension org.opendaylight.mdsal.binding.model.util.BindingGeneratorUtil.replaceAllIllegalChars
13 import com.google.common.base.CharMatcher
14 import com.google.common.base.Splitter
15 import com.google.common.collect.ImmutableMap
16 import com.google.common.collect.Iterables
17 import java.math.BigInteger
18 import java.util.Collection
20 import java.util.Locale
21 import java.util.Map.Entry
22 import java.util.StringTokenizer
23 import java.util.regex.Pattern
24 import org.eclipse.jdt.annotation.NonNull;
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.TypeMemberComment
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 static def propertyNameFromGetter(MethodSignature getter) {
107 if (BindingMapping.isGetterMethodName(getter.name)) {
108 prefix = BindingMapping.GETTER_PREFIX
109 } else if (BindingMapping.isNonnullMethodName(getter.name)) {
110 prefix = BindingMapping.NONNULL_PREFIX
112 throw new IllegalArgumentException(getter + " is not a getter")
114 return getter.name.substring(prefix.length).toFirstLower;
118 * Template method which generates the getter method for <code>field</code>
121 * generated property with data about field which is generated as the getter method
122 * @return string with the getter method source code in JAVA format
124 protected def getterMethod(GeneratedProperty field) '''
125 «val methodName = field.getterMethodName»
126 public «field.returnType.importedName» «methodName»() {
127 «val fieldName = field.fieldName»
128 «IF field.returnType.name.endsWith("[]")»
129 return «fieldName» == null ? null : «fieldName».clone();
134 «IF field.returnType == Types.BOOLEAN»
136 @«DEPRECATED.importedName»(forRemoval = true)
137 public final «field.returnType.importedName» «BindingMapping.BOOLEAN_GETTER_PREFIX»«field.name.toFirstUpper»() {
138 return «methodName»();
143 final protected def getterMethodName(GeneratedProperty field) {
144 return '''«BindingMapping.GETTER_PREFIX»«field.name.toFirstUpper»'''
148 * Template method which generates the setter method for <code>field</code>
151 * generated property with data about field which is generated as the setter method
152 * @return string with the setter method source code in JAVA format
154 final protected def setterMethod(GeneratedProperty field) '''
155 «val returnType = field.returnType.importedName»
156 public «type.name» set«field.name.toFirstUpper»(«returnType» value) {
157 this.«field.fieldName» = value;
163 * Template method which generates method parameters with their types from <code>parameters</code>.
166 * group of generated property instances which are transformed to the method parameters
167 * @return string with the list of the method parameters with their types in JAVA format
169 def final protected asArgumentsDeclaration(Iterable<GeneratedProperty> parameters) '''«IF !parameters.empty»«FOR parameter : parameters SEPARATOR ", "»«parameter.
170 returnType.importedName» «parameter.fieldName»«ENDFOR»«ENDIF»'''
173 * Template method which generates method parameters with their types from <code>parameters</code>, annotating them
174 * with {@link NonNull}.
176 * @param parameters group of generated property instances which are transformed to the method parameters
177 * @return string with the list of the method parameters with their types in JAVA format
179 def final protected asNonNullArgumentsDeclaration(Iterable<GeneratedProperty> parameters) '''«IF !parameters.empty»
180 «FOR parameter : parameters SEPARATOR ", "»«parameter.returnType.importedNonNull» «parameter
181 .fieldName»«ENDFOR»«ENDIF»'''
184 * Template method which generates sequence of the names of the class attributes from <code>parameters</code>.
187 * group of generated property instances which are transformed to the sequence of parameter names
188 * @return string with the list of the parameter names of the <code>parameters</code>
190 def final protected asArguments(Collection<GeneratedProperty> parameters) '''«IF !parameters.empty»«FOR parameter : parameters SEPARATOR ", "»«parameter.
191 fieldName»«ENDFOR»«ENDIF»'''
194 * Template method which generates JAVA comments.
196 * @param comment string with the comment for whole JAVA class
197 * @return string with comment in JAVA format
199 def final protected asJavadoc(TypeMemberComment comment) {
200 if (comment === null) {
203 return wrapToDocumentation('''
204 «comment.contractDescription»
206 «comment.referenceDescription.formatReference»
208 «comment.typeSignature»
212 def static String wrapToDocumentation(String text) {
216 val StringBuilder sb = new StringBuilder().append("/**\n")
217 for (String t : NL_SPLITTER.split(text)) {
220 sb.append(SPACE).append(t)
229 def protected String formatDataForJavaDoc(GeneratedType type) {
230 val sb = new StringBuilder()
231 val comment = type.comment
232 if (comment !== null) {
233 sb.append(comment.javadoc)
236 appendSnippet(sb, type)
245 def static encodeJavadocSymbols(String description) {
246 if (description.nullOrEmpty) {
250 return TAIL_COMMENT_PATTERN.matcher(AMP_MATCHER.replaceFrom(description, "&")).replaceAll("*/")
253 def protected String formatDataForJavaDoc(GeneratedType type, String additionalComment) {
254 val comment = type.comment
255 if (comment === null) {
261 val sb = new StringBuilder().append(comment.javadoc)
262 appendSnippet(sb, type)
267 .append(additionalComment)
274 def private static void appendSnippet(StringBuilder sb, GeneratedType type) {
275 val optDef = type.yangSourceDefinition
276 if (optDef.present) {
280 if (def instanceof Single) {
283 .append("This class represents the following YANG schema fragment defined in module <b>")
284 .append(def.module.argument).append("</b>\n")
286 appendYangSnippet(sb, def.module, (node as EffectiveStatement<?, ?>).declared)
289 if (node instanceof SchemaNode) {
290 sb.append("The schema path to identify an instance is\n")
292 .append(formatSchemaPath(def.module.argument.localName, node.path.pathFromRoot))
295 if (hasBuilderClass(node)) {
296 val builderName = type.name + "Builder";
298 sb.append("\n<p>To create instances of this class use {@link ").append(builderName)
300 .append("@see ").append(builderName).append('\n')
301 if (node instanceof ListSchemaNode) {
302 val keyDef = node.keyDefinition
303 if (keyDef !== null && !keyDef.empty) {
304 sb.append("@see ").append(type.name).append("Key")
310 } else if (def instanceof Multiple) {
312 for (SchemaNode node : def.nodes) {
313 appendYangSnippet(sb, def.module, (node as EffectiveStatement<?, ?>).declared)
315 sb.append("</pre>\n")
320 def private static void appendYangSnippet(StringBuilder sb, ModuleEffectiveStatement module,
321 DeclaredStatement<?> stmt) {
322 for (String str : YANG_FORMATTER.toYangTextSnippet(module, stmt)) {
323 sb.append(str.encodeJavadocSymbols.encodeAngleBrackets.replaceAllIllegalChars)
327 def private static boolean hasBuilderClass(SchemaNode schemaNode) {
328 return schemaNode instanceof ContainerSchemaNode || schemaNode instanceof ListSchemaNode
329 || schemaNode instanceof RpcDefinition || schemaNode instanceof NotificationDefinition;
332 def private static String formatSchemaPath(String moduleName, Iterable<QName> schemaPath) {
333 val sb = new StringBuilder().append(moduleName);
335 var currentElement = Iterables.getFirst(schemaPath, null);
336 for (QName pathElement : schemaPath) {
338 if (!currentElement.namespace.equals(pathElement.namespace)) {
339 currentElement = pathElement
340 sb.append(pathElement)
342 sb.append(pathElement.getLocalName())
345 return sb.toString();
348 def static formatReference(String reference) '''
349 «IF reference !== null»
352 «reference.encodeAngleBrackets.formatToParagraph»
359 def asLink(String text) {
360 val StringBuilder sb = new StringBuilder()
362 var char lastChar = SPACE
363 var boolean badEnding = false
365 if (text.endsWith('.') || text.endsWith(':') || text.endsWith(',')) {
366 tempText = text.substring(0, text.length - 1)
367 lastChar = text.charAt(text.length - 1)
370 sb.append("<a href = \"")
382 protected static def formatToParagraph(String inputText) {
383 val StringBuilder sb = new StringBuilder();
384 var StringBuilder lineBuilder = new StringBuilder();
385 var boolean isFirstElementOnNewLineEmptyChar = false;
387 var formattedText = WS_MATCHER.replaceFrom(inputText.encodeJavadocSymbols, SPACE)
388 formattedText = SPACES_PATTERN.matcher(formattedText).replaceAll(" ")
390 val StringTokenizer tokenizer = new StringTokenizer(formattedText, " ", true)
391 while (tokenizer.hasMoreTokens) {
392 val nextElement = tokenizer.nextToken
394 if (lineBuilder.length != 0 && lineBuilder.length + nextElement.length > 80) {
395 if (lineBuilder.charAt(lineBuilder.length - 1) == SPACE) {
396 lineBuilder.setLength(lineBuilder.length - 1)
398 if (lineBuilder.length != 0 && lineBuilder.charAt(0) == SPACE) {
399 lineBuilder.deleteCharAt(0)
402 sb.append(lineBuilder).append(NEW_LINE)
403 lineBuilder.setLength(0)
405 if (nextElement == " ") {
406 isFirstElementOnNewLineEmptyChar = !isFirstElementOnNewLineEmptyChar;
409 if (isFirstElementOnNewLineEmptyChar) {
410 isFirstElementOnNewLineEmptyChar = !isFirstElementOnNewLineEmptyChar
412 lineBuilder.append(nextElement)
416 return sb.append(lineBuilder).append(NEW_LINE).toString
420 * Template method which generates method parameters with their types from <code>parameters</code>.
423 * list of parameter instances which are transformed to the method parameters
424 * @return string with the list of the method parameters with their types in JAVA format
426 def protected generateParameters(List<MethodSignature.Parameter> parameters) '''«
427 IF !parameters.empty»«
428 FOR parameter : parameters SEPARATOR ", "»«
429 parameter.type.importedName» «parameter.name»«
434 def protected emitConstant(Constant c) '''
435 «IF BindingMapping.QNAME_STATIC_FIELD_NAME.equals(c.name)»
436 «val entry = c.value as Entry<JavaTypeName, String>»
437 public static final «c.type.importedNonNull» «c.name» = «entry.key.importedName».«BindingMapping.MODULE_INFO_QNAMEOF_METHOD_NAME»("«entry.value»");
439 public static final «c.type.importedName» «c.name» = «c.value»;
443 def protected generateCheckers(GeneratedProperty field, Restrictions restrictions, Type actualType) '''
444 «IF restrictions.rangeConstraint.present»
445 «AbstractRangeGenerator.forType(actualType).generateRangeChecker(field.name.toFirstUpper,
446 restrictions.rangeConstraint.get, this)»
448 «IF restrictions.lengthConstraint.present»
449 «LengthGenerator.generateLengthChecker(field.fieldName, actualType, restrictions.lengthConstraint.get, this)»
453 def protected checkArgument(GeneratedProperty property, Restrictions restrictions, Type actualType, String value) '''
454 «IF restrictions.getRangeConstraint.isPresent»
455 «IF actualType instanceof ConcreteType»
456 «AbstractRangeGenerator.forType(actualType).generateRangeCheckerCall(property.getName.toFirstUpper, value)»
458 «AbstractRangeGenerator.forType(actualType).generateRangeCheckerCall(property.getName.toFirstUpper, value + ".getValue()")»
461 «val fieldName = property.fieldName»
462 «IF restrictions.getLengthConstraint.isPresent»
463 «IF actualType instanceof ConcreteType»
464 «LengthGenerator.generateLengthCheckerCall(fieldName, value)»
466 «LengthGenerator.generateLengthCheckerCall(fieldName, value + ".getValue()")»
470 «val fieldUpperCase = fieldName.toUpperCase(Locale.ENGLISH)»
471 «FOR currentConstant : type.getConstantDefinitions»
472 «IF currentConstant.getName.startsWith(TypeConstants.PATTERN_CONSTANT_NAME)
473 && fieldUpperCase.equals(currentConstant.getName.substring(TypeConstants.PATTERN_CONSTANT_NAME.length))»
474 «CODEHELPERS.importedName».checkPattern(value, «Constants.MEMBER_PATTERN_LIST»«fieldName», «Constants.MEMBER_REGEX_LIST»«fieldName»);
479 def protected hashCodeResult(Collection<? extends GeneratedProperty> properties) '''
480 final int prime = 31;
482 «FOR property : properties»
483 result = prime * result + «property.importedUtilClass».hashCode(«property.fieldName»);
487 def protected final generateAnnotation(AnnotationType annotation) '''
488 @«annotation.importedName»
489 «IF annotation.parameters !== null && !annotation.parameters.empty»
491 «FOR param : annotation.parameters SEPARATOR ","»
492 «param.name»=«param.value»