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)
332 * Print length constraint.
333 * This should always be a BigInteger (only string and binary can have length restriction)
335 def printLengthConstraint(Type returnType, Class<? extends Number> clazz, String paramName, boolean isNestedType, boolean isArray) '''
336 «clazz.importedNumber» _constraint = «clazz.importedNumber».valueOf(«paramName»«IF isNestedType».getValue()«ENDIF».length«IF !isArray»()«ENDIF»);
339 def printRangeConstraint(Type returnType, String paramName, boolean isNestedType) '''
340 «IF BigDecimal.canonicalName.equals(returnType.fullyQualifiedName)»
341 «BigDecimal.importedName» _constraint = new «BigDecimal.importedName»(«paramName»«IF isNestedType».getValue()«ENDIF».toString());
344 «val propReturnType = findProperty(returnType as GeneratedTransferObject, "value").returnType»
345 «IF propReturnType.fullyQualifiedName.equals(BigInteger.canonicalName)»
346 «BigInteger.importedName» _constraint = «paramName».getValue();
348 «BigInteger.importedName» _constraint = «BigInteger.importedName».valueOf(«paramName».getValue());
351 «IF returnType.fullyQualifiedName.equals(BigInteger.canonicalName)»
352 «BigInteger.importedName» _constraint = «paramName»;
354 «BigInteger.importedName» _constraint = «BigInteger.importedName».valueOf(«paramName»);
360 def protected generateToString(Collection<GeneratedProperty> properties) '''
361 «IF !properties.empty»
363 public «String.importedName» toString() {
364 «StringBuilder.importedName» builder = new «StringBuilder.importedName»(«type.importedName».class.getSimpleName()).append(" [");
365 boolean first = true;
367 «FOR property : properties»
368 if («property.fieldName» != null) {
372 builder.append(", ");
374 builder.append("«property.fieldName»=");
375 «IF property.returnType.name.contains("[")»
376 builder.append(«Arrays.importedName».toString(«property.fieldName»));
378 builder.append(«property.fieldName»);
382 return builder.append(']').toString();
387 def getRestrictions(Type type) {
388 var Restrictions restrictions = null
389 if (type instanceof ConcreteType) {
390 restrictions = type.restrictions
391 } else if (type instanceof GeneratedTransferObject) {
392 restrictions = type.restrictions
397 def boolean isArrayType(GeneratedTransferObject type) {
399 val GeneratedProperty value = findProperty(type, "value")
400 if (value != null && value.returnType.name.contains("[")) {
406 def String toQuote(Object obj) {
407 return "\"" + obj.toString + "\"";
411 * Template method which generates method parameters with their types from <code>parameters</code>.
414 * list of parameter instances which are transformed to the method parameters
415 * @return string with the list of the method parameters with their types in JAVA format
417 def protected generateParameters(List<MethodSignature.Parameter> parameters) '''«
418 IF !parameters.empty»«
419 FOR parameter : parameters SEPARATOR ", "»«
420 parameter.type.importedName» «parameter.name»«
425 def protected generateLengthMethod(String methodName, Type type, String className, String varName) '''
426 «val Restrictions restrictions = type.restrictions»
427 «IF restrictions != null && !(restrictions.lengthConstraints.empty)»
428 «val numberClass = restrictions.lengthConstraints.iterator.next.min.class»
429 public static «List.importedName»<«Range.importedName»<«numberClass.importedNumber»>> «methodName»() {
430 «IF numberClass.equals(typeof(BigDecimal))»
431 «lengthBody(restrictions, numberClass, className, varName)»
433 «lengthBody(restrictions, typeof(BigInteger), className, varName)»
439 def private lengthBody(Restrictions restrictions, Class<? extends Number> numberClass, String className, String varName) '''
440 if («varName» == null) {
441 synchronized («className».class) {
442 if («varName» == null) {
443 «ImmutableList.importedName».Builder<«Range.importedName»<«numberClass.importedName»>> builder = «ImmutableList.importedName».builder();
444 «FOR r : restrictions.lengthConstraints»
445 builder.add(«Range.importedName».closed(«numericValue(numberClass, r.min)», «numericValue(numberClass, r.max)»));
447 «varName» = builder.build();
454 def protected String importedNumber(Class<? extends Number> clazz) {
455 if (clazz.equals(typeof(BigDecimal))) {
456 return BigDecimal.importedName
458 return BigInteger.importedName
461 def protected String importedNumber(Type clazz) {
462 if (clazz.fullyQualifiedName.equals(BigDecimal.canonicalName)) {
463 return BigDecimal.importedName
465 return BigInteger.importedName
468 def protected String numericValue(Class<? extends Number> clazz, Object numberValue) {
469 val number = clazz.importedName;
470 val value = numberValue.toString
471 if (clazz.equals(typeof(BigInteger)) || clazz.equals(typeof(BigDecimal))) {
472 if (value.equals("0")) {
473 return number + ".ZERO"
474 } else if (value.equals("1")) {
475 return number + ".ONE"
476 } else if (value.equals("10")) {
477 return number + ".TEN"
480 val Long longVal = Long.valueOf(value)
481 return number + ".valueOf(" + longVal + "L)"
482 } catch (NumberFormatException e) {
483 if (clazz.equals(typeof(BigDecimal))) {
485 val Double doubleVal = Double.valueOf(value);
486 return number + ".valueOf(" + doubleVal + ")"
487 } catch (NumberFormatException e2) {
493 return "new " + number + "(\"" + value + "\")"
496 def private GeneratedProperty findProperty(GeneratedTransferObject gto, String name) {
497 val props = gto.properties
499 if (prop.name.equals(name)) {
503 val GeneratedTransferObject parent = gto.superType
504 if (parent != null) {
505 return findProperty(parent, name)