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.yangtools.sal.java.api.generator
10 import com.google.common.collect.ImmutableList
11 import com.google.common.collect.Range
12 import java.math.BigDecimal
13 import java.math.BigInteger
14 import java.util.Arrays
15 import java.util.Collection
16 import java.util.HashMap
19 import java.util.StringTokenizer
20 import org.opendaylight.yangtools.binding.generator.util.Types
21 import org.opendaylight.yangtools.sal.binding.model.api.ConcreteType
22 import org.opendaylight.yangtools.sal.binding.model.api.GeneratedProperty
23 import org.opendaylight.yangtools.sal.binding.model.api.GeneratedTransferObject
24 import org.opendaylight.yangtools.sal.binding.model.api.GeneratedType
25 import org.opendaylight.yangtools.sal.binding.model.api.MethodSignature
26 import org.opendaylight.yangtools.sal.binding.model.api.Restrictions
27 import org.opendaylight.yangtools.sal.binding.model.api.Type
28 import org.opendaylight.yangtools.yang.common.QName
30 abstract class BaseTemplate {
31 protected val GeneratedType type;
32 protected val Map<String, String> importMap;
34 private static final String NEW_LINE = '\n'
36 new(GeneratedType _type) {
38 throw new IllegalArgumentException("Generated type reference cannot be NULL!")
41 this.importMap = new HashMap<String,String>()
44 def packageDefinition() '''package «type.packageName»;'''
46 protected def getFullyQualifiedName() {
47 return type.fullyQualifiedName
50 final public def generate() {
60 protected def imports() '''
62 «FOR entry : importMap.entrySet»
63 «IF entry.value != fullyQualifiedName»
64 import «entry.value».«entry.key»;
71 protected abstract def CharSequence body();
74 final protected def fieldName(GeneratedProperty property) '''_«property.name»'''
76 final protected def propertyNameFromGetter(MethodSignature getter) {
78 if (getter.name.startsWith("is")) {
80 } else if (getter.name.startsWith("get")) {
83 throw new IllegalArgumentException("Not a getter")
85 return getter.name.substring(prefix).toFirstLower;
89 * Template method which generates the getter method for <code>field</code>
92 * generated property with data about field which is generated as the getter method
93 * @return string with the getter method source code in JAVA format
95 final protected def getterMethod(GeneratedProperty field) {
97 public «field.returnType.importedName» «field.getterMethodName»() {
98 return «field.fieldName»;
103 final protected def getterMethodName(GeneratedProperty field) {
104 val prefix = if(field.returnType.equals(Types.BOOLEAN)) "is" else "get"
105 return '''«prefix»«field.name.toFirstUpper»'''
109 * Template method which generates the setter method for <code>field</code>
112 * generated property with data about field which is generated as the setter method
113 * @return string with the setter method source code in JAVA format
115 final protected def setterMethod(GeneratedProperty field) '''
116 «val returnType = field.returnType.importedName»
117 public «type.name» set«field.name.toFirstUpper»(«returnType» value) {
118 this.«field.fieldName» = value;
123 final protected def importedName(Type intype) {
124 GeneratorUtil.putTypeIntoImports(type, intype, importMap);
125 GeneratorUtil.getExplicitType(type, intype, importMap)
128 final protected def importedName(Class<?> cls) {
129 importedName(Types.typeForClass(cls))
133 * Template method which generates method parameters with their types from <code>parameters</code>.
136 * group of generated property instances which are transformed to the method parameters
137 * @return string with the list of the method parameters with their types in JAVA format
139 def final protected asArgumentsDeclaration(Iterable<GeneratedProperty> parameters) '''«IF !parameters.empty»«FOR parameter : parameters SEPARATOR ", "»«parameter.
140 returnType.importedName» «parameter.fieldName»«ENDFOR»«ENDIF»'''
143 * Template method which generates sequence of the names of the class attributes from <code>parameters</code>.
146 * group of generated property instances which are transformed to the sequence of parameter names
147 * @return string with the list of the parameter names of the <code>parameters</code>
149 def final protected asArguments(Iterable<GeneratedProperty> parameters) '''«IF !parameters.empty»«FOR parameter : parameters SEPARATOR ", "»«parameter.
150 fieldName»«ENDFOR»«ENDIF»'''
153 * Template method which generates JAVA comments.
155 * @param comment string with the comment for whole JAVA class
156 * @return string with comment in JAVA format
158 def protected CharSequence asJavadoc(String comment) {
159 if(comment == null) return ''
161 if (txt.contains("*/")) {
162 txt = txt.replace("*/", "*/")
165 txt = formatToParagraph(txt)
168 «wrapToDocumentation(txt)»
172 def String wrapToDocumentation(String text) {
173 val StringTokenizer tokenizer = new StringTokenizer(text, "\n", false)
174 val StringBuilder sb = new StringBuilder()
182 while(tokenizer.hasMoreTokens) {
184 sb.append(tokenizer.nextToken)
192 def protected String formatDataForJavaDoc(GeneratedType type) {
193 val typeDescription = type.getDescription();
196 «IF !typeDescription.nullOrEmpty»
202 def asLink(String text) {
203 val StringBuilder sb = new StringBuilder()
205 var char lastChar = ' '
206 var boolean badEnding = false
208 if(text.endsWith(".") || text.endsWith(":") || text.endsWith(",")) {
209 tempText = text.substring(0, text.length - 1)
210 lastChar = text.charAt(text.length - 1)
213 sb.append("<a href = \"")
225 protected def formatToParagraph(String text) {
226 if(text == null || text.isEmpty)
229 var formattedText = text
230 val StringBuilder sb = new StringBuilder();
231 var StringBuilder lineBuilder = new StringBuilder();
232 var boolean isFirstElementOnNewLineEmptyChar = false;
234 formattedText = formattedText.replace("*/", "*/")
235 formattedText = formattedText.replace(NEW_LINE, "")
236 formattedText = formattedText.replace("\t", "")
237 formattedText = formattedText.replaceAll(" +", " ");
239 val StringTokenizer tokenizer = new StringTokenizer(formattedText, " ", true);
241 while(tokenizer.hasMoreElements) {
242 val nextElement = tokenizer.nextElement.toString
244 if(lineBuilder.length + nextElement.length > 80) {
245 if (lineBuilder.charAt(lineBuilder.length - 1) == ' ') {
246 lineBuilder.setLength(0)
247 lineBuilder.append(lineBuilder.substring(0, lineBuilder.length - 1))
249 if (lineBuilder.charAt(0) == ' ') {
250 lineBuilder.setLength(0)
251 lineBuilder.append(lineBuilder.substring(1))
254 sb.append(lineBuilder);
255 lineBuilder.setLength(0)
258 if(nextElement.toString == ' ')
259 isFirstElementOnNewLineEmptyChar = !isFirstElementOnNewLineEmptyChar;
262 if(isFirstElementOnNewLineEmptyChar) {
263 isFirstElementOnNewLineEmptyChar = !isFirstElementOnNewLineEmptyChar
267 lineBuilder.append(nextElement)
270 sb.append(lineBuilder)
276 def isDocumentationParametersNullOrEmtpy(GeneratedType type) {
277 val boolean isTypeDescriptionNullOrEmpty = type.description.nullOrEmpty
278 val boolean isTypeReferenceNullOrEmpty = type.reference.nullOrEmpty
279 val boolean isTypeModuleNameNullOrEmpty = type.moduleName.nullOrEmpty
280 val boolean isTypeSchemaPathNullOrEmpty = type.schemaPath.nullOrEmpty
282 if (isTypeDescriptionNullOrEmpty && isTypeReferenceNullOrEmpty && isTypeModuleNameNullOrEmpty
283 && isTypeSchemaPathNullOrEmpty) {
289 def generateRestrictions(Type type, String paramName, Type returnType) '''
290 «val restrictions = type.getRestrictions»
291 «IF restrictions !== null»
292 «val boolean isNestedType = !(returnType instanceof ConcreteType)»
293 «IF !restrictions.lengthConstraints.empty»
294 «generateLengthRestriction(returnType, restrictions, paramName, isNestedType)»
296 «IF !restrictions.rangeConstraints.empty»
297 «generateRangeRestriction(returnType, paramName, isNestedType)»
302 def private generateLengthRestriction(Type returnType, Restrictions restrictions, String paramName, boolean isNestedType) '''
303 «val clazz = restrictions.lengthConstraints.iterator.next.min.class»
304 if («paramName» != null) {
305 «printLengthConstraint(returnType, clazz, paramName, isNestedType, returnType.name.contains("["))»
306 boolean isValidLength = false;
307 for («Range.importedName»<«clazz.importedNumber»> r : «IF isNestedType»«returnType.importedName».«ENDIF»length()) {
308 if (r.contains(_constraint)) {
309 isValidLength = true;
312 if (!isValidLength) {
313 throw new IllegalArgumentException(String.format("Invalid length: %s, expected: %s.", «paramName», «IF isNestedType»«returnType.importedName».«ENDIF»length()));
318 def private generateRangeRestriction(Type returnType, String paramName, boolean isNestedType) '''
319 if («paramName» != null) {
320 «printRangeConstraint(returnType, paramName, isNestedType)»
321 boolean isValidRange = false;
322 for («Range.importedName»<«returnType.importedNumber»> r : «IF isNestedType»«returnType.importedName».«ENDIF»range()) {
323 if (r.contains(_constraint)) {
328 throw new IllegalArgumentException(String.format("Invalid range: %s, expected: %s.", «paramName», «IF isNestedType»«returnType.importedName».«ENDIF»range()));
334 * Print length constraint.
335 * This should always be a BigInteger (only string and binary can have length restriction)
337 def printLengthConstraint(Type returnType, Class<? extends Number> clazz, String paramName, boolean isNestedType, boolean isArray) '''
338 «clazz.importedNumber» _constraint = «clazz.importedNumber».valueOf(«paramName»«IF isNestedType».getValue()«ENDIF».length«IF !isArray»()«ENDIF»);
341 def printRangeConstraint(Type returnType, String paramName, boolean isNestedType) '''
342 «IF BigDecimal.canonicalName.equals(returnType.fullyQualifiedName)»
343 «BigDecimal.importedName» _constraint = new «BigDecimal.importedName»(«paramName»«IF isNestedType».getValue()«ENDIF».toString());
346 «val propReturnType = findProperty(returnType as GeneratedTransferObject, "value").returnType»
347 «IF propReturnType.fullyQualifiedName.equals(BigInteger.canonicalName)»
348 «BigInteger.importedName» _constraint = «paramName».getValue();
350 «BigInteger.importedName» _constraint = «BigInteger.importedName».valueOf(«paramName».getValue());
353 «IF returnType.fullyQualifiedName.equals(BigInteger.canonicalName)»
354 «BigInteger.importedName» _constraint = «paramName»;
356 «BigInteger.importedName» _constraint = «BigInteger.importedName».valueOf(«paramName»);
362 def protected generateToString(Collection<GeneratedProperty> properties) '''
363 «IF !properties.empty»
365 public «String.importedName» toString() {
366 «StringBuilder.importedName» builder = new «StringBuilder.importedName»("«type.class.simpleName» [");
367 boolean first = true;
369 «FOR property : properties»
370 if («property.fieldName» != null) {
374 builder.append(", ");
376 builder.append("«property.fieldName»=");
377 «IF property.returnType.name.contains("[")»
378 builder.append(«Arrays.importedName».toString(«property.fieldName»));
380 builder.append(«property.fieldName»);
384 return builder.append(']').toString();
389 def getRestrictions(Type type) {
390 var Restrictions restrictions = null
391 if (type instanceof ConcreteType) {
392 restrictions = (type as ConcreteType).restrictions
393 } else if (type instanceof GeneratedTransferObject) {
394 restrictions = (type as GeneratedTransferObject).restrictions
399 def boolean isArrayType(GeneratedTransferObject type) {
401 val GeneratedProperty value = findProperty(type, "value")
402 if (value != null && value.returnType.name.contains("[")) {
408 def String toQuote(Object obj) {
409 return "\"" + obj.toString + "\"";
413 * Template method which generates method parameters with their types from <code>parameters</code>.
416 * list of parameter instances which are transformed to the method parameters
417 * @return string with the list of the method parameters with their types in JAVA format
419 def protected generateParameters(List<MethodSignature.Parameter> parameters) '''«
420 IF !parameters.empty»«
421 FOR parameter : parameters SEPARATOR ", "»«
422 parameter.type.importedName» «parameter.name»«
427 def protected generateLengthMethod(String methodName, Type type, String className, String varName) '''
428 «val Restrictions restrictions = type.restrictions»
429 «IF restrictions != null && !(restrictions.lengthConstraints.empty)»
430 «val numberClass = restrictions.lengthConstraints.iterator.next.min.class»
431 public static «List.importedName»<«Range.importedName»<«numberClass.importedNumber»>> «methodName»() {
432 «IF numberClass.equals(typeof(BigDecimal))»
433 «lengthBody(restrictions, numberClass, className, varName)»
435 «lengthBody(restrictions, typeof(BigInteger), className, varName)»
441 def private lengthBody(Restrictions restrictions, Class<? extends Number> numberClass, String className, String varName) '''
442 if («varName» == null) {
443 synchronized («className».class) {
444 if («varName» == null) {
445 «ImmutableList.importedName».Builder<«Range.importedName»<«numberClass.importedName»>> builder = «ImmutableList.importedName».builder();
446 «FOR r : restrictions.lengthConstraints»
447 builder.add(«Range.importedName».closed(«numericValue(numberClass, r.min)», «numericValue(numberClass, r.max)»));
449 «varName» = builder.build();
456 def protected generateRangeMethod(String methodName, Restrictions restrictions, Type returnType, String className, String varName) '''
457 «IF restrictions != null && !(restrictions.rangeConstraints.empty)»
458 «val number = returnType.importedNumber»
459 public static «List.importedName»<«Range.importedName»<«number»>> «methodName»() {
460 «IF returnType.fullyQualifiedName.equals(BigDecimal.canonicalName)»
461 «rangeBody(restrictions, BigDecimal, className, varName)»
463 «rangeBody(restrictions, BigInteger, className, varName)»
469 def protected generateRangeMethod(String methodName, Restrictions restrictions, String className, String varName, Iterable<GeneratedProperty> properties) '''
470 «IF restrictions != null && !(restrictions.rangeConstraints.empty)»
471 «val returnType = properties.iterator.next.returnType»
472 public static «List.importedName»<«Range.importedName»<«returnType.importedNumber»>> «methodName»() {
473 «IF returnType.fullyQualifiedName.equals(BigDecimal.canonicalName)»
474 «rangeBody(restrictions, BigDecimal, className, varName)»
476 «rangeBody(restrictions, BigInteger, className, varName)»
482 def private rangeBody(Restrictions restrictions, Class<? extends Number> numberClass, String className, String varName) '''
483 if («varName» == null) {
484 synchronized («className».class) {
485 if («varName» == null) {
486 «ImmutableList.importedName».Builder<«Range.importedName»<«numberClass.importedName»>> builder = «ImmutableList.importedName».builder();
487 «FOR r : restrictions.rangeConstraints»
488 builder.add(«Range.importedName».closed(«numericValue(numberClass, r.min)», «numericValue(numberClass, r.max)»));
490 «varName» = builder.build();
497 def protected String importedNumber(Class<? extends Number> clazz) {
498 if (clazz.equals(typeof(BigDecimal))) {
499 return BigDecimal.importedName
501 return BigInteger.importedName
504 def protected String importedNumber(Type clazz) {
505 if (clazz.fullyQualifiedName.equals(BigDecimal.canonicalName)) {
506 return BigDecimal.importedName
508 return BigInteger.importedName
511 def protected String numericValue(Class<? extends Number> clazz, Object numberValue) {
512 val number = clazz.importedName;
513 val value = numberValue.toString
514 if (clazz.equals(typeof(BigInteger)) || clazz.equals(typeof(BigDecimal))) {
515 if (value.equals("0")) {
516 return number + ".ZERO"
517 } else if (value.equals("1")) {
518 return number + ".ONE"
519 } else if (value.equals("10")) {
520 return number + ".TEN"
523 val Long longVal = Long.valueOf(value)
524 return number + ".valueOf(" + longVal + "L)"
525 } catch (NumberFormatException e) {
526 if (clazz.equals(typeof(BigDecimal))) {
528 val Double doubleVal = Double.valueOf(value);
529 return number + ".valueOf(" + doubleVal + ")"
530 } catch (NumberFormatException e2) {
536 return "new " + number + "(\"" + value + "\")"
539 def private GeneratedProperty findProperty(GeneratedTransferObject gto, String name) {
540 val props = gto.properties
542 if (prop.name.equals(name)) {
546 val GeneratedTransferObject parent = gto.superType
547 if (parent != null) {
548 return findProperty(parent, name)