JavaIdentifierNormalizer ThreadSafe/Memory leak fix
[mdsal.git] / binding2 / mdsal-binding2-generator-util / src / main / java / org / opendaylight / mdsal / binding / javav2 / generator / util / JavaIdentifierNormalizer.java
index 66dd4d327ccaa9dc9a0441f77501e5661e04f1dc..b2cc55207aafde6a3e07e6d6863d1ce07ef1f449 100644 (file)
@@ -8,11 +8,15 @@
 package org.opendaylight.mdsal.binding.javav2.generator.util;
 
 import com.google.common.annotations.Beta;
-import com.google.common.collect.ArrayListMultimap;
+import com.google.common.base.CharMatcher;
+import com.google.common.base.Splitter;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ListMultimap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
+import java.util.stream.Collectors;
+import org.opendaylight.mdsal.binding.javav2.generator.context.ModuleContext;
 import org.opendaylight.mdsal.binding.javav2.model.api.Enumeration;
 import org.opendaylight.mdsal.binding.javav2.model.api.Enumeration.Pair;
 import org.opendaylight.mdsal.binding.javav2.util.BindingMapping;
@@ -194,16 +198,28 @@ import org.opendaylight.mdsal.binding.javav2.util.BindingMapping;
 @Beta
 public final class JavaIdentifierNormalizer {
 
-    private static final int FIRST_CHAR = 0;
-    private static final int FIRST_INDEX = 1;
+    public static final Set<String> SPECIAL_RESERVED_PATHS = ImmutableSet.of(
+        "org.opendaylight.yangtools.concepts",
+        "org.opendaylight.yangtools.yang.common",
+        "org.opendaylight.yangtools.yang.model",
+        "org.opendaylight.mdsal.binding.javav2.spec",
+        "java",
+        "com");
+
     private static final char UNDERSCORE = '_';
     private static final char DASH = '-';
-    private static final String EMPTY_STRING = "";
     private static final String RESERVED_KEYWORD = "reserved_keyword";
-    private static final ListMultimap<String, String> PACKAGES_MAP = ArrayListMultimap.create();
-    public static final Set<String> SPECIAL_RESERVED_PATHS =
-            ImmutableSet.of("org.opendaylight.yangtools.yang.model","org.opendaylight.yangtools.concepts","org.opendaylight.yangtools.yang.common",
-                    "org.opendaylight.mdsal.binding.javav2.spec","java", "com");
+    private static final Set<String> PRIMITIVE_TYPES = ImmutableSet.of("char[]", "byte[]");
+
+    private static final CharMatcher DASH_MATCHER = CharMatcher.is(DASH);
+    private static final CharMatcher DASH_OR_SPACE_MATCHER = CharMatcher.anyOf(" -");
+    private static final CharMatcher UNDERSCORE_MATCHER = CharMatcher.is(UNDERSCORE);
+    private static final Splitter DOT_SPLITTER = Splitter.on('.');
+    private static final Splitter UNDERSCORE_SPLITTER = Splitter.on(UNDERSCORE);
+
+    // Converted to lower case
+    private static final Set<String> WINDOWS_RESERVED_WORDS = BindingMapping.WINDOWS_RESERVED_WORDS.stream()
+            .map(String::toLowerCase).collect(Collectors.collectingAndThen(Collectors.toSet(), ImmutableSet::copyOf));
 
     private JavaIdentifierNormalizer() {
         throw new UnsupportedOperationException("Util class");
@@ -236,7 +252,7 @@ public final class JavaIdentifierNormalizer {
      * @return converted and fixed name of new enum value
      */
     public static String normalizeEnumValueIdentifier(final String name, final List<Pair> values) {
-        return convertIdentifierEnumValue(name, name, values, FIRST_INDEX);
+        return convertIdentifierEnumValue(name, name, values, 1);
     }
 
     /**
@@ -247,15 +263,19 @@ public final class JavaIdentifierNormalizer {
      * @return normalized name
      */
     public static String normalizeFullPackageName(final String fullPackageName) {
-        final String[] packageNameParts = fullPackageName.split("\\.");
-        final StringBuilder sb = new StringBuilder();
-        for (int i = 0; i < packageNameParts.length; i++) {
-            sb.append(normalizePartialPackageName(packageNameParts[i]));
-            if (i != (packageNameParts.length - 1)) {
-                sb.append(".");
+        final Iterator<String> it = DOT_SPLITTER.split(fullPackageName).iterator();
+        if (!it.hasNext()) {
+            return fullPackageName;
+        }
+
+        final StringBuilder sb = new StringBuilder(fullPackageName.length());
+        while (true) {
+            sb.append(normalizePartialPackageName(it.next()));
+            if (!it.hasNext()) {
+                return sb.toString();
             }
+            sb.append('.');
         }
-        return sb.toString();
     }
 
     /**
@@ -265,32 +285,32 @@ public final class JavaIdentifierNormalizer {
      *            - part of package name
      * @return normalized name
      */
-    public static String normalizePartialPackageName(final String packageNamePart) {
+    static String normalizePartialPackageName(final String packageNamePart) {
         // if part of package name consist from java or windows reserved word, return it with
         // underscore at the end and in lower case
-        if (BindingMapping.JAVA_RESERVED_WORDS.contains(packageNamePart.toLowerCase())
-                || BindingMapping.WINDOWS_RESERVED_WORDS.contains(packageNamePart.toUpperCase())) {
-            return new StringBuilder(packageNamePart).append(UNDERSCORE).toString().toLowerCase();
-        }
-        String normalizedPackageNamePart = packageNamePart;
-        if (packageNamePart.contains(String.valueOf(DASH))) {
-            normalizedPackageNamePart = packageNamePart.replaceAll(String.valueOf(DASH), String.valueOf(UNDERSCORE));
+        final String lowerPart = packageNamePart.toLowerCase();
+        if (BindingMapping.JAVA_RESERVED_WORDS.contains(lowerPart) || WINDOWS_RESERVED_WORDS.contains(lowerPart)) {
+            return lowerPart + UNDERSCORE;
         }
+
+        final String normalizedPart = DASH_MATCHER.replaceFrom(packageNamePart, UNDERSCORE);
+
         final StringBuilder sb = new StringBuilder();
-        StringBuilder innserSb = new StringBuilder();
-        for (int i = 0; i < normalizedPackageNamePart.length(); i++) {
-            if (normalizedPackageNamePart.charAt(i) == UNDERSCORE) {
-                if (!innserSb.toString().isEmpty()) {
-                    sb.append(normalizeSpecificIdentifier(innserSb.toString(), JavaIdentifier.PACKAGE));
-                    innserSb = new StringBuilder();
+        final StringBuilder innerSb = new StringBuilder();
+        for (int i = 0; i < normalizedPart.length(); i++) {
+            final char c = normalizedPart.charAt(i);
+            if (c == UNDERSCORE) {
+                if (innerSb.length() != 0) {
+                    sb.append(normalizeSpecificIdentifier(innerSb.toString(), JavaIdentifier.PACKAGE));
+                    innerSb.setLength(0);
                 }
                 sb.append(UNDERSCORE);
             } else {
-                innserSb.append(normalizedPackageNamePart.charAt(i));
+                innerSb.append(c);
             }
         }
-        if (!innserSb.toString().isEmpty()) {
-            sb.append(normalizeSpecificIdentifier(innserSb.toString(), JavaIdentifier.PACKAGE));
+        if (innerSb.length() != 0) {
+            sb.append(normalizeSpecificIdentifier(innerSb.toString(), JavaIdentifier.PACKAGE));
         }
         // returned normalized part of package name
         return sb.toString();
@@ -309,14 +329,30 @@ public final class JavaIdentifierNormalizer {
      *            - name of identifier
      * @return - java acceptable identifier
      */
-    public static String normalizeClassIdentifier(final String packageName, final String className) {
+    static String normalizeClassIdentifier(final String packageName, final String className,
+            ModuleContext context) {
+        if (packageName.isEmpty() && PRIMITIVE_TYPES.contains(className)) {
+            return className;
+        }
         for (final String reservedPath : SPECIAL_RESERVED_PATHS) {
             if (packageName.startsWith(reservedPath)) {
                 return className;
             }
         }
         final String convertedClassName = normalizeSpecificIdentifier(className, JavaIdentifier.CLASS);
-        return normalizeClassIdentifier(packageName, convertedClassName, convertedClassName, FIRST_INDEX);
+
+        // if packageName contains class name at the end, then the className is name of inner class
+        final String basePackageName;
+        final int lastDot = packageName.lastIndexOf('.');
+        if (lastDot != -1 && Character.isUpperCase(packageName.charAt(lastDot + 1))) {
+            // ignore class name in package name - inner class name has to be normalized according to original package
+            // of parent class
+            basePackageName = packageName.substring(0, lastDot);
+        } else {
+            basePackageName = packageName;
+        }
+
+        return normalizeClassIdentifier(basePackageName, convertedClassName, convertedClassName, 1, context);
     }
 
     /**
@@ -330,23 +366,20 @@ public final class JavaIdentifierNormalizer {
      * @return - java acceptable identifier
      */
     public static String normalizeSpecificIdentifier(final String identifier, final JavaIdentifier javaIdentifier) {
-        final StringBuilder sb = new StringBuilder();
-
         // if identifier isn't PACKAGE type then check it by reserved keywords
-        if(javaIdentifier != JavaIdentifier.PACKAGE) {
-            if (BindingMapping.JAVA_RESERVED_WORDS.contains(identifier.toLowerCase())
-                    || BindingMapping.WINDOWS_RESERVED_WORDS.contains(identifier.toUpperCase())) {
-                return fixCasesByJavaType(
-                        sb.append(identifier).append(UNDERSCORE).append(RESERVED_KEYWORD).toString().toLowerCase(),
-                        javaIdentifier);
+        if (javaIdentifier != JavaIdentifier.PACKAGE) {
+            final String lower = identifier.toLowerCase();
+            if (BindingMapping.JAVA_RESERVED_WORDS.contains(lower) || WINDOWS_RESERVED_WORDS.contains(lower)) {
+                return fixCasesByJavaType(lower + UNDERSCORE + RESERVED_KEYWORD, javaIdentifier);
             }
         }
 
         // check and convert first char in identifier if there is non-java char
-        final char firstChar = identifier.charAt(FIRST_CHAR);
+        final StringBuilder sb = new StringBuilder();
+        final char firstChar = identifier.charAt(0);
         if (!Character.isJavaIdentifierStart(firstChar)) {
             // converting first char of identifier
-            sb.append(convertFirst(firstChar, existNext(identifier, FIRST_CHAR)));
+            sb.append(convertFirst(firstChar, existNext(identifier, 0)));
         } else {
             sb.append(firstChar);
         }
@@ -355,18 +388,15 @@ public final class JavaIdentifierNormalizer {
             final char actualChar = identifier.charAt(i);
             // ignore single dash as non java char - if there is more dashes in a row or dash is as
             // the last char in identifier then parse these dashes as non java chars
-            if ((actualChar == '-') && existNext(identifier, i)) {
-                if ((identifier.charAt(i - 1) != DASH) && (identifier.charAt(i + 1) != DASH)) {
+            if (actualChar == DASH && existNext(identifier, i)) {
+                if (identifier.charAt(i - 1) != DASH && identifier.charAt(i + 1) != DASH) {
                     sb.append(UNDERSCORE);
                     continue;
                 }
             }
             if (!Character.isJavaIdentifierPart(actualChar)) {
-                // prepare actual string of sb for checking if underscore exist on position of the
-                // last char
-                final String partialConvertedIdentifier = sb.toString();
-                sb.append(convert(actualChar, existNext(identifier, i),
-                        partialConvertedIdentifier.charAt(partialConvertedIdentifier.length() - 1)));
+                // prepare actual string of sb for checking if underscore exist on position of the last char
+                sb.append(convert(actualChar, existNext(identifier, i), sb.charAt(sb.length() - 1)));
             } else {
                 sb.append(actualChar);
             }
@@ -390,24 +420,28 @@ public final class JavaIdentifierNormalizer {
      * @return converted identifier
      */
     private static String normalizeClassIdentifier(final String packageName, final String origClassName,
-            final String actualClassName, final int rank) {
-        if (PACKAGES_MAP.containsKey(packageName)) {
-            for (final String existingName : PACKAGES_MAP.get(packageName)) {
-                if (existingName.toLowerCase().equals(actualClassName.toLowerCase())) {
-                    final int nextRank = rank + 1;
-                    return normalizeClassIdentifier(packageName, origClassName,
-                            new StringBuilder(origClassName).append(rank).toString(), nextRank);
+            final String actualClassName, final int rank, ModuleContext context) {
+
+        final ListMultimap<String, String> packagesMap = context.getPackagesMap();
+
+        synchronized (packagesMap) {
+            if (packagesMap.containsKey(packageName)) {
+                for (final String existingName : packagesMap.get(packageName)) {
+                    if (actualClassName.equalsIgnoreCase(existingName)) {
+                       return normalizeClassIdentifier(packageName, origClassName, origClassName + rank,
+                     rank + 1, context);
+                    }
                 }
             }
+            context.putToPackagesMap(packageName, actualClassName);
+            return actualClassName;
         }
-        PACKAGES_MAP.put(packageName, actualClassName);
-        return actualClassName;
     }
 
     /**
      * Fix cases of converted identifiers by Java type
      *
-     * @param string
+     * @param convertedIdentifier
      *            - converted identifier
      * @param javaIdentifier
      *            - java type of identifier
@@ -426,7 +460,7 @@ public final class JavaIdentifierNormalizer {
             case VARIABLE:
                 return fixCases(convertedIdentifier);
             case PACKAGE:
-                return convertedIdentifier.replaceAll(String.valueOf(UNDERSCORE), EMPTY_STRING);
+                return UNDERSCORE_MATCHER.removeFrom(convertedIdentifier);
             default:
                 throw new IllegalArgumentException("Unknown java type of identifier : " + javaIdentifier.toString());
         }
@@ -440,19 +474,15 @@ public final class JavaIdentifierNormalizer {
      * @return resolved identifier
      */
     private static String fixCases(final String convertedIdentifier) {
-        final StringBuilder sb = new StringBuilder();
-        if (convertedIdentifier.contains(String.valueOf(UNDERSCORE))) {
-            boolean isFirst = true;
-            for (final String part : convertedIdentifier.split(String.valueOf(UNDERSCORE))) {
-                if (isFirst) {
-                    isFirst = false;
-                    sb.append(part);
-                } else {
-                    sb.append(capitalize(part));
-                }
-            }
-        } else {
-            sb.append(convertedIdentifier);
+        if (convertedIdentifier.indexOf(UNDERSCORE) == -1) {
+            return convertedIdentifier;
+        }
+
+        final StringBuilder sb = new StringBuilder(convertedIdentifier.length());
+        final Iterator<String> it = UNDERSCORE_SPLITTER.split(convertedIdentifier).iterator();
+        sb.append(it.next());
+        while (it.hasNext()) {
+            sb.append(capitalize(it.next()));
         }
         return sb.toString();
     }
@@ -467,7 +497,7 @@ public final class JavaIdentifierNormalizer {
      * @return true if there is another char, false otherwise
      */
     private static boolean existNext(final String identifier, final int actual) {
-      return (identifier.length() - 1) < (actual + 1) ? false : true;
+        return identifier.length() > actual + 1;
     }
 
     /**
@@ -481,12 +511,8 @@ public final class JavaIdentifierNormalizer {
      * @return converted char
      */
     private static String convertFirst(final char c, final boolean existNext) {
-        String name = Character.getName(c);
-        if (name.contains(String.valueOf(DASH))) {
-            name = name.replaceAll(String.valueOf(DASH), String.valueOf(UNDERSCORE));
-        }
-        name = existNext ? (name + "_") : name;
-        return name.contains(" ") ? name.replaceAll(" ", "_") : name;
+        final String name = DASH_OR_SPACE_MATCHER.replaceFrom(Character.getName(c), UNDERSCORE);
+        return existNext ? name + '_' : name;
     }
 
     /**
@@ -512,19 +538,17 @@ public final class JavaIdentifierNormalizer {
      *            - string to be capitalized
      */
     private static String capitalize(final String identifier) {
-        return identifier.substring(FIRST_CHAR, FIRST_CHAR + 1).toUpperCase() + identifier.substring(1);
+        return identifier.substring(0, 1).toUpperCase() + identifier.substring(1);
     }
 
     private static String convertIdentifierEnumValue(final String name, final String origName, final List<Pair> values,
             final int rank) {
         String newName = name;
         for (final Pair pair : values) {
-            if (pair.getName().toLowerCase().equals(name.toLowerCase())
-                    || pair.getMappedName().toLowerCase().equals(name.toLowerCase())) {
+            if (name.equalsIgnoreCase(pair.getName()) || name.equalsIgnoreCase(pair.getMappedName())) {
                 int actualRank = rank;
-                final StringBuilder actualNameBuilder =
-                        new StringBuilder(origName).append(UNDERSCORE).append(actualRank);
-                newName = convertIdentifierEnumValue(actualNameBuilder.toString(), origName, values, ++actualRank);
+                final String actualName = origName + UNDERSCORE + actualRank;
+                newName = convertIdentifierEnumValue(actualName, origName, values, ++actualRank);
             }
         }
         return normalizeSpecificIdentifier(newName, JavaIdentifier.ENUM_VALUE);