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

org.apache.openjpa.lib.graph.DepthFirstAnalysis Maven / Gradle / Ivy

There is a newer version: 4.0.1
Show newest version
/*
 * 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 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.LinkedList;
import java.util.List;
import java.util.Map;

import org.apache.openjpa.lib.util.Localizer;

/**
 * 

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.

* * @author Abe White * @since 1.0.0 */ 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 (Object node : nodes) _nodeInfo.put(node, new NodeInfo()); // visit all nodes -- see intro to algo's book NodeInfo info; for (Object node : nodes) { info = _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); int maxChildTime = time - 1; int childTime; for (Edge edge : edges) { Object other = edge.getOther(node); NodeInfo otherInfo = _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 = _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; for (Object node : _graph.getNodes()) { for (Edge edge : _graph.getEdgesFrom(node)) { if (edge.getType() == type) { if (typed == null) typed = new ArrayList<>(); typed.add(edge); } } } if(typed == null ) { typed = Collections.emptyList(); } return 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.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; } 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; } List 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 path parameter. */ private boolean cycleForForwardEdge(Graph graph, Object node, Object cycleTo, List path) { boolean found = false; Collection edges = graph.getEdgesFrom(node); for (Edge edge : edges) { 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 ( 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 (Edge edge : edges) { if (edge.getCycle() != null) { return false; } } } return true; } /** * Comparator for topologically sorting entries in the node info map. */ private static class NodeInfoComparator implements Comparator> { private final Comparator _subComp; public NodeInfoComparator(Comparator subComp) { _subComp = subComp; } @Override public int compare(Map.Entry e1, Map.Entry e2) { NodeInfo n1 = e1.getValue(); NodeInfo n2 = 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; } @Override public Object get(int idx) { return _entries[idx].getKey(); } @Override public int size() { return _entries.length; } } }