blob: 0f4693b7da474ddff4439c1213684c4f5d23afac [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.openjpa.lib.graph;
import org.apache.openjpa.lib.util.Localizer;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* <p>Performs a depth-first analysis of a given {@link Graph}, caching
* information about the graph's nodes and edges. See the DFS algorithm
* in the book 'Introduction to Algorithms' by Cormen, Leiserson, and
* Rivest. The algorithm has been modified to group sibling nodes without
* connections together during the topological sort.</p>
*
* @author Abe White
* @since 1.0.0
* @nojavadoc
*/
public class DepthFirstAnalysis {
private static final Localizer _loc = Localizer.forPackage
(DepthFirstAnalysis.class);
private final Graph _graph;
private final Map _nodeInfo = new HashMap();
private Comparator _comp;
/**
* Constructor. Performs the analysis on the given graph and caches
* the resulting information.
*/
public DepthFirstAnalysis(Graph graph) {
_graph = graph;
// initialize node infos
Collection nodes = graph.getNodes();
for (Iterator itr = nodes.iterator(); itr.hasNext();)
_nodeInfo.put(itr.next(), new NodeInfo());
// visit all nodes -- see intro to algo's book
NodeInfo info;
Object node;
for (Iterator itr = nodes.iterator(); itr.hasNext();) {
node = itr.next();
info = (NodeInfo) _nodeInfo.get(node);
if (info.color == NodeInfo.COLOR_WHITE)
visit(graph, node, info, 0, new LinkedList());
}
}
/**
* Visit a node. See Introduction to Algorithms book for details.
*/
private int visit(Graph graph, Object node, NodeInfo info, int time,
List path) {
// discover node
info.color = NodeInfo.COLOR_GRAY;
// explore all vertices from that node depth first
Collection edges = graph.getEdgesFrom(node);
Edge edge;
Object other;
NodeInfo otherInfo;
int maxChildTime = time - 1;
int childTime;
for (Iterator itr = edges.iterator(); itr.hasNext();) {
edge = (Edge) itr.next();
other = edge.getOther(node);
otherInfo = (NodeInfo) _nodeInfo.get(other);
if (otherInfo.color == NodeInfo.COLOR_WHITE) {
// undiscovered node; recurse into it
path.add(edge);
childTime = visit(graph, other, otherInfo, time, path);
path.remove(edge);
edge.setType(Edge.TYPE_TREE);
} else if (otherInfo.color == NodeInfo.COLOR_GRAY) {
childTime = -1;
edge.setType(Edge.TYPE_BACK);
// calculate the cycle including this edge
edge.setCycle(cycleForBackEdge(edge, path));
} else {
childTime = otherInfo.finished;
edge.setType(Edge.TYPE_FORWARD);
// find the cycle including this edge
List cycle = new LinkedList();
cycle.add(edge);
if (cycleForForwardEdge(graph, other, node, cycle)) {
edge.setCycle(cycle);
}
}
maxChildTime = Math.max(maxChildTime, childTime);
}
// finished with node
info.color = NodeInfo.COLOR_BLACK;
info.finished = maxChildTime + 1;
return info.finished;
}
/**
* Set the comparator that should be used for ordering groups of nodes
* with the same dependencies.
*/
public void setNodeComparator(Comparator comp) {
_comp = comp;
}
/**
* Return the nodes in topologically-sorted order. This is often used
* to order dependencies. If each graph edge (u, v) represents a
* dependency of v on u, then this method will return the nodes in the
* order that they should be evaluated to satisfy all dependencies. Of
* course, if the graph is cyclic (has back edges), then no such ordering
* is possible, though this method will still return the correct order
* as if edges creating the cycles did not exist.
*/
public List getSortedNodes() {
Map.Entry[] entries = (Map.Entry[]) _nodeInfo.entrySet().
toArray(new Map.Entry[_nodeInfo.size()]);
Arrays.sort(entries, new NodeInfoComparator(_comp));
return new NodeList(entries);
}
/**
* Return all edges of the given type. This method can be used to
* discover all edges that cause cycles in the graph by passing it
* the {@link Edge#TYPE_BACK} or {@link Edge#TYPE_FORWARD} edge type.
*/
public Collection getEdges(int type) {
Collection typed = null;
Edge edge;
Object node;
for (Iterator nodes = _graph.getNodes().iterator(); nodes.hasNext();) {
node = nodes.next();
for (Iterator itr = _graph.getEdgesFrom(node).iterator();
itr.hasNext();) {
edge = (Edge) itr.next();
if (edge.getType() == type) {
if (typed == null)
typed = new ArrayList();
typed.add(edge);
}
}
}
return (typed == null) ? Collections.EMPTY_LIST : typed;
}
/**
* Return the logical time that the given node was finished in
* the graph walk, or -1 if the node is not part of the graph.
*/
public int getFinishedTime(Object node) {
NodeInfo info = (NodeInfo) _nodeInfo.get(node);
if (info == null)
return -1;
return info.finished;
}
/**
* Returns a list of graph edges forming a cycle. The cycle begins
* with a type {@link Edge#TYPE_BACK} edge.
* @param backEdge "Starting" edge of the cycle
* @param path Continuous list of graph edges, may be null
* @param pos Index of the first edge in path continuing the cycle
* @return Cycle starting with a type {@link Edge#TYPE_BACK} edge
*/
private List buildCycle(Edge backEdge, List path, int pos) {
int length = path != null ? path.size() - pos : 0;
List cycle = new ArrayList(length + 1);
cycle.add(0, backEdge);
for (int i = 0; i < length; i++) {
cycle.add(i + 1, path.get(pos + i));
}
return cycle;
}
/**
* Computes the list of edges forming a cycle. The cycle always exists for
* a type {@link Edge#TYPE_BACK} edge. This method should only be called
* for type {@link Edge#TYPE_BACK} edges.
* @param edge Edge where the cycle was detected
* @param path Path consisting of edges to the edge's starting node
* @return Cycle starting with a type {@link Edge#TYPE_BACK} edge
*/
private List cycleForBackEdge(Edge edge, List path) {
if (edge.getType() != Edge.TYPE_BACK) {
return null;
}
List cycle;
int pos = 0;
if (path != null && !edge.getFrom().equals(edge.getTo())) {
// Not a single edge loop
pos = findNodeInPath(edge.getTo(), path);
assert (pos >= 0): _loc.get("node-not-on-path", edge, edge.getTo());
} else {
assert (edge.getFrom().equals(edge.getTo())):
_loc.get("edge-no-loop", edge).getMessage();
path = null;
}
cycle = buildCycle(edge, path, pos);
assert (cycle != null): _loc.get("cycle-null", edge).getMessage();
return cycle;
}
/**
* Computes the cycle of edges including node cycleTo. The cycle must not
* necessarily exist. This method should only be called for type
* {@link Edge#TYPE_FORWARD} edges.
* @param graph Graph
* @param node Current node
* @param cycleTo End node for loop
* @param path Path from loop end node to current node
* @return True if a cycle has been found. The cycle will be contained in
* the <code>path</code> parameter.
*/
private boolean cycleForForwardEdge(Graph graph, Object node,
Object cycleTo, List path) {
boolean found = false;
Collection edges = graph.getEdgesFrom(node);
for (Iterator itr = edges.iterator(); !found && itr.hasNext();) {
Edge edge = (Edge) itr.next();
Object other = edge.getOther(node);
// Single edge loops are ignored
if (!node.equals(other)) {
if (other.equals(cycleTo)) {
// Cycle complete
path.add(edge);
found = true;
} else if (!path.contains(edge)){
// Walk this edge
path.add(edge);
found = cycleForForwardEdge(graph, other, cycleTo, path);
if (!found) {
// Remove edge again
path.remove(edge);
}
}
}
}
return found;
}
/**
* Finds the position of the edge starting from a particular node in the
* continuous list of edges.
* @param node Node on the cycle.
* @param path Continuous list of graph edges.
* @return Edge index if found, -1 otherwise.
*/
private int findNodeInPath(Object node, List path) {
int pos = -1;
if (path != null) {
for (int i = 0; i < path.size(); i++) {
if (((Edge)path.get(i)).getFrom().equals(node)) {
pos = i;
}
}
}
return pos;
}
/**
* Test, if the analysis didn't find cycles.
*/
public boolean hasNoCycles() {
// a) there must not be any back edges
if (!getEdges(Edge.TYPE_BACK).isEmpty()) {
return false;
}
// b) there might be forward edges
// make sure these don't indicate cycles
Collection edges = getEdges(Edge.TYPE_FORWARD);
if (!edges.isEmpty()) {
for (Iterator itr = edges.iterator(); itr.hasNext();) {
Edge edge = (Edge) itr.next();
if (edge.getCycle() != null) {
return false;
}
}
}
return true;
}
/**
* Comparator for toplogically sorting entries in the node info map.
*/
private static class NodeInfoComparator
implements Comparator {
private final Comparator _subComp;
public NodeInfoComparator(Comparator subComp) {
_subComp = subComp;
}
public int compare(Object o1, Object o2) {
Map.Entry e1 = (Map.Entry) o1;
Map.Entry e2 = (Map.Entry) o2;
NodeInfo n1 = (NodeInfo) e1.getValue();
NodeInfo n2 = (NodeInfo) e2.getValue();
// sort by finished order
int ret = n1.finished - n2.finished;
if (ret == 0 && _subComp != null)
ret = _subComp.compare(e1.getKey(), e2.getKey());
return ret;
}
}
/**
* List of node-to-nodeinfo entries that exposes just the nodes.
*/
private static class NodeList
extends AbstractList {
private final Map.Entry[] _entries;
public NodeList(Map.Entry[] entries) {
_entries = entries;
}
public Object get(int idx) {
return _entries[idx].getKey();
}
public int size() {
return _entries.length;
}
}
}