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) {
106 * Template method which generates the getter method for <code>field</code>
109 * generated property with data about field which is generated as the getter method
110 * @return string with the getter method source code in JAVA format
112 protected def getterMethod(GeneratedProperty field) '''
113 «val methodName = field.getterMethodName»
114 public «field.returnType.importedName» «methodName»() {
115 «val fieldName = field.fieldName»
116 «IF field.returnType.name.endsWith("[]")»
117 return «fieldName» == null ? null : «fieldName».clone();
122 «IF field.returnType == Types.BOOLEAN»
124 @«DEPRECATED.importedName»(forRemoval = true)
125 public final «field.returnType.importedName» «BindingMapping.BOOLEAN_GETTER_PREFIX»«field.name.toFirstUpper»() {
126 return «methodName»();
131 final protected def getterMethodName(GeneratedProperty field) {
132 return '''«BindingMapping.GETTER_PREFIX»«field.name.toFirstUpper»'''
136 * Template method which generates the setter method for <code>field</code>
139 * generated property with data about field which is generated as the setter method
140 * @return string with the setter method source code in JAVA format
142 final protected def setterMethod(GeneratedProperty field) '''
143 «val returnType = field.returnType.importedName»
144 public «type.name» set«field.name.toFirstUpper»(«returnType» value) {
145 this.«field.fieldName» = value;
151 * Template method which generates method parameters with their types from <code>parameters</code>.
154 * group of generated property instances which are transformed to the method parameters
155 * @return string with the list of the method parameters with their types in JAVA format
157 def final protected asArgumentsDeclaration(Iterable<GeneratedProperty> parameters) '''«IF !parameters.empty»«FOR parameter : parameters SEPARATOR ", "»«parameter.
158 returnType.importedName» «parameter.fieldName»«ENDFOR»«ENDIF»'''
161 * Template method which generates method parameters with their types from <code>parameters</code>, annotating them
162 * with {@link NonNull}.
164 * @param parameters group of generated property instances which are transformed to the method parameters
165 * @return string with the list of the method parameters with their types in JAVA format
167 def final protected asNonNullArgumentsDeclaration(Iterable<GeneratedProperty> parameters) '''«IF !parameters.empty»
168 «FOR parameter : parameters SEPARATOR ", "»«parameter.returnType.importedNonNull» «parameter
169 .fieldName»«ENDFOR»«ENDIF»'''
172 * Template method which generates sequence of the names of the class attributes from <code>parameters</code>.
175 * group of generated property instances which are transformed to the sequence of parameter names
176 * @return string with the list of the parameter names of the <code>parameters</code>
178 def final protected asArguments(Collection<GeneratedProperty> parameters) '''«IF !parameters.empty»«FOR parameter : parameters SEPARATOR ", "»«parameter.
179 fieldName»«ENDFOR»«ENDIF»'''
182 * Template method which generates JAVA comments.
184 * @param comment string with the comment for whole JAVA class
185 * @return string with comment in JAVA format
187 def final protected asJavadoc(TypeMemberComment comment) {
188 if (comment === null) {
191 return wrapToDocumentation('''
192 «comment.contractDescription»
194 «comment.referenceDescription.formatReference»
196 «comment.typeSignature»
200 def static String wrapToDocumentation(String text) {
204 val StringBuilder sb = new StringBuilder().append("/**\n")
205 for (String t : NL_SPLITTER.split(text)) {
208 sb.append(SPACE).append(t)
217 def protected String formatDataForJavaDoc(GeneratedType type) {
218 val sb = new StringBuilder()
219 val comment = type.comment
220 if (comment !== null) {
221 sb.append(comment.javadoc)
224 appendSnippet(sb, type)
233 def static encodeJavadocSymbols(String description) {
234 if (description.nullOrEmpty) {
238 return TAIL_COMMENT_PATTERN.matcher(AMP_MATCHER.replaceFrom(description, "&")).replaceAll("*/")
241 def protected String formatDataForJavaDoc(GeneratedType type, String additionalComment) {
242 val comment = type.comment
243 if (comment === null) {
249 val sb = new StringBuilder().append(comment.javadoc)
250 appendSnippet(sb, type)
255 .append(additionalComment)
262 def private static void appendSnippet(StringBuilder sb, GeneratedType type) {
263 val optDef = type.yangSourceDefinition
264 if (optDef.present) {
268 if (def instanceof Single) {
271 .append("This class represents the following YANG schema fragment defined in module <b>")
272 .append(def.module.argument.localName).append("</b>\n")
274 appendYangSnippet(sb, def.module, (node as EffectiveStatement<?, ?>).declared)
277 if (node instanceof SchemaNode) {
278 sb.append("The schema path to identify an instance is\n")
280 .append(formatSchemaPath(def.module.argument.localName, node.path.pathFromRoot))
283 if (hasBuilderClass(node)) {
284 val builderName = type.name + "Builder";
286 sb.append("\n<p>To create instances of this class use {@link ").append(builderName)
288 .append("@see ").append(builderName).append('\n')
289 if (node instanceof ListSchemaNode) {
290 val keyDef = node.keyDefinition
291 if (keyDef !== null && !keyDef.empty) {
292 sb.append("@see ").append(type.name).append("Key")
298 } else if (def instanceof Multiple) {
300 for (SchemaNode node : def.nodes) {
301 appendYangSnippet(sb, def.module, (node as EffectiveStatement<?, ?>).declared)
303 sb.append("</pre>\n")
308 def private static void appendYangSnippet(StringBuilder sb, ModuleEffectiveStatement module,
309 DeclaredStatement<?> stmt) {
310 for (String str : YANG_FORMATTER.toYangTextSnippet(module, stmt)) {
311 sb.append(str.encodeJavadocSymbols.encodeAngleBrackets.replaceAllIllegalChars)
315 def private static boolean hasBuilderClass(SchemaNode schemaNode) {
316 return schemaNode instanceof ContainerSchemaNode || schemaNode instanceof ListSchemaNode
317 || schemaNode instanceof RpcDefinition || schemaNode instanceof NotificationDefinition;
320 def private static String formatSchemaPath(String moduleName, Iterable<QName> schemaPath) {
321 val sb = new StringBuilder().append(moduleName);
323 var currentElement = Iterables.getFirst(schemaPath, null);
324 for (QName pathElement : schemaPath) {
326 if (!currentElement.namespace.equals(pathElement.namespace)) {
327 currentElement = pathElement
328 sb.append(pathElement)
330 sb.append(pathElement.getLocalName())
333 return sb.toString();
336 def static formatReference(String reference) '''
337 «IF reference !== null»
340 «reference.encodeAngleBrackets.formatToParagraph»
347 def asLink(String text) {
348 val StringBuilder sb = new StringBuilder()
350 var char lastChar = SPACE
351 var boolean badEnding = false
353 if (text.endsWith('.') || text.endsWith(':') || text.endsWith(',')) {
354 tempText = text.substring(0, text.length - 1)
355 lastChar = text.charAt(text.length - 1)
358 sb.append("<a href = \"")
370 protected static def formatToParagraph(String inputText) {
371 val StringBuilder sb = new StringBuilder();
372 var StringBuilder lineBuilder = new StringBuilder();
373 var boolean isFirstElementOnNewLineEmptyChar = false;
375 var formattedText = WS_MATCHER.replaceFrom(inputText.encodeJavadocSymbols, SPACE)
376 formattedText = SPACES_PATTERN.matcher(formattedText).replaceAll(" ")
378 val StringTokenizer tokenizer = new StringTokenizer(formattedText, " ", true)
379 while (tokenizer.hasMoreTokens) {
380 val nextElement = tokenizer.nextToken
382 if (lineBuilder.length != 0 && lineBuilder.length + nextElement.length > 80) {
383 if (lineBuilder.charAt(lineBuilder.length - 1) == SPACE) {
384 lineBuilder.setLength(lineBuilder.length - 1)
386 if (lineBuilder.length != 0 && lineBuilder.charAt(0) == SPACE) {
387 lineBuilder.deleteCharAt(0)
390 sb.append(lineBuilder).append(NEW_LINE)
391 lineBuilder.setLength(0)
393 if (nextElement == " ") {
394 isFirstElementOnNewLineEmptyChar = !isFirstElementOnNewLineEmptyChar;
397 if (isFirstElementOnNewLineEmptyChar) {
398 isFirstElementOnNewLineEmptyChar = !isFirstElementOnNewLineEmptyChar
400 lineBuilder.append(nextElement)
404 return sb.append(lineBuilder).append(NEW_LINE).toString
408 * Template method which generates method parameters with their types from <code>parameters</code>.
411 * list of parameter instances which are transformed to the method parameters
412 * @return string with the list of the method parameters with their types in JAVA format
414 def protected generateParameters(List<MethodSignature.Parameter> parameters) '''«
415 IF !parameters.empty»«
416 FOR parameter : parameters SEPARATOR ", "»«
417 parameter.type.importedName» «parameter.name»«
422 def protected emitConstant(Constant c) '''
423 «IF BindingMapping.QNAME_STATIC_FIELD_NAME.equals(c.name)»
424 «val entry = c.value as Entry<JavaTypeName, String>»
425 public static final «c.type.importedNonNull» «c.name» = «entry.key.importedName».«BindingMapping.MODULE_INFO_QNAMEOF_METHOD_NAME»("«entry.value»");
427 public static final «c.type.importedName» «c.name» = «c.value»;
431 def protected generateCheckers(GeneratedProperty field, Restrictions restrictions, Type actualType) '''
432 «IF restrictions.rangeConstraint.present»
433 «AbstractRangeGenerator.forType(actualType).generateRangeChecker(field.name.toFirstUpper,
434 restrictions.rangeConstraint.get, this)»
436 «IF restrictions.lengthConstraint.present»
437 «LengthGenerator.generateLengthChecker(field.fieldName, actualType, restrictions.lengthConstraint.get, this)»
441 def protected checkArgument(GeneratedProperty property, Restrictions restrictions, Type actualType, String value) '''
442 «IF restrictions.getRangeConstraint.isPresent»
443 «IF actualType instanceof ConcreteType»
444 «AbstractRangeGenerator.forType(actualType).generateRangeCheckerCall(property.getName.toFirstUpper, value)»
446 «AbstractRangeGenerator.forType(actualType).generateRangeCheckerCall(property.getName.toFirstUpper, value + ".getValue()")»
449 «val fieldName = property.fieldName»
450 «IF restrictions.getLengthConstraint.isPresent»
451 «IF actualType instanceof ConcreteType»
452 «LengthGenerator.generateLengthCheckerCall(fieldName, value)»
454 «LengthGenerator.generateLengthCheckerCall(fieldName, value + ".getValue()")»
458 «val fieldUpperCase = fieldName.toUpperCase(Locale.ENGLISH)»
459 «FOR currentConstant : type.getConstantDefinitions»
460 «IF currentConstant.getName.startsWith(TypeConstants.PATTERN_CONSTANT_NAME)
461 && fieldUpperCase.equals(currentConstant.getName.substring(TypeConstants.PATTERN_CONSTANT_NAME.length))»
462 «CODEHELPERS.importedName».checkPattern(value, «Constants.MEMBER_PATTERN_LIST»«fieldName», «Constants.MEMBER_REGEX_LIST»«fieldName»);
467 def protected hashCodeResult(Collection<? extends GeneratedProperty> properties) '''
468 final int prime = 31;
470 «FOR property : properties»
471 result = prime * result + «property.importedUtilClass».hashCode(«property.fieldName»);
475 def protected final generateAnnotation(AnnotationType annotation) '''
476 @«annotation.importedName»
477 «IF annotation.parameters !== null && !annotation.parameters.empty»
479 «FOR param : annotation.parameters SEPARATOR ","»
480 «param.name»=«param.value»