Compute YangInstanceIdentifier.hashCode() lazily
[yangtools.git] / data / yang-data-api / src / main / java / org / opendaylight / yangtools / yang / data / api / YangInstanceIdentifier.java
index 31ea97a0279cd82d1fe737eb7f5915f1be12390c..f0d9627fe8dfbceadbbc41a521ab39525af0c192 100644 (file)
@@ -21,8 +21,9 @@ import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 import java.io.Serializable;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
 import java.lang.reflect.Array;
 import java.util.AbstractMap.SimpleImmutableEntry;
 import java.util.ArrayList;
@@ -36,14 +37,12 @@ import java.util.Map.Entry;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
-import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
 import java.util.function.Function;
 import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jdt.annotation.Nullable;
-import org.opendaylight.yangtools.concepts.Builder;
 import org.opendaylight.yangtools.concepts.HierarchicalIdentifier;
 import org.opendaylight.yangtools.concepts.Immutable;
-import org.opendaylight.yangtools.util.HashCodeBuilder;
+import org.opendaylight.yangtools.concepts.Mutable;
 import org.opendaylight.yangtools.util.ImmutableOffsetMap;
 import org.opendaylight.yangtools.util.SingletonSet;
 import org.opendaylight.yangtools.yang.common.QName;
@@ -76,20 +75,29 @@ import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
  *
  * @see <a href="http://tools.ietf.org/html/rfc6020#section-9.13">RFC6020</a>
  */
-// 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: sealed once we have JDK17+
 public abstract class YangInstanceIdentifier implements HierarchicalIdentifier<YangInstanceIdentifier> {
-    private static final AtomicReferenceFieldUpdater<YangInstanceIdentifier, String> TOSTRINGCACHE_UPDATER =
-            AtomicReferenceFieldUpdater.newUpdater(YangInstanceIdentifier.class, String.class, "toStringCache");
     private static final long serialVersionUID = 4L;
+    private static final VarHandle TO_STRING_CACHE;
+    private static final VarHandle HASH;
 
-    private final int hash;
-    private transient volatile String toStringCache = null;
+    static {
+        final var lookup = MethodHandles.lookup();
+        try {
+            HASH = lookup.findVarHandle(YangInstanceIdentifier.class, "hash", int.class);
+            TO_STRING_CACHE = lookup.findVarHandle(YangInstanceIdentifier.class, "toStringCache", String.class);
+        } catch (NoSuchFieldException | IllegalAccessException e) {
+            throw new ExceptionInInitializerError(e);
+        }
+    }
+
+    @SuppressWarnings("unused")
+    private int hash;
+    @SuppressWarnings("unused")
+    private transient String toStringCache = null;
 
-    // Package-private to prevent outside subclassing
-    YangInstanceIdentifier(final int hash) {
-        this.hash = hash;
+    YangInstanceIdentifier() {
+        // Package-private to prevent outside subclassing
     }
 
     /**
@@ -104,9 +112,9 @@ public abstract class YangInstanceIdentifier implements HierarchicalIdentifier<Y
 
     abstract @NonNull YangInstanceIdentifier createRelativeIdentifier(int skipFromRoot);
 
-    abstract @Nullable Collection<PathArgument> tryPathArguments();
+    abstract @Nullable List<PathArgument> tryPathArguments();
 
-    abstract @Nullable Collection<PathArgument> tryReversePathArguments();
+    abstract @Nullable List<PathArgument> tryReversePathArguments();
 
     /**
      * Check if this instance identifier has empty path arguments, e.g. it is
@@ -174,21 +182,11 @@ public abstract class YangInstanceIdentifier implements HierarchicalIdentifier<Y
     public abstract PathArgument getLastPathArgument();
 
     public static @NonNull YangInstanceIdentifier create(final Iterable<? extends PathArgument> path) {
-        if (Iterables.isEmpty(path)) {
-            return empty();
-        }
-
-        final HashCodeBuilder<PathArgument> hash = new HashCodeBuilder<>();
-        for (PathArgument a : path) {
-            hash.addArgument(a);
-        }
-
-        return FixedYangInstanceIdentifier.create(path, hash.build());
+        return Iterables.isEmpty(path) ? empty() : new FixedYangInstanceIdentifier(ImmutableList.copyOf(path));
     }
 
     public static @NonNull YangInstanceIdentifier create(final PathArgument pathArgument) {
-        return new FixedYangInstanceIdentifier(ImmutableList.of(pathArgument),
-            HashCodeBuilder.nextHashCode(1, pathArgument));
+        return new FixedYangInstanceIdentifier(ImmutableList.of(pathArgument));
     }
 
     public static @NonNull YangInstanceIdentifier create(final PathArgument... path) {
@@ -230,23 +228,12 @@ public abstract class YangInstanceIdentifier implements HierarchicalIdentifier<Y
     }
 
     boolean pathArgumentsEqual(final YangInstanceIdentifier other) {
-        return Iterables.elementsEqual(getPathArguments(), other.getPathArguments());
+        return getPathArguments().equals(other.getPathArguments());
     }
 
     @Override
-    public boolean equals(final Object obj) {
-        if (this == obj) {
-            return true;
-        }
-        if (!(obj instanceof YangInstanceIdentifier)) {
-            return false;
-        }
-        YangInstanceIdentifier other = (YangInstanceIdentifier) obj;
-        if (this.hashCode() != obj.hashCode()) {
-            return false;
-        }
-
-        return pathArgumentsEqual(other);
+    public final boolean equals(final Object obj) {
+        return this == obj || obj instanceof YangInstanceIdentifier && pathArgumentsEqual((YangInstanceIdentifier) obj);
     }
 
     /**
@@ -266,7 +253,7 @@ public abstract class YangInstanceIdentifier implements HierarchicalIdentifier<Y
      * @return Instance Identifier with additional path argument added to the end.
      */
     public final @NonNull YangInstanceIdentifier node(final PathArgument arg) {
-        return new StackedYangInstanceIdentifier(this, arg, HashCodeBuilder.nextHashCode(hash, arg));
+        return new StackedYangInstanceIdentifier(this, arg);
     }
 
     /**
@@ -343,22 +330,24 @@ public abstract class YangInstanceIdentifier implements HierarchicalIdentifier<Y
          * The cache is thread-safe - if multiple computations occurs at the
          * same time, cache will be overwritten with same result.
          */
-        String ret = toStringCache;
-        if (ret == null) {
-            final StringBuilder builder = new StringBuilder("/");
-            PathArgument prev = null;
-            for (PathArgument argument : getPathArguments()) {
-                if (prev != null) {
-                    builder.append('/');
-                }
-                builder.append(argument.toRelativeString(prev));
-                prev = argument;
-            }
+        final String ret = (String) TO_STRING_CACHE.getAcquire(this);
+        return ret != null ? ret : loadToString();
+    }
 
-            ret = builder.toString();
-            TOSTRINGCACHE_UPDATER.lazySet(this, ret);
+    private String loadToString() {
+        final StringBuilder builder = new StringBuilder("/");
+        PathArgument prev = null;
+        for (PathArgument argument : getPathArguments()) {
+            if (prev != null) {
+                builder.append('/');
+            }
+            builder.append(argument.toRelativeString(prev));
+            prev = argument;
         }
-        return ret;
+
+        final String ret = builder.toString();
+        final String witness = (String) TO_STRING_CACHE.compareAndExchangeRelease(this, null, ret);
+        return witness == null ? ret : witness;
     }
 
     @Override
@@ -370,11 +359,10 @@ public abstract class YangInstanceIdentifier implements HierarchicalIdentifier<Y
          * Used lists, maps are immutable. Path Arguments (elements) are also
          * immutable, since the PathArgument contract requires immutability.
          */
-        return hash;
+        final int local = (int) HASH.getAcquire(this);
+        return local != 0 ? local : loadHashCode();
     }
 
-    @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;
@@ -397,6 +385,14 @@ public abstract class YangInstanceIdentifier implements HierarchicalIdentifier<Y
         return Objects.hashCode(value);
     }
 
+    private int loadHashCode() {
+        final int computed = computeHashCode();
+        HASH.setRelease(this, computed);
+        return computed;
+    }
+
+    abstract int computeHashCode();
+
     final Object writeReplace() {
         return new YIDv1(this);
     }
@@ -430,7 +426,7 @@ public abstract class YangInstanceIdentifier implements HierarchicalIdentifier<Y
      * @return new builder for InstanceIdentifier with path arguments copied from original instance identifier.
      */
     public static @NonNull InstanceIdentifierBuilder builder(final YangInstanceIdentifier origin) {
-        return new YangInstanceIdentifierBuilder(origin.getPathArguments(), origin.hashCode());
+        return new YangInstanceIdentifierBuilder(origin.getPathArguments());
     }
 
     /**
@@ -1036,7 +1032,7 @@ public abstract class YangInstanceIdentifier implements HierarchicalIdentifier<Y
     /**
      * Fluent Builder of Instance Identifier instances.
      */
-    public interface InstanceIdentifierBuilder extends Builder<YangInstanceIdentifier> {
+    public interface InstanceIdentifierBuilder extends Mutable {
         /**
          * Adds a {@link PathArgument} to path arguments of resulting instance identifier.
          *
@@ -1098,7 +1094,6 @@ public abstract class YangInstanceIdentifier implements HierarchicalIdentifier<Y
          *
          * @return {@link YangInstanceIdentifier}
          */
-        @Override
-        YangInstanceIdentifier build();
+        @NonNull YangInstanceIdentifier build();
     }
 }