--- /dev/null
+/*
+ * 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;
+ }
+ };
+ }
+}
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;
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;
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
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.
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());
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);