com.phoenixst.plexus.DefaultGraph.java Source code

Java tutorial

Introduction

Here is the source code for com.phoenixst.plexus.DefaultGraph.java

Source

/*
 *  $Id: DefaultGraph.java,v 1.117 2007/04/01 20:16:27 rconner Exp $
 *
 *  Copyright (C) 1994-2007 by Phoenix Software Technologists,
 *  Inc. and others.  All rights reserved.
 *
 *  THIS PROGRAM AND DOCUMENTATION IS PROVIDED UNDER THE TERMS OF THE
 *  COMMON PUBLIC LICENSE ("AGREEMENT") WHICH ACCOMPANIES IT.  ANY
 *  USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES
 *  RECIPIENT'S ACCEPTANCE OF THE AGREEMENT.
 *
 *  The license text can also be found at
 *    http://opensource.org/licenses/cpl.php
 */

package com.phoenixst.plexus;

import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.AbstractCollection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;

//import java.io.*;
//import java.util.*;

import org.apache.commons.collections.Predicate;
import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Logger;

import com.phoenixst.collections.*;
import com.phoenixst.plexus.util.*;

/**
 *  A default implementation of the {@link ObservableGraph} interface.
 *
 *  <h3>Design Criteria</h3>
 *
 *  <P>There are many ways of representing graphs in software, and
 *  this package uses just one of those for the general
 *  implementation.  Of the two most basic, adjacency list and
 *  adjacency matrix, adjacency list is the most efficient for even
 *  remotely sparse graphs.  Also, the design constraint that nodes
 *  and edges are user-provided objects would have made an adjacency
 *  matrix representation much more costly in terms of space (a
 *  <code>HashMap</code> would be required to map nodes to indices).
 *
 *  <P>In general, it seems preferable to implement a graph as a
 *  <code>HashMap</code> from nodes to their adjacency lists.  The
 *  alternative (using some non-O(1) access Collection) is just too
 *  prohibitive in time for the modest gains in space.  The design
 *  constraints (allowing both directed and undirected edges and edges
 *  having identity) really don't leave a lot of room for
 *  implementations largely different from the one found here.  An
 *  adjacency list is pretty much just a list of Edges, with some
 *  extra bookkeeping information.  Only if a single adjacency list
 *  could grow very large would it be worthwhile to implement it as
 *  something other than a simple list.
 *
 *  @version    $Revision: 1.117 $
 *  @author     Ray A. Conner
 *
 *  @since      1.0
 */
public class DefaultGraph implements ObservableGraph, Serializable {

    ////////////////////////////////////////
    // Constants and enums
    ////////////////////////////////////////

    private static final long serialVersionUID = 2L;

    /**
     *  The normal logger.
     */
    static final Logger LOGGER = Logger.getLogger(DefaultGraph.class);

    /**
     *  The event logger, just in case the user wants to just log those
     *  at a different level.
     */
    private static final Logger EVENT_LOGGER = Logger.getLogger(DefaultGraph.class.getName() + ".Event");

    /**
     *  The possible types of the parts of an Edge/TraverserPredicate.
     */
    private enum PredicateSpec {
        ANY, EQUALS_PREDICATE, PREDICATE, OBJECT
    }

    static final Cursor EMPTY_CURSOR = new Cursor() {
        public boolean hasNext() {
            return false;
        }

        public Object next() {
            throw new NoSuchElementException();
        }

        public void remove() {
            throw new IllegalStateException();
        }

        public AdjacencyList getAdjacencyList() {
            throw new IllegalStateException();
        }

        public Graph.Edge getCurrentEdge() {
            throw new IllegalStateException();
        }

        public Object getOtherNode() {
            throw new IllegalStateException();
        }

        public void removeOtherNode() {
            throw new IllegalStateException();
        }

        public void edgeRemoved(int index) {
        }
    };

    static final CursorFilter FALSE_CURSOR_FILTER = new CursorFilter() {
        public boolean evaluate(Object baseNode, Graph.Edge edge) {
            return false;
        }
    };

    private static final CursorFilter DIRECTED_CURSOR_FILTER = new CursorFilter() {
        public boolean evaluate(Object baseNode, Graph.Edge edge) {
            return edge.isDirected();
        }
    };

    private static final CursorFilter UNDIRECTED_CURSOR_FILTER = new CursorFilter() {
        public boolean evaluate(Object baseNode, Graph.Edge edge) {
            return !edge.isDirected();
        }
    };

    static final CursorFilter BASE_TAIL_CURSOR_FILTER = new CursorFilter() {
        public boolean evaluate(Object baseNode, Graph.Edge edge) {
            return GraphUtils.equals(baseNode, edge.getTail());
        }
    };

    private static final CursorFilter BASE_TAIL_DIRECTED_CURSOR_FILTER = new CursorFilter() {
        public boolean evaluate(Object baseNode, Graph.Edge edge) {
            return edge.isDirected() && GraphUtils.equals(baseNode, edge.getTail());
        }
    };

    private static final CursorFilter BASE_HEAD_DIRECTED_CURSOR_FILTER = new CursorFilter() {
        public boolean evaluate(Object baseNode, Graph.Edge edge) {
            return edge.isDirected() && GraphUtils.equals(baseNode, edge.getHead());
        }
    };

    private static final CursorFilter BASE_TAIL_UNDIRECTED_CURSOR_FILTER = new CursorFilter() {
        public boolean evaluate(Object baseNode, Graph.Edge edge) {
            return !edge.isDirected() && GraphUtils.equals(baseNode, edge.getTail());
        }
    };

    private static final CursorFilter SELF_CURSOR_FILTER = new CursorFilter() {
        public boolean evaluate(Object baseNode, Graph.Edge edge) {
            return GraphUtils.equals(edge.getTail(), edge.getHead());
        }
    };

    ////////////////////////////////////////
    // Instance fields
    ////////////////////////////////////////

    /**
     *  Map from nodes to adjacency lists.
     */
    transient Map nodeMap;

    /**
     *  The number of edges in this graph.
     */
    transient int edgeSize = 0;

    /**
     *  A lazy collection of all the nodes backed by the nodeMap.
     */
    private transient final Collection nodeCollection = new AllNodesCollection();

    /**
     *  A lazy collection of all the edges backed by the nodeMap.
     */
    private transient final Collection edgeCollection = new AllEdgesCollection();

    /**
     *  The delegate to handle observable functionality.
     */
    private transient ObservableGraphDelegate observableDelegate;

    /**
     *  A String representing this instance for logging purposes.
     */
    transient final String instanceString = "(" + System.identityHashCode(this) + ")";

    /**
     *  The reapable collection of currently iterating cursors.
     */
    transient final Collection cursors = new ReapableCollection();

    ////////////////////////////////////////
    // Constructors
    ////////////////////////////////////////

    /**
     *  Creates a new <code>DefaultGraph</code>.
     */
    public DefaultGraph() {
        this(16);
    }

    /**
     *  Creates a new <code>DefaultGraph</code> which is a copy of the
     *  specified <code>Graph</code>.
     */
    public DefaultGraph(Graph graph) {
        this(graph.nodes(null).size());
        GraphUtils.add(this, graph);
    }

    /**
     *  Creates a new <code>DefaultGraph</code> with a capacity for
     *  the specified number of nodes (avoiding unnecessary rehashing).
     */
    protected DefaultGraph(int nodeSize) {
        super();
        BasicConfigurator.configure();
        //URL url = DefaultGraph.class.getResource("../log4j.properties");
        //URL url = getClass().getResource("../log4j.properties");
        //System.out.println("url : " + url);
        //String log4jConfPath = url.toString();//.toExternalForm();
        //PropertyConfigurator.configure(log4jConfPath);
        observableDelegate = new ObservableGraphDelegate(this, EVENT_LOGGER);
        nodeMap = new HashMap(nodeSize);
    }

    ////////////////////////////////////////
    // Serialization methods
    ////////////////////////////////////////

    /**
     *  Serialize this <code>DefaultGraph</code>.
     *
     *  @serialData the number of nodes (int), all the nodes, the
     *  number of edges (int), all the edges.
     */
    private void writeObject(ObjectOutputStream out) throws IOException {
        if (LOGGER.isInfoEnabled()) {
            LOGGER.info("Serializing " + instanceString);
        }
        out.defaultWriteObject();
        out.writeInt(nodeMap.size());
        for (Iterator i = nodeMap.keySet().iterator(); i.hasNext();) {
            out.writeObject(i.next());
        }
        out.writeInt(edgeSize);
        for (Iterator i = edgeCollection.iterator(); i.hasNext();) {
            out.writeObject(i.next());
        }
    }

    /**
     *  Deserialize this <code>DefaultGraph</code>.
     *
     *  @serialData the number of nodes (int), all the nodes, the
     *  number of edges (int), all the edges.
     */
    private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException {
        in.defaultReadObject();
        observableDelegate = new ObservableGraphDelegate(this, EVENT_LOGGER);

        int nodeSize = in.readInt();
        if (nodeSize < 0) {
            throw new InvalidObjectException("Node size is less than 0: " + nodeSize);
        }
        nodeMap = new HashMap(nodeSize);
        if (LOGGER.isInfoEnabled()) {
            LOGGER.info("Deserializing " + instanceString);
        }

        for (int i = 0; i < nodeSize; i++) {
            Object node = in.readObject();
            if (nodeMap.containsKey(node)) {
                throw new InvalidObjectException("Duplicate node: " + node);
            }
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + " deserialization: Adding node " + node);
            }
            nodeMap.put(node, new AdjacencyList(node));
        }

        edgeSize = in.readInt();
        if (edgeSize < 0) {
            throw new InvalidObjectException("Edge size is less than 0: " + edgeSize);
        }
        for (int i = 0; i < edgeSize; i++) {
            Graph.Edge edge = (Graph.Edge) in.readObject();

            AdjacencyList tailAdj = (AdjacencyList) nodeMap.get(edge.getTail());
            if (tailAdj == null) {
                throw new InvalidObjectException("Graph.Edge tail is not a node: " + edge.getTail());
            }
            if (tailAdj.contains(edge)) {
                throw new InvalidObjectException("Duplicate edge: " + edge);
            }
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + " deserialization: Adding edge " + edge);
            }
            tailAdj.edges.add(edge);

            if (!GraphUtils.equals(edge.getTail(), edge.getHead())) {
                AdjacencyList headAdj = (AdjacencyList) nodeMap.get(edge.getHead());
                if (headAdj == null) {
                    throw new InvalidObjectException("Graph.Edge head is not a node: " + edge.getHead());
                }
                headAdj.edges.add(edge);
            }
        }
    }

    ////////////////////////////////////////
    // Protected Graph.Edge creation method - which can be overridden
    // by subclasses.
    ////////////////////////////////////////

    /**
     *  Creates a new <code>Graph.Edge</code>.  This method can be
     *  overridden by subclasses to provide a different implementation
     *  than this one, which produces a {@link DefaultObjectEdge}.
     *  This method should simply create the requested
     *  <code>Graph.Edge</code>, without checking to see whether it
     *  already exists.  <code>DefaultGraph</code> will not allow
     *  two edges which are <code>.equals()</code> in the same
     *  adjacency list.
     */
    protected Graph.Edge createEdge(Object object, Object tail, Object head, boolean isDirected, Object edgeState) {
        return new DefaultObjectEdge(object, tail, head, isDirected);
    }

    /**
     *  Adds a new <code>Graph.Edge</code> with additional information
     *  provided by the <code>edgeState</code> argument, which is
     *  given to the {@link #createEdge createEdge()} method.  This
     *  method is intended to be called by subclasses which require
     *  more information than just the object, tail, head, and
     *  direction to construct the edge.  This method cannot be
     *  overridden.
     *
     *  <P>Returns the newly created <code>Graph.Edge</code> if this
     *  <code>Graph</code> changed as a result of the call.  Returns
     *  <code>null</code> if this <code>Graph</code> does not allow
     *  duplicate edges and already contains the specified edge.
     */
    protected final Graph.Edge addEdge(Object object, Object tail, Object head, boolean isDirected,
            Object edgeState) {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug(
                    instanceString + ".addEdge( " + object + ", " + tail + ", " + head + ", " + isDirected + " )");
        }
        AdjacencyList tailAdj = checkNode(tail);
        AdjacencyList headAdj = checkNode(head);
        Graph.Edge edge = createEdge(object, tailAdj.node, headAdj.node, isDirected, edgeState);
        edge = tailAdj.addTo(edge, headAdj);
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("  " + instanceString + ".addEdge() returning " + edge);
        }
        return edge;
    }

    ////////////////////////////////////////
    // Protected notification methods - which can be overridden by
    // subclasses.
    ////////////////////////////////////////

    /**
     *  Invoked before a node has been added to this
     *  <code>Graph</code> and any {@link GraphListener
     *  GraphListeners} have been notified.
     */
    protected void nodeAdding(Object node) {
        // Do nothing
    }

    /**
     *  Invoked after a node has been added to this <code>Graph</code>
     *  and any {@link GraphListener GraphListeners} have been
     *  notified.
     */
    protected void nodeAdded(Object node) {
        // Do nothing
    }

    /**
     *  Invoked before a node has been removed from this
     *  <code>Graph</code> and any {@link GraphListener
     *  GraphListeners} have been notified.
     */
    protected void nodeRemoving(Object node) {
        // Do nothing
    }

    /**
     *  Invoked after a node has been removed from this
     *  <code>Graph</code> and any {@link GraphListener
     *  GraphListeners} have been notified.
     */
    protected void nodeRemoved(Object node) {
        // Do nothing
    }

    /**
     *  Invoked before an edge has been added to this
     *  <code>Graph</code> and any {@link GraphListener
     *  GraphListeners} have been notified.
     */
    protected void edgeAdding(Graph.Edge edge) {
        // Do nothing
    }

    /**
     *  Invoked after an edge has been added to this
     *  <code>Graph</code> and any {@link GraphListener
     *  GraphListeners} have been notified.
     */
    protected void edgeAdded(Graph.Edge edge) {
        // Do nothing
    }

    /**
     *  Invoked before an edge has been removed from this
     *  <code>Graph</code> and any {@link GraphListener
     *  GraphListeners} have been notified.
     */
    protected void edgeRemoving(Graph.Edge edge) {
        // Do nothing
    }

    /**
     *  Invoked after an edge has been removed from this
     *  <code>Graph</code> and any {@link GraphListener
     *  GraphListeners} have been notified.
     */
    protected void edgeRemoved(Graph.Edge edge) {
        // Do nothing
    }

    ////////////////////////////////////////
    // Package private notification methods
    ////////////////////////////////////////

    /**
     *  Invoked when an edge has been added to alert any listening
     *  Cursors.
     */
    void alertEdgeAdded(AdjacencyList adj) {
        // Do nothing - for now anyway
    }

    /**
     *  Invoked when an edge has been removed to alert any listening
     *  Cursors.
     */
    void alertEdgeRemoved(AdjacencyList adj, int index) {
        synchronized (cursors) {
            for (Iterator i = cursors.iterator(); i.hasNext();) {
                Cursor cursor = (Cursor) i.next();
                if (adj == cursor.getAdjacencyList()) {
                    cursor.edgeRemoved(index);
                }
            }
        }
    }

    /**
     *
     */
    void processNodeAdded(Object node) {
        observableDelegate.fireNodeAdded(node);
    }

    /**
     *
     */
    void processNodeRemoved(Object node) {
        observableDelegate.fireNodeRemoved(node);
    }

    /**
     *
     */
    void processEdgeAdded(Graph.Edge edge) {
        edgeSize++;
        observableDelegate.fireEdgeAdded(edge);
    }

    /**
     *
     */
    void processEdgeRemoved(Graph.Edge edge) {
        edgeSize--;
        observableDelegate.fireEdgeRemoved(edge);
    }

    ////////////////////////////////////////
    // Private debug toString method
    ////////////////////////////////////////

    /**
     *  A debugging toString method, showing the internal data
     *  structures.
     */
    private String debugToString() {
        StringBuilder s = new StringBuilder();
        String className = getClass().getName();
        s.append(className.substring(className.lastIndexOf('.') + 1));
        s.append(instanceString);
        s.append(" ( ");
        s.append(nodeCollection.size());
        s.append(" nodes, ");
        s.append(edgeCollection.size());
        s.append(" edges");
        s.append(" ): ");

        s.append("\n[");
        for (Iterator i = nodeMap.entrySet().iterator(); i.hasNext();) {
            s.append("\n  ");
            s.append(i.next());
        }
        s.append("\n]");
        return s.toString();
    }

    ////////////////////////////////////////
    // Private checkNode method
    ////////////////////////////////////////

    /**
     *  Checks to see whether <code>node</code> is in this
     *  <code>Graph</code>.  If so, return the adjacency list for the
     *  node.  If not, throw an <code>NoSuchNodeException</code>.
     *
     *  @param node the node to be checked.
     *
     *  @throws NoSuchNodeException if the node is not in this
     *  <code>Graph</code>.
     */
    private AdjacencyList checkNode(Object node) {
        AdjacencyList adj = (AdjacencyList) nodeMap.get(node);
        if (adj == null) {
            throw new NoSuchNodeException("Node is not in this graph: " + node);
        }
        return adj;
    }

    ////////////////////////////////////////
    // Private Predicate/Cursor handling methods
    ////////////////////////////////////////

    /**
     *  Returns a constant representing the type of a part of an
     *  Edge/TraverserPredicate.
     */
    private static PredicateSpec getSpecType(Object object) {
        if (object == TruePredicate.INSTANCE) {
            return PredicateSpec.ANY;
        } else if (object instanceof EqualPredicate) {
            return PredicateSpec.EQUALS_PREDICATE;
        } else if (object instanceof Predicate) {
            return PredicateSpec.PREDICATE;
        } else {
            return PredicateSpec.OBJECT;
        }
    }

    /**
     *  Converts the specified object to predicate, if necessary.
     *  This is typically used for the specifications of the
     *  user-defined object in a Graph.Edge that is in an
     *  Edge/TraverserPredicate.
     */
    private static Predicate toPredicate(Object object) {
        return (object instanceof Predicate) ? (Predicate) object : new EqualPredicate(object);
    }

    /**
     *  Creates a CursorFilter for the specified arguments, which are
     *  parts of an Edge/TraverserPredicate.
     */
    private CursorFilter createCursorFilter(Object node, Object userObject, int directionFlags) {
        Predicate userPredicate = toPredicate(userObject);

        PredicateSpec nodeType = getSpecType(node);
        if (nodeType == PredicateSpec.EQUALS_PREDICATE) {
            node = ((EqualPredicate) node).getTestObject();
        }
        // node is now ANY, PRED, or other (OBJECT)

        if (nodeType == PredicateSpec.ANY) {
            return new ToAnyCursorFilter(directionFlags, userPredicate);
        } else if (nodeType == PredicateSpec.PREDICATE) {
            return new ToPredCursorFilter(directionFlags, (Predicate) node, userPredicate);
        } else {
            // Get the "real" node from the graph
            AdjacencyList adj = (AdjacencyList) nodeMap.get(node);
            if (adj == null) {
                return FALSE_CURSOR_FILTER;
            }
            return new ToEqualsCursorFilter(directionFlags, adj.node, userPredicate);
        }
    }

    /**
     *  Creates a CursorFilter for the specified Traverser Predicate.
     *  A null return value signifies that the predicate was null or
     *  TruePredicate.INSTANCE.
     */
    private CursorFilter createCursorFilter(Predicate traverserPredicate) {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("  " + instanceString + ".createCursorFilter( " + traverserPredicate + " )");
        }
        if (traverserPredicate == null || traverserPredicate == TruePredicate.INSTANCE) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("    " + instanceString + ".createCursorFilter() returning null (true) filter");
            }
            return null;

        } else if (traverserPredicate == FalsePredicate.INSTANCE) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("    " + instanceString + ".createCursorFilter() returning false filter");
            }
            return FALSE_CURSOR_FILTER;

        } else if (traverserPredicate == GraphUtils.OUT_TRAVERSER_PREDICATE) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("    " + instanceString + ".createCursorFilter() returning directed out filter");
            }
            return BASE_TAIL_DIRECTED_CURSOR_FILTER;

        } else if (traverserPredicate == GraphUtils.IN_TRAVERSER_PREDICATE) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("    " + instanceString + ".createCursorFilter() returning directed in filter");
            }
            return BASE_HEAD_DIRECTED_CURSOR_FILTER;

        } else if (traverserPredicate == GraphUtils.DIRECTED_TRAVERSER_PREDICATE) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("    " + instanceString + ".createCursorFilter() returning directed filter");
            }
            return DIRECTED_CURSOR_FILTER;

        } else if (traverserPredicate == GraphUtils.UNDIRECTED_TRAVERSER_PREDICATE) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("    " + instanceString + ".createCursorFilter() returning undirected filter");
            }
            return UNDIRECTED_CURSOR_FILTER;

        } else if (traverserPredicate == GraphUtils.SELF_TRAVERSER_PREDICATE) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("    " + instanceString + ".createCursorFilter() returning self filter");
            }
            return SELF_CURSOR_FILTER;

        } else if (traverserPredicate instanceof TraverserPredicate) {
            TraverserPredicate predicate = (TraverserPredicate) traverserPredicate;
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("    " + instanceString + ".createCursorFilter() returning optimized filter");
            }
            return createCursorFilter(predicate.getNodeSpecification(), predicate.getUserObjectSpecification(),
                    predicate.getDirectionFlags());

        } else if (traverserPredicate instanceof EqualsTraverserPredicate) {
            Graph.Edge edge = ((EqualsTraverserPredicate) traverserPredicate).getTestEdge();
            AdjacencyList tailAdj = (AdjacencyList) nodeMap.get(edge.getTail());
            AdjacencyList headAdj = (AdjacencyList) nodeMap.get(edge.getHead());
            if (tailAdj == null || headAdj == null) {
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("    " + instanceString + ".createCursorFilter() returning false filter");
                }
                return FALSE_CURSOR_FILTER;
            }
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("    " + instanceString + ".createCursorFilter() returning optimized filter");
            }
            return new EqualsCursorFilter(edge);

        } else {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("    " + instanceString + ".createCursorFilter() returning general filter");
            }
            return new GeneralTraverserCursorFilter(traverserPredicate);
        }
    }

    ////////////////////////////////////////
    // Graph methods
    ////////////////////////////////////////

    public boolean addNode(Object node) {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug(instanceString + ".addNode( " + node + " )");
        }
        if (nodeMap.containsKey(node)) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".addNode() returning false");
            }
            return false;
        }
        nodeAdding(node);
        nodeMap.put(node, new AdjacencyList(node));
        processNodeAdded(node);
        nodeAdded(node);
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("  " + instanceString + ".addNode() returning true");
        }
        return true;
    }

    public boolean removeNode(Object node) {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug(instanceString + ".removeNode( " + node + " )");
        }
        AdjacencyList adj = (AdjacencyList) nodeMap.get(node);
        if (adj == null) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".removeNode() returning false");
            }
            return false;
        }
        nodeRemoving(node);
        adj.clear();
        nodeMap.remove(node);
        processNodeRemoved(node);
        nodeRemoved(adj.node);
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("  " + instanceString + ".removeNode() returning true");
        }
        return true;
    }

    public boolean containsNode(Object node) {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug(instanceString + ".containsNode( " + node + " )");
        }
        boolean contains = nodeMap.containsKey(node);
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("  " + instanceString + ".containsNode() returning " + contains);
        }
        return contains;
    }

    public Graph.Edge addEdge(Object object, Object tail, Object head, boolean isDirected) {
        return addEdge(object, tail, head, isDirected, null);
    }

    public boolean removeEdge(Graph.Edge edge) {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug(instanceString + ".removeEdge( " + edge + " )");
        }
        AdjacencyList tailAdj = (AdjacencyList) nodeMap.get(edge.getTail());
        boolean modified = (tailAdj != null && tailAdj.remove(edge));
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("  " + instanceString + ".removeEdge() returning " + modified);
        }
        return modified;
    }

    public boolean containsEdge(Graph.Edge edge) {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug(instanceString + ".containsEdge( " + edge + " )");
        }
        AdjacencyList tailAdj = (AdjacencyList) nodeMap.get(edge.getTail());
        boolean contains = (tailAdj != null && tailAdj.contains(edge));
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("  " + instanceString + ".containsEdge() returning " + contains);
        }
        return contains;
    }

    public int degree(Object node) {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug(instanceString + ".degree( " + node + " )");
        }
        int degree = checkNode(node).degree();
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("  " + instanceString + ".degree() returning " + degree);
        }
        return degree;
    }

    public int degree(Object node, Predicate traverserPredicate) {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug(instanceString + ".degree( " + node + ", " + traverserPredicate + " )");
        }
        AdjacencyList adj = checkNode(node);
        int degree;

        if (traverserPredicate == null || traverserPredicate == TruePredicate.INSTANCE) {
            degree = adj.size();

        } else if (traverserPredicate == FalsePredicate.INSTANCE) {
            degree = 0;

        } else if (traverserPredicate == GraphUtils.OUT_TRAVERSER_PREDICATE) {
            degree = adj.outDegree();

        } else if (traverserPredicate == GraphUtils.IN_TRAVERSER_PREDICATE) {
            degree = adj.inDegree();

        } else {
            // The general case, also catches:
            //   DIRECTED_TRAVERSER_PREDICATE
            //   UNDIRECTED_TRAVERSER_PREDICATE
            //   SELF_TRAVERSER_PREDICATE
            //   EqualsTraverserPredicate
            //   TraverserPredicate (not worth the trouble to analyze)
            degree = adj.degree(traverserPredicate);
        }

        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("  " + instanceString + ".degree() returning " + degree);
        }
        return degree;
    }

    public Collection nodes(Predicate nodePredicate) {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug(instanceString + ".nodes( " + nodePredicate + " )");
        }

        if (nodePredicate == null || nodePredicate == TruePredicate.INSTANCE) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".nodes() returning all nodes");
            }
            return nodeCollection;

        } else if (nodePredicate == FalsePredicate.INSTANCE) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".nodes() returning empty set");
            }
            return Collections.EMPTY_SET;

        } else if (nodePredicate instanceof EqualPredicate) {
            Object testNode = ((EqualPredicate) nodePredicate).getTestObject();
            if (!containsNode(testNode)) {
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("  " + instanceString + ".nodes() returning empty set");
                }
                return Collections.EMPTY_SET;
            }
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".nodes() returning singleton");
            }
            return new SingletonNodeCollection(this, testNode);

        } else {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".nodes() returning general filtered collection");
            }
            return new FilteredCollection(nodeCollection, nodePredicate);
        }
    }

    /**
     *  A special-case edge collection.
     */
    private Collection edgesHelper(EdgePredicate edgePredicate) {
        Object nodeA = edgePredicate.getFirstNodeSpecification();
        Object nodeB = edgePredicate.getSecondNodeSpecification();
        Object userObject = edgePredicate.getUserObjectSpecification();
        int directionFlags = edgePredicate.getDirectionFlags();

        // Check for either node being fixed

        PredicateSpec nodeAType = getSpecType(nodeA);
        if (nodeAType == PredicateSpec.EQUALS_PREDICATE || nodeAType == PredicateSpec.OBJECT) {
            if (nodeAType == PredicateSpec.EQUALS_PREDICATE) {
                nodeA = ((EqualPredicate) nodeA).getTestObject();
            }
            AdjacencyList adj = (AdjacencyList) nodeMap.get(nodeA);
            if (adj != null) {
                CursorFilter filter = createCursorFilter(nodeB, userObject, directionFlags);
                if (filter != FALSE_CURSOR_FILTER) {
                    if (LOGGER.isDebugEnabled()) {
                        LOGGER.debug(
                                "  " + instanceString + ".edges() returning some edges incident to " + adj.node);
                    }
                    return new IncEdgeCollection(adj, filter);
                }
            }
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".edges() returning empty set");
            }
            return Collections.EMPTY_SET;
        }

        PredicateSpec nodeBType = getSpecType(nodeB);
        if (nodeBType == PredicateSpec.EQUALS_PREDICATE || nodeBType == PredicateSpec.OBJECT) {
            if (nodeBType == PredicateSpec.EQUALS_PREDICATE) {
                nodeB = ((EqualPredicate) nodeB).getTestObject();
            }
            AdjacencyList adj = (AdjacencyList) nodeMap.get(nodeB);
            if (adj != null) {
                CursorFilter filter = createCursorFilter(nodeA, userObject,
                        GraphUtils.invertDirection(directionFlags));
                if (filter != FALSE_CURSOR_FILTER) {
                    if (LOGGER.isDebugEnabled()) {
                        LOGGER.debug(
                                "  " + instanceString + ".edges() returning some edges incident to " + adj.node);
                    }
                    return new IncEdgeCollection(adj, filter);
                }
            }
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".edges() returning empty set");
            }
            return Collections.EMPTY_SET;
        }

        // Convert userObject to a Predicate
        Predicate userPredicate = toPredicate(userObject);

        // If we reach this point, both nodes are either of PredicateSpec.ANY
        // or PredicateSpec.PREDICATE

        if (nodeAType == PredicateSpec.ANY && nodeBType == PredicateSpec.ANY) {
            if (userPredicate == TruePredicate.INSTANCE) {
                if ((directionFlags & GraphUtils.UNDIRECTED_MASK) == 0) {
                    if (LOGGER.isDebugEnabled()) {
                        LOGGER.debug("  " + instanceString + ".edges() returning all directed edges");
                    }
                    return new AnyToAnyEdgeCollection(GraphUtils.DIRECTED_EDGE_PREDICATE,
                            BASE_TAIL_DIRECTED_CURSOR_FILTER);
                } else if ((directionFlags & GraphUtils.DIRECTED_MASK) == 0) {
                    if (LOGGER.isDebugEnabled()) {
                        LOGGER.debug("  " + instanceString + ".edges() returning all undirected edges");
                    }
                    return new AnyToAnyEdgeCollection(GraphUtils.UNDIRECTED_EDGE_PREDICATE,
                            BASE_TAIL_UNDIRECTED_CURSOR_FILTER);
                } else {
                    if (LOGGER.isDebugEnabled()) {
                        LOGGER.debug("  " + instanceString + ".edges() returning all edges");
                    }
                    return edgeCollection;
                }
            }
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".edges() returning general filtered collection");
            }
            return new AnyToAnyEdgeCollection(edgePredicate,
                    new AnyToAnyCursorFilter(directionFlags, userPredicate));
        }

        // If we reach this point, at least one node is of
        // PredicateSpec.PREDICATE

        // Make sure A is a predicate
        if (nodeAType == PredicateSpec.ANY) {
            nodeA = nodeB;
            nodeB = TruePredicate.INSTANCE;
            nodeBType = PredicateSpec.ANY;
            directionFlags = GraphUtils.invertDirection(directionFlags);
        }
        Predicate basePredicate = (Predicate) nodeA;

        if (nodeBType == PredicateSpec.ANY) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".edges() returning optimized filtered collection");
            }
            return new PredToAnyEdgeCollection(edgePredicate, basePredicate,
                    new PredToAnyCursorFilter(directionFlags, basePredicate, userPredicate));
        }

        // First is P~Q, Q(base) true.
        // Second is P~Q, Q(base) false.
        CursorFilter qFilter = new PQToQCursorFilter(directionFlags, basePredicate, (Predicate) nodeB,
                userPredicate);
        CursorFilter notQFilter = new ToPredCursorFilter(directionFlags, (Predicate) nodeB, userPredicate);

        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("  " + instanceString + ".edges() returning optimized filtered collection");
        }
        return new PToQEdgeCollection(edgePredicate, basePredicate, (Predicate) nodeB, qFilter, notQFilter);
    }

    public Collection edges(Predicate edgePredicate) {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug(instanceString + ".edges( " + edgePredicate + " )");
        }

        if (edgePredicate == null || edgePredicate == TruePredicate.INSTANCE) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".edges() returning all edges");
            }
            return edgeCollection;

        } else if (edgePredicate == FalsePredicate.INSTANCE) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".edges() returning empty set");
            }
            return Collections.EMPTY_SET;

        } else if (edgePredicate == GraphUtils.DIRECTED_EDGE_PREDICATE) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".edges() returning all directed edges");
            }
            return new AnyToAnyEdgeCollection(GraphUtils.DIRECTED_EDGE_PREDICATE, BASE_TAIL_DIRECTED_CURSOR_FILTER);

        } else if (edgePredicate == GraphUtils.UNDIRECTED_EDGE_PREDICATE) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".edges() returning all undirected edges");
            }
            return new AnyToAnyEdgeCollection(GraphUtils.UNDIRECTED_EDGE_PREDICATE,
                    BASE_TAIL_UNDIRECTED_CURSOR_FILTER);

        } else if (edgePredicate == GraphUtils.SELF_EDGE_PREDICATE) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".edges() returning all self edges");
            }
            return new AnyToAnyEdgeCollection(GraphUtils.SELF_EDGE_PREDICATE, SELF_CURSOR_FILTER);

        } else if (edgePredicate instanceof EdgePredicate) {
            return edgesHelper((EdgePredicate) edgePredicate);

        } else if (edgePredicate instanceof EqualPredicate) {
            Graph.Edge testEdge = (Graph.Edge) ((EqualPredicate) edgePredicate).getTestObject();
            if (!containsEdge(testEdge)) {
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("  " + instanceString + ".edges() returning empty set");
                }
                return Collections.EMPTY_SET;
            }
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".edges() returning singleton");
            }
            return new SingletonEdgeCollection(this, testEdge);

        } else {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".edges() returning general filtered collection");
            }
            return new AnyToAnyEdgeCollection(edgePredicate, new BTailCursorFilter(edgePredicate));
        }
    }

    public Collection adjacentNodes(Object node, Predicate traverserPredicate) {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug(instanceString + ".adjacentNodes( " + node + ", " + traverserPredicate + " )");
        }
        AdjacencyList adj = checkNode(node);
        CursorFilter filter = createCursorFilter(traverserPredicate);
        if (filter == FALSE_CURSOR_FILTER) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".adjacentNodes() returning empty set");
            }
            return Collections.EMPTY_SET;
        }
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("  " + instanceString + ".adjacentNodes() returning filtered collection");
        }
        return new AdjNodeCollection(adj, filter);
    }

    public Collection incidentEdges(Object node, Predicate traverserPredicate) {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug(instanceString + ".incidentEdges( " + node + ", " + traverserPredicate + " )");
        }
        AdjacencyList adj = checkNode(node);
        CursorFilter filter = createCursorFilter(traverserPredicate);
        if (filter == FALSE_CURSOR_FILTER) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".incidentEdges() returning empty set");
            }
            return Collections.EMPTY_SET;
        }
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("  " + instanceString + ".incidentEdges() returning filtered collection");
        }
        return new IncEdgeCollection(adj, filter);
    }

    public Object getNode(Predicate nodePredicate) {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug(instanceString + ".getNode( " + nodePredicate + " )");
        }
        Object node;

        if (nodePredicate == null || nodePredicate == TruePredicate.INSTANCE) {
            Iterator i = nodeMap.keySet().iterator();
            node = i.hasNext() ? i.next() : null;

        } else if (nodePredicate == FalsePredicate.INSTANCE) {
            node = null;

        } else if (nodePredicate instanceof EqualPredicate) {
            Object testNode = ((EqualPredicate) nodePredicate).getTestObject();
            AdjacencyList adj = (AdjacencyList) nodeMap.get(testNode);
            node = (adj != null) ? adj.node : null;

        } else {
            node = null;
            for (Iterator i = nodeMap.keySet().iterator(); i.hasNext();) {
                Object testNode = i.next();
                if (nodePredicate.evaluate(testNode)) {
                    node = testNode;
                    break;
                }
            }
        }

        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("  " + instanceString + ".getNode() returning " + node);
        }
        return node;
    }

    public Graph.Edge getEdge(Predicate edgePredicate) {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug(instanceString + ".getEdge( " + edgePredicate + " )");
        }
        Graph.Edge edge;

        if (edgePredicate == FalsePredicate.INSTANCE) {
            edge = null;

        } else if (edgePredicate instanceof EqualPredicate) {
            Graph.Edge testEdge = (Graph.Edge) ((EqualPredicate) edgePredicate).getTestObject();
            edge = containsEdge(testEdge) ? testEdge : null;

        } else {
            Collection edges = edges(edgePredicate);
            if (edges instanceof EdgeCollection) {
                edge = ((EdgeCollection) edges).get();
            } else {
                Iterator i = edges.iterator();
                edge = i.hasNext() ? (Graph.Edge) i.next() : null;
            }
        }

        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("  " + instanceString + ".getEdge() returning " + edge);
        }
        return edge;
    }

    public Object getAdjacentNode(Object node, Predicate traverserPredicate) {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug(instanceString + ".getAdjacentNode( " + node + ", " + traverserPredicate + " )");
        }
        Graph.Edge edge = getIncidentEdge(node, traverserPredicate);
        Object returnNode = (edge != null) ? edge.getOtherEndpoint(node) : null;
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("  " + instanceString + ".getAdjacentNode() returning " + returnNode);
        }
        return returnNode;
    }

    public Graph.Edge getIncidentEdge(Object node, Predicate traverserPredicate) {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug(instanceString + ".getIncidentEdge( " + node + ", " + traverserPredicate + " )");
        }
        AdjacencyList adj = checkNode(node);
        Graph.Edge edge = adj.get(createCursorFilter(traverserPredicate));
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("  " + instanceString + ".getIncidentEdge() returning " + edge);
        }
        return edge;
    }

    /**
     *  Returns a <code>Traverser</code> from <code>node</code> to all
     *  adjacent nodes for which the specified filter is satisfied.
     *
     *  <P>The returned <code>Traverser</code> is tolerant of changes
     *  to the underlying <code>Graph</code>.  Note that this does not
     *  mean the <code>Traverser</code> is thread-safe.  However, if a
     *  node or edge is added or removed while the iteration is is
     *  progress, the iteration will not throw a
     *  <code>ConcurrentModificationException</code>.  In fact, its
     *  state will reflect the changes.  This means that, among other
     *  things, you should always call {@link Traverser#hasNext()}
     *  before {@link Traverser#next()} if there is a chance the
     *  structure has changed since the last call to
     *  <code>hasNext()</code>.  The one exception is that if the node
     *  upon which the returned <code>Traverser</code> is based is
     *  removed, then all operations except <code>hasNext()</code>
     *  will throw a <code>ConcurrentModificationException</code>.
     *
     *  <P><b>Description copied from interface: {@link Graph}</b><br>
     *  <P>{@inheritDoc}
     */
    public Traverser traverser(Object node, Predicate traverserPredicate) {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug(instanceString + ".traverser( " + node + ", " + traverserPredicate + " )");
        }
        AdjacencyList adj = checkNode(node);
        Traverser t = new CursorTraverserAdapter(adj.cursor(createCursorFilter(traverserPredicate)));
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("  " + instanceString + ".traverser() returning");
        }
        return t;
    }

    ////////////////////////////////////////
    // ObservableGraph methods
    ////////////////////////////////////////

    public void addGraphListener(GraphListener listener) {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug(instanceString + ".addGraphListener( " + listener + " )");
        }
        observableDelegate.addGraphListener(listener);
    }

    public void removeGraphListener(GraphListener listener) {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug(instanceString + ".removeGraphListener( " + listener + " )");
        }
        observableDelegate.removeGraphListener(listener);
    }

    ////////////////////////////////////////
    // Object
    ////////////////////////////////////////

    public boolean equals(Object object) {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug(instanceString + ".equals( " + object + " )");
        }
        boolean equals = super.equals(object);
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("  " + instanceString + ".equals() returning " + equals);
        }
        return equals;
    }

    public int hashCode() {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug(instanceString + ".hashCode()");
        }
        int hashCode = super.hashCode();
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("  " + instanceString + ".hashCode() returning " + hashCode);
        }
        return hashCode;
    }

    public String toString() {
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug(instanceString + ".toString()");
        }
        // FIXME - create a better string representation
        String string = debugToString();
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("  " + instanceString + ".toString() returning");
        }
        return string;
    }

    ////////////////////////////////////////
    // Cursor interface definitions
    ////////////////////////////////////////

    private interface Cursor extends Iterator {
        /**
         *  Returns the <code>AdjacencyList</code> for which this
         *  is a cursor.
         */
        public AdjacencyList getAdjacencyList();

        /**
         *  Gets the current edge.
         */
        public Graph.Edge getCurrentEdge();

        /**
         *  Convenience method to get the other end of the current
         *  edge.
         */
        public Object getOtherNode();

        /**
         *  Convenience method to remove the other end of the current
         *  edge.
         */
        public void removeOtherNode();

        /**
         *  Notifies this cursor that something was removed from
         *  the underlying list.
         */
        public void edgeRemoved(int index);
    }

    private interface CursorFilter {
        public boolean evaluate(Object baseNode, Graph.Edge edge);
    }

    ////////////////////////////////////////
    // CursorFilters - basically Traverser filters.
    ////////////////////////////////////////

    /**
     *  Used by edge iterators or traversers to look for a specific
     *  edge.
     */
    private static class EqualsCursorFilter implements CursorFilter {
        private final Graph.Edge testEdge;

        EqualsCursorFilter(Graph.Edge edge) {
            super();
            testEdge = edge;
        }

        public boolean evaluate(Object baseNode, Graph.Edge edge) {
            return testEdge.equals(edge);
        }
    }

    /**
     *  Used by edge iterators where no more efficient/specific
     *  processing is possible.
     */
    private static class BTailCursorFilter implements CursorFilter {
        private final Predicate edgePredicate;

        BTailCursorFilter(Predicate edgePredicate) {
            super();
            this.edgePredicate = edgePredicate;
        }

        public boolean evaluate(Object baseNode, Graph.Edge edge) {
            return GraphUtils.equals(baseNode, edge.getTail()) && edgePredicate.evaluate(edge);
        }
    }

    /**
     *  Used by traversers where no more efficient/specific processing
     *  is possible.
     */
    private static class GeneralTraverserCursorFilter implements CursorFilter {
        private final Predicate traverserPredicate;
        private final OrderedPair pair = new OrderedPair(null, null);

        GeneralTraverserCursorFilter(Predicate traverserPredicate) {
            super();
            this.traverserPredicate = traverserPredicate;
        }

        public boolean evaluate(Object baseNode, Graph.Edge edge) {
            pair.setFirst(baseNode);
            pair.setSecond(edge);
            return traverserPredicate.evaluate(pair);
        }
    }

    private static class ToAnyCursorFilter implements CursorFilter {
        private final int directionFlags;
        private final Predicate userPredicate;

        ToAnyCursorFilter(int directionFlags, Predicate userPredicate) {
            super();
            this.directionFlags = directionFlags;
            this.userPredicate = userPredicate;
        }

        protected boolean subTest(Object baseNode, Graph.Edge edge) {
            return true;
        }

        public boolean evaluate(Object baseNode, Graph.Edge edge) {
            if (edge.isDirected()) {
                if ((directionFlags & GraphUtils.DIRECTED_MASK) == GraphUtils.DIRECTED_MASK) {
                    return subTest(baseNode, edge) && userPredicate.evaluate(edge.getUserObject());
                }
                return (((directionFlags & GraphUtils.DIRECTED_OUT_MASK) != 0
                        && GraphUtils.equals(baseNode, edge.getTail()))
                        || ((directionFlags & GraphUtils.DIRECTED_IN_MASK) != 0
                                && GraphUtils.equals(baseNode, edge.getHead())))
                        && userPredicate.evaluate(edge.getUserObject());
            }
            return (directionFlags & GraphUtils.UNDIRECTED_MASK) != 0 && subTest(baseNode, edge)
                    && userPredicate.evaluate(edge.getUserObject());
        }
    }

    // This is also PtoQCursorFilter, where Q(base) is false
    private static class ToPredCursorFilter extends ToAnyCursorFilter {
        private final Predicate nodePredicate;

        ToPredCursorFilter(int directionFlags, Predicate nodePredicate, Predicate userPredicate) {
            super(directionFlags, userPredicate);
            this.nodePredicate = nodePredicate;
        }

        public boolean evaluate(Object baseNode, Graph.Edge edge) {
            return super.evaluate(baseNode, edge) && nodePredicate.evaluate(edge.getOtherEndpoint(baseNode));
        }
    }

    private static class ToEqualsCursorFilter extends ToAnyCursorFilter {
        private final Object testNode;

        ToEqualsCursorFilter(int directionFlags, Object testNode, Predicate userPredicate) {
            super(directionFlags, userPredicate);
            this.testNode = testNode;
        }

        public boolean evaluate(Object baseNode, Graph.Edge edge) {
            return super.evaluate(baseNode, edge) && GraphUtils.equals(testNode, edge.getOtherEndpoint(baseNode));
        }
    }

    private static class AnyToAnyCursorFilter extends ToAnyCursorFilter {
        AnyToAnyCursorFilter(int directionFlags, Predicate userPredicate) {
            super(directionFlags, userPredicate);
        }

        protected boolean subTest(Object baseNode, Graph.Edge edge) {
            return GraphUtils.equals(baseNode, edge.getTail());
        }
    }

    private static class PredToAnyCursorFilter extends ToAnyCursorFilter {
        private final Predicate basePredicate;

        PredToAnyCursorFilter(int directionFlags, Predicate basePredicate, Predicate userPredicate) {
            super(directionFlags, userPredicate);
            this.basePredicate = basePredicate;
        }

        protected boolean subTest(Object baseNode, Graph.Edge edge) {
            Object tail = edge.getTail();
            return GraphUtils.equals(baseNode, tail) || !basePredicate.evaluate(tail);
        }
    }

    private static class PQToQCursorFilter extends ToAnyCursorFilter {
        private final Predicate basePredicate;
        private final Predicate nodePredicate;

        PQToQCursorFilter(int directionFlags, Predicate basePredicate, Predicate nodePredicate,
                Predicate userPredicate) {
            super(directionFlags, userPredicate);
            this.basePredicate = basePredicate;
            this.nodePredicate = nodePredicate;
        }

        protected boolean subTest(Object baseNode, Graph.Edge edge) {
            Object tail = edge.getTail();
            return GraphUtils.equals(baseNode, tail) || !basePredicate.evaluate(tail);
        }

        public boolean evaluate(Object baseNode, Graph.Edge edge) {
            return super.evaluate(baseNode, edge) && nodePredicate.evaluate(edge.getOtherEndpoint(baseNode));
        }
    }

    ////////////////////////////////////////
    // Private Cursor-Iterator adapter classes
    ////////////////////////////////////////

    /**
     *  Iterates over the nodes in a Cursor.
     */
    private static class CursorNodeIteratorAdapter implements Iterator {
        private final Cursor cursor;

        CursorNodeIteratorAdapter(Cursor cursor) {
            super();
            this.cursor = cursor;
        }

        public boolean hasNext() {
            return cursor.hasNext();
        }

        public Object next() {
            cursor.next();
            return cursor.getOtherNode();
        }

        public void remove() {
            // Note that this just removes the Edge.
            cursor.remove();
        }
    }

    /**
     *  Iterates over the edges in a Cursor.
     */
    private static class CursorEdgeIteratorAdapter implements Iterator {
        private final Cursor cursor;

        CursorEdgeIteratorAdapter(Cursor cursor) {
            super();
            this.cursor = cursor;
        }

        public boolean hasNext() {
            return cursor.hasNext();
        }

        public Object next() {
            return cursor.next();
        }

        public void remove() {
            cursor.remove();
        }
    }

    /**
     *  Traverses over a Cursor.
     */
    private static class CursorTraverserAdapter implements Traverser {
        private final Cursor cursor;

        CursorTraverserAdapter(Cursor cursor) {
            super();
            this.cursor = cursor;
        }

        public boolean hasNext() {
            return cursor.hasNext();
        }

        public Object next() {
            cursor.next();
            return cursor.getOtherNode();
        }

        public void remove() {
            cursor.removeOtherNode();
        }

        public Graph.Edge getEdge() {
            return cursor.getCurrentEdge();
        }

        public void removeEdge() {
            cursor.remove();
        }
    }

    ////////////////////////////////////////
    // Private Collection view classes
    ////////////////////////////////////////

    private class AllNodesCollection implements Collection {
        AllNodesCollection() {
            super();
        }

        public int size() {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(instanceString + ".nodes().size()");
            }
            int size = nodeMap.size();
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".nodes().size() returning " + size);
            }
            return size;
        }

        public boolean isEmpty() {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(instanceString + ".nodes().isEmpty()");
            }
            boolean isEmpty = nodeMap.isEmpty();
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".nodes().isEmpty() returning " + isEmpty);
            }
            return isEmpty;
        }

        public boolean add(Object object) {
            throw new UnsupportedOperationException();
        }

        public boolean remove(Object object) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(instanceString + ".nodes().remove( " + object + " )");
            }
            boolean modified = removeNode(object);
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".nodes().remove() returning " + modified);
            }
            return modified;
        }

        public boolean contains(Object object) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(instanceString + ".nodes().contains( " + object + " )");
            }
            boolean contains = containsNode(object);
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".nodes().contains() returning " + contains);
            }
            return contains;
        }

        public Iterator iterator() {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(instanceString + ".nodes().iterator()");
            }
            Iterator i = new NodeIterator();
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".nodes().iterator() returning");
            }
            return i;
        }

        public boolean containsAll(Collection collection) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(instanceString + ".nodes().containsAll( " + collection + " )");
            }
            boolean containsAll = nodeMap.keySet().containsAll(collection);
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".nodes().containsAll() returning " + containsAll);
            }
            return containsAll;
        }

        public boolean addAll(Collection collection) {
            throw new UnsupportedOperationException();
        }

        public boolean removeAll(Collection collection) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(instanceString + ".nodes().removeAll( " + collection + " )");
            }
            // In this case, it's probably less overhead to iterate
            // over the provided collection.  Iterators over the
            // nodeset have a relatively high overhead.
            boolean modified = false;
            for (Iterator i = collection.iterator(); i.hasNext();) {
                modified |= removeNode(i.next());
            }
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".nodes().removeAll() returning " + modified);
            }
            return modified;
        }

        public boolean retainAll(Collection collection) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(instanceString + ".nodes().retainAll( " + collection + " )");
            }
            // Here, we have no choice but to iterate over the
            // nodeset.
            boolean modified = false;
            for (Iterator i = iterator(); i.hasNext();) {
                if (!collection.contains(i.next())) {
                    i.remove();
                    modified = true;
                }
            }
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".nodes().retainAll() returning " + modified);
            }
            return modified;
        }

        public void clear() {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(instanceString + ".nodes().clear()");
            }
            // Remove all the nodes
            for (Iterator i = iterator(); i.hasNext();) {
                i.next();
                i.remove();
            }
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".nodes().clear() returning");
            }
        }

        public Object[] toArray() {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(instanceString + ".nodes().toArray()");
            }
            Object[] array = nodeMap.keySet().toArray();
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".nodes().toArray() returning " + array);
            }
            return array;
        }

        public Object[] toArray(Object[] array) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(instanceString + ".nodes().toArray( " + array + " )");
            }
            Object[] returnedArray = nodeMap.keySet().toArray(array);
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".nodes().toArray() returning " + returnedArray);
            }
            return returnedArray;
        }

        public String toString() {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(instanceString + ".nodes().toString()");
            }
            String string = nodeMap.keySet().toString();
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".nodes().toString() returning");
            }
            return string;
        }

        private class NodeIterator implements Iterator {
            private final Iterator adjIter = nodeMap.values().iterator();
            private AdjacencyList adj = null;
            private boolean isCurrentValid = false;

            NodeIterator() {
                super();
            }

            public boolean hasNext() {
                return adjIter.hasNext();
            }

            public Object next() {
                adj = (AdjacencyList) adjIter.next();
                isCurrentValid = true;
                return adj.node;
            }

            public void remove() {
                if (!isCurrentValid) {
                    throw new IllegalStateException();
                }
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug(instanceString + ".nodes().iterator().remove() upon node " + adj.node);
                }
                nodeRemoving(adj.node);
                adj.clear();
                adjIter.remove();
                processNodeRemoved(adj.node);
                nodeRemoved(adj.node);
                isCurrentValid = false;
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("  " + instanceString + ".nodes().iterator().remove() returning");
                }
            }
        }
    }

    private class AdjNodeCollection extends AbstractCollection {
        private final AdjacencyList adj;
        private final CursorFilter filter;

        AdjNodeCollection(AdjacencyList adj, CursorFilter filter) {
            super();
            this.adj = adj;
            this.filter = filter;
        }

        public int size() {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(instanceString + ".adjacentNodes().size()");
            }
            int size = adj.degree(filter);
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".adjacentNodes().size() returning " + size);
            }
            return size;
        }

        public boolean isEmpty() {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(instanceString + ".adjacentNodes().isEmpty()");
            }
            boolean isEmpty = super.isEmpty();
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".adjacentNodes().isEmpty() returning " + isEmpty);
            }
            return isEmpty;
        }

        public boolean remove(Object object) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(instanceString + ".adjacentNodes().remove( " + object + " )");
            }
            if (nodeMap.get(object) == null) {
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("  " + instanceString + ".adjacentNodes().remove() returning false");
                }
                return false;
            }
            for (Cursor cursor = adj.cursor(filter); cursor.hasNext();) {
                cursor.next();
                if (GraphUtils.equals(object, cursor.getOtherNode())) {
                    // Note that this just removes the Edge.
                    cursor.remove();
                    if (LOGGER.isDebugEnabled()) {
                        LOGGER.debug("  " + instanceString + ".adjacentNodes().remove() returning true");
                    }
                    return true;
                }
            }
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".adjacentNodes().remove() returning false");
            }
            return false;
        }

        public boolean contains(Object object) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(instanceString + ".adjacentNodes().contains( " + object + " )");
            }
            if (nodeMap.get(object) == null) {
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("  " + instanceString + ".adjacentNodes().contains() returning false");
                }
                return false;
            }
            for (Cursor cursor = adj.cursor(filter); cursor.hasNext();) {
                cursor.next();
                if (GraphUtils.equals(object, cursor.getOtherNode())) {
                    if (LOGGER.isDebugEnabled()) {
                        LOGGER.debug("  " + instanceString + ".adjacentNodes().contains() returning true");
                    }
                    return true;
                }
            }
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".adjacentNodes().contains() returning false");
            }
            return false;
        }

        public Iterator iterator() {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(instanceString + ".adjacentNodes().iterator()");
            }
            Iterator i = new CursorNodeIteratorAdapter(adj.cursor(filter));
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".adjacentNodes().iterator() returning");
            }
            return i;
        }

        public boolean addAll(Collection collection) {
            throw new UnsupportedOperationException();
        }

        public boolean containsAll(Collection collection) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(instanceString + ".adjacentNodes().containsAll( " + collection + " )");
            }
            boolean containsAll = super.containsAll(collection);
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".adjacentNodes().containsAll() returning " + containsAll);
            }
            return containsAll;
        }

        public boolean removeAll(Collection collection) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(instanceString + ".adjacentNodes().removeAll( " + collection + " )");
            }
            boolean modified = super.removeAll(collection);
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".adjacentNodes().removeAll() returning " + modified);
            }
            return modified;
        }

        public boolean retainAll(Collection collection) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(instanceString + ".adjacentNodes().retainAll( " + collection + " )");
            }
            boolean modified = super.retainAll(collection);
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".adjacentNodes().retainAll() returning " + modified);
            }
            return modified;
        }

        public void clear() {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(instanceString + ".adjacentNodes().clear()");
            }
            for (Iterator i = adj.cursor(filter); i.hasNext();) {
                i.next();
                i.remove();
            }
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".adjacentNodes().clear() returning");
            }
        }

        public Object[] toArray() {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(instanceString + ".adjacentNodes().toArray()");
            }
            Object[] array = super.toArray();
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".adjacentNodes().toArray() returning " + array);
            }
            return array;
        }

        public Object[] toArray(Object[] array) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(instanceString + ".adjacentNodes().toArray( " + array + " )");
            }
            Object[] returnedArray = super.toArray(array);
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".adjacentNodes().toArray() returning " + returnedArray);
            }
            return returnedArray;
        }

        public String toString() {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(instanceString + ".adjacentNodes().toString()");
            }
            String string = super.toString();
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".adjacentNodes().toString() returning " + string);
            }
            return string;
        }
    }

    /**
     *  Abstract base class for all Edge collections, defines a
     *  private get() method to just get a single Edge from the
     *  Collection.
     */
    private abstract class EdgeCollection extends AbstractCollection {
        EdgeCollection() {
            super();
        }

        abstract Graph.Edge get();

        public boolean addAll(Collection collection) {
            throw new UnsupportedOperationException();
        }
    }

    private class IncEdgeCollection extends EdgeCollection {
        private final AdjacencyList adj;
        private final CursorFilter filter;

        IncEdgeCollection(AdjacencyList adj, CursorFilter filter) {
            super();
            this.adj = adj;
            this.filter = filter;
        }

        Graph.Edge get() {
            return adj.get(filter);
        }

        public int size() {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(instanceString + ".incidentEdges().size()");
            }
            int size = adj.degree(filter);
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".incidentEdges().size() returning " + size);
            }
            return size;
        }

        public boolean isEmpty() {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(instanceString + ".incidentEdges().isEmpty()");
            }
            boolean isEmpty = super.isEmpty();
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".incidentEdges().isEmpty() returning " + isEmpty);
            }
            return isEmpty;
        }

        public boolean remove(Object object) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(instanceString + ".incidentEdges().remove( " + object + " )");
            }
            if (!(object instanceof Graph.Edge)) {
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("  " + instanceString + ".incidentEdges().remove() returning false");
                }
                return false;
            }
            Graph.Edge edge = (Graph.Edge) object;
            boolean modified = (filter == null || filter.evaluate(adj.node, edge)) && adj.remove(edge);
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".incidentEdges().remove() returning " + modified);
            }
            return modified;
        }

        public boolean contains(Object object) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(instanceString + ".incidentEdges().contains( " + object + " )");
            }
            if (!(object instanceof Graph.Edge)) {
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("  " + instanceString + ".incidentEdges().contains() returning false");
                }
                return false;
            }
            Graph.Edge edge = (Graph.Edge) object;
            boolean contains = (filter == null || filter.evaluate(adj.node, edge)) && adj.contains(edge);
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".incidentEdges().contains() returning " + contains);
            }
            return contains;
        }

        public Iterator iterator() {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(instanceString + ".incidentEdges().iterator()");
            }
            Iterator i = new CursorEdgeIteratorAdapter(adj.cursor(filter));
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".incidentEdges().iterator() returning");
            }
            return i;
        }

        public boolean containsAll(Collection collection) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(instanceString + ".incidentEdges().containsAll( " + collection + " )");
            }
            boolean containsAll = super.containsAll(collection);
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".incidentEdges().containsAll() returning " + containsAll);
            }
            return containsAll;
        }

        public boolean removeAll(Collection collection) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(instanceString + ".incidentEdges().removeAll( " + collection + " )");
            }
            boolean modified = super.removeAll(collection);
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".incidentEdges().removeAll() returning " + modified);
            }
            return modified;
        }

        public boolean retainAll(Collection collection) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(instanceString + ".incidentEdges().retainAll( " + collection + " )");
            }
            boolean modified = super.retainAll(collection);
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".incidentEdges().retainAll() returning " + modified);
            }
            return modified;
        }

        public void clear() {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(instanceString + ".incidentEdges().clear()");
            }
            for (Iterator i = adj.cursor(filter); i.hasNext();) {
                i.next();
                i.remove();
            }
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".incidentEdges().clear() returning");
            }
        }

        public Object[] toArray() {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(instanceString + ".incidentEdges().toArray()");
            }
            Object[] array = super.toArray();
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".incidentEdges().toArray() returning " + array);
            }
            return array;
        }

        public Object[] toArray(Object[] array) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(instanceString + ".incidentEdges().toArray( " + array + " )");
            }
            Object[] returnedArray = super.toArray(array);
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".incidentEdges().toArray() returning " + returnedArray);
            }
            return returnedArray;
        }

        public String toString() {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(instanceString + ".incidentEdges().toString()");
            }
            String string = super.toString();
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".incidentEdges().toString() returning " + string);
            }
            return string;
        }
    }

    private abstract class FilteredEdgeCollection extends EdgeCollection {
        private final Predicate edgePredicate;

        FilteredEdgeCollection(Predicate edgePredicate) {
            super();
            this.edgePredicate = edgePredicate;
        }

        /**
         *  Returns the appropriate filter for the specified base
         *  node, or null if the specified base node is not allowed.
         */
        abstract CursorFilter getFilter(Object baseNode);

        Graph.Edge get() {
            for (Iterator i = nodeMap.values().iterator(); i.hasNext();) {
                AdjacencyList adj = (AdjacencyList) i.next();
                CursorFilter filter = getFilter(adj.node);
                if (filter != null) {
                    Graph.Edge edge = adj.get(filter);
                    if (edge != null) {
                        return edge;
                    }
                }
            }
            return null;
        }

        public int size() {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(instanceString + ".edges().size()");
            }
            int size = 0;
            for (Iterator i = nodeMap.values().iterator(); i.hasNext();) {
                AdjacencyList adj = (AdjacencyList) i.next();
                CursorFilter filter = getFilter(adj.node);
                if (filter != null) {
                    size += adj.degree(filter);
                }
            }
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".edges().size() returning " + size);
            }
            return size;
        }

        public boolean isEmpty() {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(instanceString + ".edges().isEmpty()");
            }
            for (Iterator i = nodeMap.values().iterator(); i.hasNext();) {
                AdjacencyList adj = (AdjacencyList) i.next();
                CursorFilter filter = getFilter(adj.node);
                if (filter != null && adj.get(filter) != null) {
                    if (LOGGER.isDebugEnabled()) {
                        LOGGER.debug("  " + instanceString + ".edges().isEmpty() returning false");
                    }
                    return false;
                }
            }
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".edges().isEmpty() returning true");
            }
            return true;
        }

        public boolean remove(Object object) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(instanceString + ".edges().remove( " + object + " )");
            }
            boolean modified = (object instanceof Graph.Edge) && edgePredicate.evaluate(object)
                    && removeEdge((Graph.Edge) object);
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".edges().remove() returning " + modified);
            }
            return modified;
        }

        public boolean contains(Object object) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(instanceString + ".edges().contains( " + object + " )");
            }
            boolean contains = (object instanceof Graph.Edge) && edgePredicate.evaluate(object)
                    && containsEdge((Graph.Edge) object);
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".edges().contains() returning " + contains);
            }
            return contains;
        }

        public Iterator iterator() {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(instanceString + ".edges().iterator()");
            }
            Iterator i = new EdgeIterator();
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".edges().iterator() returning");
            }
            return i;
        }

        public boolean containsAll(Collection collection) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(instanceString + ".edges().containsAll( " + collection + " )");
            }
            boolean containsAll = super.containsAll(collection);
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".edges().containsAll() returning " + containsAll);
            }
            return containsAll;
        }

        public boolean removeAll(Collection collection) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(instanceString + ".edges().removeAll( " + collection + " )");
            }
            boolean modified = super.removeAll(collection);
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".edges().removeAll() returning " + modified);
            }
            return modified;
        }

        public boolean retainAll(Collection collection) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(instanceString + ".edges().retainAll( " + collection + " )");
            }
            boolean modified = super.retainAll(collection);
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".edges().retainAll() returning " + modified);
            }
            return modified;
        }

        public void clear() {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(instanceString + ".edges().clear()");
            }
            super.clear();
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".edges().clear() returning");
            }
        }

        public Object[] toArray() {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(instanceString + ".edges().toArray()");
            }
            Object[] array = super.toArray();
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".edges().toArray() returning " + array);
            }
            return array;
        }

        public Object[] toArray(Object[] array) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(instanceString + ".edges().toArray( " + array + " )");
            }
            Object[] returnedArray = super.toArray(array);
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".edges().toArray() returning " + returnedArray);
            }
            return returnedArray;
        }

        public String toString() {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(instanceString + ".edges().toString()");
            }
            String string = super.toString();
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".edges().toString() returning");
            }
            return string;
        }

        private class EdgeIterator implements Iterator {
            private final Iterator adjIter = nodeMap.values().iterator();
            private Cursor cursor = EMPTY_CURSOR;
            private Object current = null;
            private Object next = null;

            EdgeIterator() {
                super();
            }

            protected void advance() {
                while (!cursor.hasNext() && adjIter.hasNext()) {
                    AdjacencyList adj = (AdjacencyList) adjIter.next();
                    CursorFilter filter = getFilter(adj.node);
                    if (filter != null) {
                        cursor = adj.cursor(filter);
                    }
                }
            }

            public boolean hasNext() {
                if (next != null) {
                    return true;
                }
                advance();
                if (!cursor.hasNext()) {
                    return false;
                }
                next = cursor.next();
                return true;
            }

            public Object next() {
                if (!hasNext()) {
                    throw new NoSuchElementException();
                }
                current = next;
                next = null;
                return current;
            }

            public void remove() {
                if (current == null) {
                    throw new IllegalStateException();
                }
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug(instanceString + ".edges().iterator().remove() upon edge " + current);
                }
                removeEdge((Graph.Edge) current);
                current = null;
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug(instanceString + ".edges().iterator().remove() returning");
                }
            }
        }
    }

    /**
     *  Edge collection for all the edges.
     */
    private class AllEdgesCollection extends FilteredEdgeCollection {
        AllEdgesCollection() {
            super(null);
        }

        CursorFilter getFilter(Object baseNode) {
            return BASE_TAIL_CURSOR_FILTER;
        }

        Graph.Edge get() {
            for (Iterator i = nodeMap.values().iterator(); i.hasNext();) {
                Graph.Edge edge = ((AdjacencyList) i.next()).get(null);
                if (edge != null) {
                    return edge;
                }
            }
            return null;
        }

        public int size() {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(instanceString + ".edges().size()");
                LOGGER.debug("  " + instanceString + ".edges().size() returning " + edgeSize);
            }
            return edgeSize;
        }

        public boolean isEmpty() {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(instanceString + ".edges().isEmpty()");
                LOGGER.debug("  " + instanceString + ".edges().isEmpty() returning " + (edgeSize == 0));
            }
            return edgeSize == 0;
        }

        public boolean remove(Object object) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(instanceString + ".edges().remove( " + object + " )");
            }
            boolean modified = (object instanceof Graph.Edge) && removeEdge((Graph.Edge) object);
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".edges().remove() returning " + modified);
            }
            return modified;
        }

        public boolean contains(Object object) {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug(instanceString + ".edges().contains( " + object + " )");
            }
            boolean contains = (object instanceof Graph.Edge) && containsEdge((Graph.Edge) object);
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".edges().contains() returning " + contains);
            }
            return contains;
        }
    }

    /**
     *  An Edge collection where there is no way to filter either
     *  endpoint.
     */
    private class AnyToAnyEdgeCollection extends FilteredEdgeCollection {
        private final CursorFilter filter;

        AnyToAnyEdgeCollection(Predicate edgePredicate, CursorFilter filter) {
            super(edgePredicate);
            this.filter = filter;
        }

        CursorFilter getFilter(Object baseNode) {
            return filter;
        }
    }

    /**
     *  An Edge collection where one endpoint is filtered.
     */
    private class PredToAnyEdgeCollection extends FilteredEdgeCollection {
        private final Predicate basePredicate;
        private final CursorFilter filter;

        PredToAnyEdgeCollection(Predicate edgePredicate, Predicate basePredicate, CursorFilter filter) {
            super(edgePredicate);
            this.basePredicate = basePredicate;
            this.filter = filter;
        }

        CursorFilter getFilter(Object baseNode) {
            return basePredicate.evaluate(baseNode) ? filter : null;
        }
    }

    /**
     *  An Edge collection where both endpoints are filtered.
     */
    private class PToQEdgeCollection extends FilteredEdgeCollection {
        private final Predicate basePredicate;
        private final Predicate nodePredicate;
        private final CursorFilter qBaseFilter;
        private final CursorFilter notQBaseFilter;

        PToQEdgeCollection(Predicate edgePredicate, Predicate basePredicate, Predicate nodePredicate,
                CursorFilter qBaseFilter, CursorFilter notQBaseFilter) {
            super(edgePredicate);
            this.basePredicate = basePredicate;
            this.nodePredicate = nodePredicate;
            this.qBaseFilter = qBaseFilter;
            this.notQBaseFilter = notQBaseFilter;
        }

        CursorFilter getFilter(Object baseNode) {
            return basePredicate.evaluate(baseNode)
                    ? (nodePredicate.evaluate(baseNode) ? qBaseFilter : notQBaseFilter)
                    : null;
        }
    }

    ////////////////////////////////////////
    // Private AdjacencyList implementation
    ////////////////////////////////////////

    /**
     *  An adjacency list implementation.  When using this method of
     *  representation, each node in the graph has an associated
     *  adjacency list.  A node's adjacency list contains information
     *  about which other nodes are adjacent to it, and through which
     *  edges.  A self-loop should only be included once.
     */
    private class AdjacencyList {
        /**
         *  The node for which this is an adjacency list.
         */
        final Object node;

        /**
         *  The list of Graph.Edge objects in this adjacency list.
         */
        final ArrayList edges = new ArrayList(5);

        /**
         *  Whether or not this adjacency list is still valid (has not
         *  been cleared).  This is used by cursors.
         */
        boolean isValid = true;

        ////////////////////////////////////////
        // Constructor
        ////////////////////////////////////////

        /**
         *  Constructs a new <code>AdjacencyList</code> for the
         *  specified node.
         */
        AdjacencyList(Object node) {
            super();
            this.node = node;
        }

        ////////////////////////////////////////
        // Add method
        ////////////////////////////////////////

        /**
         *  Adds the specified edge to this
         *  <code>AdjacencyList</code>, if not already present.
         *  Returns the added <code>Graph.Edge</code> or
         *  <code>null</code>.
         *
         *  @param edge the new edge.
         *
         *  @param headAdj the AdjacencyList of the head of the new
         *  edge.
         *
         *  @return the new edge.
         */
        Graph.Edge addTo(Graph.Edge edge, AdjacencyList headAdj) {
            if (edges.contains(edge) || (this != headAdj && headAdj.edges.contains(edge))) {
                return null;
            }
            edgeAdding(edge);
            edges.add(edge);
            alertEdgeAdded(this);
            // Don't add self-loops twice
            if (this != headAdj) {
                headAdj.edges.add(edge);
                alertEdgeAdded(headAdj);
            }
            processEdgeAdded(edge);
            edgeAdded(edge);
            return edge;
        }

        ////////////////////////////////////////
        // Remove methods
        ////////////////////////////////////////

        /**
         *  Removes the specified edge from this
         *  <code>AdjacencyList</code>.
         *
         *  @param edge the edge to be removed from this
         *  <code>AdjacencyList</code>.
         */
        boolean remove(Graph.Edge edge) {
            int index = edges.indexOf(edge);
            if (index == -1) {
                return false;
            }
            remove(index, (Graph.Edge) edges.get(index));
            return true;
        }

        /**
         *  Removes the specified edge at the specified index
         *  from this <code>AdjacencyList</code>.
         *
         *  @param index the index of the specified edge to be
         *  removed from this <code>AdjacencyList</code>.
         *
         *  @param edge the edge at the specified index to be
         *  removed from this <code>AdjacencyList</code>.
         */
        void remove(int index, Graph.Edge edge) {
            edgeRemoving(edge);
            edges.remove(index);
            alertEdgeRemoved(this, index);
            AdjacencyList otherAdj = (AdjacencyList) nodeMap.get(edge.getOtherEndpoint(node));
            if (this != otherAdj) {
                int otherIndex = otherAdj.edges.indexOf(edge);
                otherAdj.edges.remove(otherIndex);
                alertEdgeRemoved(otherAdj, otherIndex);
            }
            processEdgeRemoved(edge);
            edgeRemoved(edge);
        }

        ////////////////////////////////////////
        // Contains method
        ////////////////////////////////////////

        /**
         *  Returns whether or not this <code>AdjacencyList</code>
         *  contains the specified edge.
         *
         *  @param edge the edge whose presence in this
         *  <code>AdjacencyList</code> is to be tested.
         */
        boolean contains(Graph.Edge edge) {
            return edges.contains(edge);
        }

        ////////////////////////////////////////
        // Clear method
        ////////////////////////////////////////

        /**
         *  Clears this <code>AdjacencyList</code> and renders it
         *  invalid for further use.
         */
        void clear() {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".AdjacencyList.clear() for node " + node);
            }
            for (Cursor cursor = new CursorImpl(null); cursor.hasNext();) {
                cursor.next();
                cursor.remove();
            }
            isValid = false;
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("  " + instanceString + ".AdjacencyList.clear() returning");
            }
        }

        ////////////////////////////////////////
        // Get method
        ////////////////////////////////////////

        /**
         *  Returns the specified edge if this
         *  <code>AdjacencyList</code> contains it, or
         *  <code>null</code> if it doesn't.
         *
         *  @return the specified edge if this
         *  <code>AdjacencyList</code> contains it, or
         *  <code>null</code> if it doesn't.
         */
        Graph.Edge get(CursorFilter filter) {
            int size = edges.size();

            if (filter == null) {
                return (size > 0) ? (Graph.Edge) edges.get(0) : null;

            } else if (filter == FALSE_CURSOR_FILTER) {
                return null;

            } else {
                for (int i = 0; i < size; i++) {
                    Graph.Edge edge = (Graph.Edge) edges.get(i);
                    if (filter.evaluate(node, edge)) {
                        return edge;
                    }
                }
                return null;
            }
        }

        ////////////////////////////////////////
        // Counting methods
        ////////////////////////////////////////

        /**
         *  Returns the number of edges in this
         *  <code>AdjacencyList</code>.  If this
         *  <code>AdjacencyList</code> contains more than
         *  <code>Integer.MAX_VALUE</code> edges, returns
         *  <code>Integer.MAX_VALUE</code>.
         *
         *  @return the number of edges in this
         *  <code>AdjacencyList</code>.
         */
        int size() {
            return edges.size();
        }

        /**
         *  Returns the number of edges in this
         *  <code>AdjacencyList</code>, with self-loops counted twice.
         *  If this <code>AdjacencyList</code> contains more than
         *  <code>Integer.MAX_VALUE</code> edges, returns
         *  <code>Integer.MAX_VALUE</code>.
         *
         *  @return the number of edges in this
         *  <code>AdjacencyList</code>, with self-loops counted twice.
         */
        int degree() {
            int size = edges.size();
            int selfCount = 0;
            for (int i = 0; i < size; i++) {
                Graph.Edge edge = (Graph.Edge) edges.get(i);
                if (GraphUtils.equals(edge.getTail(), edge.getHead())) {
                    selfCount++;
                }
            }
            return size + selfCount;
        }

        /**
         *  Returns the number of edges in this
         *  <code>AdjacencyList</code> satisfying the specified
         *  predicate.  If this <code>AdjacencyList</code> contains
         *  more than <code>Integer.MAX_VALUE</code> such edges,
         *  returns <code>Integer.MAX_VALUE</code>.
         *
         *  @return the number of edges in this
         *  <code>AdjacencyList</code> satisfying the specified
         *  predicate.
         */
        int degree(Predicate traverserPredicate) {
            int size = edges.size();
            int count = 0;
            OrderedPair pair = new OrderedPair(node, null);
            for (int i = 0; i < size; i++) {
                pair.setSecond(edges.get(i));
                if (traverserPredicate.evaluate(pair)) {
                    count++;
                }
            }
            return count;
        }

        /**
         *  Returns the number of edges in this
         *  <code>AdjacencyList</code> satisfying the specified cursor
         *  filter.  If this <code>AdjacencyList</code> contains more
         *  than <code>Integer.MAX_VALUE</code> such edges, returns
         *  <code>Integer.MAX_VALUE</code>.
         *
         *  @return the number of edges in this
         *  <code>AdjacencyList</code> satisfying the specified
         *  cursor filter.
         */
        int degree(CursorFilter filter) {
            int size = edges.size();

            if (filter == null) {
                return size;

            } else if (filter == FALSE_CURSOR_FILTER) {
                return 0;

            } else {
                int count = 0;
                for (int i = 0; i < size; i++) {
                    Graph.Edge edge = (Graph.Edge) edges.get(i);
                    if (filter.evaluate(node, edge)) {
                        count++;
                    }
                }
                return count;
            }
        }

        /**
         *  Returns the number of out edges in this
         *  <code>AdjacencyList</code>.  If this
         *  <code>AdjacencyList</code> contains more than
         *  <code>Integer.MAX_VALUE</code> out edges, returns
         *  <code>Integer.MAX_VALUE</code>.
         *
         *  @return the number of out edges in this
         *  <code>AdjacencyList</code>.
         */
        int outDegree() {
            int size = edges.size();
            int count = 0;
            for (int i = 0; i < size; i++) {
                Graph.Edge edge = (Graph.Edge) edges.get(i);
                if (edge.isDirected() && GraphUtils.equals(node, edge.getTail())) {
                    count++;
                }
            }
            return count;
        }

        /**
         *  Returns the number of in edges in this
         *  <code>AdjacencyList</code>.  If this
         *  <code>AdjacencyList</code> contains more than
         *  <code>Integer.MAX_VALUE</code> in edges, returns
         *  <code>Integer.MAX_VALUE</code>.
         *
         *  @return the number of in edges in this
         *  <code>AdjacencyList</code>.
         */
        int inDegree() {
            int size = edges.size();
            int count = 0;
            for (int i = 0; i < size; i++) {
                Graph.Edge edge = (Graph.Edge) edges.get(i);
                if (edge.isDirected() && GraphUtils.equals(node, edge.getHead())) {
                    count++;
                }
            }
            return count;
        }

        ////////////////////////////////////////
        // Cursor creation method
        ////////////////////////////////////////

        /**
         *  Returns a <code>Cursor</code> over all the edges in this
         *  <code>AdjacencyList</code> which satisfy the specified
         *  filter.
         *
         *  @return a <code>Cursor</code> over all the edges in this
         *  <code>AdjacencyList</code> which satisfy the specified
         *  filter.
         */
        Cursor cursor(CursorFilter filter) {
            return filter == FALSE_CURSOR_FILTER ? EMPTY_CURSOR : new CursorImpl(filter);
        }

        ////////////////////////////////////////
        // Other methods
        ////////////////////////////////////////

        public String toString() {
            int size = edges.size();
            StringBuilder s = new StringBuilder();
            s.append("Adj( ");
            s.append(node);
            s.append(" ): [ ");
            for (int i = 0; i < size; i++) {
                Graph.Edge edge = (Graph.Edge) edges.get(i);
                s.append("(");
                s.append(edge.getUserObject());
                s.append(") ");
                if (GraphUtils.equals(node, edge.getTail())) {
                    s.append(edge.isDirected() ? "-> (" : "-- (");
                    s.append(edge.getHead());
                    s.append(")");
                } else {
                    s.append(edge.isDirected() ? "<- (" : "-- (");
                    s.append(edge.getTail());
                    s.append(")");
                }
                if (i < size - 1) {
                    s.append(", ");
                }
            }
            s.append(" ]");
            return s.toString();
        }

        ////////////////////////////////////////
        // Private Cursor implementation
        ////////////////////////////////////////

        private class CursorImpl implements Cursor {
            /**
             *  The predicate being used to filter this cursor.
             */
            private final CursorFilter filter;

            /**
             *  The index into the underlying list of the last element
             *  returned by next, or -1 if the iteration hasn't
             *  started yet.
             */
            private int currentIndex = -1;

            /**
             *  The index into the underlying list of the next element
             *  to be returned by next (if known), otherwise the same
             *  as currentIndex.  An index of -1 indicates that we
             *  haven't started yet; one of -2 indicates that next()
             *  has been called when there was nothing left.
             */
            private int nextIndex = -1;

            /**
             *  The edge at currentIndex, or null if it isn't valid.
             */
            private Graph.Edge currentEdge = null;

            /**
             *  The edge at nextIndex, if nextIndex represents a valid edge.
             */
            private Graph.Edge nextEdge = null;

            /**
             *  Create a new cursor.
             */
            CursorImpl(CursorFilter filter) {
                super();
                this.filter = filter;
                // Add this cursor to those that receive notifications
                // from the graph.
                if (cursors != null)
                    cursors.add(this);
            }

            public final boolean hasNext() {
                if (!isValid) {
                    return false;
                }
                if (nextIndex != currentIndex) {
                    return nextIndex >= 0;
                }
                int size = edges.size();
                for (int i = nextIndex + 1; i < size; i++) {
                    Graph.Edge edge = (Graph.Edge) edges.get(i);
                    if (filter == null || filter.evaluate(node, edge)) {
                        nextIndex = i;
                        nextEdge = edge;
                        return true;
                    }
                }
                return false;
            }

            public final Object next() {
                if (!isValid) {
                    throw new ConcurrentModificationException();
                }
                if (!hasNext()) {
                    nextIndex = -2;
                    currentEdge = null;
                    throw new NoSuchElementException();
                }
                currentIndex = nextIndex;
                currentEdge = nextEdge;
                return currentEdge;
            }

            public final void remove() {
                if (!isValid) {
                    throw new ConcurrentModificationException();
                }
                if (currentEdge == null) {
                    throw new IllegalStateException();
                }
                AdjacencyList.this.remove(currentIndex, currentEdge);
            }

            public final AdjacencyList getAdjacencyList() {
                return AdjacencyList.this;
            }

            public final Graph.Edge getCurrentEdge() {
                if (!isValid) {
                    throw new ConcurrentModificationException();
                }
                if (currentEdge == null) {
                    throw new IllegalStateException();
                }
                return currentEdge;
            }

            public final Object getOtherNode() {
                if (!isValid) {
                    throw new ConcurrentModificationException();
                }
                if (currentEdge == null) {
                    throw new IllegalStateException();
                }
                return currentEdge.getOtherEndpoint(node);
            }

            public final void removeOtherNode() {
                removeNode(getOtherNode());
            }

            public final void edgeRemoved(int index) {
                if (index < currentIndex) {
                    currentIndex--;
                } else if (index == currentIndex) {
                    currentIndex--;
                    currentEdge = null;
                }

                if (index < nextIndex) {
                    nextIndex--;
                } else if (index == nextIndex) {
                    int size = edges.size();
                    for (int i = nextIndex; i < size; i++) {
                        Graph.Edge edge = (Graph.Edge) edges.get(i);
                        if (filter == null || filter.evaluate(node, edge)) {
                            nextIndex = i;
                            nextEdge = edge;
                            return;
                        }
                    }
                    nextIndex = currentIndex;
                }
            }
        } // end CursorImpl definition

    } // end AdjacencyList definition

}