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.base.CharMatcher
11 import com.google.common.base.Splitter
12 import com.google.common.collect.ImmutableList
13 import com.google.common.collect.Range
14 import java.math.BigDecimal
15 import java.math.BigInteger
16 import java.util.Arrays
17 import java.util.Collection
18 import java.util.HashMap
21 import java.util.StringTokenizer
22 import java.util.regex.Pattern
23 import org.opendaylight.yangtools.binding.generator.util.Types
24 import org.opendaylight.yangtools.sal.binding.model.api.ConcreteType
25 import org.opendaylight.yangtools.sal.binding.model.api.GeneratedProperty
26 import org.opendaylight.yangtools.sal.binding.model.api.GeneratedTransferObject
27 import org.opendaylight.yangtools.sal.binding.model.api.GeneratedType
28 import org.opendaylight.yangtools.sal.binding.model.api.MethodSignature
29 import org.opendaylight.yangtools.sal.binding.model.api.Restrictions
30 import org.opendaylight.yangtools.sal.binding.model.api.Type
32 abstract class BaseTemplate {
33 protected val GeneratedType type;
34 protected val Map<String, String> importMap;
36 private static final char NEW_LINE = '\n'
37 private static final CharMatcher NL_MATCHER = CharMatcher.is(NEW_LINE)
38 private static final CharMatcher TAB_MATCHER = CharMatcher.is('\t')
39 private static final Pattern SPACES_PATTERN = Pattern.compile(" +")
40 private static final Splitter NL_SPLITTER = Splitter.on(NL_MATCHER)
42 new(GeneratedType _type) {
44 throw new IllegalArgumentException("Generated type reference cannot be NULL!")
47 this.importMap = new HashMap<String,String>()
50 def packageDefinition() '''package «type.packageName»;'''
52 protected def getFullyQualifiedName() {
53 return type.fullyQualifiedName
56 final public def generate() {
66 protected def imports() '''
68 «FOR entry : importMap.entrySet»
69 «IF !hasSamePackage(entry.value)»
70 import «entry.value».«entry.key»;
78 * Checks if packages of generated type and imported type is the same
80 * @param importedTypePackageNam
81 * the package name of imported type
82 * @return true if the packages are the same false otherwise
84 final private def boolean hasSamePackage(String importedTypePackageName) {
85 return type.packageName.equals(importedTypePackageName);
88 protected abstract def CharSequence body();
91 final protected def fieldName(GeneratedProperty property) '''_«property.name»'''
93 final protected def propertyNameFromGetter(MethodSignature getter) {
95 if (getter.name.startsWith("is")) {
97 } else if (getter.name.startsWith("get")) {
100 throw new IllegalArgumentException("Not a getter")
102 return getter.name.substring(prefix).toFirstLower;
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 final protected def getterMethod(GeneratedProperty field) {
114 public «field.returnType.importedName» «field.getterMethodName»() {
115 «IF field.returnType.importedName.contains("[]")»
116 return «field.fieldName» == null ? null : «field.fieldName».clone();
118 return «field.fieldName»;
124 final protected def getterMethodName(GeneratedProperty field) {
125 val prefix = if(field.returnType.equals(Types.BOOLEAN)) "is" else "get"
126 return '''«prefix»«field.name.toFirstUpper»'''
130 * Template method which generates the setter method for <code>field</code>
133 * generated property with data about field which is generated as the setter method
134 * @return string with the setter method source code in JAVA format
136 final protected def setterMethod(GeneratedProperty field) '''
137 «val returnType = field.returnType.importedName»
138 public «type.name» set«field.name.toFirstUpper»(«returnType» value) {
139 this.«field.fieldName» = value;
144 final protected def importedName(Type intype) {
145 GeneratorUtil.putTypeIntoImports(type, intype, importMap);
146 GeneratorUtil.getExplicitType(type, intype, importMap)
149 final protected def importedName(Class<?> cls) {
150 importedName(Types.typeForClass(cls))
154 * Template method which generates method parameters with their types from <code>parameters</code>.
157 * group of generated property instances which are transformed to the method parameters
158 * @return string with the list of the method parameters with their types in JAVA format
160 def final protected asArgumentsDeclaration(Iterable<GeneratedProperty> parameters) '''«IF !parameters.empty»«FOR parameter : parameters SEPARATOR ", "»«parameter.
161 returnType.importedName» «parameter.fieldName»«ENDFOR»«ENDIF»'''
164 * Template method which generates sequence of the names of the class attributes from <code>parameters</code>.
167 * group of generated property instances which are transformed to the sequence of parameter names
168 * @return string with the list of the parameter names of the <code>parameters</code>
170 def final protected asArguments(Iterable<GeneratedProperty> parameters) '''«IF !parameters.empty»«FOR parameter : parameters SEPARATOR ", "»«parameter.
171 fieldName»«ENDFOR»«ENDIF»'''
174 * Template method which generates JAVA comments.
176 * @param comment string with the comment for whole JAVA class
177 * @return string with comment in JAVA format
179 def protected CharSequence asJavadoc(String comment) {
180 if(comment == null) return ''
184 txt = formatToParagraph(txt)
187 «wrapToDocumentation(txt)»
191 def String wrapToDocumentation(String text) {
195 val StringBuilder sb = new StringBuilder("/**")
198 for (String t : NL_SPLITTER.split(text)) {
208 def protected String formatDataForJavaDoc(GeneratedType type) {
209 val typeDescription = type.getDescription().encodeJavadocSymbols;
212 «IF !typeDescription.nullOrEmpty»
218 private static final CharMatcher AMP_MATCHER = CharMatcher.is('&');
219 private static final CharMatcher GT_MATCHER = CharMatcher.is('>');
220 private static final CharMatcher LT_MATCHER = CharMatcher.is('<');
222 def encodeJavadocSymbols(String description) {
223 if (description.nullOrEmpty) {
227 var ret = description.replace("*/", "*/")
229 // FIXME: Use Guava's HtmlEscapers once we have it available
230 ret = AMP_MATCHER.replaceFrom(ret, "&");
231 ret = GT_MATCHER.replaceFrom(ret, ">");
232 ret = LT_MATCHER.replaceFrom(ret, "<");
236 def protected String formatDataForJavaDoc(GeneratedType type, String additionalComment) {
237 val StringBuilder typeDescription = new StringBuilder();
238 if (!type.description.nullOrEmpty) {
239 typeDescription.append(type.description)
240 typeDescription.append(NEW_LINE)
241 typeDescription.append(NEW_LINE)
242 typeDescription.append(NEW_LINE)
243 typeDescription.append(additionalComment)
245 typeDescription.append(additionalComment)
249 «typeDescription.toString»
253 def asLink(String text) {
254 val StringBuilder sb = new StringBuilder()
256 var char lastChar = ' '
257 var boolean badEnding = false
259 if (text.endsWith('.') || text.endsWith(':') || text.endsWith(',')) {
260 tempText = text.substring(0, text.length - 1)
261 lastChar = text.charAt(text.length - 1)
264 sb.append("<a href = \"")
276 protected def formatToParagraph(String text) {
277 if(text == null || text.isEmpty)
280 var formattedText = text
281 val StringBuilder sb = new StringBuilder();
282 var StringBuilder lineBuilder = new StringBuilder();
283 var boolean isFirstElementOnNewLineEmptyChar = false;
285 formattedText = formattedText.encodeJavadocSymbols
286 formattedText = NL_MATCHER.removeFrom(formattedText)
287 formattedText = TAB_MATCHER.removeFrom(formattedText)
288 formattedText = SPACES_PATTERN.matcher(formattedText).replaceAll(" ")
290 val StringTokenizer tokenizer = new StringTokenizer(formattedText, " ", true);
292 while(tokenizer.hasMoreElements) {
293 val nextElement = tokenizer.nextElement.toString
295 if(lineBuilder.length + nextElement.length > 80) {
296 if (lineBuilder.charAt(lineBuilder.length - 1) == ' ') {
297 lineBuilder.setLength(0)
298 lineBuilder.append(lineBuilder.substring(0, lineBuilder.length - 1))
300 if (lineBuilder.charAt(0) == ' ') {
301 lineBuilder.setLength(0)
302 lineBuilder.append(lineBuilder.substring(1))
305 sb.append(lineBuilder);
306 lineBuilder.setLength(0)
309 if(nextElement.toString == ' ') {
310 isFirstElementOnNewLineEmptyChar = !isFirstElementOnNewLineEmptyChar;
314 if(isFirstElementOnNewLineEmptyChar) {
315 isFirstElementOnNewLineEmptyChar = !isFirstElementOnNewLineEmptyChar
319 lineBuilder.append(nextElement)
322 sb.append(lineBuilder)
328 def generateRestrictions(Type type, String paramName, Type returnType) '''
329 «val restrictions = type.getRestrictions»
330 «IF restrictions !== null»
331 «val boolean isNestedType = !(returnType instanceof ConcreteType)»
332 «IF !restrictions.lengthConstraints.empty»
333 «generateLengthRestriction(returnType, restrictions, paramName, isNestedType)»
335 «IF !restrictions.rangeConstraints.empty»
336 «generateRangeRestriction(returnType, paramName, isNestedType)»
341 def private generateLengthRestriction(Type returnType, Restrictions restrictions, String paramName, boolean isNestedType) '''
342 «val clazz = restrictions.lengthConstraints.iterator.next.min.class»
343 if («paramName» != null) {
344 «printLengthConstraint(returnType, clazz, paramName, isNestedType, returnType.name.contains("["))»
345 boolean isValidLength = false;
346 for («Range.importedName»<«clazz.importedNumber»> r : «IF isNestedType»«returnType.importedName».«ENDIF»length()) {
347 if (r.contains(_constraint)) {
348 isValidLength = true;
351 if (!isValidLength) {
352 throw new IllegalArgumentException(String.format("Invalid length: %s, expected: %s.", «paramName», «IF isNestedType»«returnType.importedName».«ENDIF»length()));
357 def private generateRangeRestriction(Type returnType, String paramName, boolean isNestedType) '''
358 if («paramName» != null) {
359 «printRangeConstraint(returnType, paramName, isNestedType)»
360 boolean isValidRange = false;
361 for («Range.importedName»<«returnType.importedNumber»> r : «IF isNestedType»«returnType.importedName».«ENDIF»range()) {
362 if (r.contains(_constraint)) {
367 throw new IllegalArgumentException(String.format("Invalid range: %s, expected: %s.", «paramName», «IF isNestedType»«returnType.importedName».«ENDIF»range()));
373 * Print length constraint.
374 * This should always be a BigInteger (only string and binary can have length restriction)
376 def printLengthConstraint(Type returnType, Class<? extends Number> clazz, String paramName, boolean isNestedType, boolean isArray) '''
377 «clazz.importedNumber» _constraint = «clazz.importedNumber».valueOf(«paramName»«IF isNestedType».getValue()«ENDIF».length«IF !isArray»()«ENDIF»);
380 def printRangeConstraint(Type returnType, String paramName, boolean isNestedType) '''
381 «IF BigDecimal.canonicalName.equals(returnType.fullyQualifiedName)»
382 «BigDecimal.importedName» _constraint = new «BigDecimal.importedName»(«paramName»«IF isNestedType».getValue()«ENDIF».toString());
385 «val propReturnType = findProperty(returnType as GeneratedTransferObject, "value").returnType»
386 «IF propReturnType.fullyQualifiedName.equals(BigInteger.canonicalName)»
387 «BigInteger.importedName» _constraint = «paramName».getValue();
389 «BigInteger.importedName» _constraint = «BigInteger.importedName».valueOf(«paramName».getValue());
392 «IF returnType.fullyQualifiedName.equals(BigInteger.canonicalName)»
393 «BigInteger.importedName» _constraint = «paramName»;
395 «BigInteger.importedName» _constraint = «BigInteger.importedName».valueOf(«paramName»);
401 def protected generateToString(Collection<GeneratedProperty> properties) '''
402 «IF !properties.empty»
404 public «String.importedName» toString() {
405 «StringBuilder.importedName» builder = new «StringBuilder.importedName»(«type.importedName».class.getSimpleName()).append(" [");
406 boolean first = true;
408 «FOR property : properties»
409 if («property.fieldName» != null) {
413 builder.append(", ");
415 builder.append("«property.fieldName»=");
416 «IF property.returnType.name.contains("[")»
417 builder.append(«Arrays.importedName».toString(«property.fieldName»));
419 builder.append(«property.fieldName»);
423 return builder.append(']').toString();
428 def getRestrictions(Type type) {
429 var Restrictions restrictions = null
430 if (type instanceof ConcreteType) {
431 restrictions = type.restrictions
432 } else if (type instanceof GeneratedTransferObject) {
433 restrictions = type.restrictions
438 def boolean isArrayType(GeneratedTransferObject type) {
440 val GeneratedProperty value = findProperty(type, "value")
441 if (value != null && value.returnType.name.contains("[")) {
447 def String toQuote(Object obj) {
448 return "\"" + obj.toString + "\"";
452 * Template method which generates method parameters with their types from <code>parameters</code>.
455 * list of parameter instances which are transformed to the method parameters
456 * @return string with the list of the method parameters with their types in JAVA format
458 def protected generateParameters(List<MethodSignature.Parameter> parameters) '''«
459 IF !parameters.empty»«
460 FOR parameter : parameters SEPARATOR ", "»«
461 parameter.type.importedName» «parameter.name»«
466 def protected generateLengthMethod(String methodName, Type type, String className, String varName) '''
467 «val Restrictions restrictions = type.restrictions»
468 «IF restrictions != null && !(restrictions.lengthConstraints.empty)»
469 «val numberClass = restrictions.lengthConstraints.iterator.next.min.class»
470 public static «List.importedName»<«Range.importedName»<«numberClass.importedNumber»>> «methodName»() {
471 «IF numberClass.equals(typeof(BigDecimal))»
472 «lengthBody(restrictions, numberClass, className, varName)»
474 «lengthBody(restrictions, typeof(BigInteger), className, varName)»
480 def private lengthBody(Restrictions restrictions, Class<? extends Number> numberClass, String className, String varName) '''
481 if («varName» == null) {
482 synchronized («className».class) {
483 if («varName» == null) {
484 «ImmutableList.importedName».Builder<«Range.importedName»<«numberClass.importedName»>> builder = «ImmutableList.importedName».builder();
485 «FOR r : restrictions.lengthConstraints»
486 builder.add(«Range.importedName».closed(«numericValue(numberClass, r.min)», «numericValue(numberClass, r.max)»));
488 «varName» = builder.build();
495 def protected generateRangeMethod(String methodName, Restrictions restrictions, Type returnType, String className, String varName) '''
496 «IF restrictions != null && !(restrictions.rangeConstraints.empty)»
497 «val number = returnType.importedNumber»
498 public static «List.importedName»<«Range.importedName»<«number»>> «methodName»() {
499 «IF returnType.fullyQualifiedName.equals(BigDecimal.canonicalName)»
500 «rangeBody(restrictions, BigDecimal, className, varName)»
502 «rangeBody(restrictions, BigInteger, className, varName)»
508 def protected generateRangeMethod(String methodName, Restrictions restrictions, String className, String varName, Iterable<GeneratedProperty> properties) '''
509 «IF restrictions != null && !(restrictions.rangeConstraints.empty)»
510 «val returnType = properties.iterator.next.returnType»
511 public static «List.importedName»<«Range.importedName»<«returnType.importedNumber»>> «methodName»() {
512 «IF returnType.fullyQualifiedName.equals(BigDecimal.canonicalName)»
513 «rangeBody(restrictions, BigDecimal, className, varName)»
515 «rangeBody(restrictions, BigInteger, className, varName)»
521 def private rangeBody(Restrictions restrictions, Class<? extends Number> numberClass, String className, String varName) '''
522 if («varName» == null) {
523 synchronized («className».class) {
524 if («varName» == null) {
525 «ImmutableList.importedName».Builder<«Range.importedName»<«numberClass.importedName»>> builder = «ImmutableList.importedName».builder();
526 «FOR r : restrictions.rangeConstraints»
527 builder.add(«Range.importedName».closed(«numericValue(numberClass, r.min)», «numericValue(numberClass, r.max)»));
529 «varName» = builder.build();
536 def protected String importedNumber(Class<? extends Number> clazz) {
537 if (clazz.equals(typeof(BigDecimal))) {
538 return BigDecimal.importedName
540 return BigInteger.importedName
543 def protected String importedNumber(Type clazz) {
544 if (clazz.fullyQualifiedName.equals(BigDecimal.canonicalName)) {
545 return BigDecimal.importedName
547 return BigInteger.importedName
550 def protected String numericValue(Class<? extends Number> clazz, Object numberValue) {
551 val number = clazz.importedName;
552 val value = numberValue.toString
553 if (clazz.equals(typeof(BigInteger)) || clazz.equals(typeof(BigDecimal))) {
554 if (value.equals("0")) {
555 return number + ".ZERO"
556 } else if (value.equals("1")) {
557 return number + ".ONE"
558 } else if (value.equals("10")) {
559 return number + ".TEN"
562 val Long longVal = Long.valueOf(value)
563 return number + ".valueOf(" + longVal + "L)"
564 } catch (NumberFormatException e) {
565 if (clazz.equals(typeof(BigDecimal))) {
567 val Double doubleVal = Double.valueOf(value);
568 return number + ".valueOf(" + doubleVal + ")"
569 } catch (NumberFormatException e2) {
575 return "new " + number + "(\"" + value + "\")"
578 def private GeneratedProperty findProperty(GeneratedTransferObject gto, String name) {
579 val props = gto.properties
581 if (prop.name.equals(name)) {
585 val GeneratedTransferObject parent = gto.superType
586 if (parent != null) {
587 return findProperty(parent, name)