*/
package org.opendaylight.mdsal.dom.spi;
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.MoreObjects;
+import java.lang.ref.Reference;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.StampedLock;
import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.yangtools.concepts.AbstractRegistration;
+import org.opendaylight.yangtools.concepts.Identifiable;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* An abstract tree of registrations. Allows a read-only snapshot to be taken.
* @param <T> Type of registered object
*/
public abstract class AbstractRegistrationTree<T> {
- private final RegistrationTreeNode<T> rootNode = new RegistrationTreeNode<>(null, null);
- private final Lock writeLock;
- private final Lock readLock;
+ /**
+ * This is a single node within the registration tree. Note that the data returned from and instance of this class
+ * is guaranteed to have any relevance or consistency only as long as the {@link Snapshot} instance through which it
+ * is reached remains unclosed.
+ *
+ * @param <T> registration type
+ */
+ protected static final class Node<T> implements Identifiable<PathArgument> {
+ private static final Logger LOG = LoggerFactory.getLogger(Node.class);
+
+ private final Map<PathArgument, Node<T>> children = new HashMap<>();
+ private final List<T> registrations = new ArrayList<>(2);
+ private final List<T> publicRegistrations = Collections.unmodifiableList(registrations);
+ private final Reference<Node<T>> parent;
+ private final PathArgument identifier;
+
+ Node(final Node<T> parent, final PathArgument identifier) {
+ this.parent = new WeakReference<>(parent);
+ this.identifier = identifier;
+ }
+
+ @Override
+ public PathArgument getIdentifier() {
+ return identifier;
+ }
+
+ /**
+ * Return the child matching a {@link PathArgument} specification.
+ *
+ * @param arg Child identifier
+ * @return Child matching exactly, or {@code null}.
+ */
+ public @Nullable Node<T> getExactChild(final @NonNull PathArgument arg) {
+ return children.get(requireNonNull(arg));
+ }
+
+ /**
+ * Return a collection children which match a {@link PathArgument} specification inexactly.
+ * This explicitly excludes the child returned by {@link #getExactChild(PathArgument)}.
+ *
+ * @param arg Child identifier
+ * @return Collection of children, guaranteed to be non-null.
+ */
+ public @NonNull Collection<Node<T>> getInexactChildren(final @NonNull PathArgument arg) {
+ requireNonNull(arg);
+ if (arg instanceof NodeWithValue || arg instanceof NodeIdentifierWithPredicates) {
+ /*
+ * TODO: This just all-or-nothing wildcards, which we have historically supported. Given
+ * that the argument is supposed to have all the elements filled out, we could support
+ * partial wildcards by iterating over the registrations and matching the maps for
+ * partial matches.
+ */
+ final var child = children.get(new NodeIdentifier(arg.getNodeType()));
+ return child == null ? List.of() : Collections.singletonList(child);
+ }
+
+ return List.of();
+ }
+
+ public Collection<T> getRegistrations() {
+ return publicRegistrations;
+ }
+
+ @VisibleForTesting
+ @NonNull Node<T> ensureChild(final @NonNull PathArgument child) {
+ return children.computeIfAbsent(requireNonNull(child), key -> new Node<>(this, key));
+ }
+
+ @VisibleForTesting
+ void addRegistration(final @NonNull T registration) {
+ registrations.add(requireNonNull(registration));
+ LOG.debug("Registration {} added", registration);
+ }
+
+ @VisibleForTesting
+ void removeRegistration(final @NonNull T registration) {
+ if (registrations.remove(requireNonNull(registration))) {
+ LOG.debug("Registration {} removed", registration);
+
+ // We have been called with the write-lock held, so we can perform some cleanup.
+ removeThisIfUnused();
+ }
+ }
+
+ private void removeThisIfUnused() {
+ final var p = parent.get();
+ if (p != null && registrations.isEmpty() && children.isEmpty()) {
+ p.removeChild(identifier);
+ }
+ }
+
+ private void removeChild(final PathArgument arg) {
+ children.remove(arg);
+ removeThisIfUnused();
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("identifier", identifier)
+ .add("registrations", registrations.size())
+ .add("children", children.size())
+ .toString();
+ }
+ }
+
+ /**
+ * A stable read-only snapshot of a {@link AbstractRegistrationTree}.
+ */
+ @NonNullByDefault
+ protected static final class Snapshot<T> extends AbstractRegistration {
+ private final Node<T> node;
+ private final Lock lock;
+
+ Snapshot(final Lock lock, final Node<T> node) {
+ this.lock = requireNonNull(lock);
+ this.node = requireNonNull(node);
+ }
+
+ public Node<T> getRootNode() {
+ return node;
+ }
+
+ @Override
+ protected void removeRegistration() {
+ lock.unlock();
+ }
+ }
+
+ private final @NonNull Node<T> rootNode = new Node<>(null, null);
+ private final @NonNull Lock writeLock;
+ private final @NonNull Lock readLock;
protected AbstractRegistrationTree() {
- final StampedLock lock = new StampedLock();
+ final var lock = new StampedLock();
readLock = lock.asReadLock();
writeLock = lock.asWriteLock();
}
* @param path Path to find a node for
* @return A registration node for the specified path
*/
- protected final @NonNull RegistrationTreeNode<T> findNodeFor(final @NonNull Iterable<PathArgument> path) {
- RegistrationTreeNode<T> walkNode = rootNode;
- for (final PathArgument arg : path) {
+ protected final @NonNull Node<T> findNodeFor(final @NonNull Iterable<PathArgument> path) {
+ var walkNode = rootNode;
+ for (var arg : path) {
walkNode = walkNode.ensureChild(arg);
}
-
return walkNode;
}
* @param node Tree node
* @param registration Registration instance
*/
- protected final void addRegistration(final @NonNull RegistrationTreeNode<T> node, final @NonNull T registration) {
+ protected final void addRegistration(final @NonNull Node<T> node, final @NonNull T registration) {
node.addRegistration(registration);
}
* @param node Tree node
* @param registration Registration instance
*/
- protected final void removeRegistration(final @NonNull RegistrationTreeNode<T> node,
+ protected final void removeRegistration(final @NonNull Node<T> node,
final @NonNull T registration) {
// Take the write lock
writeLock.lock();
}
/**
- * Obtain a tree snapshot. This snapshot ensures a consistent view of
- * registrations. The snapshot should be closed as soon as it is not required,
- * because each unclosed instance blocks modification of this tree.
+ * Obtain a tree snapshot. This snapshot ensures a consistent view of registrations. The snapshot should be closed
+ * as soon as it is not required, because each unclosed instance blocks modification of this tree.
*
* @return A snapshot instance.
*/
- public final @NonNull RegistrationTreeSnapshot<T> takeSnapshot() {
+ protected final @NonNull Snapshot<T> takeSnapshot() {
readLock.lock();
- return new RegistrationTreeSnapshot<>(readLock, rootNode);
+ return new Snapshot<>(readLock, rootNode);
}
}
+++ /dev/null
-/*
- * Copyright (c) 2015 Cisco Systems, Inc. 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.mdsal.dom.spi;
-
-import static java.util.Objects.requireNonNull;
-
-import com.google.common.base.MoreObjects;
-import java.lang.ref.Reference;
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-import org.eclipse.jdt.annotation.NonNull;
-import org.opendaylight.yangtools.concepts.Identifiable;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * This is a single node within the registration tree. Note that the data returned from
- * and instance of this class is guaranteed to have any relevance or consistency
- * only as long as the {@link RegistrationTreeSnapshot} instance through which it is reached
- * remains unclosed.
- *
- * @param <T> registration type
- * @author Robert Varga
- */
-public final class RegistrationTreeNode<T> implements Identifiable<PathArgument> {
- private static final Logger LOG = LoggerFactory.getLogger(RegistrationTreeNode.class);
-
- private final Map<PathArgument, RegistrationTreeNode<T>> children = new HashMap<>();
- private final Collection<T> registrations = new ArrayList<>(2);
- private final Collection<T> publicRegistrations = Collections.unmodifiableCollection(registrations);
- private final Reference<RegistrationTreeNode<T>> parent;
- private final PathArgument identifier;
-
- RegistrationTreeNode(final RegistrationTreeNode<T> parent, final PathArgument identifier) {
- this.parent = new WeakReference<>(parent);
- this.identifier = identifier;
- }
-
- @Override
- public PathArgument getIdentifier() {
- return identifier;
- }
-
- /**
- * Return the child matching a {@link PathArgument} specification.
- *
- * @param arg Child identifier
- * @return Child matching exactly, or null.
- */
- public RegistrationTreeNode<T> getExactChild(final @NonNull PathArgument arg) {
- return children.get(requireNonNull(arg));
- }
-
- /**
- * Return a collection children which match a {@link PathArgument} specification inexactly.
- * This explicitly excludes the child returned by {@link #getExactChild(PathArgument)}.
- *
- * @param arg Child identifier
- * @return Collection of children, guaranteed to be non-null.
- */
- public @NonNull Collection<RegistrationTreeNode<T>> getInexactChildren(final @NonNull PathArgument arg) {
- requireNonNull(arg);
- if (arg instanceof NodeWithValue || arg instanceof NodeIdentifierWithPredicates) {
- /*
- * TODO: This just all-or-nothing wildcards, which we have historically supported. Given
- * that the argument is supposed to have all the elements filled out, we could support
- * partial wildcards by iterating over the registrations and matching the maps for
- * partial matches.
- */
- final RegistrationTreeNode<T> child = children.get(new NodeIdentifier(arg.getNodeType()));
- if (child == null) {
- return Collections.emptyList();
- }
-
- return Collections.singletonList(child);
- }
-
- return Collections.emptyList();
- }
-
- public Collection<T> getRegistrations() {
- return publicRegistrations;
- }
-
- RegistrationTreeNode<T> ensureChild(final @NonNull PathArgument child) {
- RegistrationTreeNode<T> potential = children.get(requireNonNull(child));
- if (potential == null) {
- potential = new RegistrationTreeNode<>(this, child);
- children.put(child, potential);
- }
- return potential;
- }
-
- void addRegistration(final @NonNull T registration) {
- registrations.add(requireNonNull(registration));
- LOG.debug("Registration {} added", registration);
- }
-
- void removeRegistration(final @NonNull T registration) {
- registrations.remove(requireNonNull(registration));
- LOG.debug("Registration {} removed", registration);
-
- // We have been called with the write-lock held, so we can perform some cleanup.
- removeThisIfUnused();
- }
-
- private void removeThisIfUnused() {
- final RegistrationTreeNode<T> p = parent.get();
- if (p != null && registrations.isEmpty() && children.isEmpty()) {
- p.removeChild(identifier);
- }
- }
-
- private void removeChild(final PathArgument arg) {
- children.remove(arg);
- removeThisIfUnused();
- }
-
- @Override
- public String toString() {
- return MoreObjects.toStringHelper(this)
- .add("identifier", identifier)
- .add("registrations", registrations.size())
- .add("children", children.size()).toString();
- }
-}
+++ /dev/null
-/*
- * Copyright (c) 2015 Cisco Systems, Inc. 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.mdsal.dom.spi;
-
-import static java.util.Objects.requireNonNull;
-
-import java.util.concurrent.locks.Lock;
-import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.opendaylight.yangtools.concepts.AbstractRegistration;
-
-/**
- * A stable read-only snapshot of a {@link AbstractRegistrationTree}.
- *
- * @author Robert Varga
- */
-@NonNullByDefault
-public final class RegistrationTreeSnapshot<T> extends AbstractRegistration {
- private final RegistrationTreeNode<T> node;
- private final Lock lock;
-
- RegistrationTreeSnapshot(final Lock lock, final RegistrationTreeNode<T> node) {
- this.lock = requireNonNull(lock);
- this.node = requireNonNull(node);
- }
-
- public RegistrationTreeNode<T> getRootNode() {
- return node;
- }
-
- @Override
- protected void removeRegistration() {
- lock.unlock();
- }
-}
import org.opendaylight.mdsal.dom.api.DOMDataTreeChangeListener;
import org.opendaylight.mdsal.dom.spi.AbstractDOMDataTreeChangeListenerRegistration;
import org.opendaylight.mdsal.dom.spi.AbstractRegistrationTree;
-import org.opendaylight.mdsal.dom.spi.RegistrationTreeNode;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
import org.opendaylight.yangtools.yang.data.tree.api.DataTreeCandidate;
// Take the write lock
takeLock();
try {
- final RegistrationTreeNode<AbstractDOMDataTreeChangeListenerRegistration<?>> node =
+ final Node<AbstractDOMDataTreeChangeListenerRegistration<?>> node =
findNodeFor(treeId.getPathArguments());
final var reg = new AbstractDOMDataTreeChangeListenerRegistration<>(listener) {
@Override
}
private void lookupAndNotify(final List<PathArgument> args,
- final int offset, final RegistrationTreeNode<AbstractDOMDataTreeChangeListenerRegistration<?>> node,
+ final int offset, final Node<AbstractDOMDataTreeChangeListenerRegistration<?>> node,
final DataTreeCandidate candidate,
final Multimap<AbstractDOMDataTreeChangeListenerRegistration<?>, DataTreeCandidate> listenerChanges) {
if (args.size() != offset) {
final PathArgument arg = args.get(offset);
- final RegistrationTreeNode<AbstractDOMDataTreeChangeListenerRegistration<?>> exactChild
+ final Node<AbstractDOMDataTreeChangeListenerRegistration<?>> exactChild
= node.getExactChild(arg);
if (exactChild != null) {
lookupAndNotify(args, offset + 1, exactChild, candidate, listenerChanges);
}
- for (RegistrationTreeNode<AbstractDOMDataTreeChangeListenerRegistration<?>> c :
+ for (Node<AbstractDOMDataTreeChangeListenerRegistration<?>> c :
node.getInexactChildren(arg)) {
lookupAndNotify(args, offset + 1, c, candidate, listenerChanges);
}
}
private void notifyNode(final YangInstanceIdentifier path,
- final RegistrationTreeNode<AbstractDOMDataTreeChangeListenerRegistration<?>> regNode,
+ final Node<AbstractDOMDataTreeChangeListenerRegistration<?>> regNode,
final DataTreeCandidateNode candNode,
final Multimap<AbstractDOMDataTreeChangeListenerRegistration<?>, DataTreeCandidate> listenerChanges) {
if (candNode.modificationType() == ModificationType.UNMODIFIED) {
@Test
public void basicTest() throws Exception {
final NodeIdentifier pathArgument = new NodeIdentifier(QName.create("", "pathArgument"));
- final RegistrationTreeNode<Object> registrationTreeNodeParent = new RegistrationTreeNode<>(null, pathArgument);
- final RegistrationTreeNode<Object> registrationTreeNode =
- new RegistrationTreeNode<>(registrationTreeNodeParent, pathArgument);
+ final Node<Object> registrationTreeNodeParent = new Node<>(null, pathArgument);
+ final Node<Object> registrationTreeNode =
+ new Node<>(registrationTreeNodeParent, pathArgument);
final Object registration = new Object();
takeLock();
*/
package org.opendaylight.mdsal.dom.spi;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.List;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
+import org.opendaylight.mdsal.dom.spi.AbstractRegistrationTree.Node;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
-public class RegistrationTreeNodeTest {
+class RegistrationTreeNodeTest {
@Test
- public void basicTest() throws Exception {
- final NodeIdentifier pathArgument = new NodeIdentifier(QName.create("", "pathArgument"));
- final RegistrationTreeNode<Object> registrationTreeNodeParent = new RegistrationTreeNode<>(null, pathArgument);
- final RegistrationTreeNode<Object> registrationTreeNode =
- new RegistrationTreeNode<>(registrationTreeNodeParent, pathArgument);
+ void basicTest() {
+ final var pathArgument = new NodeIdentifier(QName.create("", "pathArgument"));
+ final var registrationTreeNodeParent = new Node<>(null, pathArgument);
+ final var registrationTreeNode = new Node<>(registrationTreeNodeParent, pathArgument);
assertEquals(pathArgument, registrationTreeNode.getIdentifier());
- final Object registration = new Object();
- assertFalse(registrationTreeNode.getRegistrations().contains(registration));
+ final var registration = new Object();
+ final var registrations = registrationTreeNode.getRegistrations();
+ assertEquals(List.of(), registrations);
registrationTreeNode.addRegistration(registration);
- assertTrue(registrationTreeNode.getRegistrations().contains(registration));
+ assertEquals(List.of(registration), registrations);
registrationTreeNode.removeRegistration(registration);
- assertFalse(registrationTreeNode.getRegistrations().contains(registration));
+ assertEquals(List.of(), registrations);
registrationTreeNode.removeRegistration(registration);
+ assertEquals(List.of(), registrations);
assertNotNull(registrationTreeNode.ensureChild(pathArgument));
assertNotNull(registrationTreeNode.getExactChild(pathArgument));
- final NodeWithValue<?> nodeWithValue = new NodeWithValue<>(QName.create("", "testNode"), new Object());
+ final var nodeWithValue = new NodeWithValue<>(QName.create("", "testNode"), new Object());
assertEquals(List.of(), registrationTreeNode.getInexactChildren(nodeWithValue));
assertEquals(List.of(), registrationTreeNode.getInexactChildren(pathArgument));
- final NodeIdentifier nodeWithoutValue = new NodeIdentifier(QName.create("", "testNode"));
+ final var nodeWithoutValue = new NodeIdentifier(QName.create("", "testNode"));
assertNotNull(registrationTreeNode.ensureChild(nodeWithoutValue));
assertFalse(registrationTreeNode.getInexactChildren(nodeWithValue).isEmpty());
*/
package org.opendaylight.mdsal.dom.spi;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
import java.util.concurrent.locks.Lock;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.opendaylight.mdsal.dom.spi.AbstractRegistrationTree.Node;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
-public class RegistrationTreeSnapshotTest {
- @Test
- public void basicTest() throws Exception {
- final Lock lock = mock(Lock.class);
- final NodeIdentifier pathArgument = new NodeIdentifier(QName.create("", "pathArgument"));
- final RegistrationTreeNode<?> registrationTreeNode = new RegistrationTreeNode<>(null, pathArgument);
- final RegistrationTreeSnapshot<?> registrationTreeSnapshot =
- new RegistrationTreeSnapshot<>(lock, registrationTreeNode);
-
- assertNotNull(registrationTreeSnapshot.getRootNode());
- assertEquals(registrationTreeNode, registrationTreeSnapshot.getRootNode());
+@ExtendWith(MockitoExtension.class)
+class RegistrationTreeSnapshotTest {
+ @Mock
+ private Lock lock;
- doNothing().when(lock).unlock();
- registrationTreeSnapshot.close();
- verify(lock).unlock();
+ @Test
+ void basicTest() throws Exception {
+ final var pathArgument = new NodeIdentifier(QName.create("", "pathArgument"));
+ final var registrationTreeNode = new Node<>(null, pathArgument);
+ try (var registrationTreeSnapshot = new AbstractRegistrationTree.Snapshot<>(lock, registrationTreeNode)) {
+ assertNotNull(registrationTreeSnapshot.getRootNode());
+ assertEquals(registrationTreeNode, registrationTreeSnapshot.getRootNode());
+ doNothing().when(lock).unlock();
+ }
}
}
\ No newline at end of file