Add NormalizedNode.contract()
[yangtools.git] / yang / yang-data-api / src / main / java / org / opendaylight / yangtools / yang / data / api / YangInstanceIdentifier.java
index 0aa4a97de37c3413f75f662386fce19727266350..a7539e9167f08cb0917ba2b532e12f22e925194e 100644 (file)
@@ -12,6 +12,7 @@ import static com.google.common.base.Verify.verify;
 import static java.util.Objects.requireNonNull;
 
 import com.google.common.annotations.Beta;
+import com.google.common.base.VerifyException;
 import com.google.common.cache.CacheBuilder;
 import com.google.common.cache.CacheLoader;
 import com.google.common.cache.LoadingCache;
@@ -23,6 +24,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;
@@ -43,7 +45,7 @@ import org.opendaylight.yangtools.concepts.Immutable;
 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;
@@ -52,47 +54,32 @@ import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
  * Unique identifier of a particular node instance in the data tree.
  *
  * <p>
- * Java representation of YANG Built-in type <code>instance-identifier</code>,
- * which conceptually is XPath expression minimized to uniquely identify element
- * in data tree which conforms to constraints maintained by YANG Model,
+ * Java representation of YANG Built-in type {@code instance-identifier}, which conceptually is XPath expression
+ * minimized to uniquely identify element in data tree which conforms to constraints maintained by YANG Model,
  * effectively this makes Instance Identifier a path to element in data tree.
  *
  * <p>
- * Constraints put in YANG specification on instance-identifier allowed it to be
- * effectively represented in Java and it's evaluation does not require
- * full-blown XPath processor.
+ * Constraints put in YANG specification on instance-identifier allowed it to be effectively represented in Java and its
+ * evaluation does not require a full-blown XPath processor.
  *
- * <p>
- * <h3>Path Arguments</h3>
- * Path to the node represented in instance identifier consists of
- * {@link PathArgument} which carries necessary information to uniquely identify
- * node on particular level in the subtree.
+ * <h2>Path Arguments</h2>
+ * Path to the node represented in instance identifier consists of {@link PathArgument} which carries necessary
+ * information to uniquely identify node on particular level in the subtree.
  *
  * <ul>
- * <li>{@link NodeIdentifier} - Identifier of node, which has cardinality
- * <code>0..1</code> in particular subtree in data tree.</li>
- * <li>{@link NodeIdentifierWithPredicates} - Identifier of node (list item),
- * which has cardinality <code>0..n</code>.</li>
- * <li>{@link NodeWithValue} - Identifier of instance <code>leaf</code> node or
- * <code>leaf-list</code> node.</li>
- * <li>{@link AugmentationIdentifier} - Identifier of instance of
- * <code>augmentation</code> node.</li>
+ *   <li>{@link NodeIdentifier} - Identifier of node, which has cardinality {@code 0..1} in particular subtree in data
+ *       tree</li>
+ *   <li>{@link NodeIdentifierWithPredicates} - Identifier of node (list item), which has cardinality {@code 0..n}</li>
+ *   <li>{@link NodeWithValue} - Identifier of instance {@code leaf} node or {@code leaf-list} node</li>
+ *   <li>{@link AugmentationIdentifier} - Identifier of instance of {@code augmentation} node</li>
  * </ul>
  *
  * @see <a href="http://tools.ietf.org/html/rfc6020#section-9.13">RFC6020</a>
  */
-// FIXME: 4.0.0: this concept needs to be moved to yang-common, as parser components need the ability to refer
+// FIXME: 7.0.0: this concept needs to be moved to yang-common, as parser components need the ability to refer
 //               to data nodes -- most notably XPath expressions and {@code default} statement arguments need to be able
 //               to represent these.
-// FIXME: FixedYangInstanceIdentifier needs YangInstanceIdentifier initialized, but that includes initializing
-//        this field. Figure out a way out of this pickle.
-@SuppressFBWarnings("IC_SUPERCLASS_USES_SUBCLASS_DURING_INITIALIZATION")
 public abstract class YangInstanceIdentifier implements Path<YangInstanceIdentifier>, Immutable, Serializable {
-    /**
-     * An empty {@link YangInstanceIdentifier}. It corresponds to the path of the conceptual root of the YANG namespace.
-     */
-    public static final @NonNull YangInstanceIdentifier EMPTY = FixedYangInstanceIdentifier.EMPTY_INSTANCE;
-
     private static final AtomicReferenceFieldUpdater<YangInstanceIdentifier, String> TOSTRINGCACHE_UPDATER =
             AtomicReferenceFieldUpdater.newUpdater(YangInstanceIdentifier.class, String.class, "toStringCache");
     private static final long serialVersionUID = 4L;
@@ -105,6 +92,16 @@ public abstract class YangInstanceIdentifier implements Path<YangInstanceIdentif
         this.hash = hash;
     }
 
+    /**
+     * Return An empty {@link YangInstanceIdentifier}. It corresponds to the path of the conceptual root of the YANG
+     * namespace.
+     *
+     * @return An empty YangInstanceIdentifier
+     */
+    public static @NonNull YangInstanceIdentifier empty() {
+        return FixedYangInstanceIdentifier.EMPTY_INSTANCE;
+    }
+
     abstract @NonNull YangInstanceIdentifier createRelativeIdentifier(int skipFromRoot);
 
     abstract @Nullable Collection<PathArgument> tryPathArguments();
@@ -113,7 +110,7 @@ public abstract class YangInstanceIdentifier implements Path<YangInstanceIdentif
 
     /**
      * Check if this instance identifier has empty path arguments, e.g. it is
-     * empty and corresponds to {@link #EMPTY}.
+     * empty and corresponds to {@link #empty()}.
      *
      * @return True if this instance identifier is empty, false otherwise.
      */
@@ -125,17 +122,25 @@ public abstract class YangInstanceIdentifier implements Path<YangInstanceIdentif
      *
      * @return A optimized equivalent instance.
      */
-    @Beta
     public abstract @NonNull YangInstanceIdentifier toOptimized();
 
     /**
      * Return the conceptual parent {@link YangInstanceIdentifier}, which has
      * one item less in {@link #getPathArguments()}.
      *
-     * @return Parent {@link YangInstanceIdentifier}, or null if this object is {@link #EMPTY}.
+     * @return Parent {@link YangInstanceIdentifier}, or null if this object is {@link #empty()}.
      */
     public abstract @Nullable YangInstanceIdentifier getParent();
 
+    /**
+     * Return the conceptual parent {@link YangInstanceIdentifier}, which has one item less in
+     * {@link #getPathArguments()}.
+     *
+     * @return Parent {@link YangInstanceIdentifier}
+     * @throws VerifyException if this object is {@link #empty()}.
+     */
+    public abstract @NonNull YangInstanceIdentifier coerceParent();
+
     /**
      * Return the ancestor {@link YangInstanceIdentifier} with a particular depth, e.g. number of path arguments.
      *
@@ -170,7 +175,7 @@ public abstract class YangInstanceIdentifier implements Path<YangInstanceIdentif
 
     public static @NonNull YangInstanceIdentifier create(final Iterable<? extends PathArgument> path) {
         if (Iterables.isEmpty(path)) {
-            return EMPTY;
+            return empty();
         }
 
         final HashCodeBuilder<PathArgument> hash = new HashCodeBuilder<>();
@@ -181,6 +186,11 @@ public abstract class YangInstanceIdentifier implements Path<YangInstanceIdentif
         return FixedYangInstanceIdentifier.create(path, hash.build());
     }
 
+    public static @NonNull YangInstanceIdentifier create(final PathArgument pathArgument) {
+        return new FixedYangInstanceIdentifier(ImmutableList.of(pathArgument),
+            HashCodeBuilder.nextHashCode(1, pathArgument));
+    }
+
     public static @NonNull YangInstanceIdentifier create(final PathArgument... path) {
         // We are forcing a copy, since we cannot trust the user
         return create(Arrays.asList(path));
@@ -270,7 +280,7 @@ public abstract class YangInstanceIdentifier implements Path<YangInstanceIdentif
      */
     public Optional<YangInstanceIdentifier> relativeTo(final YangInstanceIdentifier ancestor) {
         if (this == ancestor) {
-            return Optional.of(EMPTY);
+            return Optional.of(empty());
         }
         if (ancestor.isEmpty()) {
             return Optional.of(this);
@@ -293,7 +303,7 @@ public abstract class YangInstanceIdentifier implements Path<YangInstanceIdentif
             return Optional.of(this);
         }
         if (!lit.hasNext()) {
-            return Optional.of(EMPTY);
+            return Optional.of(empty());
         }
 
         return Optional.of(createRelativeIdentifier(common));
@@ -487,7 +497,7 @@ public abstract class YangInstanceIdentifier implements Path<YangInstanceIdentif
         }
 
         protected int hashCodeImpl() {
-            return 31 + getNodeType().hashCode();
+            return nodeType.hashCode();
         }
 
         @Override
@@ -567,38 +577,170 @@ 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 {
+        @Beta
+        public 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(singleEntry());
+            }
 
-        private final @NonNull Map<QName, Object> keyValues;
+            @Override
+            public SingletonSet<QName> keySet() {
+                return SingletonSet.of(key);
+            }
+
+            @Override
+            public boolean containsKey(final QName qname) {
+                return key.equals(requireNonNull(qname));
+            }
+
+            @Override
+            public SingletonSet<Object> values() {
+                return SingletonSet.of(value);
+            }
+
+            @Override
+            public int size() {
+                return 1;
+            }
 
-        // Exposed for NIPv1
-        NodeIdentifierWithPredicates(final Map<QName, Object> keyValues, final QName node) {
+            @Override
+            public ImmutableMap<QName, Object> asMap() {
+                return ImmutableMap.of(key, value);
+            }
+
+            /**
+             * Return the single entry contained in this object. This is equivalent to
+             * {@code entrySet().iterator().next()}.
+             *
+             * @return A single entry.
+             */
+            public @NonNull Entry<QName, Object> singleEntry() {
+                return new SimpleImmutableEntry<>(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;
+
+            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 boolean containsKey(final QName qname) {
+                return keyValues.containsKey(requireNonNull(qname));
+            }
+
+            @Override
+            public Collection<Object> values() {
+                return keyValues.values();
+            }
+
+            @Override
+            public int size() {
+                return keyValues.size();
+            }
+
+            @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 Map<QName, Object> keyValues) {
-            // Retains ImmutableMap for empty maps. For larger sizes uses a shared key set.
-            return new NodeIdentifierWithPredicates(ImmutableOffsetMap.unorderedCopyOf(keyValues), node);
+        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 ImmutableOffsetMap<QName, Object> keyValues) {
-            return new NodeIdentifierWithPredicates(keyValues, node);
+        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) {
+            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 SharedSingletonMap<QName, Object> keyValues) {
-            return new NodeIdentifierWithPredicates(keyValues, node);
+                final ImmutableOffsetMap<QName, Object> keyValues) {
+            return keyValues.size() == 1 ? of(keyValues, node) : new Regular(node, keyValues);
         }
 
-        public static @NonNull NodeIdentifierWithPredicates of(final QName node, final QName key, final Object value) {
-            return of(node, SharedSingletonMap.unorderedOf(key, value));
+        private static @NonNull NodeIdentifierWithPredicates of(final Map<QName, Object> keyValues, final QName node) {
+            return of(node, keyValues.entrySet().iterator().next());
         }
 
         /**
@@ -606,121 +748,87 @@ 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()}.
          *
          * @return Predicate values.
          */
-        @Beta
-        public @NonNull Set<QName> keySet() {
-            return keyValues.keySet();
-        }
+        public abstract @NonNull Set<QName> keySet();
+
+        /**
+         * Determine whether a particular predicate key is present.
+         *
+         * @param key Predicate key
+         * @return True if the predicate is present, false otherwise
+         * @throws NullPointerException if {@code key} is null
+         */
+        public abstract boolean containsKey(QName key);
 
         /**
          * Return the predicate values in the iteration order of {@link #entrySet()}.
          *
          * @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. 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
-         *             contact <a href="mailto:yangtools-dev@lists.opendaylight.org">yangtools-dev</a> for migration
-         *             guidelines. Callers are strongly encouraged to explore {@link #entrySet()}, {@link #size()},
-         *             {@link #values()} and {@link #keySet()} as an alternative.
          */
         @Beta
-        @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() {
+        final Object writeReplace() {
             return new NIPv2(this);
         }
     }
@@ -732,23 +840,20 @@ public abstract class YangInstanceIdentifier implements Path<YangInstanceIdentif
     public static final class NodeWithValue<T> extends AbstractPathArgument {
         private static final long serialVersionUID = -3637456085341738431L;
 
-        private final T value;
+        private final @NonNull T value;
 
         public NodeWithValue(final QName node, final T value) {
             super(node);
-            this.value = value;
+            this.value = requireNonNull(value);
         }
 
-        public T getValue() {
+        public @NonNull T getValue() {
             return value;
         }
 
         @Override
         protected int hashCodeImpl() {
-            final int prime = 31;
-            int result = super.hashCodeImpl();
-            result = prime * result + YangInstanceIdentifier.hashCode(value);
-            return result;
+            return 31 * super.hashCodeImpl() + YangInstanceIdentifier.hashCode(value);
         }
 
         @Override
@@ -975,7 +1080,6 @@ public abstract class YangInstanceIdentifier implements Path<YangInstanceIdentif
          * @return this builder
          * @throws NullPointerException if any of the arguments is null
          */
-        @Beta
         @NonNull InstanceIdentifierBuilder append(Collection<? extends PathArgument> args);
 
         /**
@@ -985,7 +1089,6 @@ public abstract class YangInstanceIdentifier implements Path<YangInstanceIdentif
          * @return this builder
          * @throws NullPointerException if any of the arguments is null
          */
-        @Beta
         default @NonNull InstanceIdentifierBuilder append(final PathArgument... args) {
             return append(Arrays.asList(args));
         }