Package co.paralleluniverse.galaxy.cluster

Source Code of co.paralleluniverse.galaxy.cluster.DistributedBranchHelper$Node

/*
* Galaxy
* Copyright (c) 2012-2014, Parallel Universe Software Co. All rights reserved.
*
* This program and the accompanying materials are dual-licensed under
* either the terms of the Eclipse Public License v1.0 as published by
* the Eclipse Foundation
*   or (per the licensee's choosing)
* under the terms of the GNU Lesser General Public License version 3.0
* as published by the Free Software Foundation.
*/
package co.paralleluniverse.galaxy.cluster;

import co.paralleluniverse.common.collection.LinkedHashMap2;
import co.paralleluniverse.galaxy.cluster.DistributedTree.Listener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Helps deal with a branch of a distributed tree, when we want to consider the nodes only when they have given sub-nodes ("required properties").
* <p/>
* If the underlying nodes are ordered and the helper is constructed with the ordered option, the handlers are called in the order the main nodes
* (the branch's children) were originally created, not the order in which they've become "completed" (their required properties satisfied)! <b>Important:</b> Assumes that once required properties are
* set they are never removed or unset (set to null), though they can be changed.
*
* @author pron
*/
public abstract class DistributedBranchHelper {
    private static final Logger LOG = LoggerFactory.getLogger(DistributedBranchHelper.class);
    private final DistributedTree tree;
    private final String branchRoot;
    private final boolean ordered;
    private final LinkedHashMap2<String, Node> nodes = new LinkedHashMap2<String, Node>();
    private final List<Listener> listeners = new CopyOnWriteArrayList<Listener>();
    private boolean doneInit = false;

    public DistributedBranchHelper(DistributedTree tree, final String branchRoot, boolean ordered) {
        if (!branchRoot.startsWith("/"))
            throw new IllegalArgumentException("Branch root must start with a '/', but is " + branchRoot);
        this.tree = tree;
        this.branchRoot = branchRoot;
        this.ordered = ordered;

        tree.addListener(branchRoot, new Listener() {
            @Override
            public void nodeChildAdded(String node, String childName) {
                assert node.equals(branchRoot);
                DistributedBranchHelper.this.nodeAdded(childName);
            }

            @Override
            public void nodeChildDeleted(String node, String childName) {
                assert node.equals(branchRoot);
                DistributedBranchHelper.this.nodeRemoved(childName);
            }

            @Override
            public void nodeChildUpdated(String node, String childName) {
                assert node.equals(branchRoot);
                DistributedBranchHelper.this.nodeUpdated(childName);
            }

            @Override
            public void nodeUpdated(String node) {
                assert node.equals(branchRoot);
                for (Listener listener : listeners)
                    listener.nodeUpdated(node);
            }

            @Override
            public void nodeAdded(String node) {
                assert node.equals(branchRoot);
                for (Listener listener : listeners)
                    listener.nodeAdded(node);
            }

            @Override
            public void nodeDeleted(String node) {
                assert node.equals(branchRoot);
                for (Listener listener : listeners)
                    listener.nodeDeleted(node);
            }
        });
    }

    public final void init() {
        synchronized (nodes) {

            if (ordered) {
                final Node first = getFirstNode();
                if (first != null)
                    first.predecessorsComplete();
            }
            doneInit = true;
            nodes.notifyAll();
        }
    }

    public void addListener(Listener listener) {
        synchronized (nodes) {
            for (Node node : nodes.values()) {
                if (node.isComplete())
                    listener.nodeChildAdded(branchRoot, node.name);
                else if (ordered)
                    break;
            }
            listeners.add(listener);
        }
    }

    public void removeListener(Listener listener) {
        listeners.remove(listener);
    }

    public List<String> getChildren() {
        final List<String> children = new ArrayList<String>();
        synchronized (nodes) {
            for (Node node : nodes.values()) {
                if (!node.isComplete())
                    break;
                children.add(node.name);
            }
        }
        return Collections.unmodifiableList(children);
    }

    private void nodeAdded(String name) {
        synchronized (nodes) {
            // by re-inserting existent nodes, we take care of race conditions between the handler and the getChildren call in the constructor
            // we make sure that the order is that of getChildren

            Node node = nodes.remove(name);
            if (node == null)
                node = new Node(name);
            nodes.put(name, node);

            LOG.debug("Node {} added.", name);

            if (ordered) {
                final Node previous = getPreviousNode(name);
                if ((previous == null && doneInit) || (previous != null && previous.isComplete()))
                    node.predecessorsComplete();
            }
        }
    }

    private void nodeCompleted(String name) {
        LOG.debug("Node {} completed", name);
        for (Listener listener : listeners)
            listener.nodeChildAdded(branchRoot, name);

        if (ordered) {
            synchronized (nodes) {
                final Node next = getNextNode(name);
                if (next != null)
                    next.predecessorsComplete(); // possibly sets off recursion
            }
        }
    }

    private void nodeUpdated(String name) {
        try {
            final Node node;
            synchronized (nodes) {
                while (!doneInit)
                    nodes.wait();

                node = nodes.get(name);
            }
            assert node != null;
            LOG.debug("Node {} updated. ({})", name, node.isComplete() ? "complete" : "incomplete");
            if (node.isComplete()) {
                for (Listener listener : listeners)
                    listener.nodeChildUpdated(branchRoot, name);
            }
        } catch (InterruptedException e) {
            throw new RuntimeException();
        }
    }

    private void nodeRemoved(String name) {
        try {
            boolean _doneInit;
            final Node node;
            Node previous = null, next = null;
            synchronized (nodes) {
                while (!doneInit)
                    nodes.wait();

                if (ordered) {
                    previous = nodes.previousValue(name);
                    next = nodes.nextValue(name);
                }
                node = nodes.remove(name);
                _doneInit = this.doneInit;
            }
            if (node.isComplete()) {
                for (Listener listener : listeners)
                    listener.nodeChildDeleted(branchRoot, name);
            }
            if (ordered) {
                if (_doneInit && next != null && (previous == null || previous.isComplete()))
                    next.predecessorsComplete();
            }
        } catch (InterruptedException e) {
            throw new RuntimeException();
        }
    }

    private Node getNextNode(String node) {
        assert ordered;
        synchronized (nodes) {
            final Map.Entry<String, Node> entry = nodes.nextEntry(node);
            return (entry != null ? entry.getValue() : null);
        }
    }

    private Node getPreviousNode(String node) {
        assert ordered;
        synchronized (nodes) {
            final Map.Entry<String, Node> entry = nodes.previousEntry(node);
            return (entry != null ? entry.getValue() : null);
        }
    }

    private Node getFirstNode() {
        assert ordered;
        synchronized (nodes) {
            final Map.Entry<String, Node> entry = nodes.firstEntry();
            return (entry != null ? entry.getValue() : null);
        }
    }

    protected abstract boolean isNodeComplete(String node, Set<String> properties);

    private class Node extends DistributedTree.ListenerAdapter {
        final String name;
        private final String treePath;
        private final Set<String> properties = new HashSet<String>();
        private boolean predecessorsComplete;
        private boolean complete = false;

        public Node(String name) {
            this.name = name;
            this.treePath = branchRoot + '/' + name;
            this.predecessorsComplete = !ordered;
            tree.addListener(treePath, this);
            for (String property : tree.getChildren(treePath))
                testProperty(property);
        }

        public synchronized boolean isComplete() {
            return complete;
        }

        public void predecessorsComplete() {
            assert ordered;
            synchronized (this) {
                if (predecessorsComplete)
                    return;
                predecessorsComplete = true;
            }
            testComplete();
        }

        private void testProperty(String property) {
            LOG.debug("Test property for {}: {}", name, property);
            synchronized (this) {
                if (tree.get(treePath + '/' + property) == null)
                    return;
                properties.add(property);
            }
            testComplete();
        }

        private void testComplete() {
            synchronized (this) {
                if (complete)
                    return;
                if (!predecessorsComplete || !isNodeComplete(treePath, properties))
                    return;
                complete = true;
                tree.removeListener(treePath, this);
                LOG.debug("Node {} is now complete!", name);
            }
            nodeCompleted(name); // must be called outside the sync block because it syncs on nodes - might cause deadlock
        }

        @Override
        public void nodeChildAdded(String node, String childName) {
            nodeChildUpdated(node, childName); // possibly wasteful. consider.
        }

        @Override
        public void nodeChildUpdated(String node, String childName) {
            assert node.equals(treePath);
            testProperty(childName);
        }
    }
}
TOP

Related Classes of co.paralleluniverse.galaxy.cluster.DistributedBranchHelper$Node

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.