Add AbstractHierarchicalIdentifier 50/109050/3
authorRobert Varga <robert.varga@pantheon.tech>
Wed, 22 Nov 2023 15:00:22 +0000 (16:00 +0100)
committerRobert Varga <robert.varga@pantheon.tech>
Fri, 24 Nov 2023 14:59:55 +0000 (15:59 +0100)
We have a few implementations of Hierarc hicalIdentifier, some of which
are composed of a series of steps and nothing more.

Introduce AbstractHierarchicalIdentifier to serve as the common
superclass, formalizing the common bits.

JIRA: YANGTOOLS-1549
Change-Id: I958b2092ba4da3d8f6ca358b46b66ccc96d3b1ef
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
common/concepts/src/main/java/org/opendaylight/yangtools/concepts/AbstractHierarchicalIdentifier.java [new file with mode: 0644]
common/concepts/src/main/java/org/opendaylight/yangtools/concepts/HierarchicalIdentifier.java
data/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/YangInstanceIdentifier.java
data/yang-data-api/src/test/java/org/opendaylight/yangtools/yang/data/api/YangInstanceIdentifierTest.java

diff --git a/common/concepts/src/main/java/org/opendaylight/yangtools/concepts/AbstractHierarchicalIdentifier.java b/common/concepts/src/main/java/org/opendaylight/yangtools/concepts/AbstractHierarchicalIdentifier.java
new file mode 100644 (file)
index 0000000..25bfd2f
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2023 PANTHEON.tech, s.r.o. 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.concepts;
+
+import java.io.IOException;
+import java.io.NotSerializableException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.ObjectStreamException;
+import java.util.Iterator;
+import org.eclipse.jdt.annotation.NonNull;
+
+/**
+ * An opinionated superclass for implementing {@link HierarchicalIdentifier}s.
+ *
+ * <p>
+ * It assumes that the identifier is composed of multiple non-null steps available via {@link #itemIterator()} and that
+ * {@link #contains(AbstractHierarchicalIdentifier)} semantics can be implemented using simple in-order comparison of
+ * these steps.
+ *
+ * <p>
+ * Furthermore it mandates that serialization occurs via {@link #writeReplace()}, following the Serialization Proxy
+ * pattern.
+ */
+public abstract class AbstractHierarchicalIdentifier<T extends AbstractHierarchicalIdentifier<T, I>, I>
+        implements HierarchicalIdentifier<T> {
+    @java.io.Serial
+    private static final long serialVersionUID = 1L;
+
+    @Override
+    public final boolean contains(final T other) {
+        if (this != other) {
+            final var oit = other.itemIterator();
+            final var it = itemIterator();
+            while (it.hasNext()) {
+                if (!oit.hasNext() || !it.next().equals(oit.next())) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    protected abstract @NonNull Iterator<@NonNull I> itemIterator();
+
+    @java.io.Serial
+    protected abstract @NonNull Object writeReplace() throws ObjectStreamException;
+
+    /**
+     * Utility method throwing a {@link NotSerializableException}. It is useful when implementing
+     * {@link #readObject(ObjectInputStream)}, {@link #readObjectNoData()} and {@link #writeObject(ObjectOutputStream)}
+     * methods, which all subclasses should define as serialization is driven via {@link #writeReplace()}.
+     *
+     * @throws NotSerializableException always
+     */
+    protected final void throwNSE() throws NotSerializableException {
+        throw new NotSerializableException(getClass().getName());
+    }
+
+    @Override
+    public abstract int hashCode();
+
+    @Override
+    public abstract boolean equals(Object obj);
+
+    @Override
+    public abstract String toString();
+
+    @java.io.Serial
+    private void readObject(final ObjectInputStream stream) throws IOException, ClassNotFoundException {
+        throwNSE();
+    }
+
+    @java.io.Serial
+    private void readObjectNoData() throws ObjectStreamException {
+        throwNSE();
+    }
+
+    @java.io.Serial
+    private void writeObject(final ObjectOutputStream stream) throws IOException {
+        throwNSE();
+    }
+}
index 10a66afd1d3d36cf18191efb130811b0b755274b..444ce3e14eacc307197d94a72eca024229d3d73a 100644 (file)
@@ -21,9 +21,9 @@ public interface HierarchicalIdentifier<T extends HierarchicalIdentifier<T>> ext
      * Check if this identifier contains some other identifier. If we take HierarchicalIdentifier to be similar to a
      * {@link java.nio.file.Path}, this is method is the equivalent of {@code other.startsWith(this)}.
      *
-     * @param other Other identifier, may not be null
+     * @param other Other identifier, may not be {@code null}
      * @return True if this identifier contains the other identifier
-     * @throws NullPointerException if {@code other} is null
+     * @throws NullPointerException if {@code other} is {@code null}
      */
     boolean contains(T other);
 }
index 203add8399d1d44dcf3a596b2b89c1ebdf41d4ed..7a533965e334d13095660afb1bac8eeb54d7534c 100644 (file)
@@ -7,7 +7,6 @@
  */
 package org.opendaylight.yangtools.yang.data.api;
 
-import static com.google.common.base.Preconditions.checkArgument;
 import static java.util.Objects.requireNonNull;
 
 import com.google.common.annotations.Beta;
@@ -17,6 +16,10 @@ import com.google.common.cache.CacheLoader;
 import com.google.common.cache.LoadingCache;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.ObjectStreamException;
 import java.lang.invoke.MethodHandles;
 import java.lang.invoke.VarHandle;
 import java.lang.reflect.Array;
@@ -34,7 +37,7 @@ import java.util.Set;
 import java.util.function.Function;
 import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
-import org.opendaylight.yangtools.concepts.HierarchicalIdentifier;
+import org.opendaylight.yangtools.concepts.AbstractHierarchicalIdentifier;
 import org.opendaylight.yangtools.concepts.Identifier;
 import org.opendaylight.yangtools.concepts.Mutable;
 import org.opendaylight.yangtools.util.ImmutableOffsetMap;
@@ -67,7 +70,8 @@ import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
  *
  * @see <a href="http://www.rfc-editor.org/rfc/rfc6020#section-9.13">RFC6020</a>
  */
-public abstract sealed class YangInstanceIdentifier implements HierarchicalIdentifier<YangInstanceIdentifier>
+public abstract sealed class YangInstanceIdentifier
+        extends AbstractHierarchicalIdentifier<YangInstanceIdentifier, YangInstanceIdentifier.PathArgument>
         permits FixedYangInstanceIdentifier, StackedYangInstanceIdentifier {
     @java.io.Serial
     private static final long serialVersionUID = 4L;
@@ -384,10 +388,10 @@ public abstract sealed class YangInstanceIdentifier implements HierarchicalIdent
             return Optional.of(this);
         }
 
-        final Iterator<PathArgument> lit = getPathArguments().iterator();
+        final var lit = getPathArguments().iterator();
         int common = 0;
 
-        for (PathArgument element : ancestor.getPathArguments()) {
+        for (var element : ancestor.getPathArguments()) {
             // Ancestor is not really an ancestor
             if (!lit.hasNext() || !lit.next().equals(element)) {
                 return Optional.empty();
@@ -407,25 +411,28 @@ public abstract sealed class YangInstanceIdentifier implements HierarchicalIdent
     }
 
     @Override
-    public final boolean contains(final YangInstanceIdentifier other) {
-        if (this == other) {
-            return true;
-        }
+    protected final Iterator<PathArgument> itemIterator() {
+        return getPathArguments().iterator();
+    }
 
-        checkArgument(other != null, "other should not be null");
-        final Iterator<PathArgument> oit = other.getPathArguments().iterator();
+    @Override
+    protected final Object writeReplace() {
+        return new YIDv1(this);
+    }
 
-        for (PathArgument element : getPathArguments()) {
-            if (!oit.hasNext()) {
-                return false;
-            }
+    @java.io.Serial
+    private void readObject(final ObjectInputStream stream) throws IOException, ClassNotFoundException {
+        throwNSE();
+    }
 
-            if (!element.equals(oit.next())) {
-                return false;
-            }
-        }
+    @java.io.Serial
+    private void readObjectNoData() throws ObjectStreamException {
+        throwNSE();
+    }
 
-        return true;
+    @java.io.Serial
+    private void writeObject(final ObjectOutputStream stream) throws IOException {
+        throwNSE();
     }
 
     @Override
@@ -439,7 +446,7 @@ public abstract sealed class YangInstanceIdentifier implements HierarchicalIdent
          * The cache is thread-safe - if multiple computations occurs at the
          * same time, cache will be overwritten with same result.
          */
-        final String ret = (String) TO_STRING_CACHE.getAcquire(this);
+        final var ret = (String) TO_STRING_CACHE.getAcquire(this);
         return ret != null ? ret : loadToString();
     }
 
@@ -502,11 +509,6 @@ public abstract sealed class YangInstanceIdentifier implements HierarchicalIdent
 
     abstract int computeHashCode();
 
-    @java.io.Serial
-    final Object writeReplace() {
-        return new YIDv1(this);
-    }
-
     /**
      * Returns new builder for InstanceIdentifier with empty path arguments.
      *
index db31a494e6fcf69843c283e42f0b898a93101522..869b6260fe630ec760b241d9e632ad8c3a7d99c8 100644 (file)
@@ -136,7 +136,7 @@ public class YangInstanceIdentifierTest {
     @Test
     public void testContainsNull() {
         final var id = YangInstanceIdentifier.of(NODENAME1);
-        assertThrows(IllegalArgumentException.class, () -> id.contains(null));
+        assertThrows(NullPointerException.class, () -> id.contains(null));
     }
 
     @Test