org.eclipse.virgo.util.common.ThreadSafeGraphNode Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of org.eclipse.virgo.util.common Show documentation
Show all versions of org.eclipse.virgo.util.common Show documentation
Virgo from EclipseRT is a completely module-based Java application server
The newest version!
/*******************************************************************************
* Copyright (c) 2011 VMware 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
*
* Contributors:
* VMware Inc. - initial contribution (ThreadSafeArrayListTree.java)
* EclipseSource - reworked from generic tree to DAG (Bug 358697)
*******************************************************************************/
package org.eclipse.virgo.util.common;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* {@link GraphNode} is a node in a {@link DirectedAcyclicGraph}. Each node has a value.
*
*
* Concurrent Semantics
*
* This class is thread safe.
*
* @param type of values in the graph
*/
class ThreadSafeGraphNode implements GraphNode {
private final V value;
private final Object monitor;
private static final Object tieMonitor = new Object();
private final List> children = new ArrayList>();
private final List> parents = new ArrayList>();
/**
* Construct a {@link ThreadSafeGraphNode} with the given value, which may be null
.
*
* @param value the value of the node, which may be null
* @param monitor the shared monitor of the graph
*/
ThreadSafeGraphNode(V value, Object monitor) {
this.value = value;
this.monitor = monitor;
}
/**
* {@inheritDoc}
*/
@Override
public V getValue() {
return this.value;
}
/**
* Returns a list of this node's children (not copies of the children). If the node has no children, returns an
* empty list. Never returns null
.
*
* The returned list is synchronized to preserve thread safety, but may still result in
* ConcurrentModificationException being thrown.
*
* @return this node's children
*/
@Override
public List> getChildren() {
synchronized (this.monitor) {
return new SynchronizedList>(this.children, this.monitor);
}
}
/**
* Adds the given node as child to this node. The child node is not copied.
*
* @param child the node to add
* @throws IllegalArgumentException if the given node does not belong to the same {@link DirectedAcyclicGraph}.
* @throws IllegalArgumentException if the given node is already a child of this node.
* @throws IllegalArgumentException if the given node is not a {@link ThreadSafeGraphNode}.
*/
@Override
public void addChild(GraphNode child) {
ThreadSafeGraphNode concreteChild = assertTypeAndMembership(child);
synchronized (this.monitor) {
Assert.isFalse(this.children.contains(child), "The node '%s' is already a child of '%s'", child, this);
doCycleCheck(concreteChild);
this.children.add(concreteChild);
concreteChild.parents.add(this);
}
}
private ThreadSafeGraphNode assertTypeAndMembership(GraphNode child) {
Assert.isInstanceOf(ThreadSafeGraphNode.class, child, "A child must be of type %s.", this.getClass().getName());
ThreadSafeGraphNode concreteChild = (ThreadSafeGraphNode) child;
Assert.isTrue(belongsToSameGraph(concreteChild), "The node '%s' does not belong to the same graph as '%s'", concreteChild, this);
return concreteChild;
}
private boolean belongsToSameGraph(ThreadSafeGraphNode other) {
return this.monitor == other.monitor;
}
protected boolean belongsToSameGraph(Object monitor) {
return this.monitor == monitor;
}
// check for cycles when adding a new child to a parent (is this node a descendant of the new child?).
private void doCycleCheck(GraphNode child) {
synchronized (this.monitor) {
DescendentChecker descendentChecker = new DescendentChecker(this);
child.visit(descendentChecker);
Assert.isFalse(descendentChecker.isDescendent(), "Can't add '%s'. This node is a descendent of the new child.", child);
}
}
/**
* Removes the occurrence of the given node from this node's children. Returns true
if the child was
* found and removed, otherwise false
.
*
* @param child the node to remove
* @throws IllegalArgumentException if the given node does not belong to the same {@link DirectedAcyclicGraph}.
* @throws IllegalArgumentException if the given node is not a {@link ThreadSafeGraphNode}.
* @return true
if the node was removed successfully, otherwise false
.
* @see java.util.List#remove
*/
@Override
public boolean removeChild(GraphNode child) {
ThreadSafeGraphNode concreteChild = assertTypeAndMembership(child);
synchronized (this.monitor) {
boolean removed = this.children.remove(concreteChild);
if (removed) {
removeParent(child, this);
}
return removed;
}
}
/*
* All the children in this.children share this.monitor.
*/
private void removeParent(GraphNode child, GraphNode parent) {
synchronized (this.monitor) {
if (child instanceof ThreadSafeGraphNode>) {
ThreadSafeGraphNode concreteChild = (ThreadSafeGraphNode) child;
concreteChild.parents.remove(parent);
}
}
}
/**
* {@inheritDoc}
*/
@Override
public void visit(DirectedAcyclicGraphVisitor visitor) {
visitInternal(visitor, new HashMap, Boolean>());
}
private void visitInternal(DirectedAcyclicGraphVisitor visitor, Map, Boolean> visitedFlags) {
if (visitedFlags.containsKey(this)) {
return;
}
visitedFlags.put(this, Boolean.TRUE);
if (visitor.visit(this)) {
for (int i = 0; i < numChildren(); i++) {
ThreadSafeGraphNode nextChild = getChild(i);
if (nextChild != null) {
nextChild.visitInternal(visitor, visitedFlags);
} else {
break;
}
}
}
}
/**
* {@inheritDoc}
*/
@Override
public void visit(ExceptionThrowingDirectedAcyclicGraphVisitor visitor) throws E {
visitInternal(visitor, new HashMap, Boolean>());
}
private void visitInternal(ExceptionThrowingDirectedAcyclicGraphVisitor visitor,
Map, Boolean> visitedFlags) throws E {
if (visitedFlags.containsKey(this)) {
return;
}
visitedFlags.put(this, Boolean.TRUE);
if (visitor.visit(this)) {
for (int i = 0; i < numChildren(); i++) {
ThreadSafeGraphNode nextChild = getChild(i);
if (nextChild != null) {
nextChild.visitInternal(visitor, visitedFlags);
} else {
break;
}
}
}
}
private ThreadSafeGraphNode getChild(int i) {
synchronized (this.monitor) {
try {
return this.children.get(i);
} catch (IndexOutOfBoundsException e) {
return null;
}
}
}
private int numChildren() {
synchronized (this.monitor) {
return this.children.size();
}
}
private static class DescendentChecker implements DirectedAcyclicGraphVisitor {
private final ThreadSafeGraphNode prospect;
private boolean descendent = false;
public DescendentChecker(ThreadSafeGraphNode prospect) {
this.prospect = prospect;
}
@Override
public boolean visit(GraphNode node) {
if (this.descendent) {
return false;
}
if (this.prospect == node) {
this.descendent = true;
return false;
}
return true;
}
public boolean isDescendent() {
return this.descendent;
}
}
private static class SizeVisitor implements DirectedAcyclicGraphVisitor {
private int size;
@Override
public boolean visit(GraphNode dag) {
this.size += 1;
return true;
}
public int getSize() {
return this.size;
}
};
/**
* {@inheritDoc}
*/
@Override
public int size() {
SizeVisitor sizeVisitor = new SizeVisitor();
visit(sizeVisitor);
return sizeVisitor.getSize();
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
synchronized (this.monitor) {
final int prime = 31;
int result = 1;
result = prime * result + this.children.hashCode();
result = prime * result + (this.value == null ? 0 : this.value.hashCode());
return result;
}
}
/**
* {@inheritDoc}
*/
// TODO TSGN.equals regards nodes as equal which have different sets of parents.
// TODO TSGN.equals regards distinct nodes with no children (no parents once the todo above is fixed) and the same
// value as equal. These nodes were created separately, possibly even from distinct DAGs as currently coded.
@SuppressWarnings("unchecked")
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
ThreadSafeGraphNode other = (ThreadSafeGraphNode) obj;
int thisHash = System.identityHashCode(this);
int otherHash = System.identityHashCode(other);
if (thisHash < otherHash) {
synchronized (this.monitor) {
synchronized (other.monitor) {
if (!this.children.equals(other.children)) {
return false;
}
}
}
} else if (thisHash > otherHash) {
synchronized (other.monitor) {
synchronized (this.monitor) {
if (!this.children.equals(other.children)) {
return false;
}
}
}
} else {
synchronized (tieMonitor) {
synchronized (this.monitor) {
synchronized (other.monitor) {
if (!this.children.equals(other.children)) {
return false;
}
}
}
}
}
if (this.value == null) {
if (other.value != null) {
return false;
}
} else if (!this.value.equals(other.value)) {
return false;
}
return true;
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
StringBuffer result = new StringBuffer();
result.append(this.value != null ? this.value : "null").append("<");
synchronized (this.monitor) {
boolean first = true;
for (ThreadSafeGraphNode child : this.children) {
if (!first) {
result.append(", ");
}
result.append(child.toString());
first = false;
}
}
result.append(">");
return result.toString();
}
/**
* Returns a list of this graph's parents (not copies of the parents). If the graph has no parents, returns an empty
* list. Never returns null
.
*
* The returned list is synchronized to preserve thread safety, but may still result in
* ConcurrentModificationException being thrown.
*
* @return this graph's parents
*/
// TODO TSGN.getParents share its monitor with return values of getParents and getChildren. It feels as if the user
// might get some surprising deadlocks.
@Override
public List> getParents() {
synchronized (this.monitor) {
return new SynchronizedList>(this.parents, this.monitor);
}
}
/**
* {@inheritDoc}
*/
@Override
public boolean isRootNode() {
return this.parents.isEmpty();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy