Generalize ForwardingIdentityObject 20/83720/1
authorRobert Varga <robert.varga@pantheon.tech>
Fri, 16 Aug 2019 17:01:18 +0000 (19:01 +0200)
committerRobert Varga <nite@hq.sk>
Sat, 17 Aug 2019 09:12:30 +0000 (09:12 +0000)
Wrapping objects in identity comparison is useful, and there
seems to be no readily-avaible utility. This introduces an
extension to ForwardingObject and a simple utility class to
implement its construct.

JIRA: YANGTOOLS-1016
Change-Id: If0d7a2f731b0a294570f8ba39ce5da7236e2c752
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
(cherry picked from commit 6a039ee1b2179eb5b77ca30a366e866970806547)

common/util/src/main/java/org/opendaylight/yangtools/util/ForwardingIdentityObject.java [new file with mode: 0644]
common/util/src/main/java/org/opendaylight/yangtools/util/concurrent/QueuedNotificationManager.java

diff --git a/common/util/src/main/java/org/opendaylight/yangtools/util/ForwardingIdentityObject.java b/common/util/src/main/java/org/opendaylight/yangtools/util/ForwardingIdentityObject.java
new file mode 100644 (file)
index 0000000..f775562
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2019 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.util;
+
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.annotations.Beta;
+import com.google.common.collect.ForwardingObject;
+import java.util.concurrent.ConcurrentHashMap;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.yangtools.concepts.Delegator;
+
+/**
+ * A {@link ForwardingObject} which additionally masks {@link #hashCode()}/{@link #equals(Object)} of a delegate object,
+ * so that it can be a data transfer object with data-dependent implementations of those contracts can be use in
+ * collections and maps which need to work on identity. This is useful in situations where identity equality needs to
+ * be used with the conjunction with the collections library, for example {@link ConcurrentHashMap}.  All instances are
+ * considered equal if they refer to the same delegate object.
+ *
+ * <p>
+ * Note this class forms its own equality domain, and its use may lead to surprising results, especially where
+ * {@link #toString()} is involved. For example a {@code Map.toString()} may end up emitting two keys which have the
+ * same String representation.
+ *
+ * @param <T> Type of wrapped object
+ * @author Robert Varga
+ */
+@Beta
+@NonNullByDefault
+public abstract class ForwardingIdentityObject<T> extends ForwardingObject implements Delegator<T> {
+    protected ForwardingIdentityObject() {
+        // Mask public constructor
+    }
+
+    public static <T> ForwardingIdentityObject<T> of(final T obj) {
+        return checkedOf(requireNonNull(obj));
+    }
+
+    @Override
+    public final @NonNull T getDelegate() {
+        return delegate();
+    }
+
+    @Override
+    public final int hashCode() {
+        return System.identityHashCode(delegate());
+    }
+
+    @Override
+    public final boolean equals(final @Nullable Object obj) {
+        return obj == this || obj instanceof ForwardingIdentityObject
+                && delegate() == ((ForwardingIdentityObject<?>) obj).delegate();
+    }
+
+    @Override
+    protected abstract @NonNull T delegate();
+
+    private static <T> ForwardingIdentityObject<T> checkedOf(final @NonNull T delegate) {
+        return new ForwardingIdentityObject<T>() {
+            @Override
+            protected @NonNull T delegate() {
+                return delegate;
+            }
+        };
+    }
+}
index 68055fda957b3c2f7bde722765c2a119a475c86e..dd0bcb7c1ef3ebf0dba162a3e7f53bba691ea6ac 100644 (file)
@@ -26,6 +26,7 @@ import java.util.concurrent.locks.ReentrantLock;
 import java.util.stream.Collectors;
 import org.checkerframework.checker.lock.qual.GuardedBy;
 import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.yangtools.util.ForwardingIdentityObject;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -68,7 +69,15 @@ public final class QueuedNotificationManager<L, N> implements NotificationManage
     private static final long GIVE_UP_NANOS = TimeUnit.MINUTES.toNanos(MAX_NOTIFICATION_OFFER_MINUTES);
     private static final long TASK_WAIT_NANOS = TimeUnit.MILLISECONDS.toNanos(10);
 
-    private final ConcurrentMap<ListenerKey<L>, NotificationTask> listenerCache = new ConcurrentHashMap<>();
+    /**
+     * We key by listener reference identity hashCode/equals.
+     * Since we don't know anything about the listener class implementations and we're mixing
+     * multiple listener class instances in the same map, this avoids any potential issue with an
+     * equals implementation that just blindly casts the other Object to compare instead of checking
+     * for instanceof.
+     */
+    private final ConcurrentMap<ForwardingIdentityObject<L>, NotificationTask> listenerCache =
+            new ConcurrentHashMap<>();
     private final @NonNull QueuedNotificationManagerMXBean mxBean = new QueuedNotificationManagerMXBeanImpl(this);
     private final @NonNull BatchedInvoker<L, N> listenerInvoker;
     private final @NonNull Executor executor;
@@ -144,7 +153,7 @@ public final class QueuedNotificationManager<L, N> implements NotificationManage
 
         LOG.trace("{}: submitNotifications for listener {}: {}", name, listener, notifications);
 
-        final ListenerKey<L> key = new ListenerKey<>(listener);
+        final ForwardingIdentityObject<L> key = ForwardingIdentityObject.of(listener);
 
         // Keep looping until we are either able to add a new NotificationTask or are able to
         // add our notifications to an existing NotificationTask. Eventually one or the other
@@ -215,40 +224,6 @@ public final class QueuedNotificationManager<L, N> implements NotificationManage
         executor.execute(task);
     }
 
-    /**
-     * Used as the listenerCache map key. We key by listener reference identity hashCode/equals.
-     * Since we don't know anything about the listener class implementations and we're mixing
-     * multiple listener class instances in the same map, this avoids any potential issue with an
-     * equals implementation that just blindly casts the other Object to compare instead of checking
-     * for instanceof.
-     */
-    private static final class ListenerKey<L> {
-        private final @NonNull L listener;
-
-        ListenerKey(final L listener) {
-            this.listener = requireNonNull(listener);
-        }
-
-        @NonNull L getListener() {
-            return listener;
-        }
-
-        @Override
-        public int hashCode() {
-            return System.identityHashCode(listener);
-        }
-
-        @Override
-        public boolean equals(final Object obj) {
-            return obj == this || obj instanceof ListenerKey<?> && listener == ((ListenerKey<?>) obj).listener;
-        }
-
-        @Override
-        public String toString() {
-            return listener.toString();
-        }
-    }
-
     /**
      * Executor task for a single listener that queues notifications and sends them serially to the
      * listener.
@@ -257,14 +232,15 @@ public final class QueuedNotificationManager<L, N> implements NotificationManage
         private final Lock lock = new ReentrantLock();
         private final Condition notEmpty = lock.newCondition();
         private final Condition notFull = lock.newCondition();
-        private final @NonNull ListenerKey<L> listenerKey;
+        private final @NonNull ForwardingIdentityObject<L> listenerKey;
 
         @GuardedBy("lock")
         private final Queue<N> queue = new ArrayDeque<>();
         @GuardedBy("lock")
         private boolean exiting;
 
-        NotificationTask(final @NonNull ListenerKey<L> listenerKey, final @NonNull Iterator<N> notifications) {
+        NotificationTask(final @NonNull ForwardingIdentityObject<L> listenerKey,
+                final @NonNull Iterator<N> notifications) {
             this.listenerKey = requireNonNull(listenerKey);
             while (notifications.hasNext()) {
                 queue.add(notifications.next());
@@ -389,7 +365,7 @@ public final class QueuedNotificationManager<L, N> implements NotificationManage
         private void invokeListener(final @NonNull ImmutableList<N> notifications) {
             LOG.debug("{}: Invoking listener {} with notification: {}", name, listenerKey, notifications);
             try {
-                listenerInvoker.invokeListener(listenerKey.getListener(), notifications);
+                listenerInvoker.invokeListener(listenerKey.getDelegate(), notifications);
             } catch (Exception e) {
                 // We'll let a RuntimeException from the listener slide and keep sending any remaining notifications.
                 LOG.error("{}: Error notifying listener {} with {}", name, listenerKey, notifications, e);