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 «IF field.returnType.importedName.contains("[]")»
105 return «field.fieldName» == null ? null : «field.fieldName».clone();
107 return «field.fieldName»;
113 final protected def getterMethodName(GeneratedProperty field) {
114 val prefix = if(field.returnType.equals(Types.BOOLEAN)) "is" else "get"
115 return '''«prefix»«field.name.toFirstUpper»'''
119 * Template method which generates the setter method for <code>field</code>
122 * generated property with data about field which is generated as the setter method
123 * @return string with the setter method source code in JAVA format
125 final protected def setterMethod(GeneratedProperty field) '''
126 «val returnType = field.returnType.importedName»
127 public «type.name» set«field.name.toFirstUpper»(«returnType» value) {
128 this.«field.fieldName» = value;
133 final protected def importedName(Type intype) {
134 GeneratorUtil.putTypeIntoImports(type, intype, importMap);
135 GeneratorUtil.getExplicitType(type, intype, importMap)
138 final protected def importedName(Class<?> cls) {
139 importedName(Types.typeForClass(cls))
143 * Template method which generates method parameters with their types from <code>parameters</code>.
146 * group of generated property instances which are transformed to the method parameters
147 * @return string with the list of the method parameters with their types in JAVA format
149 def final protected asArgumentsDeclaration(Iterable<GeneratedProperty> parameters) '''«IF !parameters.empty»«FOR parameter : parameters SEPARATOR ", "»«parameter.
150 returnType.importedName» «parameter.fieldName»«ENDFOR»«ENDIF»'''
153 * Template method which generates sequence of the names of the class attributes from <code>parameters</code>.
156 * group of generated property instances which are transformed to the sequence of parameter names
157 * @return string with the list of the parameter names of the <code>parameters</code>
159 def final protected asArguments(Iterable<GeneratedProperty> parameters) '''«IF !parameters.empty»«FOR parameter : parameters SEPARATOR ", "»«parameter.
160 fieldName»«ENDFOR»«ENDIF»'''
163 * Template method which generates JAVA comments.
165 * @param comment string with the comment for whole JAVA class
166 * @return string with comment in JAVA format
168 def protected CharSequence asJavadoc(String comment) {
169 if(comment == null) return ''
173 txt = formatToParagraph(txt)
176 «wrapToDocumentation(txt)»
180 def String wrapToDocumentation(String text) {
184 val StringBuilder sb = new StringBuilder("/**")
187 for (String t : NL_SPLITTER.split(text)) {
197 def protected String formatDataForJavaDoc(GeneratedType type) {
198 val typeDescription = type.getDescription().encodeJavadocSymbols;
201 «IF !typeDescription.nullOrEmpty»
207 private static final CharMatcher AMP_MATCHER = CharMatcher.is('&');
208 private static final CharMatcher GT_MATCHER = CharMatcher.is('>');
209 private static final CharMatcher LT_MATCHER = CharMatcher.is('<');
211 def encodeJavadocSymbols(String description) {
212 if (description.nullOrEmpty) {
216 var ret = description.replace("*/", "*/")
218 // FIXME: Use Guava's HtmlEscapers once we have it available
219 ret = AMP_MATCHER.replaceFrom(ret, "&");
220 ret = GT_MATCHER.replaceFrom(ret, ">");
221 ret = LT_MATCHER.replaceFrom(ret, "<");
225 def protected String formatDataForJavaDoc(GeneratedType type, String additionalComment) {
226 val StringBuilder typeDescription = new StringBuilder();
227 if (!type.description.nullOrEmpty) {
228 typeDescription.append(type.description)
229 typeDescription.append(NEW_LINE)
230 typeDescription.append(NEW_LINE)
231 typeDescription.append(NEW_LINE)
232 typeDescription.append(additionalComment)
234 typeDescription.append(additionalComment)
238 «typeDescription.toString»
242 def asLink(String text) {
243 val StringBuilder sb = new StringBuilder()
245 var char lastChar = ' '
246 var boolean badEnding = false
248 if (text.endsWith('.') || text.endsWith(':') || text.endsWith(',')) {
249 tempText = text.substring(0, text.length - 1)
250 lastChar = text.charAt(text.length - 1)
253 sb.append("<a href = \"")
265 protected def formatToParagraph(String text) {
266 if(text == null || text.isEmpty)
269 var formattedText = text
270 val StringBuilder sb = new StringBuilder();
271 var StringBuilder lineBuilder = new StringBuilder();
272 var boolean isFirstElementOnNewLineEmptyChar = false;
274 formattedText = formattedText.encodeJavadocSymbols
275 formattedText = NL_MATCHER.removeFrom(formattedText)
276 formattedText = TAB_MATCHER.removeFrom(formattedText)
277 formattedText = SPACES_PATTERN.matcher(formattedText).replaceAll(" ")
279 val StringTokenizer tokenizer = new StringTokenizer(formattedText, " ", true);
281 while(tokenizer.hasMoreElements) {
282 val nextElement = tokenizer.nextElement.toString
284 if(lineBuilder.length + nextElement.length > 80) {
285 if (lineBuilder.charAt(lineBuilder.length - 1) == ' ') {
286 lineBuilder.setLength(0)
287 lineBuilder.append(lineBuilder.substring(0, lineBuilder.length - 1))
289 if (lineBuilder.charAt(0) == ' ') {
290 lineBuilder.setLength(0)
291 lineBuilder.append(lineBuilder.substring(1))
294 sb.append(lineBuilder);
295 lineBuilder.setLength(0)
298 if(nextElement.toString == ' ') {
299 isFirstElementOnNewLineEmptyChar = !isFirstElementOnNewLineEmptyChar;
303 if(isFirstElementOnNewLineEmptyChar) {
304 isFirstElementOnNewLineEmptyChar = !isFirstElementOnNewLineEmptyChar
308 lineBuilder.append(nextElement)
311 sb.append(lineBuilder)
317 def generateRestrictions(Type type, String paramName, Type returnType) '''
318 «val restrictions = type.getRestrictions»
319 «IF restrictions !== null»
320 «val boolean isNestedType = !(returnType instanceof ConcreteType)»
321 «IF !restrictions.lengthConstraints.empty»
322 «generateLengthRestriction(returnType, restrictions, paramName, isNestedType)»
324 «IF !restrictions.rangeConstraints.empty»
325 «generateRangeRestriction(returnType, paramName, isNestedType)»
330 def private generateLengthRestriction(Type returnType, Restrictions restrictions, String paramName, boolean isNestedType) '''
331 «val clazz = restrictions.lengthConstraints.iterator.next.min.class»
332 if («paramName» != null) {
333 «printLengthConstraint(returnType, clazz, paramName, isNestedType, returnType.name.contains("["))»
334 boolean isValidLength = false;
335 for («Range.importedName»<«clazz.importedNumber»> r : «IF isNestedType»«returnType.importedName».«ENDIF»length()) {
336 if (r.contains(_constraint)) {
337 isValidLength = true;
340 if (!isValidLength) {
341 throw new IllegalArgumentException(String.format("Invalid length: %s, expected: %s.", «paramName», «IF isNestedType»«returnType.importedName».«ENDIF»length()));
346 def private generateRangeRestriction(Type returnType, String paramName, boolean isNestedType) '''
347 if («paramName» != null) {
348 «printRangeConstraint(returnType, paramName, isNestedType)»
349 boolean isValidRange = false;
350 for («Range.importedName»<«returnType.importedNumber»> r : «IF isNestedType»«returnType.importedName».«ENDIF»range()) {
351 if (r.contains(_constraint)) {
356 throw new IllegalArgumentException(String.format("Invalid range: %s, expected: %s.", «paramName», «IF isNestedType»«returnType.importedName».«ENDIF»range()));
362 * Print length constraint.
363 * This should always be a BigInteger (only string and binary can have length restriction)
365 def printLengthConstraint(Type returnType, Class<? extends Number> clazz, String paramName, boolean isNestedType, boolean isArray) '''
366 «clazz.importedNumber» _constraint = «clazz.importedNumber».valueOf(«paramName»«IF isNestedType».getValue()«ENDIF».length«IF !isArray»()«ENDIF»);
369 def printRangeConstraint(Type returnType, String paramName, boolean isNestedType) '''
370 «IF BigDecimal.canonicalName.equals(returnType.fullyQualifiedName)»
371 «BigDecimal.importedName» _constraint = new «BigDecimal.importedName»(«paramName»«IF isNestedType».getValue()«ENDIF».toString());
374 «val propReturnType = findProperty(returnType as GeneratedTransferObject, "value").returnType»
375 «IF propReturnType.fullyQualifiedName.equals(BigInteger.canonicalName)»
376 «BigInteger.importedName» _constraint = «paramName».getValue();
378 «BigInteger.importedName» _constraint = «BigInteger.importedName».valueOf(«paramName».getValue());
381 «IF returnType.fullyQualifiedName.equals(BigInteger.canonicalName)»
382 «BigInteger.importedName» _constraint = «paramName»;
384 «BigInteger.importedName» _constraint = «BigInteger.importedName».valueOf(«paramName»);
390 def protected generateToString(Collection<GeneratedProperty> properties) '''
391 «IF !properties.empty»
393 public «String.importedName» toString() {
394 «StringBuilder.importedName» builder = new «StringBuilder.importedName»(«type.importedName».class.getSimpleName()).append(" [");
395 boolean first = true;
397 «FOR property : properties»
398 if («property.fieldName» != null) {
402 builder.append(", ");
404 builder.append("«property.fieldName»=");
405 «IF property.returnType.name.contains("[")»
406 builder.append(«Arrays.importedName».toString(«property.fieldName»));
408 builder.append(«property.fieldName»);
412 return builder.append(']').toString();
417 def getRestrictions(Type type) {
418 var Restrictions restrictions = null
419 if (type instanceof ConcreteType) {
420 restrictions = type.restrictions
421 } else if (type instanceof GeneratedTransferObject) {
422 restrictions = type.restrictions
427 def boolean isArrayType(GeneratedTransferObject type) {
429 val GeneratedProperty value = findProperty(type, "value")
430 if (value != null && value.returnType.name.contains("[")) {
436 def String toQuote(Object obj) {
437 return "\"" + obj.toString + "\"";
441 * Template method which generates method parameters with their types from <code>parameters</code>.
444 * list of parameter instances which are transformed to the method parameters
445 * @return string with the list of the method parameters with their types in JAVA format
447 def protected generateParameters(List<MethodSignature.Parameter> parameters) '''«
448 IF !parameters.empty»«
449 FOR parameter : parameters SEPARATOR ", "»«
450 parameter.type.importedName» «parameter.name»«
455 def protected generateLengthMethod(String methodName, Type type, String className, String varName) '''
456 «val Restrictions restrictions = type.restrictions»
457 «IF restrictions != null && !(restrictions.lengthConstraints.empty)»
458 «val numberClass = restrictions.lengthConstraints.iterator.next.min.class»
459 public static «List.importedName»<«Range.importedName»<«numberClass.importedNumber»>> «methodName»() {
460 «IF numberClass.equals(typeof(BigDecimal))»
461 «lengthBody(restrictions, numberClass, className, varName)»
463 «lengthBody(restrictions, typeof(BigInteger), className, varName)»
469 def private lengthBody(Restrictions restrictions, Class<? extends Number> numberClass, String className, String varName) '''
470 if («varName» == null) {
471 synchronized («className».class) {
472 if («varName» == null) {
473 «ImmutableList.importedName».Builder<«Range.importedName»<«numberClass.importedName»>> builder = «ImmutableList.importedName».builder();
474 «FOR r : restrictions.lengthConstraints»
475 builder.add(«Range.importedName».closed(«numericValue(numberClass, r.min)», «numericValue(numberClass, r.max)»));
477 «varName» = builder.build();
484 def protected generateRangeMethod(String methodName, Restrictions restrictions, Type returnType, String className, String varName) '''
485 «IF restrictions != null && !(restrictions.rangeConstraints.empty)»
486 «val number = returnType.importedNumber»
487 public static «List.importedName»<«Range.importedName»<«number»>> «methodName»() {
488 «IF returnType.fullyQualifiedName.equals(BigDecimal.canonicalName)»
489 «rangeBody(restrictions, BigDecimal, className, varName)»
491 «rangeBody(restrictions, BigInteger, className, varName)»
497 def protected generateRangeMethod(String methodName, Restrictions restrictions, String className, String varName, Iterable<GeneratedProperty> properties) '''
498 «IF restrictions != null && !(restrictions.rangeConstraints.empty)»
499 «val returnType = properties.iterator.next.returnType»
500 public static «List.importedName»<«Range.importedName»<«returnType.importedNumber»>> «methodName»() {
501 «IF returnType.fullyQualifiedName.equals(BigDecimal.canonicalName)»
502 «rangeBody(restrictions, BigDecimal, className, varName)»
504 «rangeBody(restrictions, BigInteger, className, varName)»
510 def private rangeBody(Restrictions restrictions, Class<? extends Number> numberClass, String className, String varName) '''
511 if («varName» == null) {
512 synchronized («className».class) {
513 if («varName» == null) {
514 «ImmutableList.importedName».Builder<«Range.importedName»<«numberClass.importedName»>> builder = «ImmutableList.importedName».builder();
515 «FOR r : restrictions.rangeConstraints»
516 builder.add(«Range.importedName».closed(«numericValue(numberClass, r.min)», «numericValue(numberClass, r.max)»));
518 «varName» = builder.build();
525 def protected String importedNumber(Class<? extends Number> clazz) {
526 if (clazz.equals(typeof(BigDecimal))) {
527 return BigDecimal.importedName
529 return BigInteger.importedName
532 def protected String importedNumber(Type clazz) {
533 if (clazz.fullyQualifiedName.equals(BigDecimal.canonicalName)) {
534 return BigDecimal.importedName
536 return BigInteger.importedName
539 def protected String numericValue(Class<? extends Number> clazz, Object numberValue) {
540 val number = clazz.importedName;
541 val value = numberValue.toString
542 if (clazz.equals(typeof(BigInteger)) || clazz.equals(typeof(BigDecimal))) {
543 if (value.equals("0")) {
544 return number + ".ZERO"
545 } else if (value.equals("1")) {
546 return number + ".ONE"
547 } else if (value.equals("10")) {
548 return number + ".TEN"
551 val Long longVal = Long.valueOf(value)
552 return number + ".valueOf(" + longVal + "L)"
553 } catch (NumberFormatException e) {
554 if (clazz.equals(typeof(BigDecimal))) {
556 val Double doubleVal = Double.valueOf(value);
557 return number + ".valueOf(" + doubleVal + ")"
558 } catch (NumberFormatException e2) {
564 return "new " + number + "(\"" + value + "\")"
567 def private GeneratedProperty findProperty(GeneratedTransferObject gto, String name) {
568 val props = gto.properties
570 if (prop.name.equals(name)) {
574 val GeneratedTransferObject parent = gto.superType
575 if (parent != null) {
576 return findProperty(parent, name)