Fix for resolving leafSchemaNode asProperty in BindingGeneratorImpl
[yangtools.git] / code-generator / binding-generator-util / src / main / java / org / opendaylight / yangtools / binding / generator / util / BindingGeneratorUtil.java
index 27961a9065f8f12820aae6ffb06d09ccf2b8e328..41bcb83c71d01d33513d1c26f04602813c31d08f 100644 (file)
@@ -1,37 +1,54 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
 package org.opendaylight.yangtools.binding.generator.util;
 
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-import java.util.Arrays;
-import java.util.HashSet;
+import com.google.common.base.CharMatcher;
+import com.google.common.collect.Iterables;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
 import java.util.List;
-import java.util.Set;
-
+import org.opendaylight.yangtools.sal.binding.model.api.AccessModifier;
+import org.opendaylight.yangtools.sal.binding.model.api.Restrictions;
+import org.opendaylight.yangtools.sal.binding.model.api.Type;
+import org.opendaylight.yangtools.sal.binding.model.api.type.builder.GeneratedPropertyBuilder;
+import org.opendaylight.yangtools.sal.binding.model.api.type.builder.GeneratedTypeBuilderBase;
+import org.opendaylight.yangtools.sal.binding.model.api.type.builder.MethodSignatureBuilder;
+import org.opendaylight.yangtools.sal.binding.model.api.type.builder.TypeMemberBuilder;
+import org.opendaylight.yangtools.yang.binding.BindingMapping;
 import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.QNameModule;
 import org.opendaylight.yangtools.yang.model.api.Module;
 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.type.DecimalTypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.type.IntegerTypeDefinition;
+import org.opendaylight.yangtools.yang.model.api.type.LengthConstraint;
+import org.opendaylight.yangtools.yang.model.api.type.PatternConstraint;
+import org.opendaylight.yangtools.yang.model.api.type.RangeConstraint;
+import org.opendaylight.yangtools.yang.model.api.type.UnsignedIntegerTypeDefinition;
+import org.opendaylight.yangtools.yang.model.util.ExtendedType;
 
 /**
  * Contains the methods for converting strings to valid JAVA language strings
  * (package names, class names, attribute names).
- * 
- * 
+ *
+ *
  */
 public final class BindingGeneratorUtil {
 
-    private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyMMdd");
-    
-    /**
-     * Array of strings values which represents JAVA reserved words.
-     */
-    private static final String[] SET_VALUES = new String[] { "abstract", "assert", "boolean", "break", "byte", "case",
-            "catch", "char", "class", "const", "continue", "default", "double", "do", "else", "enum", "extends",
-            "false", "final", "finally", "float", "for", "goto", "if", "implements", "import", "instanceof", "int",
-            "interface", "long", "native", "new", "null", "package", "private", "protected", "public", "return",
-            "short", "static", "strictfp", "super", "switch", "synchronized", "this", "throw", "throws", "transient",
-            "true", "try", "void", "volatile", "while" };
-
     /**
      * Impossible to instantiate this class. All of the methods or attributes
      * are static.
@@ -40,55 +57,59 @@ public final class BindingGeneratorUtil {
     }
 
     /**
-     * Hash set of words which are reserved in JAVA language.
+     * Pre-compiled replacement pattern.
      */
-    private static final Set<String> JAVA_RESERVED_WORDS = new HashSet<String>(Arrays.asList(SET_VALUES));
+    private static final CharMatcher DOT_MATCHER = CharMatcher.is('.');
+    private static final CharMatcher DASH_COLON_MATCHER = CharMatcher.anyOf("-:");
 
-    /**
-     * Converts string <code>packageName</code> to valid JAVA package name.
-     * 
-     * If some words of package name are digits of JAVA reserved words they are
-     * prefixed with underscore character.
-     * 
-     * @param packageName
-     *            string which contains words separated by point.
-     * @return package name which contains words separated by point.
-     */
-    private static String validateJavaPackage(final String packageName) {
-        if (packageName != null) {
-            final String[] packNameParts = packageName.split("\\.");
-            if (packNameParts != null) {
-                final StringBuilder builder = new StringBuilder();
-                for (int i = 0; i < packNameParts.length; ++i) {
-                    final String packNamePart = packNameParts[i];
-                    if (Character.isDigit(packNamePart.charAt(0))) {
-                        packNameParts[i] = "_" + packNamePart;
-                    } else if (JAVA_RESERVED_WORDS.contains(packNamePart)) {
-                        packNameParts[i] = "_" + packNamePart;
-                    }
-                    if (i > 0) {
-                        builder.append(".");
-                    }
-                    builder.append(packNameParts[i]);
-                }
-                return builder.toString();
-            }
+    private static final Restrictions EMPTY_RESTRICTIONS = new Restrictions() {
+        @Override
+        public List<LengthConstraint> getLengthConstraints() {
+            return Collections.emptyList();
         }
-        return packageName;
-    }
+
+        @Override
+        public List<PatternConstraint> getPatternConstraints() {
+            return Collections.emptyList();
+        }
+
+        @Override
+        public List<RangeConstraint> getRangeConstraints() {
+            return Collections.emptyList();
+        }
+
+        @Override
+        public boolean isEmpty() {
+            return true;
+        }
+    };
+
+    private static final Comparator<TypeMemberBuilder<?>> SUID_MEMBER_COMPARATOR = new Comparator<TypeMemberBuilder<?>>() {
+        @Override
+        public int compare(final TypeMemberBuilder<?> o1, final TypeMemberBuilder<?> o2) {
+            return o1.getName().compareTo(o2.getName());
+        }
+    };
+
+    private static final Comparator<Type> SUID_NAME_COMPARATOR = new Comparator<Type>() {
+        @Override
+        public int compare(final Type o1, final Type o2) {
+            return o1.getFullyQualifiedName().compareTo(o2.getFullyQualifiedName());
+        }
+    };
 
     /**
      * Converts <code>parameterName</code> to valid JAVA parameter name.
-     * 
+     *
      * If the <code>parameterName</code> is one of the JAVA reserved words then
      * it is prefixed with underscore character.
-     * 
+     *
      * @param parameterName
      *            string with the parameter name
      * @return string with the admissible parameter name
      */
-    public static String validateParameterName(final String parameterName) {
-        if (parameterName != null && JAVA_RESERVED_WORDS.contains(parameterName)) {
+    public static String resolveJavaReservedWordEquivalency(final String parameterName) {
+        if (parameterName != null && BindingMapping.JAVA_RESERVED_WORDS.contains(parameterName)) {
             return "_" + parameterName;
         }
         return parameterName;
@@ -96,7 +117,7 @@ public final class BindingGeneratorUtil {
 
     /**
      * Converts module name to valid JAVA package name.
-     * 
+     *
      * The package name consists of:
      * <ul>
      * <li>prefix - <i>org.opendaylight.yang.gen.v</i></li>
@@ -105,54 +126,32 @@ public final class BindingGeneratorUtil {
      * <li>revision prefix - <i>.rev</i></li>
      * <li>revision - YYYYMMDD (MM and DD aren't spread to the whole length)</li>
      * </ul>
-     * 
+     *
      * @param module
      *            module which contains data about namespace and revision date
      * @return string with the valid JAVA package name
      * @throws IllegalArgumentException
      *             if the revision date of the <code>module</code> equals
      *             <code>null</code>
+     * @deprecated USe {@link BindingMapping#getRootPackageName(QNameModule)} with {@link Module#getQNameModule()}.
      */
+    @Deprecated
     public static String moduleNamespaceToPackageName(final Module module) {
-        final StringBuilder packageNameBuilder = new StringBuilder();
+        return BindingMapping.getRootPackageName(module.getQNameModule());
+    }
 
-        if (module.getRevision() == null) {
-            throw new IllegalArgumentException("Module " + module.getName() + " does not specify revision date!");
-        }
-        packageNameBuilder.append("org.opendaylight.yang.gen.v");
-        packageNameBuilder.append(module.getYangVersion());
-        packageNameBuilder.append(".");
-
-        String namespace = module.getNamespace().toString();
-        namespace = namespace.replace("://", ".");
-        namespace = namespace.replace("/", ".");
-        namespace = namespace.replace(":", ".");
-        namespace = namespace.replace("-", ".");
-        namespace = namespace.replace("@", ".");
-        namespace = namespace.replace("$", ".");
-        namespace = namespace.replace("#", ".");
-        namespace = namespace.replace("'", ".");
-        namespace = namespace.replace("*", ".");
-        namespace = namespace.replace("+", ".");
-        namespace = namespace.replace(",", ".");
-        namespace = namespace.replace(";", ".");
-        namespace = namespace.replace("=", ".");
-
-        packageNameBuilder.append(namespace);
-        packageNameBuilder.append(".rev");
-        packageNameBuilder.append(DATE_FORMAT.format(module.getRevision()));
-        
-        return validateJavaPackage(packageNameBuilder.toString());
+    public static String packageNameForGeneratedType(final String basePackageName, final SchemaPath schemaPath) {
+        return packageNameForGeneratedType(basePackageName, schemaPath, false);
     }
 
     /**
      * Creates package name from specified <code>basePackageName</code> (package
      * name for module) and <code>schemaPath</code>.
-     * 
+     *
      * Resulting package name is concatenation of <code>basePackageName</code>
      * and all local names of YANG nodes which are parents of some node for
      * which <code>schemaPath</code> is specified.
-     * 
+     *
      * @param basePackageName
      *            string with package name of the module
      * @param schemaPath
@@ -160,7 +159,8 @@ public final class BindingGeneratorUtil {
      *            name of this node
      * @return string with valid JAVA package name
      */
-    public static String packageNameForGeneratedType(final String basePackageName, final SchemaPath schemaPath) {
+    public static String packageNameForGeneratedType(final String basePackageName, final SchemaPath schemaPath,
+            final boolean isUsesAugment) {
         if (basePackageName == null) {
             throw new IllegalArgumentException("Base Package Name cannot be NULL!");
         }
@@ -170,23 +170,28 @@ public final class BindingGeneratorUtil {
 
         final StringBuilder builder = new StringBuilder();
         builder.append(basePackageName);
-        final List<QName> pathToNode = schemaPath.getPath();
-        final int traversalSteps = (pathToNode.size() - 1);
+        final Iterable<QName> iterable = schemaPath.getPathFromRoot();
+        final Iterator<QName> iterator = iterable.iterator();
+        int size = Iterables.size(iterable);
+        final int traversalSteps;
+        if (isUsesAugment) {
+            traversalSteps = size;
+        } else {
+            traversalSteps = size - 1;
+        }
         for (int i = 0; i < traversalSteps; ++i) {
-            builder.append(".");
-            String nodeLocalName = pathToNode.get(i).getLocalName();
-
-            nodeLocalName = nodeLocalName.replace(":", ".");
-            nodeLocalName = nodeLocalName.replace("-", ".");
-            builder.append(nodeLocalName);
+            builder.append('.');
+            String nodeLocalName = iterator.next().getLocalName();
+            // FIXME: Collon ":" is invalid in node local name as per RFC6020, identifier statement.
+            builder.append(DASH_COLON_MATCHER.replaceFrom(nodeLocalName, '.'));
         }
-        return validateJavaPackage(builder.toString());
+        return BindingMapping.normalizePackageName(builder.toString());
     }
 
     /**
      * Generates the package name for type definition from
      * <code>typeDefinition</code> and <code>basePackageName</code>.
-     * 
+     *
      * @param basePackageName
      *            string with the package name of the module
      * @param typeDefinition
@@ -209,75 +214,51 @@ public final class BindingGeneratorUtil {
 
         final StringBuilder builder = new StringBuilder();
         builder.append(basePackageName);
-        return validateJavaPackage(builder.toString());
+        return BindingMapping.normalizePackageName(builder.toString());
     }
 
     /**
      * Converts <code>token</code> to string which is in accordance with best
      * practices for JAVA class names.
-     * 
+     *
      * @param token
      *            string which contains characters which should be converted to
      *            JAVA class name
      * @return string which is in accordance with best practices for JAVA class
      *         name.
+     *
+     * @deprecated Use {@link BindingMapping#getClassName(QName)} instead.
      */
-    public static String parseToClassName(String token) {
-        String correctStr = token.replace(".", "");
-        correctStr = parseToCamelCase(correctStr);
-
-        // make first char upper-case
-        char first = Character.toUpperCase(correctStr.charAt(0));
-        if (first >= '0' && first <= '9') {
-
-            correctStr = "_" + correctStr;
-        } else {
-            correctStr = first + correctStr.substring(1);
-        }
-        return correctStr;
+    @Deprecated
+    public static String parseToClassName(final String token) {
+        return parseToCamelCase(token, true);
     }
 
     /**
      * Converts <code>token</code> to string which is in accordance with best
      * practices for JAVA parameter names.
-     * 
+     *
      * @param token
      *            string which contains characters which should be converted to
      *            JAVA parameter name
      * @return string which is in accordance with best practices for JAVA
      *         parameter name.
+     *
+     * @deprecated Use {@link BindingMapping#getPropertyName(String)} instead.
      */
-    public static String parseToValidParamName(final String token) {
-        final String validToken = token.replace(".", "");
-        String correctStr = parseToCamelCase(validToken);
-
-        // make first char lower-case
-        char first = Character.toLowerCase(correctStr.charAt(0));
-        correctStr = first + correctStr.substring(1);
-        return validateParameterName(correctStr);
-    }
-
-    /**
-     * Converts <code>token</code> to capital letters and removes invalid
-     * characters.
-     * 
-     * @param token
-     *            string with characters which should be conversed to capital
-     * @return string with capital letters
-     */
-    public static String convertToCapitalLetters(final String token) {
-        String convertedStr = token.replace(" ", "_");
-        convertedStr = convertedStr.replace(".", "_");
-        convertedStr = convertedStr.toUpperCase();
-        return convertedStr;
+    @Deprecated public static String parseToValidParamName(final String token) {
+        return resolveJavaReservedWordEquivalency(parseToCamelCase(token, false));
     }
 
     /**
-     * 
+     *
      * Converts string <code>token</code> to the cammel case format.
-     * 
+     *
      * @param token
      *            string which should be converted to the cammel case format
+     * @param uppercase
+     *            boolean value which says whether the first character of the
+     *            <code>token</code> should|shuldn't be uppercased
      * @return string in the cammel case format
      * @throws IllegalArgumentException
      *             <ul>
@@ -285,27 +266,36 @@ public final class BindingGeneratorUtil {
      *             <li>if <code>token</code> equals null</li>
      *             </ul>
      */
-    private static String parseToCamelCase(String token) {
+
+    private static String parseToCamelCase(final String token, final boolean uppercase) {
         if (token == null) {
             throw new IllegalArgumentException("Name can not be null");
         }
 
-        String correctStr = token.trim();
+        String correctStr = DOT_MATCHER.removeFrom(token.trim());
         if (correctStr.isEmpty()) {
-            throw new IllegalArgumentException("Name can not be emty");
+            throw new IllegalArgumentException("Name can not be empty");
         }
 
         correctStr = replaceWithCamelCase(correctStr, ' ');
         correctStr = replaceWithCamelCase(correctStr, '-');
         correctStr = replaceWithCamelCase(correctStr, '_');
-        return correctStr;
+
+        char firstChar = correctStr.charAt(0);
+        firstChar = uppercase ? Character.toUpperCase(firstChar) : Character.toLowerCase(firstChar);
+
+        if (firstChar >= '0' && firstChar <= '9') {
+            return correctStr = '_' + correctStr;
+        } else {
+            return correctStr = firstChar + correctStr.substring(1);
+        }
     }
 
     /**
-     * Replaces all the occurances of the <code>removalChar</code> in the
+     * Replaces all the occurrences of the <code>removalChar</code> in the
      * <code>text</code> with empty string and converts following character to
      * upper case.
-     * 
+     *
      * @param text
      *            string with source text which should be converted
      * @param removalChar
@@ -315,22 +305,128 @@ public final class BindingGeneratorUtil {
      * @throws IllegalArgumentException
      *             if the length of the returning string has length 0
      */
-    private static String replaceWithCamelCase(String text, char removalChar) {
+    private static String replaceWithCamelCase(final String text, final char removalChar) {
+        int toBeRemovedPos = text.indexOf(removalChar);
+        if (toBeRemovedPos == -1) {
+            return text;
+        }
+
         StringBuilder sb = new StringBuilder(text);
         String toBeRemoved = String.valueOf(removalChar);
-
-        int toBeRemovedPos = sb.indexOf(toBeRemoved);
-        while (toBeRemovedPos != -1) {
+        do {
             sb.replace(toBeRemovedPos, toBeRemovedPos + 1, "");
             // check if 'toBeRemoved' character is not the only character in
             // 'text'
             if (sb.length() == 0) {
                 throw new IllegalArgumentException("The resulting string can not be empty");
             }
-            String replacement = String.valueOf(sb.charAt(toBeRemovedPos)).toUpperCase();
-            sb.setCharAt(toBeRemovedPos, replacement.charAt(0));
+            char replacement = Character.toUpperCase(sb.charAt(toBeRemovedPos));
+            sb.setCharAt(toBeRemovedPos, replacement);
             toBeRemovedPos = sb.indexOf(toBeRemoved);
-        }
+        } while (toBeRemovedPos != -1);
+
         return sb.toString();
     }
+
+    private static <T> Iterable<T> sortedCollection(final Comparator<? super T> comparator, final Collection<T> input) {
+        if (input.size() > 1) {
+            final List<T> ret = new ArrayList<>(input);
+            Collections.sort(ret, comparator);
+            return ret;
+        } else {
+            return input;
+        }
+    }
+
+    public static long computeDefaultSUID(final GeneratedTypeBuilderBase<?> to) {
+        try {
+            ByteArrayOutputStream bout = new ByteArrayOutputStream();
+            DataOutputStream dout = new DataOutputStream(bout);
+
+            dout.writeUTF(to.getName());
+            dout.writeInt(to.isAbstract() ? 3 : 7);
+
+            for (Type ifc : sortedCollection(SUID_NAME_COMPARATOR, to.getImplementsTypes())) {
+                dout.writeUTF(ifc.getFullyQualifiedName());
+            }
+
+            for (GeneratedPropertyBuilder gp : sortedCollection(SUID_MEMBER_COMPARATOR, to.getProperties())) {
+                dout.writeUTF(gp.getName());
+            }
+
+            for (MethodSignatureBuilder m : sortedCollection(SUID_MEMBER_COMPARATOR, to.getMethodDefinitions())) {
+                if (!(m.getAccessModifier().equals(AccessModifier.PRIVATE))) {
+                    dout.writeUTF(m.getName());
+                    dout.write(m.getAccessModifier().ordinal());
+                }
+            }
+
+            dout.flush();
+
+            try {
+                MessageDigest md = MessageDigest.getInstance("SHA");
+                byte[] hashBytes = md.digest(bout.toByteArray());
+                long hash = 0;
+                for (int i = Math.min(hashBytes.length, 8) - 1; i >= 0; i--) {
+                    hash = (hash << 8) | (hashBytes[i] & 0xFF);
+                }
+                return hash;
+            } catch (NoSuchAlgorithmException ex) {
+                throw new SecurityException(ex.getMessage());
+            }
+        } catch (IOException ex) {
+            throw new InternalError();
+        }
+    }
+
+    public static Restrictions getRestrictions(final TypeDefinition<?> type) {
+        // Base types have no constraints, so get over it quickly
+        if (!(type instanceof ExtendedType)) {
+            return EMPTY_RESTRICTIONS;
+        }
+
+        // Take care of the extended types ...
+        final ExtendedType ext = (ExtendedType)type;
+        final List<LengthConstraint> length = ext.getLengthConstraints();
+        final List<PatternConstraint> pattern = ext.getPatternConstraints();
+
+        List<RangeConstraint> tmp = ext.getRangeConstraints();
+        if (tmp.isEmpty()) {
+            final TypeDefinition<?> base = ext.getBaseType();
+            if (base instanceof IntegerTypeDefinition) {
+                tmp = ((IntegerTypeDefinition)base).getRangeConstraints();
+            } else if (base instanceof UnsignedIntegerTypeDefinition) {
+                tmp = ((UnsignedIntegerTypeDefinition)base).getRangeConstraints();
+            } else if (base instanceof DecimalTypeDefinition) {
+                tmp = ((DecimalTypeDefinition)base).getRangeConstraints();
+            }
+        }
+
+        // Now, this may have ended up being empty, too...
+        if (length.isEmpty() && pattern.isEmpty() && tmp.isEmpty()) {
+            return EMPTY_RESTRICTIONS;
+        }
+
+        // Nope, not empty allocate a holder
+        final List<RangeConstraint> range = tmp;
+        return new Restrictions() {
+            @Override
+            public List<RangeConstraint> getRangeConstraints() {
+                return range;
+            }
+            @Override
+            public List<PatternConstraint> getPatternConstraints() {
+                return pattern;
+            }
+            @Override
+            public List<LengthConstraint> getLengthConstraints() {
+                return length;
+            }
+            @Override
+            public boolean isEmpty() {
+                return false;
+            }
+        };
+    }
+
 }