Add a singleton NodeIdentifierWithPredicates
[yangtools.git] / yang / yang-data-api / src / main / java / org / opendaylight / yangtools / yang / data / api / YangInstanceIdentifier.java
index 3667d15b62420796abbac15631fadbce84679c27..40265f83ffc9a801b1504ced3dbc71ce45974d69 100644 (file)
@@ -23,6 +23,7 @@ import com.google.common.collect.Sets;
 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 import java.io.Serializable;
 import java.lang.reflect.Array;
+import java.util.AbstractMap.SimpleImmutableEntry;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -44,6 +45,7 @@ import org.opendaylight.yangtools.concepts.Path;
 import org.opendaylight.yangtools.util.HashCodeBuilder;
 import org.opendaylight.yangtools.util.ImmutableOffsetMap;
 import org.opendaylight.yangtools.util.SharedSingletonMap;
+import org.opendaylight.yangtools.util.SingletonSet;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.common.QNameModule;
 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
@@ -363,6 +365,8 @@ public abstract class YangInstanceIdentifier implements Path<YangInstanceIdentif
         return hash;
     }
 
+    @SuppressFBWarnings(value = "UPM_UNCALLED_PRIVATE_METHOD",
+            justification = "https://github.com/spotbugs/spotbugs/issues/811")
     private static int hashCode(final Object value) {
         if (value == null) {
             return 0;
@@ -445,16 +449,12 @@ public abstract class YangInstanceIdentifier implements Path<YangInstanceIdentif
      */
     public interface PathArgument extends Comparable<PathArgument>, Immutable, Serializable {
         /**
-         * If applicable returns unique QName of data node as defined in YANG
-         * Schema.
-         *
-         * <p>
-         * This method may return null, if the corresponding schema node, does
-         * not have QName associated, such as in cases of augmentations.
+         * Returns unique QName of data node as defined in YANG Schema, if available.
          *
          * @return Node type
+         * @throws UnsupportedOperationException if node type is not applicable, for example in case of an augmentation.
          */
-        QName getNodeType();
+        @NonNull QName getNodeType();
 
         /**
          * Return the string representation of this object for use in context
@@ -478,7 +478,7 @@ public abstract class YangInstanceIdentifier implements Path<YangInstanceIdentif
         }
 
         @Override
-        public final @NonNull QName getNodeType() {
+        public final QName getNodeType() {
             return nodeType;
         }
 
@@ -569,51 +569,155 @@ public abstract class YangInstanceIdentifier implements Path<YangInstanceIdentif
      * Composite path argument identifying a {@link org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode} leaf
      * overall data tree.
      */
-    public static final class NodeIdentifierWithPredicates extends AbstractPathArgument {
-        private static final long serialVersionUID = -4787195606494761540L;
+    public abstract static class NodeIdentifierWithPredicates extends AbstractPathArgument {
+        private static final class Singleton extends NodeIdentifierWithPredicates {
+            private static final long serialVersionUID = 1L;
+
+            private final @NonNull QName key;
+            private final @NonNull Object value;
+
+            Singleton(final QName node, final QName key, final Object value) {
+                super(node);
+                this.key = requireNonNull(key);
+                this.value = requireNonNull(value);
+            }
+
+            @Override
+            public SingletonSet<Entry<QName, Object>> entrySet() {
+                return SingletonSet.of(new SimpleImmutableEntry<>(key, value));
+            }
+
+            @Override
+            public SingletonSet<QName> keySet() {
+                return SingletonSet.of(key);
+            }
+
+            @Override
+            public SingletonSet<Object> values() {
+                return SingletonSet.of(value);
+            }
+
+            @Override
+            public int size() {
+                return 1;
+            }
+
+            @Override
+            public ImmutableMap<QName, Object> asMap() {
+                return ImmutableMap.of(key, value);
+            }
+
+            @Override
+            boolean equalMapping(final NodeIdentifierWithPredicates other) {
+                final Singleton single = (Singleton) other;
+                return key.equals(single.key) && Objects.deepEquals(value, single.value);
+            }
+
+            @Override
+            Object keyValue(final QName qname) {
+                return key.equals(qname) ? value : null;
+            }
+        }
+
+        private static final class Regular extends NodeIdentifierWithPredicates {
+            private static final long serialVersionUID = 1L;
+
+            private final @NonNull Map<QName, Object> keyValues;
 
-        private final @NonNull Map<QName, Object> keyValues;
+            Regular(final QName node, final Map<QName, Object> keyValues) {
+                super(node);
+                this.keyValues = requireNonNull(keyValues);
+            }
+
+            @Override
+            public Set<Entry<QName, Object>> entrySet() {
+                return keyValues.entrySet();
+            }
+
+            @Override
+            public Set<QName> keySet() {
+                return keyValues.keySet();
+            }
+
+            @Override
+            public Collection<Object> values() {
+                return keyValues.values();
+            }
+
+            @Override
+            public int size() {
+                return keyValues.size();
+            }
 
-        // Exposed for NIPv1
-        NodeIdentifierWithPredicates(final Map<QName, Object> keyValues, final QName node) {
+            @Override
+            public Map<QName, Object> asMap() {
+                return keyValues;
+            }
+
+            @Override
+            Object keyValue(final QName qname) {
+                return keyValues.get(qname);
+            }
+
+            @Override
+            boolean equalMapping(final NodeIdentifierWithPredicates other) {
+                final Map<QName, Object> otherKeyValues = ((Regular) other).keyValues;
+                // TODO: benchmark to see if just calling equals() on the two maps is not faster
+                if (keyValues == otherKeyValues) {
+                    return true;
+                }
+                if (keyValues.size() != otherKeyValues.size()) {
+                    return false;
+                }
+
+                for (Entry<QName, Object> entry : entrySet()) {
+                    final Object otherValue = otherKeyValues.get(entry.getKey());
+                    if (otherValue == null || !Objects.deepEquals(entry.getValue(), otherValue)) {
+                        return false;
+                    }
+                }
+
+                return true;
+            }
+        }
+
+        private static final long serialVersionUID = -4787195606494761540L;
+
+        NodeIdentifierWithPredicates(final QName node) {
             super(node);
-            this.keyValues = requireNonNull(keyValues);
         }
 
         public static @NonNull NodeIdentifierWithPredicates of(final QName node) {
-            return new NodeIdentifierWithPredicates(ImmutableMap.of(), node);
+            return new Regular(node, ImmutableMap.of());
+        }
+
+        public static @NonNull NodeIdentifierWithPredicates of(final QName node, final QName key, final Object value) {
+            return new Singleton(node, key, value);
+        }
+
+        public static @NonNull NodeIdentifierWithPredicates of(final QName node, final Entry<QName, Object> entry) {
+            return of(node, entry.getKey(), entry.getValue());
         }
 
         public static @NonNull NodeIdentifierWithPredicates of(final QName node, final Map<QName, Object> keyValues) {
-            // Retains ImmutableMap for empty maps. For larger sizes uses a shared key set.
-            return new NodeIdentifierWithPredicates(ImmutableOffsetMap.unorderedCopyOf(keyValues), node);
+            return keyValues.size() == 1 ? of(keyValues, node)
+                    // Retains ImmutableMap for empty maps. For larger sizes uses a shared key set.
+                    : new Regular(node, ImmutableOffsetMap.unorderedCopyOf(keyValues));
         }
 
         public static @NonNull NodeIdentifierWithPredicates of(final QName node,
                 final ImmutableOffsetMap<QName, Object> keyValues) {
-            return new NodeIdentifierWithPredicates(keyValues, node);
+            return keyValues.size() == 1 ? of(keyValues, node) : new Regular(node, keyValues);
         }
 
+        @Deprecated
         public static @NonNull NodeIdentifierWithPredicates of(final QName node,
                 final SharedSingletonMap<QName, Object> keyValues) {
-            return new NodeIdentifierWithPredicates(keyValues, node);
+            return of(node, keyValues.getEntry());
         }
 
-        public static @NonNull NodeIdentifierWithPredicates of(final QName node, final QName key, final Object value) {
-            return of(node, SharedSingletonMap.unorderedOf(key, value));
-        }
-
-        /**
-         * Return predicates as a Map.
-         *
-         * @return Predicate map.
-         * @deprecated Map view is actively discouraged and should be considered deprecated. For replacement, please
-         *             see {@link #keySet()}, #entrySet()}, {@link #values()} and {@link #size()}. As a last resort,
-         *             please migrate to {@link #asMap()}.
-         */
-        @Deprecated
-        public @NonNull Map<QName, Object> getKeyValues() {
-            return keyValues;
+        private static @NonNull NodeIdentifierWithPredicates of(final Map<QName, Object> keyValues, final QName node) {
+            return of(node, keyValues.entrySet().iterator().next());
         }
 
         /**
@@ -622,9 +726,7 @@ public abstract class YangInstanceIdentifier implements Path<YangInstanceIdentif
          * @return Predicate set.
          */
         @Beta
-        public @NonNull Set<Entry<QName, Object>> entrySet() {
-            return keyValues.entrySet();
-        }
+        public abstract @NonNull Set<Entry<QName, Object>> entrySet();
 
         /**
          * Return the predicate key in the iteration order of {@link #entrySet()}.
@@ -632,9 +734,7 @@ public abstract class YangInstanceIdentifier implements Path<YangInstanceIdentif
          * @return Predicate values.
          */
         @Beta
-        public @NonNull Set<QName> keySet() {
-            return keyValues.keySet();
-        }
+        public abstract @NonNull Set<QName> keySet();
 
         /**
          * Return the predicate values in the iteration order of {@link #entrySet()}.
@@ -642,33 +742,28 @@ public abstract class YangInstanceIdentifier implements Path<YangInstanceIdentif
          * @return Predicate values.
          */
         @Beta
-        public @NonNull Collection<Object> values() {
-            return keyValues.values();
-        }
+        public abstract @NonNull Collection<Object> values();
 
         @Beta
-        public @Nullable Object getValue(final QName key) {
-            return keyValues.get(requireNonNull(key));
+        public final @Nullable Object getValue(final QName key) {
+            return keyValue(requireNonNull(key));
         }
 
         @Beta
-        public <T> @Nullable T getValue(final QName key, final Class<T> valueClass) {
+        public final <T> @Nullable T getValue(final QName key, final Class<T> valueClass) {
             return valueClass.cast(getValue(key));
         }
 
         /**
          * Return the number of predicates present.
          *
-         * @return Thee number of predicates present.
+         * @return The number of predicates present.
          */
         @Beta
-        public int size() {
-            return keyValues.size();
-        }
+        public abstract int size();
 
         /**
-         * A Map-like view of this identifier's predicates. While this method is deprecated, it is preferred to
-         * {@link #getKeyValues()}. The view is expected to be stable and effectively-immutable.
+         * A Map-like view of this identifier's predicates. The view is expected to be stable and effectively-immutable.
          *
          * @return Map of predicates.
          * @deprecated This method in a provisional one. It can be used in the code base, but users requiring it should
@@ -680,64 +775,40 @@ public abstract class YangInstanceIdentifier implements Path<YangInstanceIdentif
         @Deprecated
         // FIXME: 4.0.0: evaluate the real usefulness of this. The problem here is Map.hashCode() and Map.equals(),
         //               which limits our options.
-        public @NonNull Map<QName, Object> asMap() {
-            return keyValues;
-        }
+        public abstract @NonNull Map<QName, Object> asMap();
 
         @Override
-        protected int hashCodeImpl() {
-            final int prime = 31;
-            int result = super.hashCodeImpl();
-            result = prime * result;
-
-            for (Entry<QName, Object> entry : keyValues.entrySet()) {
-                // FIXME: 4.0.0: key and value expected to be non-null here
-                result += Objects.hashCode(entry.getKey()) + YangInstanceIdentifier.hashCode(entry.getValue());
+        protected final int hashCodeImpl() {
+            int result = 31 * super.hashCodeImpl();
+            for (Entry<QName, Object> entry : entrySet()) {
+                result += entry.getKey().hashCode() + YangInstanceIdentifier.hashCode(entry.getValue());
             }
             return result;
         }
 
         @Override
         @SuppressWarnings("checkstyle:equalsHashCode")
-        public boolean equals(final Object obj) {
-            if (!super.equals(obj)) {
-                return false;
-            }
-
-            final Map<QName, Object> otherKeyValues = ((NodeIdentifierWithPredicates) obj).keyValues;
-
-            // TODO: benchmark to see if just calling equals() on the two maps is not faster
-            if (keyValues == otherKeyValues) {
-                return true;
-            }
-            if (keyValues.size() != otherKeyValues.size()) {
-                return false;
-            }
+        public final boolean equals(final Object obj) {
+            return super.equals(obj) && equalMapping((NodeIdentifierWithPredicates) obj);
+        }
 
-            for (Entry<QName, Object> entry : keyValues.entrySet()) {
-                if (!otherKeyValues.containsKey(entry.getKey())
-                        || !Objects.deepEquals(entry.getValue(), otherKeyValues.get(entry.getKey()))) {
+        abstract boolean equalMapping(NodeIdentifierWithPredicates other);
 
-                    return false;
-                }
-            }
-
-            return true;
-        }
+        abstract @Nullable Object keyValue(@NonNull QName qname);
 
         @Override
-        public String toString() {
-            return super.toString() + '[' + keyValues + ']';
+        public final String toString() {
+            return super.toString() + '[' + asMap() + ']';
         }
 
         @Override
-        public String toRelativeString(final PathArgument previous) {
-            return super.toRelativeString(previous) + '[' + keyValues + ']';
+        public final String toRelativeString(final PathArgument previous) {
+            return super.toRelativeString(previous) + '[' + asMap() + ']';
         }
 
         @Override
-        Object writeReplace() {
-            return new NIPv1(this);
+        final Object writeReplace() {
+            return new NIPv2(this);
         }
     }