Map identities to proper objects
[mdsal.git] / binding / mdsal-binding-spec-util / src / main / java / org / opendaylight / mdsal / binding / spec / naming / BindingMapping.java
index de0037a3a8ca1b972f19676fb491ecd2f3a27cc8..57dae3eac9dfb31eb391fca8f223177ee4f84857 100644 (file)
@@ -8,6 +8,7 @@
 package org.opendaylight.mdsal.binding.spec.naming;
 
 import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.Objects.requireNonNull;
 
 import com.google.common.annotations.Beta;
 import com.google.common.base.CharMatcher;
@@ -20,12 +21,13 @@ import com.google.common.collect.Interners;
 import java.util.Collection;
 import java.util.Locale;
 import java.util.Optional;
-import java.util.Set;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.yangtools.yang.binding.Augmentable;
-import org.opendaylight.yangtools.yang.binding.DataContainer;
+import org.opendaylight.yangtools.yang.binding.BindingContract;
 import org.opendaylight.yangtools.yang.binding.Identifiable;
+import org.opendaylight.yangtools.yang.binding.ScalarTypeObject;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.common.QNameModule;
 import org.opendaylight.yangtools.yang.common.Revision;
@@ -33,26 +35,38 @@ import org.opendaylight.yangtools.yang.common.Revision;
 @Beta
 public final class BindingMapping {
 
-    public static final String VERSION = "0.6";
+    public static final @NonNull String VERSION = "0.6";
 
-    public static final Set<String> JAVA_RESERVED_WORDS = ImmutableSet.of(
-        // https://docs.oracle.com/javase/specs/jls/se9/html/jls-3.html#jls-3.9
+    // Note: these are not just JLS keywords, but rather character sequences which are reserved in codegen contexts
+    public static final ImmutableSet<String> JAVA_RESERVED_WORDS = ImmutableSet.of(
+        // https://docs.oracle.com/javase/specs/jls/se9/html/jls-3.html#jls-3.9 except module-info.java constructs
         "abstract", "assert", "boolean", "break", "byte", "case", "catch", "char", "class", "const", "continue",
         "default", "do", "double", "else", "enum", "extends", "final", "finally", "float", "for", "goto", "if",
         "implements", "import", "instanceof", "int", "interface", "long", "native", "new", "package", "private",
         "protected", "public", "return", "short", "static", "strictfp", "super", "switch", "synchronized", "this",
         "throw", "throws", "transient", "try", "void", "volatile", "while", "_",
+        // "open", "module", "requires", "transitive", "exports, "opens", "to", "uses", "provides", "with",
+
         // https://docs.oracle.com/javase/specs/jls/se9/html/jls-3.html#jls-3.10.3
         "false", "true",
         // https://docs.oracle.com/javase/specs/jls/se9/html/jls-3.html#jls-3.10.7
-        "null");
-
-    public static final String DATA_ROOT_SUFFIX = "Data";
-    public static final String RPC_SERVICE_SUFFIX = "Service";
-    public static final String NOTIFICATION_LISTENER_SUFFIX = "Listener";
-    public static final String QNAME_STATIC_FIELD_NAME = "QNAME";
-    public static final String PACKAGE_PREFIX = "org.opendaylight.yang.gen.v1";
-    public static final String AUGMENTATION_FIELD = "augmentation";
+        "null",
+        // https://docs.oracle.com/javase/specs/jls/se10/html/jls-3.html#jls-3.9
+        "var",
+        // https://docs.oracle.com/javase/specs/jls/se14/html/jls-3.html#jls-3.9
+        "yield",
+        // https://docs.oracle.com/javase/specs/jls/se16/html/jls-3.html#jls-3.9
+        "record");
+
+    public static final @NonNull String DATA_ROOT_SUFFIX = "Data";
+    public static final @NonNull String RPC_SERVICE_SUFFIX = "Service";
+    public static final @NonNull String NOTIFICATION_LISTENER_SUFFIX = "Listener";
+    public static final @NonNull String BUILDER_SUFFIX = "Builder";
+    public static final @NonNull String KEY_SUFFIX = "Key";
+    public static final @NonNull String QNAME_STATIC_FIELD_NAME = "QNAME";
+    public static final @NonNull String VALUE_STATIC_FIELD_NAME = "VALUE";
+    public static final @NonNull String PACKAGE_PREFIX = "org.opendaylight.yang.gen.v1";
+    public static final @NonNull String AUGMENTATION_FIELD = "augmentation";
 
     private static final Splitter CAMEL_SPLITTER = Splitter.on(CharMatcher.anyOf(" _.-/").precomputed())
             .omitEmptyStrings().trimResults();
@@ -60,67 +74,78 @@ public final class BindingMapping {
     private static final String QUOTED_DOT = Matcher.quoteReplacement(".");
     private static final Splitter DOT_SPLITTER = Splitter.on('.');
 
-    public static final String MODULE_INFO_CLASS_NAME = "$YangModuleInfoImpl";
-    public static final String MODULE_INFO_QNAMEOF_METHOD_NAME = "qnameOf";
-    public static final String MODEL_BINDING_PROVIDER_CLASS_NAME = "$YangModelBindingProvider";
+    public static final @NonNull String MODULE_INFO_CLASS_NAME = "$YangModuleInfoImpl";
+    public static final @NonNull String MODULE_INFO_QNAMEOF_METHOD_NAME = "qnameOf";
+    public static final @NonNull String MODEL_BINDING_PROVIDER_CLASS_NAME = "$YangModelBindingProvider";
 
     /**
      * Name of {@link Augmentable#augmentation(Class)}.
      */
-    public static final String AUGMENTABLE_AUGMENTATION_NAME = "augmentation";
+    public static final @NonNull String AUGMENTABLE_AUGMENTATION_NAME = "augmentation";
 
     /**
      * Name of {@link Identifiable#key()}.
      */
-    public static final String IDENTIFIABLE_KEY_NAME = "key";
+    public static final @NonNull String IDENTIFIABLE_KEY_NAME = "key";
+
+    /**
+     * Name of {@link BindingContract#implementedInterface()}.
+     */
+    public static final @NonNull String BINDING_CONTRACT_IMPLEMENTED_INTERFACE_NAME = "implementedInterface";
+
+    /**
+     * Name of default {@link Object#hashCode()} implementation for instantiated DataObjects. Each such generated
+     * interface contains this static method.
+     */
+    public static final @NonNull String BINDING_HASHCODE_NAME = "bindingHashCode";
 
     /**
-     * Name of {@link DataContainer#getImplementedInterface()}.
+     * Name of default {@link Object#equals(Object)} implementation for instantiated DataObjects. Each such generated
+     * interface contains this static method.
      */
-    // FIXME: 4.0.0: remove this constant
-    public static final String DATA_CONTAINER_GET_IMPLEMENTED_INTERFACE_NAME = "getImplementedInterface";
+    public static final @NonNull String BINDING_EQUALS_NAME = "bindingEquals";
 
     /**
-     * Name of {@link DataContainer#implementedInterface()}.
+     * Name of default {@link Object#toString()} implementation for instantiated DataObjects. Each such generated
+     * interface contains this static method.
      */
-    public static final String DATA_CONTAINER_IMPLEMENTED_INTERFACE_NAME = "implementedInterface";
+    public static final @NonNull String BINDING_TO_STRING_NAME = "bindingToString";
 
     /**
-     * Prefix for getter methods working on top of boolean.
+     * Name of {@link ScalarTypeObject#getValue()}.
      */
-    public static final String BOOLEAN_GETTER_PREFIX = "is";
+    public static final @NonNull String SCALAR_TYPE_OBJECT_GET_VALUE_NAME = "getValue";
 
     /**
      * Prefix for normal getter methods.
      */
-    public static final String GETTER_PREFIX = "get";
+    public static final @NonNull String GETTER_PREFIX = "get";
 
     /**
      * Prefix for non-null default wrapper methods. These methods always wrap a corresponding normal getter.
      */
-    public static final String NONNULL_PREFIX = "nonnull";
+    public static final @NonNull String NONNULL_PREFIX = "nonnull";
 
-    public static final String RPC_INPUT_SUFFIX = "Input";
-    public static final String RPC_OUTPUT_SUFFIX = "Output";
+    /**
+     * Prefix for require default wrapper methods. These methods always wrap a corresponding normal getter
+     * of leaf objects.
+     */
+    public static final @NonNull String REQUIRE_PREFIX = "require";
+    public static final @NonNull String RPC_INPUT_SUFFIX = "Input";
+    public static final @NonNull String RPC_OUTPUT_SUFFIX = "Output";
 
     private static final Interner<String> PACKAGE_INTERNER = Interners.newWeakInterner();
 
     private BindingMapping() {
-        throw new UnsupportedOperationException("Utility class should not be instantiated");
+        // Hidden on purpose
     }
 
-    public static String getRootPackageName(final QName module) {
+    public static @NonNull String getRootPackageName(final QName module) {
         return getRootPackageName(module.getModule());
     }
 
-    public static String getRootPackageName(final QNameModule module) {
-        checkArgument(module != null, "Module must not be null");
-        checkArgument(module.getRevision() != null, "Revision must not be null");
-        checkArgument(module.getNamespace() != null, "Namespace must not be null");
-        final StringBuilder packageNameBuilder = new StringBuilder();
-
-        packageNameBuilder.append(BindingMapping.PACKAGE_PREFIX);
-        packageNameBuilder.append('.');
+    public static @NonNull String getRootPackageName(final QNameModule module) {
+        final StringBuilder packageNameBuilder = new StringBuilder().append(BindingMapping.PACKAGE_PREFIX).append('.');
 
         String namespace = module.getNamespace().toString();
         namespace = COLON_SLASH_SLASH.matcher(namespace).replaceAll(QUOTED_DOT);
@@ -167,11 +192,7 @@ public final class BindingMapping {
         return normalizePackageName(packageNameBuilder.toString());
     }
 
-    public static String normalizePackageName(final String packageName) {
-        if (packageName == null) {
-            return null;
-        }
-
+    public static @NonNull String normalizePackageName(final String packageName) {
         final StringBuilder builder = new StringBuilder();
         boolean first = true;
 
@@ -192,48 +213,40 @@ public final class BindingMapping {
         return PACKAGE_INTERNER.intern(builder.toString());
     }
 
-    public static String getClassName(final String localName) {
-        checkArgument(localName != null, "Name should not be null.");
+    public static @NonNull String getClassName(final String localName) {
         return toFirstUpper(toCamelCase(localName));
     }
 
-    public static String getClassName(final QName name) {
-        checkArgument(name != null, "Name should not be null.");
+    public static @NonNull String getClassName(final QName name) {
         return toFirstUpper(toCamelCase(name.getLocalName()));
     }
 
-    public static String getMethodName(final String yangIdentifier) {
-        checkArgument(yangIdentifier != null,"Identifier should not be null");
+    public static @NonNull String getMethodName(final String yangIdentifier) {
         return toFirstLower(toCamelCase(yangIdentifier));
     }
 
-    public static String getMethodName(final QName name) {
-        checkArgument(name != null, "Name should not be null.");
+    public static @NonNull String getMethodName(final QName name) {
         return getMethodName(name.getLocalName());
     }
 
-    public static String getGetterPrefix(final boolean isBoolean) {
-        return isBoolean ? BOOLEAN_GETTER_PREFIX : GETTER_PREFIX;
-    }
-
-    public static String getGetterMethodName(final String localName, final boolean isBoolean) {
-        return getGetterPrefix(isBoolean) + toFirstUpper(getPropertyName(localName));
+    public static @NonNull String getGetterMethodName(final String localName) {
+        return GETTER_PREFIX + toFirstUpper(getPropertyName(localName));
     }
 
-    public static String getGetterMethodName(final QName name, final boolean isBoolean) {
-        return getGetterPrefix(isBoolean) + getGetterSuffix(name);
+    public static @NonNull String getGetterMethodName(final QName name) {
+        return GETTER_PREFIX + getGetterSuffix(name);
     }
 
     public static boolean isGetterMethodName(final String methodName) {
-        return methodName.startsWith(GETTER_PREFIX) || methodName.startsWith(BOOLEAN_GETTER_PREFIX);
+        return methodName.startsWith(GETTER_PREFIX);
     }
 
-    public static String getGetterMethodForNonnull(final String methodName) {
+    public static @NonNull String getGetterMethodForNonnull(final String methodName) {
         checkArgument(isNonnullMethodName(methodName));
         return GETTER_PREFIX + methodName.substring(NONNULL_PREFIX.length());
     }
 
-    public static String getNonnullMethodName(final String localName) {
+    public static @NonNull String getNonnullMethodName(final String localName) {
         return NONNULL_PREFIX + toFirstUpper(getPropertyName(localName));
     }
 
@@ -241,13 +254,25 @@ public final class BindingMapping {
         return methodName.startsWith(NONNULL_PREFIX);
     }
 
-    public static String getGetterSuffix(final QName name) {
-        checkArgument(name != null, "Name should not be null.");
+    public static @NonNull String getGetterMethodForRequire(final String methodName) {
+        checkArgument(isRequireMethodName(methodName));
+        return GETTER_PREFIX + methodName.substring(REQUIRE_PREFIX.length());
+    }
+
+    public static @NonNull String getRequireMethodName(final String localName) {
+        return REQUIRE_PREFIX + toFirstUpper(getPropertyName(localName));
+    }
+
+    public static boolean isRequireMethodName(final String methodName) {
+        return methodName.startsWith(REQUIRE_PREFIX);
+    }
+
+    public static @NonNull String getGetterSuffix(final QName name) {
         final String candidate = toFirstUpper(toCamelCase(name.getLocalName()));
         return "Class".equals(candidate) ? "XmlClass" : candidate;
     }
 
-    public static String getPropertyName(final String yangIdentifier) {
+    public static @NonNull String getPropertyName(final String yangIdentifier) {
         final String potential = toFirstLower(toCamelCase(yangIdentifier));
         if ("class".equals(potential)) {
             return "xmlClass";
@@ -255,18 +280,23 @@ public final class BindingMapping {
         return potential;
     }
 
-    private static String toCamelCase(final String rawString) {
-        checkArgument(rawString != null, "String should not be null");
-        Iterable<String> components = CAMEL_SPLITTER.split(rawString);
+    // FIXME: this is legacy union/leafref property handling. The resulting value is *not* normalized for use as a
+    //        property.
+    public static @NonNull String getUnionLeafrefMemberName(final String unionClassSimpleName,
+            final String referencedClassSimpleName) {
+        return requireNonNull(referencedClassSimpleName) + requireNonNull(unionClassSimpleName) + "Value";
+    }
+
+    private static @NonNull String toCamelCase(final String rawString) {
         StringBuilder builder = new StringBuilder();
-        for (String comp : components) {
+        for (String comp : CAMEL_SPLITTER.split(rawString)) {
             builder.append(toFirstUpper(comp));
         }
         return checkNumericPrefix(builder.toString());
     }
 
-    private static String checkNumericPrefix(final String rawString) {
-        if (rawString == null || rawString.isEmpty()) {
+    private static @NonNull String checkNumericPrefix(final String rawString) {
+        if (rawString.isEmpty()) {
             return rawString;
         }
         final char firstChar = rawString.charAt(0);
@@ -274,15 +304,13 @@ public final class BindingMapping {
     }
 
     /**
-     * Returns the {@link String} {@code s} with an {@link Character#isUpperCase(char) upper case} first character. This
-     * function is null-safe.
+     * Returns the {@link String} {@code s} with an {@link Character#isUpperCase(char) upper case} first character.
      *
-     * @param str the string that should get an upper case first character. May be <code>null</code>.
-     * @return the {@link String} {@code str} with an upper case first character or <code>null</code> if the input
-     *         {@link String} {@code str} was <code>null</code>.
+     * @param str the string that should get an upper case first character.
+     * @return the {@link String} {@code str} with an upper case first character.
      */
-    public static String toFirstUpper(final String str) {
-        if (str == null || str.length() == 0) {
+    public static @NonNull String toFirstUpper(final @NonNull String str) {
+        if (str.isEmpty()) {
             return str;
         }
         if (Character.isUpperCase(str.charAt(0))) {
@@ -300,10 +328,10 @@ public final class BindingMapping {
      *
      * @param str the string that should get an lower case first character. May be <code>null</code>.
      * @return the {@link String} {@code str} with an lower case first character or <code>null</code> if the input
-     *         {@link String} {@code str} was <code>null</code>.
+     *         {@link String} {@code str} was empty.
      */
-    private static String toFirstLower(final String str) {
-        if (str == null || str.length() == 0) {
+    private static @NonNull String toFirstLower(final @NonNull String str) {
+        if (str.isEmpty()) {
             return str;
         }
         if (Character.isLowerCase(str.charAt(0))) {
@@ -315,6 +343,18 @@ public final class BindingMapping {
         return str.substring(0, 1).toLowerCase(Locale.ENGLISH) + str.substring(1);
     }
 
+    /**
+     * Returns the {@link String} {@code s} with a '$' character as suffix.
+     *
+     * @param qname RPC QName
+     * @return The RPC method name as determined by considering the localname against the JLS.
+     * @throws NullPointerException if {@code qname} is null
+     */
+    public static @NonNull String getRpcMethodName(final @NonNull QName qname) {
+        final String methodName = getMethodName(qname);
+        return JAVA_RESERVED_WORDS.contains(methodName) ? methodName + "$" : methodName;
+    }
+
     /**
      * Returns Java identifiers, conforming to JLS9 Section 3.8 to use for specified YANG assigned names
      * (RFC7950 Section 9.6.4). This method considers two distinct encodings: one the pre-Fluorine mapping, which is
@@ -359,7 +399,9 @@ public final class BindingMapping {
         return javaToYang.inverse();
     }
 
-    // See https://docs.oracle.com/javase/specs/jls/se9/html/jls-3.html#jls-3.8
+    // See https://docs.oracle.com/javase/specs/jls/se16/html/jls-3.html#jls-3.8
+    // TODO: we are being conservative here, but should differentiate TypeIdentifier and UnqualifiedMethodIdentifier,
+    //       which have different exclusions
     private static boolean isValidJavaIdentifier(final String str) {
         return !str.isEmpty() && !JAVA_RESERVED_WORDS.contains(str)
                 && Character.isJavaIdentifierStart(str.codePointAt(0))