Make sure we do not generate trailing whitespace
[yangtools.git] / code-generator / binding-java-api-generator / src / main / java / org / opendaylight / yangtools / sal / java / api / generator / BaseTemplate.xtend
index eb7b948c545283c6e7e3d162a202574365eb0723..d98b52fc471bd2b6ac4f152cae09be14d1dc6a20 100644 (file)
@@ -7,6 +7,8 @@
  */
 package org.opendaylight.yangtools.sal.java.api.generator
 
+import com.google.common.base.CharMatcher
+import com.google.common.base.Splitter
 import com.google.common.collect.ImmutableList
 import com.google.common.collect.Range
 import java.math.BigDecimal
@@ -17,6 +19,7 @@ import java.util.HashMap
 import java.util.List
 import java.util.Map
 import java.util.StringTokenizer
+import java.util.regex.Pattern
 import org.opendaylight.yangtools.binding.generator.util.Types
 import org.opendaylight.yangtools.sal.binding.model.api.ConcreteType
 import org.opendaylight.yangtools.sal.binding.model.api.GeneratedProperty
@@ -25,13 +28,16 @@ import org.opendaylight.yangtools.sal.binding.model.api.GeneratedType
 import org.opendaylight.yangtools.sal.binding.model.api.MethodSignature
 import org.opendaylight.yangtools.sal.binding.model.api.Restrictions
 import org.opendaylight.yangtools.sal.binding.model.api.Type
-import org.opendaylight.yangtools.yang.common.QName
 
 abstract class BaseTemplate {
     protected val GeneratedType type;
     protected val Map<String, String> importMap;
 
-    private static final String NEW_LINE = '\n'
+    private static final char NEW_LINE = '\n'
+    private static final CharMatcher NL_MATCHER = CharMatcher.is(NEW_LINE)
+    private static final CharMatcher TAB_MATCHER = CharMatcher.is('\t')
+    private static final Pattern SPACES_PATTERN = Pattern.compile(" +")
+    private static final Splitter NL_SPLITTER = Splitter.on(NL_MATCHER)
 
     new(GeneratedType _type) {
         if (_type == null) {
@@ -57,10 +63,10 @@ abstract class BaseTemplate {
         '''.toString
     }
 
-    protected def imports() ''' 
+    protected def imports() '''
         «IF !importMap.empty»
             «FOR entry : importMap.entrySet»
-                «IF entry.value != fullyQualifiedName»
+                «IF !hasSamePackage(entry.value)»
                     import «entry.value».«entry.key»;
                 «ENDIF»
             «ENDFOR»
@@ -68,6 +74,17 @@ abstract class BaseTemplate {
 
     '''
 
+    /**
+     * Checks if packages of generated type and imported type is the same
+     *
+     * @param importedTypePackageNam
+     * the package name of imported type
+     * @return true if the packages are the same false otherwise
+     */
+    final private def boolean hasSamePackage(String importedTypePackageName) {
+        return type.packageName.equals(importedTypePackageName);
+    }
+
     protected abstract def CharSequence body();
 
     // Helper patterns
@@ -87,15 +104,19 @@ abstract class BaseTemplate {
 
     /**
      * Template method which generates the getter method for <code>field</code>
-     * 
-     * @param field 
+     *
+     * @param field
      * generated property with data about field which is generated as the getter method
-     * @return string with the getter method source code in JAVA format 
+     * @return string with the getter method source code in JAVA format
      */
     final protected def getterMethod(GeneratedProperty field) {
         '''
             public «field.returnType.importedName» «field.getterMethodName»() {
+                «IF field.returnType.importedName.contains("[]")»
+                return «field.fieldName» == null ? null : «field.fieldName».clone();
+                «ELSE»
                 return «field.fieldName»;
+                «ENDIF»
             }
         '''
     }
@@ -107,10 +128,10 @@ abstract class BaseTemplate {
 
     /**
      * Template method which generates the setter method for <code>field</code>
-     * 
-     * @param field 
+     *
+     * @param field
      * generated property with data about field which is generated as the setter method
-     * @return string with the setter method source code in JAVA format 
+     * @return string with the setter method source code in JAVA format
      */
     final protected def setterMethod(GeneratedProperty field) '''
         «val returnType = field.returnType.importedName»
@@ -131,7 +152,7 @@ abstract class BaseTemplate {
 
     /**
      * Template method which generates method parameters with their types from <code>parameters</code>.
-     * 
+     *
      * @param parameters
      * group of generated property instances which are transformed to the method parameters
      * @return string with the list of the method parameters with their types in JAVA format
@@ -141,26 +162,24 @@ abstract class BaseTemplate {
 
     /**
      * Template method which generates sequence of the names of the class attributes from <code>parameters</code>.
-     * 
-     * @param parameters 
+     *
+     * @param parameters
      * group of generated property instances which are transformed to the sequence of parameter names
-     * @return string with the list of the parameter names of the <code>parameters</code> 
+     * @return string with the list of the parameter names of the <code>parameters</code>
      */
     def final protected asArguments(Iterable<GeneratedProperty> parameters) '''«IF !parameters.empty»«FOR parameter : parameters SEPARATOR ", "»«parameter.
         fieldName»«ENDFOR»«ENDIF»'''
 
     /**
      * Template method which generates JAVA comments.
-     * 
+     *
      * @param comment string with the comment for whole JAVA class
      * @return string with comment in JAVA format
      */
     def protected CharSequence asJavadoc(String comment) {
         if(comment == null) return ''
         var txt = comment
-        if (txt.contains("*/")) {
-            txt = txt.replace("*/", "&#42;&#47;")
-        }
+
         txt = comment.trim
         txt = formatToParagraph(txt)
 
@@ -170,18 +189,18 @@ abstract class BaseTemplate {
     }
 
     def String wrapToDocumentation(String text) {
-        val StringTokenizer tokenizer = new StringTokenizer(text, "\n", false)
-        val StringBuilder sb = new StringBuilder()
-
-        if(text.empty)
+        if (text.empty)
             return ""
 
-        sb.append("/**")
+        val StringBuilder sb = new StringBuilder("/**")
         sb.append(NEW_LINE)
 
-        while(tokenizer.hasMoreTokens) {
-            sb.append(" * ")
-            sb.append(tokenizer.nextToken)
+        for (String t : NL_SPLITTER.split(text)) {
+            sb.append(" *")
+            if (!t.isEmpty()) {
+                sb.append(' ');
+                sb.append(t)
+            }
             sb.append(NEW_LINE)
         }
         sb.append(" */")
@@ -190,68 +209,48 @@ abstract class BaseTemplate {
     }
 
     def protected String formatDataForJavaDoc(GeneratedType type) {
-        val typeDescription = type.description
-        val typeReference = type.reference
-        val typeModuleName = type.moduleName
-        val typeSchemaPath = type.schemaPath
+        val typeDescription = type.getDescription().encodeJavadocSymbols;
 
         return '''
-            «IF !type.isDocumentationParametersNullOrEmtpy»
-               «IF typeDescription != null && !typeDescription.empty»
-                «formatToParagraph(typeDescription)»
-               «ENDIF»
-               «IF typeReference != null && !typeReference.empty»
-                Reference:
-                    «formatReference(typeReference)»
-               «ENDIF»
-               «IF typeModuleName != null && !typeModuleName.empty»
-                Module name:
-                    «typeModuleName»
-               «ENDIF»
-               «IF typeSchemaPath != null && !typeSchemaPath.empty»
-                Schema path:
-                    «formatPath(typeSchemaPath)»
-               «ENDIF»
+            «IF !typeDescription.nullOrEmpty»
+            «typeDescription»
             «ENDIF»
         '''.toString
     }
 
-    def formatPath(Iterable<QName> schemaPath) {
-        var currentElement = schemaPath.head
-        val StringBuilder sb = new StringBuilder()
-        sb.append('[')
-        sb.append(currentElement)
-
-        for(pathElement : schemaPath) {
-            if(!currentElement.namespace.equals(pathElement.namespace)) {
-                currentElement = pathElement
-                sb.append('/')
-                sb.append(pathElement)
-            }
-            else {
-                sb.append('/')
-                sb.append(pathElement.localName)
-            }
+    private static final CharMatcher AMP_MATCHER = CharMatcher.is('&');
+    private static final CharMatcher GT_MATCHER = CharMatcher.is('>');
+    private static final CharMatcher LT_MATCHER = CharMatcher.is('<');
+
+    def encodeJavadocSymbols(String description) {
+        if (description.nullOrEmpty) {
+            return description;
         }
-        sb.append(']')
-        return sb.toString
-    }
 
-    def formatReference(String reference) {
-        if(reference == null || reference.isEmpty)
-            return reference
+        var ret = description.replace("*/", "&#42;&#47;")
 
-        val StringTokenizer tokenizer = new StringTokenizer(reference, " ", true)
-        val StringBuilder sb = new StringBuilder();
+        // FIXME: Use Guava's HtmlEscapers once we have it available
+        ret = AMP_MATCHER.replaceFrom(ret, "&amp;");
+        ret = GT_MATCHER.replaceFrom(ret, "&gt;");
+        ret = LT_MATCHER.replaceFrom(ret, "&lt;");
+        return ret;
+    }
 
-        while(tokenizer.hasMoreTokens) {
-            var String oneElement = tokenizer.nextToken
-            if (oneElement.contains("http://")) {
-                oneElement = asLink(oneElement)
-            }
-            sb.append(oneElement)
+    def protected String formatDataForJavaDoc(GeneratedType type, String additionalComment) {
+        val StringBuilder typeDescription = new StringBuilder();
+        if (!type.description.nullOrEmpty) {
+            typeDescription.append(type.description)
+            typeDescription.append(NEW_LINE)
+            typeDescription.append(NEW_LINE)
+            typeDescription.append(NEW_LINE)
+            typeDescription.append(additionalComment)
+        } else {
+            typeDescription.append(additionalComment)
         }
-        return sb.toString
+
+        return '''
+            «typeDescription.toString»
+        '''.toString
     }
 
     def asLink(String text) {
@@ -260,7 +259,7 @@ abstract class BaseTemplate {
         var char lastChar = ' '
         var boolean badEnding = false
 
-        if(text.endsWith(".") || text.endsWith(":") || text.endsWith(",")) {
+        if (text.endsWith('.') || text.endsWith(':') || text.endsWith(',')) {
             tempText = text.substring(0, text.length - 1)
             lastChar = text.charAt(text.length - 1)
             badEnding = true
@@ -286,10 +285,10 @@ abstract class BaseTemplate {
         var StringBuilder lineBuilder = new StringBuilder();
         var boolean isFirstElementOnNewLineEmptyChar = false;
 
-        formattedText = formattedText.replace("*/", "&#42;&#47;")
-        formattedText = formattedText.replace(NEW_LINE, "")
-        formattedText = formattedText.replace("\t", "")
-        formattedText = formattedText.replaceAll(" +", " ");
+        formattedText = formattedText.encodeJavadocSymbols
+        formattedText = NL_MATCHER.removeFrom(formattedText)
+        formattedText = TAB_MATCHER.removeFrom(formattedText)
+        formattedText = SPACES_PATTERN.matcher(formattedText).replaceAll(" ")
 
         val StringTokenizer tokenizer = new StringTokenizer(formattedText, " ", true);
 
@@ -310,8 +309,9 @@ abstract class BaseTemplate {
                 lineBuilder.setLength(0)
                 sb.append(NEW_LINE)
 
-                if(nextElement.toString == ' ')
+                if(nextElement.toString == ' ') {
                     isFirstElementOnNewLineEmptyChar = !isFirstElementOnNewLineEmptyChar;
+                }
             }
 
             if(isFirstElementOnNewLineEmptyChar) {
@@ -328,32 +328,6 @@ abstract class BaseTemplate {
         return sb.toString
     }
 
-    def isDocumentationParametersNullOrEmtpy(GeneratedType type) {
-        var boolean isNullOrEmpty = true
-        val String typeDescription = type.description
-        val String typeReference = type.reference
-        val String typeModuleName = type.moduleName
-        val Iterable<QName> typeSchemaPath = type.schemaPath
-
-        if(typeDescription != null && !typeDescription.empty) {
-            isNullOrEmpty = false
-            return isNullOrEmpty
-        }
-        if(typeReference != null && !typeReference.empty) {
-            isNullOrEmpty = false
-            return isNullOrEmpty
-        }
-        if(typeModuleName != null && !typeModuleName.empty) {
-            isNullOrEmpty = false
-            return isNullOrEmpty
-        }
-        if(typeSchemaPath != null && !typeSchemaPath.empty) {
-            isNullOrEmpty = false
-            return isNullOrEmpty
-        }
-        return isNullOrEmpty
-    }
-
     def generateRestrictions(Type type, String paramName, Type returnType) '''
         «val restrictions = type.getRestrictions»
         «IF restrictions !== null»
@@ -431,7 +405,7 @@ abstract class BaseTemplate {
         «IF !properties.empty»
             @Override
             public «String.importedName» toString() {
-                «StringBuilder.importedName» builder = new «StringBuilder.importedName»("«type.name» [");
+                «StringBuilder.importedName» builder = new «StringBuilder.importedName»(«type.importedName».class.getSimpleName()).append(" [");
                 boolean first = true;
 
                 «FOR property : properties»
@@ -457,9 +431,9 @@ abstract class BaseTemplate {
     def getRestrictions(Type type) {
         var Restrictions restrictions = null
         if (type instanceof ConcreteType) {
-            restrictions = (type as ConcreteType).restrictions
+            restrictions = type.restrictions
         } else if (type instanceof GeneratedTransferObject) {
-            restrictions = (type as GeneratedTransferObject).restrictions
+            restrictions = type.restrictions
         }
         return restrictions
     }
@@ -479,7 +453,7 @@ abstract class BaseTemplate {
 
     /**
      * Template method which generates method parameters with their types from <code>parameters</code>.
-     * 
+     *
      * @param parameters
      * list of parameter instances which are transformed to the method parameters
      * @return string with the list of the method parameters with their types in JAVA format