2 * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.mdsal.dom.spi;
10 import static java.util.Objects.requireNonNull;
12 import com.google.common.annotations.VisibleForTesting;
13 import com.google.common.base.MoreObjects;
14 import java.lang.ref.Reference;
15 import java.lang.ref.WeakReference;
16 import java.util.ArrayList;
17 import java.util.Collection;
18 import java.util.Collections;
19 import java.util.HashMap;
20 import java.util.List;
22 import java.util.concurrent.locks.Lock;
23 import java.util.concurrent.locks.StampedLock;
24 import org.eclipse.jdt.annotation.NonNull;
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.opendaylight.yangtools.concepts.AbstractRegistration;
28 import org.opendaylight.yangtools.concepts.Identifiable;
29 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
30 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
31 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeWithValue;
32 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
33 import org.slf4j.Logger;
34 import org.slf4j.LoggerFactory;
37 * An abstract tree of registrations. Allows a read-only snapshot to be taken.
39 * @param <T> Type of registered object
41 public abstract class AbstractRegistrationTree<T> {
43 * This is a single node within the registration tree. Note that the data returned from and instance of this class
44 * is guaranteed to have any relevance or consistency only as long as the {@link Snapshot} instance through which it
45 * is reached remains unclosed.
47 * @param <T> registration type
49 protected static final class Node<T> implements Identifiable<PathArgument> {
50 private static final Logger LOG = LoggerFactory.getLogger(Node.class);
52 private final Map<PathArgument, Node<T>> children = new HashMap<>();
53 private final List<T> registrations = new ArrayList<>(2);
54 private final List<T> publicRegistrations = Collections.unmodifiableList(registrations);
55 private final Reference<Node<T>> parent;
56 private final PathArgument identifier;
58 Node(final Node<T> parent, final PathArgument identifier) {
59 this.parent = new WeakReference<>(parent);
60 this.identifier = identifier;
64 public PathArgument getIdentifier() {
69 * Return the child matching a {@link PathArgument} specification.
71 * @param arg Child identifier
72 * @return Child matching exactly, or {@code null}.
74 public @Nullable Node<T> getExactChild(final @NonNull PathArgument arg) {
75 return children.get(requireNonNull(arg));
79 * Return a collection children which match a {@link PathArgument} specification inexactly.
80 * This explicitly excludes the child returned by {@link #getExactChild(PathArgument)}.
82 * @param arg Child identifier
83 * @return Collection of children, guaranteed to be non-null.
85 public @NonNull Collection<Node<T>> getInexactChildren(final @NonNull PathArgument arg) {
87 if (arg instanceof NodeWithValue || arg instanceof NodeIdentifierWithPredicates) {
89 * TODO: This just all-or-nothing wildcards, which we have historically supported. Given
90 * that the argument is supposed to have all the elements filled out, we could support
91 * partial wildcards by iterating over the registrations and matching the maps for
94 final var child = children.get(new NodeIdentifier(arg.getNodeType()));
95 return child == null ? List.of() : Collections.singletonList(child);
101 public Collection<T> getRegistrations() {
102 return publicRegistrations;
106 @NonNull Node<T> ensureChild(final @NonNull PathArgument child) {
107 return children.computeIfAbsent(requireNonNull(child), key -> new Node<>(this, key));
111 void addRegistration(final @NonNull T registration) {
112 registrations.add(requireNonNull(registration));
113 LOG.debug("Registration {} added", registration);
117 void removeRegistration(final @NonNull T registration) {
118 if (registrations.remove(requireNonNull(registration))) {
119 LOG.debug("Registration {} removed", registration);
121 // We have been called with the write-lock held, so we can perform some cleanup.
122 removeThisIfUnused();
126 private void removeThisIfUnused() {
127 final var p = parent.get();
128 if (p != null && registrations.isEmpty() && children.isEmpty()) {
129 p.removeChild(identifier);
133 private void removeChild(final PathArgument arg) {
134 children.remove(arg);
135 removeThisIfUnused();
139 public String toString() {
140 return MoreObjects.toStringHelper(this)
141 .add("identifier", identifier)
142 .add("registrations", registrations.size())
143 .add("children", children.size())
149 * A stable read-only snapshot of a {@link AbstractRegistrationTree}.
152 protected static final class Snapshot<T> extends AbstractRegistration {
153 private final Node<T> node;
154 private final Lock lock;
156 Snapshot(final Lock lock, final Node<T> node) {
157 this.lock = requireNonNull(lock);
158 this.node = requireNonNull(node);
161 public Node<T> getRootNode() {
166 protected void removeRegistration() {
171 private final @NonNull Node<T> rootNode = new Node<>(null, null);
172 private final @NonNull Lock writeLock;
173 private final @NonNull Lock readLock;
175 protected AbstractRegistrationTree() {
176 final var lock = new StampedLock();
177 readLock = lock.asReadLock();
178 writeLock = lock.asWriteLock();
182 * Acquire the read-write lock. This should be done before invoking {@link #findNodeFor(Iterable)}. This method
183 * must not be called when the lock is already held by this thread.
185 protected final void takeLock() {
190 * Release the read-write lock. This should be done after invocation of {@link #findNodeFor(Iterable)}
191 * and modification of the returned node. Note that callers should do so in a finally block.
193 protected final void releaseLock() {
198 * Find an existing, or allocate a fresh, node for a particular path. Must be called with the lock held.
200 * @param path Path to find a node for
201 * @return A registration node for the specified path
203 protected final @NonNull Node<T> findNodeFor(final @NonNull Iterable<PathArgument> path) {
204 var walkNode = rootNode;
205 for (var arg : path) {
206 walkNode = walkNode.ensureChild(arg);
212 * Add a registration to a particular node. The node must have been returned via {@link #findNodeFor(Iterable)}
213 * and the lock must still be held.
215 * @param node Tree node
216 * @param registration Registration instance
218 protected final void addRegistration(final @NonNull Node<T> node, final @NonNull T registration) {
219 node.addRegistration(registration);
223 * Remove a registration from a particular node. This method must not be called while the lock is held.
225 * @param node Tree node
226 * @param registration Registration instance
228 protected final void removeRegistration(final @NonNull Node<T> node,
229 final @NonNull T registration) {
230 // Take the write lock
233 node.removeRegistration(registration);
235 // Always release the lock
241 * Obtain a tree snapshot. This snapshot ensures a consistent view of registrations. The snapshot should be closed
242 * as soon as it is not required, because each unclosed instance blocks modification of this tree.
244 * @return A snapshot instance.
246 protected final @NonNull Snapshot<T> takeSnapshot() {
248 return new Snapshot<>(readLock, rootNode);