Clean up AbstractRegistrationTree
[mdsal.git] / dom / mdsal-dom-spi / src / main / java / org / opendaylight / mdsal / dom / spi / AbstractRegistrationTree.java
1 /*
2  * Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.mdsal.dom.spi;
9
10 import static java.util.Objects.requireNonNull;
11
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;
21 import java.util.Map;
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;
35
36 /**
37  * An abstract tree of registrations. Allows a read-only snapshot to be taken.
38  *
39  * @param <T> Type of registered object
40  */
41 public abstract class AbstractRegistrationTree<T> {
42     /**
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.
46      *
47      * @param <T> registration type
48      */
49     protected static final class Node<T> implements Identifiable<PathArgument> {
50         private static final Logger LOG = LoggerFactory.getLogger(Node.class);
51
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;
57
58         Node(final Node<T> parent, final PathArgument identifier) {
59             this.parent = new WeakReference<>(parent);
60             this.identifier = identifier;
61         }
62
63         @Override
64         public PathArgument getIdentifier() {
65             return identifier;
66         }
67
68         /**
69          * Return the child matching a {@link PathArgument} specification.
70          *
71          * @param arg Child identifier
72          * @return Child matching exactly, or {@code null}.
73          */
74         public @Nullable Node<T> getExactChild(final @NonNull PathArgument arg) {
75             return children.get(requireNonNull(arg));
76         }
77
78         /**
79          * Return a collection children which match a {@link PathArgument} specification inexactly.
80          * This explicitly excludes the child returned by {@link #getExactChild(PathArgument)}.
81          *
82          * @param arg Child identifier
83          * @return Collection of children, guaranteed to be non-null.
84          */
85         public @NonNull Collection<Node<T>> getInexactChildren(final @NonNull PathArgument arg) {
86             requireNonNull(arg);
87             if (arg instanceof NodeWithValue || arg instanceof NodeIdentifierWithPredicates) {
88                 /*
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
92                  *       partial matches.
93                  */
94                 final var child = children.get(new NodeIdentifier(arg.getNodeType()));
95                 return child == null ? List.of() : Collections.singletonList(child);
96             }
97
98             return List.of();
99         }
100
101         public Collection<T> getRegistrations() {
102             return publicRegistrations;
103         }
104
105         @VisibleForTesting
106         @NonNull Node<T> ensureChild(final @NonNull PathArgument child) {
107             return children.computeIfAbsent(requireNonNull(child), key -> new Node<>(this, key));
108         }
109
110         @VisibleForTesting
111         void addRegistration(final @NonNull T registration) {
112             registrations.add(requireNonNull(registration));
113             LOG.debug("Registration {} added", registration);
114         }
115
116         @VisibleForTesting
117         void removeRegistration(final @NonNull T registration) {
118             if (registrations.remove(requireNonNull(registration))) {
119                 LOG.debug("Registration {} removed", registration);
120
121                 // We have been called with the write-lock held, so we can perform some cleanup.
122                 removeThisIfUnused();
123             }
124         }
125
126         private void removeThisIfUnused() {
127             final var p = parent.get();
128             if (p != null && registrations.isEmpty() && children.isEmpty()) {
129                 p.removeChild(identifier);
130             }
131         }
132
133         private void removeChild(final PathArgument arg) {
134             children.remove(arg);
135             removeThisIfUnused();
136         }
137
138         @Override
139         public String toString() {
140             return MoreObjects.toStringHelper(this)
141                 .add("identifier", identifier)
142                 .add("registrations", registrations.size())
143                 .add("children", children.size())
144                 .toString();
145         }
146     }
147
148     /**
149      * A stable read-only snapshot of a {@link AbstractRegistrationTree}.
150      */
151     @NonNullByDefault
152     protected static final class Snapshot<T> extends AbstractRegistration {
153         private final Node<T> node;
154         private final Lock lock;
155
156         Snapshot(final Lock lock, final Node<T> node) {
157             this.lock = requireNonNull(lock);
158             this.node = requireNonNull(node);
159         }
160
161         public Node<T> getRootNode() {
162             return node;
163         }
164
165         @Override
166         protected void removeRegistration() {
167             lock.unlock();
168         }
169     }
170
171     private final @NonNull Node<T> rootNode = new Node<>(null, null);
172     private final @NonNull Lock writeLock;
173     private final @NonNull Lock readLock;
174
175     protected AbstractRegistrationTree() {
176         final var lock = new StampedLock();
177         readLock = lock.asReadLock();
178         writeLock = lock.asWriteLock();
179     }
180
181     /**
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.
184      */
185     protected final void takeLock() {
186         writeLock.lock();
187     }
188
189     /**
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.
192      */
193     protected final void releaseLock() {
194         writeLock.unlock();
195     }
196
197     /**
198      * Find an existing, or allocate a fresh, node for a particular path. Must be called with the lock held.
199      *
200      * @param path Path to find a node for
201      * @return A registration node for the specified path
202      */
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);
207         }
208         return walkNode;
209     }
210
211     /**
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.
214      *
215      * @param node Tree node
216      * @param registration Registration instance
217      */
218     protected final void addRegistration(final @NonNull Node<T> node, final @NonNull T registration) {
219         node.addRegistration(registration);
220     }
221
222     /**
223      * Remove a registration from a particular node. This method must not be called while the lock is held.
224      *
225      * @param node Tree node
226      * @param registration Registration instance
227      */
228     protected final void removeRegistration(final @NonNull Node<T> node,
229             final @NonNull T registration) {
230         // Take the write lock
231         writeLock.lock();
232         try {
233             node.removeRegistration(registration);
234         } finally {
235             // Always release the lock
236             writeLock.unlock();
237         }
238     }
239
240     /**
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.
243      *
244      * @return A snapshot instance.
245      */
246     protected final @NonNull Snapshot<T> takeSnapshot() {
247         readLock.lock();
248         return new Snapshot<>(readLock, rootNode);
249     }
250 }