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 entry.value != fullyQualifiedName»
70 import «entry.value».«entry.key»;
77 protected abstract def CharSequence body();
80 final protected def fieldName(GeneratedProperty property) '''_«property.name»'''
82 final protected def propertyNameFromGetter(MethodSignature getter) {
84 if (getter.name.startsWith("is")) {
86 } else if (getter.name.startsWith("get")) {
89 throw new IllegalArgumentException("Not a getter")
91 return getter.name.substring(prefix).toFirstLower;
95 * Template method which generates the getter method for <code>field</code>
98 * generated property with data about field which is generated as the getter method
99 * @return string with the getter method source code in JAVA format
101 final protected def getterMethod(GeneratedProperty field) {
103 public «field.returnType.importedName» «field.getterMethodName»() {
104 return «field.fieldName»;
109 final protected def getterMethodName(GeneratedProperty field) {
110 val prefix = if(field.returnType.equals(Types.BOOLEAN)) "is" else "get"
111 return '''«prefix»«field.name.toFirstUpper»'''
115 * Template method which generates the setter method for <code>field</code>
118 * generated property with data about field which is generated as the setter method
119 * @return string with the setter method source code in JAVA format
121 final protected def setterMethod(GeneratedProperty field) '''
122 «val returnType = field.returnType.importedName»
123 public «type.name» set«field.name.toFirstUpper»(«returnType» value) {
124 this.«field.fieldName» = value;
129 final protected def importedName(Type intype) {
130 GeneratorUtil.putTypeIntoImports(type, intype, importMap);
131 GeneratorUtil.getExplicitType(type, intype, importMap)
134 final protected def importedName(Class<?> cls) {
135 importedName(Types.typeForClass(cls))
139 * Template method which generates method parameters with their types from <code>parameters</code>.
142 * group of generated property instances which are transformed to the method parameters
143 * @return string with the list of the method parameters with their types in JAVA format
145 def final protected asArgumentsDeclaration(Iterable<GeneratedProperty> parameters) '''«IF !parameters.empty»«FOR parameter : parameters SEPARATOR ", "»«parameter.
146 returnType.importedName» «parameter.fieldName»«ENDFOR»«ENDIF»'''
149 * Template method which generates sequence of the names of the class attributes from <code>parameters</code>.
152 * group of generated property instances which are transformed to the sequence of parameter names
153 * @return string with the list of the parameter names of the <code>parameters</code>
155 def final protected asArguments(Iterable<GeneratedProperty> parameters) '''«IF !parameters.empty»«FOR parameter : parameters SEPARATOR ", "»«parameter.
156 fieldName»«ENDFOR»«ENDIF»'''
159 * Template method which generates JAVA comments.
161 * @param comment string with the comment for whole JAVA class
162 * @return string with comment in JAVA format
164 def protected CharSequence asJavadoc(String comment) {
165 if(comment == null) return ''
169 txt = formatToParagraph(txt)
172 «wrapToDocumentation(txt)»
176 def String wrapToDocumentation(String text) {
180 val StringBuilder sb = new StringBuilder("/**")
183 for (String t : NL_SPLITTER.split(text)) {
193 def protected String formatDataForJavaDoc(GeneratedType type) {
194 val typeDescription = type.getDescription().encodeJavadocSymbols;
197 «IF !typeDescription.nullOrEmpty»
203 private static final CharMatcher AMP_MATCHER = CharMatcher.is('&');
204 private static final CharMatcher GT_MATCHER = CharMatcher.is('>');
205 private static final CharMatcher LT_MATCHER = CharMatcher.is('<');
207 def encodeJavadocSymbols(String description) {
208 if (description.nullOrEmpty) {
212 var ret = description.replace("*/", "*/")
214 // FIXME: Use Guava's HtmlEscapers once we have it available
215 ret = AMP_MATCHER.replaceFrom(ret, "&");
216 ret = GT_MATCHER.replaceFrom(ret, ">");
217 ret = LT_MATCHER.replaceFrom(ret, "<");
221 def protected String formatDataForJavaDoc(GeneratedType type, String additionalComment) {
222 val StringBuilder typeDescription = new StringBuilder();
223 if (!type.description.nullOrEmpty) {
224 typeDescription.append(type.description)
225 typeDescription.append(NEW_LINE)
226 typeDescription.append(NEW_LINE)
227 typeDescription.append(NEW_LINE)
228 typeDescription.append(additionalComment)
230 typeDescription.append(additionalComment)
234 «typeDescription.toString»
238 def asLink(String text) {
239 val StringBuilder sb = new StringBuilder()
241 var char lastChar = ' '
242 var boolean badEnding = false
244 if (text.endsWith('.') || text.endsWith(':') || text.endsWith(',')) {
245 tempText = text.substring(0, text.length - 1)
246 lastChar = text.charAt(text.length - 1)
249 sb.append("<a href = \"")
261 protected def formatToParagraph(String text) {
262 if(text == null || text.isEmpty)
265 var formattedText = text
266 val StringBuilder sb = new StringBuilder();
267 var StringBuilder lineBuilder = new StringBuilder();
268 var boolean isFirstElementOnNewLineEmptyChar = false;
270 formattedText = formattedText.encodeJavadocSymbols
271 formattedText = NL_MATCHER.removeFrom(formattedText)
272 formattedText = TAB_MATCHER.removeFrom(formattedText)
273 formattedText = SPACES_PATTERN.matcher(formattedText).replaceAll(" ")
275 val StringTokenizer tokenizer = new StringTokenizer(formattedText, " ", true);
277 while(tokenizer.hasMoreElements) {
278 val nextElement = tokenizer.nextElement.toString
280 if(lineBuilder.length + nextElement.length > 80) {
281 if (lineBuilder.charAt(lineBuilder.length - 1) == ' ') {
282 lineBuilder.setLength(0)
283 lineBuilder.append(lineBuilder.substring(0, lineBuilder.length - 1))
285 if (lineBuilder.charAt(0) == ' ') {
286 lineBuilder.setLength(0)
287 lineBuilder.append(lineBuilder.substring(1))
290 sb.append(lineBuilder);
291 lineBuilder.setLength(0)
294 if(nextElement.toString == ' ') {
295 isFirstElementOnNewLineEmptyChar = !isFirstElementOnNewLineEmptyChar;
299 if(isFirstElementOnNewLineEmptyChar) {
300 isFirstElementOnNewLineEmptyChar = !isFirstElementOnNewLineEmptyChar
304 lineBuilder.append(nextElement)
307 sb.append(lineBuilder)
313 def isDocumentationParametersNullOrEmtpy(GeneratedType type) {
314 val boolean isTypeDescriptionNullOrEmpty = type.description.nullOrEmpty
315 val boolean isTypeReferenceNullOrEmpty = type.reference.nullOrEmpty
316 val boolean isTypeModuleNameNullOrEmpty = type.moduleName.nullOrEmpty
317 val boolean isTypeSchemaPathNullOrEmpty = type.schemaPath.nullOrEmpty
319 if (isTypeDescriptionNullOrEmpty && isTypeReferenceNullOrEmpty && isTypeModuleNameNullOrEmpty
320 && isTypeSchemaPathNullOrEmpty) {
326 def generateRestrictions(Type type, String paramName, Type returnType) '''
327 «val restrictions = type.getRestrictions»
328 «IF restrictions !== null»
329 «val boolean isNestedType = !(returnType instanceof ConcreteType)»
330 «IF !restrictions.lengthConstraints.empty»
331 «generateLengthRestriction(returnType, restrictions, paramName, isNestedType)»
333 «IF !restrictions.rangeConstraints.empty»
334 «generateRangeRestriction(returnType, paramName, isNestedType)»
339 def private generateLengthRestriction(Type returnType, Restrictions restrictions, String paramName, boolean isNestedType) '''
340 «val clazz = restrictions.lengthConstraints.iterator.next.min.class»
341 if («paramName» != null) {
342 «printLengthConstraint(returnType, clazz, paramName, isNestedType, returnType.name.contains("["))»
343 boolean isValidLength = false;
344 for («Range.importedName»<«clazz.importedNumber»> r : «IF isNestedType»«returnType.importedName».«ENDIF»length()) {
345 if (r.contains(_constraint)) {
346 isValidLength = true;
349 if (!isValidLength) {
350 throw new IllegalArgumentException(String.format("Invalid length: %s, expected: %s.", «paramName», «IF isNestedType»«returnType.importedName».«ENDIF»length()));
355 def private generateRangeRestriction(Type returnType, String paramName, boolean isNestedType) '''
356 if («paramName» != null) {
357 «printRangeConstraint(returnType, paramName, isNestedType)»
358 boolean isValidRange = false;
359 for («Range.importedName»<«returnType.importedNumber»> r : «IF isNestedType»«returnType.importedName».«ENDIF»range()) {
360 if (r.contains(_constraint)) {
365 throw new IllegalArgumentException(String.format("Invalid range: %s, expected: %s.", «paramName», «IF isNestedType»«returnType.importedName».«ENDIF»range()));
371 * Print length constraint.
372 * This should always be a BigInteger (only string and binary can have length restriction)
374 def printLengthConstraint(Type returnType, Class<? extends Number> clazz, String paramName, boolean isNestedType, boolean isArray) '''
375 «clazz.importedNumber» _constraint = «clazz.importedNumber».valueOf(«paramName»«IF isNestedType».getValue()«ENDIF».length«IF !isArray»()«ENDIF»);
378 def printRangeConstraint(Type returnType, String paramName, boolean isNestedType) '''
379 «IF BigDecimal.canonicalName.equals(returnType.fullyQualifiedName)»
380 «BigDecimal.importedName» _constraint = new «BigDecimal.importedName»(«paramName»«IF isNestedType».getValue()«ENDIF».toString());
383 «val propReturnType = findProperty(returnType as GeneratedTransferObject, "value").returnType»
384 «IF propReturnType.fullyQualifiedName.equals(BigInteger.canonicalName)»
385 «BigInteger.importedName» _constraint = «paramName».getValue();
387 «BigInteger.importedName» _constraint = «BigInteger.importedName».valueOf(«paramName».getValue());
390 «IF returnType.fullyQualifiedName.equals(BigInteger.canonicalName)»
391 «BigInteger.importedName» _constraint = «paramName»;
393 «BigInteger.importedName» _constraint = «BigInteger.importedName».valueOf(«paramName»);
399 def protected generateToString(Collection<GeneratedProperty> properties) '''
400 «IF !properties.empty»
402 public «String.importedName» toString() {
403 «StringBuilder.importedName» builder = new «StringBuilder.importedName»(«type.importedName».class.getSimpleName()).append(" [");
404 boolean first = true;
406 «FOR property : properties»
407 if («property.fieldName» != null) {
411 builder.append(", ");
413 builder.append("«property.fieldName»=");
414 «IF property.returnType.name.contains("[")»
415 builder.append(«Arrays.importedName».toString(«property.fieldName»));
417 builder.append(«property.fieldName»);
421 return builder.append(']').toString();
426 def getRestrictions(Type type) {
427 var Restrictions restrictions = null
428 if (type instanceof ConcreteType) {
429 restrictions = (type as ConcreteType).restrictions
430 } else if (type instanceof GeneratedTransferObject) {
431 restrictions = (type as GeneratedTransferObject).restrictions
436 def boolean isArrayType(GeneratedTransferObject type) {
438 val GeneratedProperty value = findProperty(type, "value")
439 if (value != null && value.returnType.name.contains("[")) {
445 def String toQuote(Object obj) {
446 return "\"" + obj.toString + "\"";
450 * Template method which generates method parameters with their types from <code>parameters</code>.
453 * list of parameter instances which are transformed to the method parameters
454 * @return string with the list of the method parameters with their types in JAVA format
456 def protected generateParameters(List<MethodSignature.Parameter> parameters) '''«
457 IF !parameters.empty»«
458 FOR parameter : parameters SEPARATOR ", "»«
459 parameter.type.importedName» «parameter.name»«
464 def protected generateLengthMethod(String methodName, Type type, String className, String varName) '''
465 «val Restrictions restrictions = type.restrictions»
466 «IF restrictions != null && !(restrictions.lengthConstraints.empty)»
467 «val numberClass = restrictions.lengthConstraints.iterator.next.min.class»
468 public static «List.importedName»<«Range.importedName»<«numberClass.importedNumber»>> «methodName»() {
469 «IF numberClass.equals(typeof(BigDecimal))»
470 «lengthBody(restrictions, numberClass, className, varName)»
472 «lengthBody(restrictions, typeof(BigInteger), className, varName)»
478 def private lengthBody(Restrictions restrictions, Class<? extends Number> numberClass, String className, String varName) '''
479 if («varName» == null) {
480 synchronized («className».class) {
481 if («varName» == null) {
482 «ImmutableList.importedName».Builder<«Range.importedName»<«numberClass.importedName»>> builder = «ImmutableList.importedName».builder();
483 «FOR r : restrictions.lengthConstraints»
484 builder.add(«Range.importedName».closed(«numericValue(numberClass, r.min)», «numericValue(numberClass, r.max)»));
486 «varName» = builder.build();
493 def protected generateRangeMethod(String methodName, Restrictions restrictions, Type returnType, String className, String varName) '''
494 «IF restrictions != null && !(restrictions.rangeConstraints.empty)»
495 «val number = returnType.importedNumber»
496 public static «List.importedName»<«Range.importedName»<«number»>> «methodName»() {
497 «IF returnType.fullyQualifiedName.equals(BigDecimal.canonicalName)»
498 «rangeBody(restrictions, BigDecimal, className, varName)»
500 «rangeBody(restrictions, BigInteger, className, varName)»
506 def protected generateRangeMethod(String methodName, Restrictions restrictions, String className, String varName, Iterable<GeneratedProperty> properties) '''
507 «IF restrictions != null && !(restrictions.rangeConstraints.empty)»
508 «val returnType = properties.iterator.next.returnType»
509 public static «List.importedName»<«Range.importedName»<«returnType.importedNumber»>> «methodName»() {
510 «IF returnType.fullyQualifiedName.equals(BigDecimal.canonicalName)»
511 «rangeBody(restrictions, BigDecimal, className, varName)»
513 «rangeBody(restrictions, BigInteger, className, varName)»
519 def private rangeBody(Restrictions restrictions, Class<? extends Number> numberClass, String className, String varName) '''
520 if («varName» == null) {
521 synchronized («className».class) {
522 if («varName» == null) {
523 «ImmutableList.importedName».Builder<«Range.importedName»<«numberClass.importedName»>> builder = «ImmutableList.importedName».builder();
524 «FOR r : restrictions.rangeConstraints»
525 builder.add(«Range.importedName».closed(«numericValue(numberClass, r.min)», «numericValue(numberClass, r.max)»));
527 «varName» = builder.build();
534 def protected String importedNumber(Class<? extends Number> clazz) {
535 if (clazz.equals(typeof(BigDecimal))) {
536 return BigDecimal.importedName
538 return BigInteger.importedName
541 def protected String importedNumber(Type clazz) {
542 if (clazz.fullyQualifiedName.equals(BigDecimal.canonicalName)) {
543 return BigDecimal.importedName
545 return BigInteger.importedName
548 def protected String numericValue(Class<? extends Number> clazz, Object numberValue) {
549 val number = clazz.importedName;
550 val value = numberValue.toString
551 if (clazz.equals(typeof(BigInteger)) || clazz.equals(typeof(BigDecimal))) {
552 if (value.equals("0")) {
553 return number + ".ZERO"
554 } else if (value.equals("1")) {
555 return number + ".ONE"
556 } else if (value.equals("10")) {
557 return number + ".TEN"
560 val Long longVal = Long.valueOf(value)
561 return number + ".valueOf(" + longVal + "L)"
562 } catch (NumberFormatException e) {
563 if (clazz.equals(typeof(BigDecimal))) {
565 val Double doubleVal = Double.valueOf(value);
566 return number + ".valueOf(" + doubleVal + ")"
567 } catch (NumberFormatException e2) {
573 return "new " + number + "(\"" + value + "\")"
576 def private GeneratedProperty findProperty(GeneratedTransferObject gto, String name) {
577 val props = gto.properties
579 if (prop.name.equals(name)) {
583 val GeneratedTransferObject parent = gto.superType
584 if (parent != null) {
585 return findProperty(parent, name)