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)) {
211 def protected String formatDataForJavaDoc(GeneratedType type) {
212 val typeDescription = type.getDescription().encodeJavadocSymbols;
215 «IF !typeDescription.nullOrEmpty»
221 private static final CharMatcher AMP_MATCHER = CharMatcher.is('&');
222 private static final CharMatcher GT_MATCHER = CharMatcher.is('>');
223 private static final CharMatcher LT_MATCHER = CharMatcher.is('<');
225 def encodeJavadocSymbols(String description) {
226 if (description.nullOrEmpty) {
230 var ret = description.replace("*/", "*/")
232 // FIXME: Use Guava's HtmlEscapers once we have it available
233 ret = AMP_MATCHER.replaceFrom(ret, "&");
234 ret = GT_MATCHER.replaceFrom(ret, ">");
235 ret = LT_MATCHER.replaceFrom(ret, "<");
239 def protected String formatDataForJavaDoc(GeneratedType type, String additionalComment) {
240 val StringBuilder typeDescription = new StringBuilder();
241 if (!type.description.nullOrEmpty) {
242 typeDescription.append(type.description)
243 typeDescription.append(NEW_LINE)
244 typeDescription.append(NEW_LINE)
245 typeDescription.append(NEW_LINE)
246 typeDescription.append(additionalComment)
248 typeDescription.append(additionalComment)
252 «typeDescription.toString»
256 def asLink(String text) {
257 val StringBuilder sb = new StringBuilder()
259 var char lastChar = ' '
260 var boolean badEnding = false
262 if (text.endsWith('.') || text.endsWith(':') || text.endsWith(',')) {
263 tempText = text.substring(0, text.length - 1)
264 lastChar = text.charAt(text.length - 1)
267 sb.append("<a href = \"")
279 protected def formatToParagraph(String text) {
280 if(text == null || text.isEmpty)
283 var formattedText = text
284 val StringBuilder sb = new StringBuilder();
285 var StringBuilder lineBuilder = new StringBuilder();
286 var boolean isFirstElementOnNewLineEmptyChar = false;
288 formattedText = formattedText.encodeJavadocSymbols
289 formattedText = NL_MATCHER.removeFrom(formattedText)
290 formattedText = TAB_MATCHER.removeFrom(formattedText)
291 formattedText = SPACES_PATTERN.matcher(formattedText).replaceAll(" ")
293 val StringTokenizer tokenizer = new StringTokenizer(formattedText, " ", true);
295 while(tokenizer.hasMoreElements) {
296 val nextElement = tokenizer.nextElement.toString
298 if(lineBuilder.length + nextElement.length > 80) {
299 if (lineBuilder.charAt(lineBuilder.length - 1) == ' ') {
300 lineBuilder.setLength(0)
301 lineBuilder.append(lineBuilder.substring(0, lineBuilder.length - 1))
303 if (lineBuilder.charAt(0) == ' ') {
304 lineBuilder.setLength(0)
305 lineBuilder.append(lineBuilder.substring(1))
308 sb.append(lineBuilder);
309 lineBuilder.setLength(0)
312 if(nextElement.toString == ' ') {
313 isFirstElementOnNewLineEmptyChar = !isFirstElementOnNewLineEmptyChar;
317 if(isFirstElementOnNewLineEmptyChar) {
318 isFirstElementOnNewLineEmptyChar = !isFirstElementOnNewLineEmptyChar
322 lineBuilder.append(nextElement)
325 sb.append(lineBuilder)
331 def generateRestrictions(Type type, String paramName, Type returnType) '''
332 «val restrictions = type.getRestrictions»
333 «IF restrictions !== null»
334 «val boolean isNestedType = !(returnType instanceof ConcreteType)»
335 «IF !restrictions.lengthConstraints.empty»
336 «generateLengthRestriction(returnType, restrictions, paramName, isNestedType)»
338 «IF !restrictions.rangeConstraints.empty»
339 «generateRangeRestriction(returnType, paramName, isNestedType)»
344 def private generateLengthRestriction(Type returnType, Restrictions restrictions, String paramName, boolean isNestedType) '''
345 «val clazz = restrictions.lengthConstraints.iterator.next.min.class»
346 if («paramName» != null) {
347 «printLengthConstraint(returnType, clazz, paramName, isNestedType, returnType.name.contains("["))»
348 boolean isValidLength = false;
349 for («Range.importedName»<«clazz.importedNumber»> r : «IF isNestedType»«returnType.importedName».«ENDIF»length()) {
350 if (r.contains(_constraint)) {
351 isValidLength = true;
354 if (!isValidLength) {
355 throw new IllegalArgumentException(String.format("Invalid length: %s, expected: %s.", «paramName», «IF isNestedType»«returnType.importedName».«ENDIF»length()));
360 def private generateRangeRestriction(Type returnType, String paramName, boolean isNestedType) '''
361 if («paramName» != null) {
362 «printRangeConstraint(returnType, paramName, isNestedType)»
363 boolean isValidRange = false;
364 for («Range.importedName»<«returnType.importedNumber»> r : «IF isNestedType»«returnType.importedName».«ENDIF»range()) {
365 if (r.contains(_constraint)) {
370 throw new IllegalArgumentException(String.format("Invalid range: %s, expected: %s.", «paramName», «IF isNestedType»«returnType.importedName».«ENDIF»range()));
376 * Print length constraint.
377 * This should always be a BigInteger (only string and binary can have length restriction)
379 def printLengthConstraint(Type returnType, Class<? extends Number> clazz, String paramName, boolean isNestedType, boolean isArray) '''
380 «clazz.importedNumber» _constraint = «clazz.importedNumber».valueOf(«paramName»«IF isNestedType».getValue()«ENDIF».length«IF !isArray»()«ENDIF»);
383 def printRangeConstraint(Type returnType, String paramName, boolean isNestedType) '''
384 «IF BigDecimal.canonicalName.equals(returnType.fullyQualifiedName)»
385 «BigDecimal.importedName» _constraint = new «BigDecimal.importedName»(«paramName»«IF isNestedType».getValue()«ENDIF».toString());
388 «val propReturnType = findProperty(returnType as GeneratedTransferObject, "value").returnType»
389 «IF propReturnType.fullyQualifiedName.equals(BigInteger.canonicalName)»
390 «BigInteger.importedName» _constraint = «paramName».getValue();
392 «BigInteger.importedName» _constraint = «BigInteger.importedName».valueOf(«paramName».getValue());
395 «IF returnType.fullyQualifiedName.equals(BigInteger.canonicalName)»
396 «BigInteger.importedName» _constraint = «paramName»;
398 «BigInteger.importedName» _constraint = «BigInteger.importedName».valueOf(«paramName»);
404 def protected generateToString(Collection<GeneratedProperty> properties) '''
405 «IF !properties.empty»
407 public «String.importedName» toString() {
408 «StringBuilder.importedName» builder = new «StringBuilder.importedName»(«type.importedName».class.getSimpleName()).append(" [");
409 boolean first = true;
411 «FOR property : properties»
412 if («property.fieldName» != null) {
416 builder.append(", ");
418 builder.append("«property.fieldName»=");
419 «IF property.returnType.name.contains("[")»
420 builder.append(«Arrays.importedName».toString(«property.fieldName»));
422 builder.append(«property.fieldName»);
426 return builder.append(']').toString();
431 def getRestrictions(Type type) {
432 var Restrictions restrictions = null
433 if (type instanceof ConcreteType) {
434 restrictions = type.restrictions
435 } else if (type instanceof GeneratedTransferObject) {
436 restrictions = type.restrictions
441 def boolean isArrayType(GeneratedTransferObject type) {
443 val GeneratedProperty value = findProperty(type, "value")
444 if (value != null && value.returnType.name.contains("[")) {
450 def String toQuote(Object obj) {
451 return "\"" + obj.toString + "\"";
455 * Template method which generates method parameters with their types from <code>parameters</code>.
458 * list of parameter instances which are transformed to the method parameters
459 * @return string with the list of the method parameters with their types in JAVA format
461 def protected generateParameters(List<MethodSignature.Parameter> parameters) '''«
462 IF !parameters.empty»«
463 FOR parameter : parameters SEPARATOR ", "»«
464 parameter.type.importedName» «parameter.name»«
469 def protected generateLengthMethod(String methodName, Type type, String className, String varName) '''
470 «val Restrictions restrictions = type.restrictions»
471 «IF restrictions != null && !(restrictions.lengthConstraints.empty)»
472 «val numberClass = restrictions.lengthConstraints.iterator.next.min.class»
473 public static «List.importedName»<«Range.importedName»<«numberClass.importedNumber»>> «methodName»() {
474 «IF numberClass.equals(typeof(BigDecimal))»
475 «lengthBody(restrictions, numberClass, className, varName)»
477 «lengthBody(restrictions, typeof(BigInteger), className, varName)»
483 def private lengthBody(Restrictions restrictions, Class<? extends Number> numberClass, String className, String varName) '''
484 if («varName» == null) {
485 synchronized («className».class) {
486 if («varName» == null) {
487 «ImmutableList.importedName».Builder<«Range.importedName»<«numberClass.importedName»>> builder = «ImmutableList.importedName».builder();
488 «FOR r : restrictions.lengthConstraints»
489 builder.add(«Range.importedName».closed(«numericValue(numberClass, r.min)», «numericValue(numberClass, r.max)»));
491 «varName» = builder.build();
498 def protected generateRangeMethod(String methodName, Restrictions restrictions, Type returnType, String className, String varName) '''
499 «IF restrictions != null && !(restrictions.rangeConstraints.empty)»
500 «val number = returnType.importedNumber»
501 public static «List.importedName»<«Range.importedName»<«number»>> «methodName»() {
502 «IF returnType.fullyQualifiedName.equals(BigDecimal.canonicalName)»
503 «rangeBody(restrictions, BigDecimal, className, varName)»
505 «rangeBody(restrictions, BigInteger, className, varName)»
511 def protected generateRangeMethod(String methodName, Restrictions restrictions, String className, String varName, Iterable<GeneratedProperty> properties) '''
512 «IF restrictions != null && !(restrictions.rangeConstraints.empty)»
513 «val returnType = properties.iterator.next.returnType»
514 public static «List.importedName»<«Range.importedName»<«returnType.importedNumber»>> «methodName»() {
515 «IF returnType.fullyQualifiedName.equals(BigDecimal.canonicalName)»
516 «rangeBody(restrictions, BigDecimal, className, varName)»
518 «rangeBody(restrictions, BigInteger, className, varName)»
524 def private rangeBody(Restrictions restrictions, Class<? extends Number> numberClass, String className, String varName) '''
525 if («varName» == null) {
526 synchronized («className».class) {
527 if («varName» == null) {
528 «ImmutableList.importedName».Builder<«Range.importedName»<«numberClass.importedName»>> builder = «ImmutableList.importedName».builder();
529 «FOR r : restrictions.rangeConstraints»
530 builder.add(«Range.importedName».closed(«numericValue(numberClass, r.min)», «numericValue(numberClass, r.max)»));
532 «varName» = builder.build();
539 def protected String importedNumber(Class<? extends Number> clazz) {
540 if (clazz.equals(typeof(BigDecimal))) {
541 return BigDecimal.importedName
543 return BigInteger.importedName
546 def protected String importedNumber(Type clazz) {
547 if (clazz.fullyQualifiedName.equals(BigDecimal.canonicalName)) {
548 return BigDecimal.importedName
550 return BigInteger.importedName
553 def protected String numericValue(Class<? extends Number> clazz, Object numberValue) {
554 val number = clazz.importedName;
555 val value = numberValue.toString
556 if (clazz.equals(typeof(BigInteger)) || clazz.equals(typeof(BigDecimal))) {
557 if (value.equals("0")) {
558 return number + ".ZERO"
559 } else if (value.equals("1")) {
560 return number + ".ONE"
561 } else if (value.equals("10")) {
562 return number + ".TEN"
565 val Long longVal = Long.valueOf(value)
566 return number + ".valueOf(" + longVal + "L)"
567 } catch (NumberFormatException e) {
568 if (clazz.equals(typeof(BigDecimal))) {
570 val Double doubleVal = Double.valueOf(value);
571 return number + ".valueOf(" + doubleVal + ")"
572 } catch (NumberFormatException e2) {
578 return "new " + number + "(\"" + value + "\")"
581 def private GeneratedProperty findProperty(GeneratedTransferObject gto, String name) {
582 val props = gto.properties
584 if (prop.name.equals(name)) {
588 val GeneratedTransferObject parent = gto.superType
589 if (parent != null) {
590 return findProperty(parent, name)