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.TypeMember
36 import org.opendaylight.mdsal.binding.model.api.TypeMemberComment
37 import org.opendaylight.mdsal.binding.model.api.YangSourceDefinition.Single
38 import org.opendaylight.mdsal.binding.model.api.YangSourceDefinition.Multiple
39 import org.opendaylight.mdsal.binding.model.util.TypeConstants
40 import org.opendaylight.mdsal.binding.model.util.Types
41 import org.opendaylight.mdsal.binding.spec.naming.BindingMapping
42 import org.opendaylight.yangtools.yang.common.QName
43 import org.opendaylight.yangtools.yang.common.Uint8
44 import org.opendaylight.yangtools.yang.common.Uint16
45 import org.opendaylight.yangtools.yang.common.Uint32
46 import org.opendaylight.yangtools.yang.common.Uint64
47 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode
48 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode
49 import org.opendaylight.yangtools.yang.model.api.NotificationDefinition
50 import org.opendaylight.yangtools.yang.model.api.RpcDefinition
51 import org.opendaylight.yangtools.yang.model.api.SchemaNode
52 import org.opendaylight.yangtools.yang.model.api.YangStmtMapping
53 import org.opendaylight.yangtools.yang.model.api.meta.DeclaredStatement
54 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement
55 import org.opendaylight.yangtools.yang.model.api.stmt.ModuleEffectiveStatement
56 import org.opendaylight.yangtools.yang.model.export.DeclaredStatementFormatter
59 abstract class BaseTemplate extends JavaFileTemplate {
60 static final char NEW_LINE = '\n'
61 static final char SPACE = ' '
62 static val AMP_MATCHER = CharMatcher.is('&')
63 static val WS_MATCHER = CharMatcher.anyOf("\n\t")
64 static val SPACES_PATTERN = Pattern.compile(" +")
65 static val NL_SPLITTER = Splitter.on(NEW_LINE)
66 static val TAIL_COMMENT_PATTERN = Pattern.compile("*/", Pattern.LITERAL);
67 static val YANG_FORMATTER = DeclaredStatementFormatter.builder()
68 .addIgnoredStatement(YangStmtMapping.CONTACT)
69 .addIgnoredStatement(YangStmtMapping.DESCRIPTION)
70 .addIgnoredStatement(YangStmtMapping.REFERENCE)
71 .addIgnoredStatement(YangStmtMapping.ORGANIZATION)
74 protected static val UINT_TYPES = ImmutableMap.of(
75 Types.typeForClass(Uint8), Types.typeForClass(Short),
76 Types.typeForClass(Uint16), Types.typeForClass(Integer),
77 Types.typeForClass(Uint32), Types.typeForClass(Long),
78 Types.typeForClass(Uint64), Types.typeForClass(BigInteger)
81 new(GeneratedType type) {
85 new(AbstractJavaGeneratedType javaType, GeneratedType type) {
89 final def generate() {
92 package «type.packageName»;
99 protected abstract def CharSequence body();
102 final protected def fieldName(GeneratedProperty property) {
106 final protected static def propertyNameFromGetter(MethodSignature getter) {
108 if (getter.name.startsWith(BindingMapping.BOOLEAN_GETTER_PREFIX)) {
109 prefix = BindingMapping.BOOLEAN_GETTER_PREFIX
110 } else if (getter.name.startsWith(BindingMapping.GETTER_PREFIX)) {
111 prefix = BindingMapping.GETTER_PREFIX
112 } else if (getter.name.startsWith(BindingMapping.NONNULL_PREFIX)) {
113 prefix = BindingMapping.NONNULL_PREFIX
115 throw new IllegalArgumentException(getter + " is not a getter")
117 return getter.name.substring(prefix.length).toFirstLower;
121 * Template method which generates the getter method for <code>field</code>
124 * generated property with data about field which is generated as the getter method
125 * @return string with the getter method source code in JAVA format
127 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();
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 method parameters with their types from <code>parameters</code>, annotating them
170 * with {@link NonNull}.
172 * @param parameters group of generated property instances which are transformed to the method parameters
173 * @return string with the list of the method parameters with their types in JAVA format
175 def final protected asNonNullArgumentsDeclaration(Iterable<GeneratedProperty> parameters) '''«IF !parameters.empty»
176 «FOR parameter : parameters SEPARATOR ", "»«parameter.returnType.importedNonNull» «parameter
177 .fieldName»«ENDFOR»«ENDIF»'''
180 * Template method which generates sequence of the names of the class attributes from <code>parameters</code>.
183 * group of generated property instances which are transformed to the sequence of parameter names
184 * @return string with the list of the parameter names of the <code>parameters</code>
186 def final protected asArguments(Collection<GeneratedProperty> parameters) '''«IF !parameters.empty»«FOR parameter : parameters SEPARATOR ", "»«parameter.
187 fieldName»«ENDFOR»«ENDIF»'''
190 * Template method which generates JAVA comments.
192 * @param comment string with the comment for whole JAVA class
193 * @return string with comment in JAVA format
195 def final protected asJavadoc(TypeMemberComment comment) {
196 if (comment === null) {
199 return wrapToDocumentation('''
200 «comment.contractDescription»
202 «comment.referenceDescription.formatReference»
204 «comment.typeSignature»
208 def static String wrapToDocumentation(String text) {
212 val StringBuilder sb = new StringBuilder().append("/**\n")
213 for (String t : NL_SPLITTER.split(text)) {
216 sb.append(SPACE).append(t)
225 def protected String formatDataForJavaDoc(GeneratedType type) {
226 val sb = new StringBuilder()
227 val comment = type.comment
228 if (comment !== null) {
229 sb.append(comment.javadoc)
232 appendSnippet(sb, type)
241 def static encodeJavadocSymbols(String description) {
242 if (description.nullOrEmpty) {
246 return TAIL_COMMENT_PATTERN.matcher(AMP_MATCHER.replaceFrom(description, "&")).replaceAll("*/")
249 def protected String formatDataForJavaDoc(GeneratedType type, String additionalComment) {
250 val comment = type.comment
251 if (comment === null) {
257 val sb = new StringBuilder().append(comment.javadoc)
258 appendSnippet(sb, type)
263 .append(additionalComment)
270 def private static void appendSnippet(StringBuilder sb, GeneratedType type) {
271 val optDef = type.yangSourceDefinition
272 if (optDef.present) {
276 if (def instanceof Single) {
279 .append("This class represents the following YANG schema fragment defined in module <b>")
280 .append(def.module.argument).append("</b>\n")
282 appendYangSnippet(sb, def.module, (node as EffectiveStatement<?, ?>).declared)
285 if (node instanceof SchemaNode) {
286 sb.append("The schema path to identify an instance is\n")
288 .append(formatSchemaPath(def.module.argument.localName, node.path.pathFromRoot))
291 if (hasBuilderClass(node)) {
292 val builderName = type.name + "Builder";
294 sb.append("\n<p>To create instances of this class use {@link ").append(builderName)
296 .append("@see ").append(builderName).append('\n')
297 if (node instanceof ListSchemaNode) {
298 val keyDef = node.keyDefinition
299 if (keyDef !== null && !keyDef.empty) {
300 sb.append("@see ").append(type.name).append("Key")
306 } else if (def instanceof Multiple) {
308 for (SchemaNode node : def.nodes) {
309 appendYangSnippet(sb, def.module, (node as EffectiveStatement<?, ?>).declared)
311 sb.append("</pre>\n")
316 def private static void appendYangSnippet(StringBuilder sb, ModuleEffectiveStatement module,
317 DeclaredStatement<?> stmt) {
318 for (String str : YANG_FORMATTER.toYangTextSnippet(module, stmt)) {
319 sb.append(str.encodeJavadocSymbols.encodeAngleBrackets.replaceAllIllegalChars)
323 def private static boolean hasBuilderClass(SchemaNode schemaNode) {
324 return schemaNode instanceof ContainerSchemaNode || schemaNode instanceof ListSchemaNode
325 || schemaNode instanceof RpcDefinition || schemaNode instanceof NotificationDefinition;
328 def private static String formatSchemaPath(String moduleName, Iterable<QName> schemaPath) {
329 val sb = new StringBuilder().append(moduleName);
331 var currentElement = Iterables.getFirst(schemaPath, null);
332 for (QName pathElement : schemaPath) {
334 if (!currentElement.namespace.equals(pathElement.namespace)) {
335 currentElement = pathElement
336 sb.append(pathElement)
338 sb.append(pathElement.getLocalName())
341 return sb.toString();
344 def static formatReference(String reference) '''
345 «IF reference !== null»
348 «reference.encodeAngleBrackets.formatToParagraph»
355 def asLink(String text) {
356 val StringBuilder sb = new StringBuilder()
358 var char lastChar = SPACE
359 var boolean badEnding = false
361 if (text.endsWith('.') || text.endsWith(':') || text.endsWith(',')) {
362 tempText = text.substring(0, text.length - 1)
363 lastChar = text.charAt(text.length - 1)
366 sb.append("<a href = \"")
378 protected static def formatToParagraph(String inputText) {
379 val StringBuilder sb = new StringBuilder();
380 var StringBuilder lineBuilder = new StringBuilder();
381 var boolean isFirstElementOnNewLineEmptyChar = false;
383 var formattedText = WS_MATCHER.replaceFrom(inputText.encodeJavadocSymbols, SPACE)
384 formattedText = SPACES_PATTERN.matcher(formattedText).replaceAll(" ")
386 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;
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»