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.description
194 val typeReference = type.reference
195 val typeModuleName = type.moduleName
196 val typeSchemaPath = type.schemaPath
199 «IF !type.isDocumentationParametersNullOrEmtpy»
200 «IF typeDescription != null && !typeDescription.empty»
201 «formatToParagraph(typeDescription)»
203 «IF typeReference != null && !typeReference.empty»
205 «formatReference(typeReference)»
207 «IF typeModuleName != null && !typeModuleName.empty»
211 «IF typeSchemaPath != null && !typeSchemaPath.empty»
213 «formatPath(typeSchemaPath)»
219 def formatPath(Iterable<QName> schemaPath) {
220 var currentElement = schemaPath.head
221 val StringBuilder sb = new StringBuilder()
223 sb.append(currentElement)
225 for(pathElement : schemaPath) {
226 if(!currentElement.namespace.equals(pathElement.namespace)) {
227 currentElement = pathElement
229 sb.append(pathElement)
233 sb.append(pathElement.localName)
240 def formatReference(String reference) {
241 if(reference == null || reference.isEmpty)
244 val StringTokenizer tokenizer = new StringTokenizer(reference, " ", true)
245 val StringBuilder sb = new StringBuilder();
247 while(tokenizer.hasMoreTokens) {
248 var String oneElement = tokenizer.nextToken
249 if (oneElement.contains("http://")) {
250 oneElement = asLink(oneElement)
252 sb.append(oneElement)
257 def asLink(String text) {
258 val StringBuilder sb = new StringBuilder()
260 var char lastChar = ' '
261 var boolean badEnding = false
263 if(text.endsWith(".") || text.endsWith(":") || text.endsWith(",")) {
264 tempText = text.substring(0, text.length - 1)
265 lastChar = text.charAt(text.length - 1)
268 sb.append("<a href = \"")
280 protected def formatToParagraph(String text) {
281 if(text == null || text.isEmpty)
284 var formattedText = text
285 val StringBuilder sb = new StringBuilder();
286 var StringBuilder lineBuilder = new StringBuilder();
287 var boolean isFirstElementOnNewLineEmptyChar = false;
289 formattedText = formattedText.replace("*/", "*/")
290 formattedText = formattedText.replace(NEW_LINE, "")
291 formattedText = formattedText.replace("\t", "")
292 formattedText = formattedText.replaceAll(" +", " ");
294 val StringTokenizer tokenizer = new StringTokenizer(formattedText, " ", true);
296 while(tokenizer.hasMoreElements) {
297 val nextElement = tokenizer.nextElement.toString
299 if(lineBuilder.length + nextElement.length > 80) {
300 if (lineBuilder.charAt(lineBuilder.length - 1) == ' ') {
301 lineBuilder.setLength(0)
302 lineBuilder.append(lineBuilder.substring(0, lineBuilder.length - 1))
304 if (lineBuilder.charAt(0) == ' ') {
305 lineBuilder.setLength(0)
306 lineBuilder.append(lineBuilder.substring(1))
309 sb.append(lineBuilder);
310 lineBuilder.setLength(0)
313 if(nextElement.toString == ' ')
314 isFirstElementOnNewLineEmptyChar = !isFirstElementOnNewLineEmptyChar;
317 if(isFirstElementOnNewLineEmptyChar) {
318 isFirstElementOnNewLineEmptyChar = !isFirstElementOnNewLineEmptyChar
322 lineBuilder.append(nextElement)
325 sb.append(lineBuilder)
331 def isDocumentationParametersNullOrEmtpy(GeneratedType type) {
332 var boolean isNullOrEmpty = true
333 val String typeDescription = type.description
334 val String typeReference = type.reference
335 val String typeModuleName = type.moduleName
336 val Iterable<QName> typeSchemaPath = type.schemaPath
338 if(typeDescription != null && !typeDescription.empty) {
339 isNullOrEmpty = false
342 if(typeReference != null && !typeReference.empty) {
343 isNullOrEmpty = false
346 if(typeModuleName != null && !typeModuleName.empty) {
347 isNullOrEmpty = false
350 if(typeSchemaPath != null && !typeSchemaPath.empty) {
351 isNullOrEmpty = false
357 def generateRestrictions(Type type, String paramName, Type returnType) '''
358 «val restrictions = type.getRestrictions»
359 «IF restrictions !== null»
360 «val boolean isNestedType = !(returnType instanceof ConcreteType)»
361 «IF !restrictions.lengthConstraints.empty»
362 «generateLengthRestriction(returnType, restrictions, paramName, isNestedType)»
364 «IF !restrictions.rangeConstraints.empty»
365 «generateRangeRestriction(returnType, paramName, isNestedType)»
370 def private generateLengthRestriction(Type returnType, Restrictions restrictions, String paramName, boolean isNestedType) '''
371 «val clazz = restrictions.lengthConstraints.iterator.next.min.class»
372 if («paramName» != null) {
373 «printLengthConstraint(returnType, clazz, paramName, isNestedType, returnType.name.contains("["))»
374 boolean isValidLength = false;
375 for («Range.importedName»<«clazz.importedNumber»> r : «IF isNestedType»«returnType.importedName».«ENDIF»length()) {
376 if (r.contains(_constraint)) {
377 isValidLength = true;
380 if (!isValidLength) {
381 throw new IllegalArgumentException(String.format("Invalid length: %s, expected: %s.", «paramName», «IF isNestedType»«returnType.importedName».«ENDIF»length()));
386 def private generateRangeRestriction(Type returnType, String paramName, boolean isNestedType) '''
387 if («paramName» != null) {
388 «printRangeConstraint(returnType, paramName, isNestedType)»
389 boolean isValidRange = false;
390 for («Range.importedName»<«returnType.importedNumber»> r : «IF isNestedType»«returnType.importedName».«ENDIF»range()) {
391 if (r.contains(_constraint)) {
396 throw new IllegalArgumentException(String.format("Invalid range: %s, expected: %s.", «paramName», «IF isNestedType»«returnType.importedName».«ENDIF»range()));
402 * Print length constraint.
403 * This should always be a BigInteger (only string and binary can have length restriction)
405 def printLengthConstraint(Type returnType, Class<? extends Number> clazz, String paramName, boolean isNestedType, boolean isArray) '''
406 «clazz.importedNumber» _constraint = «clazz.importedNumber».valueOf(«paramName»«IF isNestedType».getValue()«ENDIF».length«IF !isArray»()«ENDIF»);
409 def printRangeConstraint(Type returnType, String paramName, boolean isNestedType) '''
410 «IF BigDecimal.canonicalName.equals(returnType.fullyQualifiedName)»
411 «BigDecimal.importedName» _constraint = new «BigDecimal.importedName»(«paramName»«IF isNestedType».getValue()«ENDIF».toString());
414 «val propReturnType = findProperty(returnType as GeneratedTransferObject, "value").returnType»
415 «IF propReturnType.fullyQualifiedName.equals(BigInteger.canonicalName)»
416 «BigInteger.importedName» _constraint = «paramName».getValue();
418 «BigInteger.importedName» _constraint = «BigInteger.importedName».valueOf(«paramName».getValue());
421 «IF returnType.fullyQualifiedName.equals(BigInteger.canonicalName)»
422 «BigInteger.importedName» _constraint = «paramName»;
424 «BigInteger.importedName» _constraint = «BigInteger.importedName».valueOf(«paramName»);
430 def protected generateToString(Collection<GeneratedProperty> properties) '''
431 «IF !properties.empty»
433 public «String.importedName» toString() {
434 «StringBuilder.importedName» builder = new «StringBuilder.importedName»("«type.name» [");
435 boolean first = true;
437 «FOR property : properties»
438 if («property.fieldName» != null) {
442 builder.append(", ");
444 builder.append("«property.fieldName»=");
445 «IF property.returnType.name.contains("[")»
446 builder.append(«Arrays.importedName».toString(«property.fieldName»));
448 builder.append(«property.fieldName»);
452 return builder.append(']').toString();
457 def getRestrictions(Type type) {
458 var Restrictions restrictions = null
459 if (type instanceof ConcreteType) {
460 restrictions = (type as ConcreteType).restrictions
461 } else if (type instanceof GeneratedTransferObject) {
462 restrictions = (type as GeneratedTransferObject).restrictions
467 def boolean isArrayType(GeneratedTransferObject type) {
469 val GeneratedProperty value = findProperty(type, "value")
470 if (value != null && value.returnType.name.contains("[")) {
476 def String toQuote(Object obj) {
477 return "\"" + obj.toString + "\"";
481 * Template method which generates method parameters with their types from <code>parameters</code>.
484 * list of parameter instances which are transformed to the method parameters
485 * @return string with the list of the method parameters with their types in JAVA format
487 def protected generateParameters(List<MethodSignature.Parameter> parameters) '''«
488 IF !parameters.empty»«
489 FOR parameter : parameters SEPARATOR ", "»«
490 parameter.type.importedName» «parameter.name»«
495 def protected generateLengthMethod(String methodName, Type type, String className, String varName) '''
496 «val Restrictions restrictions = type.restrictions»
497 «IF restrictions != null && !(restrictions.lengthConstraints.empty)»
498 «val numberClass = restrictions.lengthConstraints.iterator.next.min.class»
499 public static «List.importedName»<«Range.importedName»<«numberClass.importedNumber»>> «methodName»() {
500 «IF numberClass.equals(typeof(BigDecimal))»
501 «lengthMethodBody(restrictions, numberClass, className, varName)»
503 «lengthMethodBody(restrictions, typeof(BigInteger), className, varName)»
509 def private lengthMethodBody(Restrictions restrictions, Class<? extends Number> numberClass, String className, String varName) '''
510 if («varName» == null) {
511 synchronized («className».class) {
512 if («varName» == null) {
513 «ImmutableList.importedName».Builder<«Range.importedName»<«numberClass.importedName»>> builder = «ImmutableList.importedName».builder();
514 «FOR r : restrictions.lengthConstraints»
515 builder.add(«Range.importedName».closed(«numericValue(numberClass, r.min)», «numericValue(numberClass, r.max)»));
517 «varName» = builder.build();
524 def protected generateRangeMethod(String methodName, Restrictions restrictions, Type returnType, String className, String varName) '''
525 «IF restrictions != null && !(restrictions.rangeConstraints.empty)»
526 «val number = returnType.importedNumber»
527 public static «List.importedName»<«Range.importedName»<«number»>> «methodName»() {
528 «IF returnType.fullyQualifiedName.equals(BigDecimal.canonicalName)»
529 «rangeMethodBody(restrictions, BigDecimal, className, varName)»
531 «rangeMethodBody(restrictions, BigInteger, className, varName)»
537 def protected generateRangeMethod(String methodName, Restrictions restrictions, String className, String varName, Iterable<GeneratedProperty> properties) '''
538 «IF restrictions != null && !(restrictions.rangeConstraints.empty)»
539 «val returnType = properties.iterator.next.returnType»
540 public static «List.importedName»<«Range.importedName»<«returnType.importedNumber»>> «methodName»() {
541 «IF returnType.fullyQualifiedName.equals(BigDecimal.canonicalName)»
542 «rangeMethodBody(restrictions, BigDecimal, className, varName)»
544 «rangeMethodBody(restrictions, BigInteger, className, varName)»
550 def private rangeMethodBody(Restrictions restrictions, Class<? extends Number> numberClass, String className, String varName) '''
551 if («varName» == null) {
552 synchronized («className».class) {
553 if («varName» == null) {
554 «ImmutableList.importedName».Builder<«Range.importedName»<«numberClass.importedName»>> builder = «ImmutableList.importedName».builder();
555 «FOR r : restrictions.rangeConstraints»
556 builder.add(«Range.importedName».closed(«numericValue(numberClass, r.min)», «numericValue(numberClass, r.max)»));
558 «varName» = builder.build();
565 def protected String importedNumber(Class<? extends Number> clazz) {
566 if (clazz.equals(typeof(BigDecimal))) {
567 return BigDecimal.importedName
569 return BigInteger.importedName
572 def protected String importedNumber(Type clazz) {
573 if (clazz.fullyQualifiedName.equals(BigDecimal.canonicalName)) {
574 return BigDecimal.importedName
576 return BigInteger.importedName
579 def private String numericValue(Class<? extends Number> clazz, Object numberValue) {
580 val number = clazz.importedName;
581 val value = numberValue.toString
582 if (clazz.equals(typeof(BigInteger)) || clazz.equals(typeof(BigDecimal))) {
583 if (value.equals("0")) {
584 return number + ".ZERO"
585 } else if (value.equals("1")) {
586 return number + ".ONE"
587 } else if (value.equals("10")) {
588 return number + ".TEN"
591 val Long longVal = Long.valueOf(value)
592 return number + ".valueOf(" + longVal + "L)"
593 } catch (NumberFormatException e) {
594 if (clazz.equals(typeof(BigDecimal))) {
596 val Double doubleVal = Double.valueOf(value);
597 return number + ".valueOf(" + doubleVal + ")"
598 } catch (NumberFormatException e2) {
604 return "new " + number + "(\"" + value + "\")"
607 def private GeneratedProperty findProperty(GeneratedTransferObject gto, String name) {
608 val props = gto.properties
610 if (prop.name.equals(name)) {
614 val GeneratedTransferObject parent = gto.superType
615 if (parent != null) {
616 return findProperty(parent, name)