All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opendaylight.mdsal.dom.spi.AbstractRegistrationTree Maven / Gradle / Ivy

The newest version!
/*
 * 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.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  Type of registered object
 */
public abstract class AbstractRegistrationTree {
    /**
     * 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  registration type
     */
    protected static final class Node implements Identifiable {
        private static final Logger LOG = LoggerFactory.getLogger(Node.class);

        private final Map> children = new HashMap<>();
        private final List registrations = new ArrayList<>(2);
        private final List publicRegistrations = Collections.unmodifiableList(registrations);
        private final Reference> parent;
        private final PathArgument identifier;

        Node(final Node 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 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> 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 getRegistrations() {
            return publicRegistrations;
        }

        @VisibleForTesting
        @NonNull Node 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 extends AbstractRegistration {
        private final Node node;
        private final Lock lock;

        Snapshot(final Lock lock, final Node node) {
            this.lock = requireNonNull(lock);
            this.node = requireNonNull(node);
        }

        public Node getRootNode() {
            return node;
        }

        @Override
        protected void removeRegistration() {
            lock.unlock();
        }
    }

    private final @NonNull Node rootNode = new Node<>(null, null);
    private final @NonNull Lock writeLock;
    private final @NonNull Lock readLock;

    protected AbstractRegistrationTree() {
        final var lock = new StampedLock();
        readLock = lock.asReadLock();
        writeLock = lock.asWriteLock();
    }

    /**
     * Acquire the read-write lock. This should be done before invoking {@link #findNodeFor(Iterable)}. This method
     * must not be called when the lock is already held by this thread.
     */
    protected final void takeLock() {
        writeLock.lock();
    }

    /**
     * Release the read-write lock. This should be done after invocation of {@link #findNodeFor(Iterable)}
     * and modification of the returned node. Note that callers should do so in a finally block.
     */
    protected final void releaseLock() {
        writeLock.unlock();
    }

    /**
     * Find an existing, or allocate a fresh, node for a particular path. Must be called with the lock held.
     *
     * @param path Path to find a node for
     * @return A registration node for the specified path
     */
    protected final @NonNull Node findNodeFor(final @NonNull Iterable path) {
        var walkNode = rootNode;
        for (var arg : path) {
            walkNode = walkNode.ensureChild(arg);
        }
        return walkNode;
    }

    /**
     * Add a registration to a particular node. The node must have been returned via {@link #findNodeFor(Iterable)}
     * and the lock must still be held.
     *
     * @param node Tree node
     * @param registration Registration instance
     */
    protected final void addRegistration(final @NonNull Node node, final @NonNull T registration) {
        node.addRegistration(registration);
    }

    /**
     * Remove a registration from a particular node. This method must not be called while the lock is held.
     *
     * @param node Tree node
     * @param registration Registration instance
     */
    protected final void removeRegistration(final @NonNull Node node,
            final @NonNull T registration) {
        // Take the write lock
        writeLock.lock();
        try {
            node.removeRegistration(registration);
        } finally {
            // Always release the lock
            writeLock.unlock();
        }
    }

    /**
     * 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.
     */
    protected final @NonNull Snapshot takeSnapshot() {
        readLock.lock();
        return new Snapshot<>(readLock, rootNode);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy