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) {
129 public «field.returnType.importedName» «field.getterMethodName»() {
130 «val fieldName = field.fieldName»
131 «IF field.returnType.name.endsWith("[]")»
132 return «fieldName» == null ? null : «fieldName».clone();
140 final protected def getterMethodName(GeneratedProperty field) {
141 val prefix = if(field.returnType.equals(Types.BOOLEAN)) "is" else "get"
142 return '''«prefix»«field.name.toFirstUpper»'''
146 * Template method which generates the setter method for <code>field</code>
149 * generated property with data about field which is generated as the setter method
150 * @return string with the setter method source code in JAVA format
152 final protected def setterMethod(GeneratedProperty field) '''
153 «val returnType = field.returnType.importedName»
154 public «type.name» set«field.name.toFirstUpper»(«returnType» value) {
155 this.«field.fieldName» = value;
161 * Template method which generates method parameters with their types from <code>parameters</code>.
164 * 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 asArgumentsDeclaration(Iterable<GeneratedProperty> parameters) '''«IF !parameters.empty»«FOR parameter : parameters SEPARATOR ", "»«parameter.
168 returnType.importedName» «parameter.fieldName»«ENDFOR»«ENDIF»'''
171 * Template method which generates method parameters with their types from <code>parameters</code>, annotating them
172 * with {@link NonNull}.
174 * @param parameters group of generated property instances which are transformed to the method parameters
175 * @return string with the list of the method parameters with their types in JAVA format
177 def final protected asNonNullArgumentsDeclaration(Iterable<GeneratedProperty> parameters) '''«IF !parameters.empty»
178 «FOR parameter : parameters SEPARATOR ", "»«parameter.returnType.importedNonNull» «parameter
179 .fieldName»«ENDFOR»«ENDIF»'''
182 * Template method which generates sequence of the names of the class attributes from <code>parameters</code>.
185 * group of generated property instances which are transformed to the sequence of parameter names
186 * @return string with the list of the parameter names of the <code>parameters</code>
188 def final protected asArguments(Collection<GeneratedProperty> parameters) '''«IF !parameters.empty»«FOR parameter : parameters SEPARATOR ", "»«parameter.
189 fieldName»«ENDFOR»«ENDIF»'''
192 * Template method which generates JAVA comments.
194 * @param comment string with the comment for whole JAVA class
195 * @return string with comment in JAVA format
197 def final protected asJavadoc(TypeMemberComment comment) {
198 if (comment === null) {
201 return wrapToDocumentation('''
202 «comment.contractDescription»
204 «comment.referenceDescription.formatReference»
206 «comment.typeSignature»
210 def static String wrapToDocumentation(String text) {
214 val StringBuilder sb = new StringBuilder().append("/**\n")
215 for (String t : NL_SPLITTER.split(text)) {
218 sb.append(SPACE).append(t)
227 def protected String formatDataForJavaDoc(GeneratedType type) {
228 val sb = new StringBuilder()
229 val comment = type.comment
230 if (comment !== null) {
231 sb.append(comment.javadoc)
234 appendSnippet(sb, type)
243 def static encodeJavadocSymbols(String description) {
244 if (description.nullOrEmpty) {
248 return TAIL_COMMENT_PATTERN.matcher(AMP_MATCHER.replaceFrom(description, "&")).replaceAll("*/")
251 def protected String formatDataForJavaDoc(GeneratedType type, String additionalComment) {
252 val comment = type.comment
253 if (comment === null) {
259 val sb = new StringBuilder().append(comment.javadoc)
260 appendSnippet(sb, type)
265 .append(additionalComment)
272 def private static void appendSnippet(StringBuilder sb, GeneratedType type) {
273 val optDef = type.yangSourceDefinition
274 if (optDef.present) {
278 if (def instanceof Single) {
281 .append("This class represents the following YANG schema fragment defined in module <b>")
282 .append(def.module.argument).append("</b>\n")
284 appendYangSnippet(sb, def.module, (node as EffectiveStatement<?, ?>).declared)
287 if (node instanceof SchemaNode) {
288 sb.append("The schema path to identify an instance is\n")
290 .append(formatSchemaPath(def.module.argument.localName, node.path.pathFromRoot))
293 if (hasBuilderClass(node)) {
294 val builderName = type.name + "Builder";
296 sb.append("\n<p>To create instances of this class use {@link ").append(builderName)
298 .append("@see ").append(builderName).append('\n')
299 if (node instanceof ListSchemaNode) {
300 val keyDef = node.keyDefinition
301 if (keyDef !== null && !keyDef.empty) {
302 sb.append("@see ").append(type.name).append("Key")
308 } else if (def instanceof Multiple) {
310 for (SchemaNode node : def.nodes) {
311 appendYangSnippet(sb, def.module, (node as EffectiveStatement<?, ?>).declared)
313 sb.append("</pre>\n")
318 def private static void appendYangSnippet(StringBuilder sb, ModuleEffectiveStatement module,
319 DeclaredStatement<?> stmt) {
320 for (String str : YANG_FORMATTER.toYangTextSnippet(module, stmt)) {
321 sb.append(str.encodeJavadocSymbols.encodeAngleBrackets.replaceAllIllegalChars)
325 def private static boolean hasBuilderClass(SchemaNode schemaNode) {
326 return schemaNode instanceof ContainerSchemaNode || schemaNode instanceof ListSchemaNode
327 || schemaNode instanceof RpcDefinition || schemaNode instanceof NotificationDefinition;
330 def private static String formatSchemaPath(String moduleName, Iterable<QName> schemaPath) {
331 val sb = new StringBuilder().append(moduleName);
333 var currentElement = Iterables.getFirst(schemaPath, null);
334 for (QName pathElement : schemaPath) {
336 if (!currentElement.namespace.equals(pathElement.namespace)) {
337 currentElement = pathElement
338 sb.append(pathElement)
340 sb.append(pathElement.getLocalName())
343 return sb.toString();
346 def static formatReference(String reference) '''
347 «IF reference !== null»
350 «reference.encodeAngleBrackets.formatToParagraph»
357 def asLink(String text) {
358 val StringBuilder sb = new StringBuilder()
360 var char lastChar = SPACE
361 var boolean badEnding = false
363 if (text.endsWith('.') || text.endsWith(':') || text.endsWith(',')) {
364 tempText = text.substring(0, text.length - 1)
365 lastChar = text.charAt(text.length - 1)
368 sb.append("<a href = \"")
380 protected static def formatToParagraph(String inputText) {
381 val StringBuilder sb = new StringBuilder();
382 var StringBuilder lineBuilder = new StringBuilder();
383 var boolean isFirstElementOnNewLineEmptyChar = false;
385 var formattedText = WS_MATCHER.replaceFrom(inputText.encodeJavadocSymbols, SPACE)
386 formattedText = SPACES_PATTERN.matcher(formattedText).replaceAll(" ")
388 val StringTokenizer tokenizer = new StringTokenizer(formattedText, " ", true)
389 while (tokenizer.hasMoreTokens) {
390 val nextElement = tokenizer.nextToken
392 if (lineBuilder.length != 0 && lineBuilder.length + nextElement.length > 80) {
393 if (lineBuilder.charAt(lineBuilder.length - 1) == SPACE) {
394 lineBuilder.setLength(lineBuilder.length - 1)
396 if (lineBuilder.length != 0 && lineBuilder.charAt(0) == SPACE) {
397 lineBuilder.deleteCharAt(0)
400 sb.append(lineBuilder).append(NEW_LINE)
401 lineBuilder.setLength(0)
403 if (nextElement == " ") {
404 isFirstElementOnNewLineEmptyChar = !isFirstElementOnNewLineEmptyChar;
407 if (isFirstElementOnNewLineEmptyChar) {
408 isFirstElementOnNewLineEmptyChar = !isFirstElementOnNewLineEmptyChar
410 lineBuilder.append(nextElement)
414 return sb.append(lineBuilder).append(NEW_LINE).toString
418 * Template method which generates method parameters with their types from <code>parameters</code>.
421 * list of parameter instances which are transformed to the method parameters
422 * @return string with the list of the method parameters with their types in JAVA format
424 def protected generateParameters(List<MethodSignature.Parameter> parameters) '''«
425 IF !parameters.empty»«
426 FOR parameter : parameters SEPARATOR ", "»«
427 parameter.type.importedName» «parameter.name»«
432 def protected emitConstant(Constant c) '''
433 «IF BindingMapping.QNAME_STATIC_FIELD_NAME.equals(c.name)»
434 «val entry = c.value as Entry<JavaTypeName, String>»
435 public static final «c.type.importedNonNull» «c.name» = «entry.key.importedName».«BindingMapping.MODULE_INFO_QNAMEOF_METHOD_NAME»("«entry.value»");
437 public static final «c.type.importedName» «c.name» = «c.value»;
441 def protected generateCheckers(GeneratedProperty field, Restrictions restrictions, Type actualType) '''
442 «IF restrictions.rangeConstraint.present»
443 «AbstractRangeGenerator.forType(actualType).generateRangeChecker(field.name.toFirstUpper,
444 restrictions.rangeConstraint.get, this)»
446 «IF restrictions.lengthConstraint.present»
447 «LengthGenerator.generateLengthChecker(field.fieldName, actualType, restrictions.lengthConstraint.get, this)»
451 def protected checkArgument(GeneratedProperty property, Restrictions restrictions, Type actualType, String value) '''
452 «IF restrictions.getRangeConstraint.isPresent»
453 «IF actualType instanceof ConcreteType»
454 «AbstractRangeGenerator.forType(actualType).generateRangeCheckerCall(property.getName.toFirstUpper, value)»
456 «AbstractRangeGenerator.forType(actualType).generateRangeCheckerCall(property.getName.toFirstUpper, value + ".getValue()")»
459 «val fieldName = property.fieldName»
460 «IF restrictions.getLengthConstraint.isPresent»
461 «IF actualType instanceof ConcreteType»
462 «LengthGenerator.generateLengthCheckerCall(fieldName, value)»
464 «LengthGenerator.generateLengthCheckerCall(fieldName, value + ".getValue()")»
468 «val fieldUpperCase = fieldName.toUpperCase(Locale.ENGLISH)»
469 «FOR currentConstant : type.getConstantDefinitions»
470 «IF currentConstant.getName.startsWith(TypeConstants.PATTERN_CONSTANT_NAME)
471 && fieldUpperCase.equals(currentConstant.getName.substring(TypeConstants.PATTERN_CONSTANT_NAME.length))»
472 «CODEHELPERS.importedName».checkPattern(value, «Constants.MEMBER_PATTERN_LIST»«fieldName», «Constants.MEMBER_REGEX_LIST»«fieldName»);
477 def protected hashCodeResult(Collection<? extends GeneratedProperty> properties) '''
478 final int prime = 31;
480 «FOR property : properties»
481 result = prime * result + «property.importedUtilClass».hashCode(«property.fieldName»);
485 def protected final generateAnnotation(AnnotationType annotation) '''
486 @«annotation.importedName»
487 «IF annotation.parameters !== null && !annotation.parameters.empty»
489 «FOR param : annotation.parameters SEPARATOR ","»
490 «param.name»=«param.value»