mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-11-12 23:23:17 +00:00
Merge remote-tracking branch 'origin/dragonmacher-graph-algo-find-paths-fix'
This commit is contained in:
commit
70bc692900
@ -87,6 +87,10 @@ public class FunctionReachabilityTableModel
|
||||
|
||||
Accumulator<List<FRVertex>> pathAccumulator = new PassThroughAccumulator(accumulator);
|
||||
|
||||
if (v1.equals(v2)) {
|
||||
return;
|
||||
}
|
||||
|
||||
monitor.setMessage("Finding paths...");
|
||||
GraphAlgorithms.findPaths(graph, v1, v2, pathAccumulator, monitor);
|
||||
}
|
||||
|
@ -388,6 +388,7 @@ public class GraphAlgorithms {
|
||||
* @param monitor the timeout task monitor
|
||||
* @return the circuits
|
||||
* @throws CancelledException if the monitor is cancelled
|
||||
* @throws TimeoutException if the algorithm times-out, as defined by the monitor
|
||||
*/
|
||||
public static <V, E extends GEdge<V>> List<List<V>> findCircuits(GDirectedGraph<V, E> g,
|
||||
boolean uniqueCircuits, TimeoutTaskMonitor monitor)
|
||||
@ -405,10 +406,6 @@ public class GraphAlgorithms {
|
||||
* <P><B><U>Warning:</U></B> for large, dense graphs (those with many interconnected
|
||||
* vertices) this algorithm could run indeterminately, possibly causing the JVM to
|
||||
* run out of memory.
|
||||
*
|
||||
* <P><B><U>Warning:</U></B> This is a recursive algorithm. As such, it is limited in how
|
||||
* deep it can recurse. Any path that exceeds the {@link #JAVA_STACK_DEPTH_LIMIT} will
|
||||
* not be found.
|
||||
*
|
||||
* <P>You are encouraged to call this method with a monitor that will limit the work to
|
||||
* be done, such as the {@link TimeoutTaskMonitor}.
|
||||
@ -423,8 +420,8 @@ public class GraphAlgorithms {
|
||||
public static <V, E extends GEdge<V>> void findPaths(GDirectedGraph<V, E> g, V start, V end,
|
||||
Accumulator<List<V>> accumulator, TaskMonitor monitor) throws CancelledException {
|
||||
|
||||
// the algorithm gets run at construction; the results are sent to the accumulator
|
||||
new FindPathsAlgorithm<>(g, start, end, accumulator, monitor);
|
||||
IterativeFindPathsAlgorithm<V, E> algo = new IterativeFindPathsAlgorithm<>();
|
||||
algo.findPaths(g, start, end, accumulator, monitor);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -436,10 +433,6 @@ public class GraphAlgorithms {
|
||||
* <P><B><U>Warning:</U></B> for large, dense graphs (those with many interconnected
|
||||
* vertices) this algorithm could run indeterminately, possibly causing the JVM to
|
||||
* run out of memory.
|
||||
*
|
||||
* <P><B><U>Warning:</U></B> This is a recursive algorithm. As such, it is limited in how
|
||||
* deep it can recurse. Any path that exceeds the {@link #JAVA_STACK_DEPTH_LIMIT} will
|
||||
* not be found.
|
||||
*
|
||||
* @param g the graph
|
||||
* @param start the start vertex
|
||||
@ -453,8 +446,8 @@ public class GraphAlgorithms {
|
||||
Accumulator<List<V>> accumulator, TimeoutTaskMonitor monitor)
|
||||
throws CancelledException, TimeoutException {
|
||||
|
||||
// the algorithm gets run at construction; the results are sent to the accumulator
|
||||
new FindPathsAlgorithm<>(g, start, end, accumulator, monitor);
|
||||
FindPathsAlgorithm<V, E> algo = new IterativeFindPathsAlgorithm<>();
|
||||
algo.findPaths(g, start, end, accumulator, monitor);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -549,6 +542,7 @@ public class GraphAlgorithms {
|
||||
* A method to debug the given graph by printing it.
|
||||
*
|
||||
* @param g the graph to print
|
||||
* @param ps the output stream
|
||||
*/
|
||||
public static <V, E extends GEdge<V>> void printGraph(GDirectedGraph<V, E> g, PrintStream ps) {
|
||||
Set<V> sources = getSources(g);
|
||||
|
@ -15,7 +15,7 @@
|
||||
*/
|
||||
package ghidra.graph.algo;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
|
||||
import ghidra.graph.GDirectedGraph;
|
||||
import ghidra.graph.GEdge;
|
||||
@ -23,122 +23,10 @@ import ghidra.util.datastruct.Accumulator;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* Finds all paths between two vertices for a given graph.
|
||||
*
|
||||
* <P><B><U>Warning:</U></B> This is a recursive algorithm. As such, it is limited in how deep
|
||||
* it can recurse. Any path that exceeds the {@link #JAVA_STACK_DEPTH_LIMIT} will not be found.
|
||||
*
|
||||
* <P>Note: this algorithm is based entirely on the {@link JohnsonCircuitsAlgorithm}.
|
||||
*
|
||||
* @param <V> the vertex type
|
||||
* @param <E> the edge type
|
||||
*/
|
||||
public class FindPathsAlgorithm<V, E extends GEdge<V>> {
|
||||
public interface FindPathsAlgorithm<V, E extends GEdge<V>> {
|
||||
|
||||
public static final int JAVA_STACK_DEPTH_LIMIT = 2700;
|
||||
private GDirectedGraph<V, E> g;
|
||||
private V startVertex;
|
||||
private V endVertex;
|
||||
public void findPaths(GDirectedGraph<V, E> g, V start, V end, Accumulator<List<V>> accumulator,
|
||||
TaskMonitor monitor) throws CancelledException;
|
||||
|
||||
private Stack<V> stack = new Stack<>();
|
||||
private Set<V> blockedSet = new HashSet<>();
|
||||
private Map<V, Set<V>> blockedBackEdgesMap = new HashMap<>();
|
||||
|
||||
public FindPathsAlgorithm(GDirectedGraph<V, E> g, V start, V end,
|
||||
Accumulator<List<V>> accumulator, TaskMonitor monitor) throws CancelledException {
|
||||
this.g = g;
|
||||
this.startVertex = start;
|
||||
this.endVertex = end;
|
||||
|
||||
monitor.initialize(g.getEdgeCount());
|
||||
find(accumulator, monitor);
|
||||
}
|
||||
|
||||
private void find(Accumulator<List<V>> accumulator, TaskMonitor monitor)
|
||||
throws CancelledException {
|
||||
|
||||
explore(startVertex, accumulator, 0, monitor);
|
||||
}
|
||||
|
||||
private boolean explore(V v, Accumulator<List<V>> accumulator, int depth, TaskMonitor monitor)
|
||||
throws CancelledException {
|
||||
|
||||
// TODO
|
||||
// Sigh. We are greatly limited in the size of paths we can processes due to the
|
||||
// recursive nature of this algorithm. This should be changed to be non-recursive.
|
||||
if (depth > JAVA_STACK_DEPTH_LIMIT) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean foundPath = false;
|
||||
blockedSet.add(v);
|
||||
stack.push(v);
|
||||
Collection<E> outEdges = getOutEdges(v);
|
||||
for (E e : outEdges) {
|
||||
monitor.checkCanceled();
|
||||
|
||||
V u = e.getEnd();
|
||||
if (u.equals(endVertex)) {
|
||||
outputCircuit(accumulator);
|
||||
foundPath = true;
|
||||
monitor.incrementProgress(1);
|
||||
}
|
||||
else if (!blockedSet.contains(u)) {
|
||||
foundPath |= explore(u, accumulator, depth + 1, monitor);
|
||||
monitor.incrementProgress(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (foundPath) {
|
||||
unblock(v);
|
||||
}
|
||||
else {
|
||||
for (E e : outEdges) {
|
||||
monitor.checkCanceled();
|
||||
V u = e.getEnd();
|
||||
addBackEdge(u, v);
|
||||
}
|
||||
}
|
||||
|
||||
stack.pop();
|
||||
return foundPath;
|
||||
}
|
||||
|
||||
private Collection<E> getOutEdges(V v) {
|
||||
Collection<E> outEdges = g.getOutEdges(v);
|
||||
if (outEdges == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return outEdges;
|
||||
}
|
||||
|
||||
private void unblock(V v) {
|
||||
blockedSet.remove(v);
|
||||
Set<V> set = blockedBackEdgesMap.get(v);
|
||||
if (set == null) {
|
||||
return;
|
||||
}
|
||||
for (V u : set) {
|
||||
if (blockedSet.contains(u)) {
|
||||
unblock(u);
|
||||
}
|
||||
}
|
||||
set.clear();
|
||||
}
|
||||
|
||||
private void addBackEdge(V u, V v) {
|
||||
Set<V> set = blockedBackEdgesMap.get(u);
|
||||
if (set == null) {
|
||||
set = new HashSet<>();
|
||||
blockedBackEdgesMap.put(u, set);
|
||||
}
|
||||
set.add(v);
|
||||
}
|
||||
|
||||
private void outputCircuit(Accumulator<List<V>> accumulator) {
|
||||
List<V> path = new ArrayList<>(stack);
|
||||
path.add(endVertex);
|
||||
accumulator.add(path);
|
||||
}
|
||||
public void setStatusListener(GraphAlgorithmStatusListener<V> listener);
|
||||
}
|
||||
|
@ -0,0 +1,43 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed 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 ghidra.graph.algo;
|
||||
|
||||
/**
|
||||
* An interface and state values used to follow the state of vertices as they are processed by
|
||||
* algorithms
|
||||
*
|
||||
* @param <V> the vertex type
|
||||
*/
|
||||
public class GraphAlgorithmStatusListener<V> {
|
||||
|
||||
public enum STATUS {
|
||||
WAITING, SCHEDULED, EXPLORING, BLOCKED, IN_PATH,
|
||||
}
|
||||
|
||||
protected int totalStatusChanges;
|
||||
|
||||
public void statusChanged(V v, STATUS s) {
|
||||
// stub
|
||||
}
|
||||
|
||||
public void finished() {
|
||||
// stub
|
||||
}
|
||||
|
||||
public int getTotalStatusChanges() {
|
||||
return totalStatusChanges;
|
||||
}
|
||||
}
|
@ -0,0 +1,247 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed 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 ghidra.graph.algo;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import org.apache.commons.collections4.map.LazyMap;
|
||||
import org.apache.commons.collections4.set.ListOrderedSet;
|
||||
|
||||
import ghidra.graph.GDirectedGraph;
|
||||
import ghidra.graph.GEdge;
|
||||
import ghidra.graph.algo.GraphAlgorithmStatusListener.STATUS;
|
||||
import ghidra.util.datastruct.Accumulator;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* Finds all paths between two vertices for a given graph.
|
||||
*
|
||||
* <P>Note: this algorithm is based on the {@link JohnsonCircuitsAlgorithm}, modified to be
|
||||
* iterative instead of recursive.
|
||||
*
|
||||
* @param <V> the vertex type
|
||||
* @param <E> the edge type
|
||||
*/
|
||||
public class IterativeFindPathsAlgorithm<V, E extends GEdge<V>>
|
||||
implements FindPathsAlgorithm<V, E> {
|
||||
|
||||
private GDirectedGraph<V, E> g;
|
||||
private V start;
|
||||
private V end;
|
||||
|
||||
private Set<V> blockedSet = new HashSet<>();
|
||||
private Map<V, Set<V>> blockedBackEdgesMap =
|
||||
LazyMap.lazyMap(new HashMap<>(), k -> new HashSet<>());
|
||||
|
||||
private GraphAlgorithmStatusListener<V> listener = new GraphAlgorithmStatusListener<>();
|
||||
private TaskMonitor monitor;
|
||||
private Accumulator<List<V>> accumulator;
|
||||
|
||||
@Override
|
||||
public void setStatusListener(GraphAlgorithmStatusListener<V> listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@SuppressWarnings("hiding") // squash warning on names of variables
|
||||
@Override
|
||||
public void findPaths(GDirectedGraph<V, E> g, V start, V end, Accumulator<List<V>> accumulator,
|
||||
TaskMonitor monitor) throws CancelledException {
|
||||
this.g = g;
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.accumulator = accumulator;
|
||||
this.monitor = monitor;
|
||||
|
||||
if (start.equals(end)) {
|
||||
// can't find the paths between a node and itself
|
||||
throw new IllegalArgumentException("Start and end vertex cannot be the same: " + start);
|
||||
}
|
||||
|
||||
if (!g.containsVertex(start)) {
|
||||
throw new IllegalArgumentException("Start vertex is not in the graph: " + start);
|
||||
}
|
||||
|
||||
if (!g.containsVertex(end)) {
|
||||
throw new IllegalArgumentException("End vertex is not in the graph: " + end);
|
||||
}
|
||||
|
||||
find();
|
||||
listener.finished();
|
||||
}
|
||||
|
||||
private void find() throws CancelledException {
|
||||
Stack<Node> path = new Stack<>();
|
||||
path.push(new Node(null, start));
|
||||
|
||||
monitor.initialize(g.getEdgeCount());
|
||||
|
||||
while (!path.isEmpty()) {
|
||||
|
||||
monitor.checkCanceled();
|
||||
monitor.incrementProgress(1);
|
||||
Node node = path.peek();
|
||||
|
||||
setStatus(node.v, STATUS.EXPLORING);
|
||||
|
||||
if (node.v.equals(end)) {
|
||||
outputCircuit(path);
|
||||
node.setParentFound();
|
||||
path.pop();
|
||||
}
|
||||
else if (node.isExplored()) {
|
||||
node.setDone();
|
||||
path.pop();
|
||||
}
|
||||
else {
|
||||
node = node.getNext();
|
||||
path.push(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void unblock(V v) {
|
||||
|
||||
ListOrderedSet<V> toProcess = new ListOrderedSet<>();
|
||||
toProcess.add(v);
|
||||
|
||||
while (!toProcess.isEmpty()) {
|
||||
V next = toProcess.remove(0);
|
||||
Set<V> childBlocked = doUnblock(next);
|
||||
if (childBlocked != null && !childBlocked.isEmpty()) {
|
||||
toProcess.addAll(childBlocked);
|
||||
childBlocked.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Set<V> doUnblock(V v) {
|
||||
|
||||
blockedSet.remove(v);
|
||||
setStatus(v, STATUS.WAITING);
|
||||
Set<V> set = blockedBackEdgesMap.get(v);
|
||||
return set;
|
||||
}
|
||||
|
||||
private void blockBackEdge(V u, V v) {
|
||||
Set<V> set = blockedBackEdgesMap.get(u);
|
||||
set.add(v);
|
||||
}
|
||||
|
||||
private void outputCircuit(Stack<Node> stack) throws CancelledException {
|
||||
List<V> path = new ArrayList<>();
|
||||
for (Node vv : stack) {
|
||||
path.add(vv.v);
|
||||
}
|
||||
setStatus(path, STATUS.IN_PATH);
|
||||
accumulator.add(path);
|
||||
|
||||
monitor.checkCanceled(); // pause for listener
|
||||
}
|
||||
|
||||
private void setStatus(List<V> path, STATUS s) {
|
||||
for (V v : path) {
|
||||
listener.statusChanged(v, s);
|
||||
}
|
||||
}
|
||||
|
||||
private void setStatus(V v, STATUS s) {
|
||||
if (blockedSet.contains(v) && s == STATUS.WAITING) {
|
||||
listener.statusChanged(v, STATUS.BLOCKED);
|
||||
}
|
||||
else {
|
||||
listener.statusChanged(v, s);
|
||||
}
|
||||
}
|
||||
|
||||
private Collection<E> getOutEdges(V v) {
|
||||
Collection<E> outEdges = g.getOutEdges(v);
|
||||
if (outEdges == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return outEdges;
|
||||
}
|
||||
//==================================================================================================
|
||||
// Inner Classes
|
||||
//==================================================================================================
|
||||
|
||||
/**
|
||||
* Simple class to maintain a relationship between a given node and its children that need
|
||||
* processing. It also knows if it has been found in a path from start to end.
|
||||
*/
|
||||
private class Node {
|
||||
private Node parent;
|
||||
private V v;
|
||||
private Deque<V> unexplored;
|
||||
private boolean found;
|
||||
|
||||
Node(Node parent, V v) {
|
||||
this.parent = parent;
|
||||
this.v = v;
|
||||
|
||||
blockedSet.add(v);
|
||||
setStatus(v, STATUS.SCHEDULED);
|
||||
|
||||
Collection<E> outEdges = getOutEdges(v);
|
||||
unexplored = new ArrayDeque<>(outEdges.size());
|
||||
for (E e : getOutEdges(v)) {
|
||||
V u = e.getEnd();
|
||||
if (!blockedSet.contains(u)) {
|
||||
unexplored.add(u);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setDone() {
|
||||
if (found) {
|
||||
setParentFound();
|
||||
}
|
||||
else {
|
||||
// block back edges
|
||||
for (E e : getOutEdges(v)) {
|
||||
V u = e.getEnd();
|
||||
blockBackEdge(u, v);
|
||||
}
|
||||
setStatus(v, STATUS.BLOCKED);
|
||||
}
|
||||
}
|
||||
|
||||
void setParentFound() {
|
||||
if (parent != null) {
|
||||
parent.found = true;
|
||||
}
|
||||
unblock(v);
|
||||
}
|
||||
|
||||
boolean isExplored() {
|
||||
return unexplored.isEmpty();
|
||||
}
|
||||
|
||||
Node getNext() {
|
||||
if (isExplored()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Node node = new Node(this, unexplored.pop());
|
||||
return node;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return v.toString();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,178 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed 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 ghidra.graph.algo;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import ghidra.graph.GDirectedGraph;
|
||||
import ghidra.graph.GEdge;
|
||||
import ghidra.graph.algo.GraphAlgorithmStatusListener.STATUS;
|
||||
import ghidra.util.datastruct.Accumulator;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* Finds all paths between two vertices for a given graph.
|
||||
*
|
||||
* <P><B><U>Warning:</U></B> This is a recursive algorithm. As such, it is limited in how deep
|
||||
* it can recurse. Any path that exceeds the {@link #JAVA_STACK_DEPTH_LIMIT} will not be found.
|
||||
*
|
||||
* <P>Note: this algorithm is based entirely on the {@link JohnsonCircuitsAlgorithm}.
|
||||
*
|
||||
* @param <V> the vertex type
|
||||
* @param <E> the edge type
|
||||
*/
|
||||
public class RecursiveFindPathsAlgorithm<V, E extends GEdge<V>>
|
||||
implements FindPathsAlgorithm<V, E> {
|
||||
|
||||
public static final int JAVA_STACK_DEPTH_LIMIT = 2700;
|
||||
private GDirectedGraph<V, E> g;
|
||||
private V startVertex;
|
||||
private V endVertex;
|
||||
|
||||
private Stack<V> stack = new Stack<>();
|
||||
private Set<V> blockedSet = new HashSet<>();
|
||||
private Map<V, Set<V>> blockedBackEdgesMap = new HashMap<>();
|
||||
private Accumulator<List<V>> accumulator;
|
||||
private TaskMonitor monitor;
|
||||
|
||||
private GraphAlgorithmStatusListener<V> listener = new GraphAlgorithmStatusListener<>();
|
||||
|
||||
@Override
|
||||
public void setStatusListener(GraphAlgorithmStatusListener<V> listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@SuppressWarnings("hiding") // squash warning on names of variables
|
||||
@Override
|
||||
public void findPaths(GDirectedGraph<V, E> g, V start, V end, Accumulator<List<V>> accumulator,
|
||||
TaskMonitor monitor) throws CancelledException {
|
||||
this.g = g;
|
||||
this.startVertex = start;
|
||||
this.endVertex = end;
|
||||
this.accumulator = accumulator;
|
||||
this.monitor = monitor;
|
||||
|
||||
explore(startVertex, 0);
|
||||
listener.finished();
|
||||
}
|
||||
|
||||
private boolean explore(V v, int depth) throws CancelledException {
|
||||
|
||||
// TODO
|
||||
// Sigh. We are greatly limited in the size of paths we can processes due to the
|
||||
// recursive nature of this algorithm. This should be changed to be non-recursive.
|
||||
if (depth > JAVA_STACK_DEPTH_LIMIT) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean foundPath = false;
|
||||
blockedSet.add(v);
|
||||
stack.push(v);
|
||||
|
||||
setStatus(v, STATUS.EXPLORING);
|
||||
|
||||
Collection<E> outEdges = getOutEdges(v);
|
||||
for (E e : outEdges) {
|
||||
monitor.checkCanceled();
|
||||
|
||||
V u = e.getEnd();
|
||||
if (u.equals(endVertex)) {
|
||||
outputCircuit();
|
||||
foundPath = true;
|
||||
monitor.incrementProgress(1);
|
||||
}
|
||||
else if (!blockedSet.contains(u)) {
|
||||
foundPath |= explore(u, depth + 1);
|
||||
monitor.incrementProgress(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (foundPath) {
|
||||
unblock(v);
|
||||
}
|
||||
else {
|
||||
for (E e : outEdges) {
|
||||
monitor.checkCanceled();
|
||||
V u = e.getEnd();
|
||||
blockBackEdge(u, v);
|
||||
}
|
||||
}
|
||||
|
||||
stack.pop();
|
||||
setStatus(v, STATUS.WAITING);
|
||||
return foundPath;
|
||||
}
|
||||
|
||||
private Collection<E> getOutEdges(V v) {
|
||||
Collection<E> outEdges = g.getOutEdges(v);
|
||||
if (outEdges == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return outEdges;
|
||||
}
|
||||
|
||||
private void unblock(V v) {
|
||||
blockedSet.remove(v);
|
||||
setStatus(v, STATUS.WAITING);
|
||||
|
||||
Set<V> set = blockedBackEdgesMap.get(v);
|
||||
if (set == null) {
|
||||
return;
|
||||
}
|
||||
for (V u : set) {
|
||||
if (blockedSet.contains(u)) {
|
||||
unblock(u);
|
||||
}
|
||||
}
|
||||
set.clear();
|
||||
}
|
||||
|
||||
private void blockBackEdge(V u, V v) {
|
||||
Set<V> set = blockedBackEdgesMap.get(u);
|
||||
if (set == null) {
|
||||
set = new HashSet<>();
|
||||
blockedBackEdgesMap.put(u, set);
|
||||
}
|
||||
set.add(v);
|
||||
setStatus(v, STATUS.BLOCKED);
|
||||
}
|
||||
|
||||
private void outputCircuit() throws CancelledException {
|
||||
List<V> path = new LinkedList<>(stack);
|
||||
path.add(endVertex);
|
||||
setStatus(path, STATUS.IN_PATH);
|
||||
|
||||
accumulator.add(path);
|
||||
monitor.checkCanceled(); // pause for listener
|
||||
setStatus(endVertex, STATUS.WAITING);
|
||||
}
|
||||
|
||||
private void setStatus(List<V> path, STATUS s) {
|
||||
for (V v : path) {
|
||||
listener.statusChanged(v, s);
|
||||
}
|
||||
}
|
||||
|
||||
private void setStatus(V v, STATUS s) {
|
||||
if (blockedSet.contains(v) && s == STATUS.WAITING) {
|
||||
listener.statusChanged(v, STATUS.BLOCKED);
|
||||
}
|
||||
else {
|
||||
listener.statusChanged(v, s);
|
||||
}
|
||||
}
|
||||
}
|
@ -722,10 +722,12 @@ public class VisualGraphPathHighlighter<V extends VisualVertex, E extends Visual
|
||||
return t;
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
Msg.trace(VisualGraphPathHighlighter.this, "Unable to complete the future", e);
|
||||
Msg.trace(VisualGraphPathHighlighter.this,
|
||||
"Unable to calculate graph path highlights - interrupted", e);
|
||||
}
|
||||
catch (ExecutionException e) {
|
||||
Msg.debug(VisualGraphPathHighlighter.this, "Unable to complete the future", e);
|
||||
Msg.debug(VisualGraphPathHighlighter.this, "Unable to calculate graph path highlights",
|
||||
e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@ -824,6 +826,10 @@ public class VisualGraphPathHighlighter<V extends VisualVertex, E extends Visual
|
||||
|
||||
private void calculatePathsBetweenVerticesAsync(V v1, V v2) {
|
||||
|
||||
if (v1.equals(v2)) {
|
||||
return;
|
||||
}
|
||||
|
||||
CallbackAccumulator<List<V>> accumulator = new CallbackAccumulator<>(path -> {
|
||||
|
||||
Collection<E> edges = pathToEdgesAsync(path);
|
||||
|
@ -33,6 +33,7 @@ import ghidra.graph.VisualGraph;
|
||||
import ghidra.graph.viewer.*;
|
||||
import ghidra.graph.viewer.layout.LayoutListener.ChangeType;
|
||||
import ghidra.graph.viewer.renderer.ArticulatedEdgeRenderer;
|
||||
import ghidra.graph.viewer.renderer.VisualGraphRenderer;
|
||||
import ghidra.graph.viewer.shape.ArticulatedEdgeTransformer;
|
||||
import ghidra.graph.viewer.vertex.VisualGraphVertexShapeTransformer;
|
||||
import ghidra.util.datastruct.WeakDataStructureFactory;
|
||||
@ -308,8 +309,8 @@ public abstract class AbstractVisualGraphLayout<V extends VisualVertex,
|
||||
layoutLocations);
|
||||
|
||||
// DEGUG triggers grid lines to be printed; useful for debugging
|
||||
// VisualGraphRenderer.DEBUG_ROW_COL_MAP.put((Graph<?, ?>) visualGraph,
|
||||
// layoutLocations.copy());
|
||||
// VisualGraphRenderer.DEBUG_ROW_COL_MAP.put((Graph<?, ?>) visualGraph,
|
||||
// layoutLocations.copy());
|
||||
|
||||
Rectangle graphBounds =
|
||||
getTotalGraphSize(vertexLayoutLocations, edgeLayoutArticulationLocations, transformer);
|
||||
|
@ -118,6 +118,7 @@ public class VisualGraphRenderer<V extends VisualVertex, E extends VisualEdge<V>
|
||||
|
||||
private void paintLayoutGridCells(RenderContext<V, E> renderContext, Layout<V, E> layout) {
|
||||
|
||||
// to enable this debug, search java files for commented-out uses of 'DEBUG_ROW_COL_MAP'
|
||||
Graph<V, E> graph = layout.getGraph();
|
||||
LayoutLocationMap<?, ?> locationMap = DEBUG_ROW_COL_MAP.get(graph);
|
||||
if (locationMap == null) {
|
||||
|
@ -224,6 +224,34 @@ public abstract class AbstractGraphAlgorithmsTest extends AbstractGenericTest {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void assertPathExists(List<List<TestV>> paths, TestV... vertices) {
|
||||
|
||||
List<TestV> expectedPath = List.of(vertices);
|
||||
for (List<TestV> path : paths) {
|
||||
if (path.equals(expectedPath)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
fail("List of paths does not contain: " + expectedPath + "\n\tactual paths: " + paths);
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
protected final <V> void assertListEqualsOneOf(List<V> actual, List<V>... expected) {
|
||||
|
||||
StringBuilder buffy = new StringBuilder();
|
||||
for (List<V> list : expected) {
|
||||
if (areListsEquals(actual, list)) {
|
||||
return;
|
||||
}
|
||||
buffy.append(list.toString());
|
||||
}
|
||||
fail("Expected : " + buffy + "\nActual: " + actual);
|
||||
}
|
||||
|
||||
private <V> boolean areListsEquals(List<V> l1, List<V> l2) {
|
||||
return l1.equals(l2);
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Inner Classes
|
||||
//==================================================================================================
|
||||
@ -245,6 +273,34 @@ public abstract class AbstractGraphAlgorithmsTest extends AbstractGenericTest {
|
||||
return id;
|
||||
}
|
||||
|
||||
// TODO put this in
|
||||
//
|
||||
// @Override
|
||||
// public int hashCode() {
|
||||
// final int prime = 31;
|
||||
// int result = 1;
|
||||
// result = prime * result + ((id == null) ? 0 : id.hashCode());
|
||||
// return result;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public boolean equals(Object obj) {
|
||||
// if (this == obj) {
|
||||
// return true;
|
||||
// }
|
||||
// if (obj == null) {
|
||||
// return false;
|
||||
// }
|
||||
// if (getClass() != obj.getClass()) {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// TestV other = (TestV) obj;
|
||||
// if (!Objects.equals(id, other.id)) {
|
||||
// return false;
|
||||
// }
|
||||
// return true;
|
||||
// }
|
||||
}
|
||||
|
||||
protected static class TestE extends DefaultGEdge<TestV> {
|
||||
|
@ -26,7 +26,7 @@ import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import ghidra.graph.algo.*;
|
||||
import ghidra.util.datastruct.Accumulator;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.datastruct.ListAccumulator;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.exception.TimeoutException;
|
||||
@ -1105,23 +1105,6 @@ public class GraphAlgorithmsTest extends AbstractGraphAlgorithmsTest {
|
||||
Arrays.asList(v1, v2, v4, v5, v6, v3));
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
private final <V> void assertListEqualsOneOf(List<V> actual, List<V>... expected) {
|
||||
|
||||
StringBuilder buffy = new StringBuilder();
|
||||
for (List<V> list : expected) {
|
||||
if (areListsEquals(actual, list)) {
|
||||
return;
|
||||
}
|
||||
buffy.append(list.toString());
|
||||
}
|
||||
fail("Expected : " + buffy + "\nActual: " + actual);
|
||||
}
|
||||
|
||||
private <V> boolean areListsEquals(List<V> l1, List<V> l2) {
|
||||
return l1.equals(l2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDepthFirstPostOrder_MiddleAlternatingPaths() {
|
||||
/*
|
||||
@ -1362,19 +1345,355 @@ public class GraphAlgorithmsTest extends AbstractGraphAlgorithmsTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindPaths_LimitRecursion() throws CancelledException {
|
||||
public void testFindPaths_FullyConnected() throws CancelledException {
|
||||
|
||||
g = GraphFactory.createDirectedGraph();
|
||||
TestV[] vertices =
|
||||
generateSimplyConnectedGraph(FindPathsAlgorithm.JAVA_STACK_DEPTH_LIMIT + 100);
|
||||
TestV v1 = vertex(1);
|
||||
TestV v2 = vertex(2);
|
||||
TestV v3 = vertex(3);
|
||||
|
||||
TestV v1 = vertices[0];
|
||||
TestV v2 = vertices[vertices.length - 1];
|
||||
edge(v1, v2);
|
||||
edge(v1, v3);
|
||||
|
||||
Accumulator<List<TestV>> accumulator = new ListAccumulator<>();
|
||||
edge(v2, v3);
|
||||
edge(v2, v1);
|
||||
|
||||
edge(v3, v2);
|
||||
edge(v3, v1);
|
||||
|
||||
ListAccumulator<List<TestV>> accumulator = new ListAccumulator<>();
|
||||
GraphAlgorithms.findPaths(g, v1, v2, accumulator, TaskMonitor.DUMMY);
|
||||
assertTrue("The Find Paths algorithm should have failed, due to hitting the recursion " +
|
||||
"limite, before finding a path", accumulator.isEmpty());
|
||||
|
||||
List<List<TestV>> paths = accumulator.asList();
|
||||
assertPathExists(paths, v1, v2);
|
||||
assertPathExists(paths, v1, v3, v2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindPaths_FullyConnected2() throws CancelledException {
|
||||
|
||||
TestV v1 = vertex(1);
|
||||
TestV v2 = vertex(2);
|
||||
TestV v3 = vertex(3);
|
||||
TestV v4 = vertex(4);
|
||||
TestV v5 = vertex(5);
|
||||
|
||||
edge(v1, v2);
|
||||
edge(v1, v3);
|
||||
edge(v1, v4);
|
||||
edge(v1, v5);
|
||||
|
||||
edge(v2, v1);
|
||||
edge(v2, v3);
|
||||
edge(v2, v4);
|
||||
edge(v2, v5);
|
||||
|
||||
edge(v3, v1);
|
||||
edge(v3, v2);
|
||||
edge(v3, v4);
|
||||
edge(v3, v5);
|
||||
|
||||
edge(v4, v1);
|
||||
edge(v4, v2);
|
||||
edge(v4, v3);
|
||||
edge(v4, v5);
|
||||
|
||||
edge(v5, v1);
|
||||
edge(v5, v2);
|
||||
edge(v5, v3);
|
||||
edge(v5, v4);
|
||||
|
||||
ListAccumulator<List<TestV>> accumulator = new ListAccumulator<>();
|
||||
GraphAlgorithms.findPaths(g, v1, v5, accumulator, TaskMonitor.DUMMY);
|
||||
|
||||
List<List<TestV>> paths = accumulator.asList();
|
||||
assertEquals(16, paths.size());
|
||||
assertPathExists(paths, v1, v5);
|
||||
|
||||
assertPathExists(paths, v1, v2, v5);
|
||||
assertPathExists(paths, v1, v3, v5);
|
||||
assertPathExists(paths, v1, v4, v5);
|
||||
|
||||
assertPathExists(paths, v1, v2, v3, v5);
|
||||
assertPathExists(paths, v1, v2, v4, v5);
|
||||
assertPathExists(paths, v1, v3, v2, v5);
|
||||
assertPathExists(paths, v1, v3, v4, v5);
|
||||
assertPathExists(paths, v1, v4, v2, v5);
|
||||
assertPathExists(paths, v1, v4, v3, v5);
|
||||
|
||||
assertPathExists(paths, v1, v2, v3, v4, v5);
|
||||
assertPathExists(paths, v1, v2, v4, v3, v5);
|
||||
assertPathExists(paths, v1, v3, v2, v4, v5);
|
||||
assertPathExists(paths, v1, v3, v4, v2, v5);
|
||||
assertPathExists(paths, v1, v4, v2, v3, v5);
|
||||
assertPathExists(paths, v1, v4, v3, v2, v5);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindPaths_MultiPaths() throws CancelledException {
|
||||
|
||||
/*
|
||||
v1
|
||||
/ \
|
||||
v2 v3
|
||||
| / | \
|
||||
| v4 v5 v6
|
||||
| * | |
|
||||
| | v7
|
||||
| | | \
|
||||
| | v8 v9
|
||||
| | * |
|
||||
\ | /
|
||||
\ | /
|
||||
\|/
|
||||
v10
|
||||
|
||||
|
||||
Paths:
|
||||
v1, v2, v10
|
||||
v1, v3, v5, v10
|
||||
v1, v3, v6, v7, v9, v10
|
||||
*/
|
||||
|
||||
TestV v1 = vertex(1);
|
||||
TestV v2 = vertex(2);
|
||||
TestV v3 = vertex(3);
|
||||
TestV v4 = vertex(4);
|
||||
TestV v5 = vertex(5);
|
||||
TestV v6 = vertex(6);
|
||||
TestV v7 = vertex(7);
|
||||
TestV v8 = vertex(8);
|
||||
TestV v9 = vertex(9);
|
||||
TestV v10 = vertex(10);
|
||||
|
||||
edge(v1, v2);
|
||||
edge(v1, v3);
|
||||
|
||||
edge(v2, v10);
|
||||
|
||||
edge(v3, v4);
|
||||
edge(v3, v5);
|
||||
edge(v3, v6);
|
||||
|
||||
edge(v5, v10);
|
||||
|
||||
edge(v6, v7);
|
||||
edge(v7, v8);
|
||||
edge(v7, v9);
|
||||
|
||||
edge(v9, v10);
|
||||
|
||||
ListAccumulator<List<TestV>> accumulator = new ListAccumulator<>();
|
||||
GraphAlgorithms.findPaths(g, v1, v10, accumulator, TaskMonitor.DUMMY);
|
||||
|
||||
List<List<TestV>> paths = accumulator.asList();
|
||||
assertEquals(3, paths.size());
|
||||
assertPathExists(paths, v1, v2, v10);
|
||||
assertPathExists(paths, v1, v3, v5, v10);
|
||||
assertPathExists(paths, v1, v3, v6, v7, v9, v10);
|
||||
|
||||
accumulator = new ListAccumulator<>();
|
||||
GraphAlgorithms.findPaths(g, v1, v10, accumulator, TaskMonitor.DUMMY);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindPathsNew_MultiPaths_BackFlows() throws CancelledException {
|
||||
|
||||
/*
|
||||
--> v1
|
||||
| / \
|
||||
-v2 v3
|
||||
/ | \
|
||||
v4 v5 v6 <--
|
||||
* | | |
|
||||
| v7 |
|
||||
| | \ |
|
||||
| v8 v9 -
|
||||
| *
|
||||
|
|
||||
v10
|
||||
|
||||
|
||||
Paths: v1, v3, v5, v10
|
||||
*/
|
||||
|
||||
TestV v1 = vertex(1);
|
||||
TestV v2 = vertex(2);
|
||||
TestV v3 = vertex(3);
|
||||
TestV v4 = vertex(4);
|
||||
TestV v5 = vertex(5);
|
||||
TestV v6 = vertex(6);
|
||||
TestV v7 = vertex(7);
|
||||
TestV v8 = vertex(8);
|
||||
TestV v9 = vertex(9);
|
||||
TestV v10 = vertex(10);
|
||||
|
||||
edge(v1, v2);
|
||||
edge(v1, v3);
|
||||
|
||||
edge(v2, v1); // back edge
|
||||
|
||||
edge(v3, v4);
|
||||
edge(v3, v5);
|
||||
edge(v3, v6);
|
||||
|
||||
edge(v5, v10);
|
||||
|
||||
edge(v6, v7);
|
||||
|
||||
edge(v7, v8);
|
||||
edge(v7, v9);
|
||||
|
||||
edge(v9, v6); // back edge
|
||||
|
||||
ListAccumulator<List<TestV>> accumulator = new ListAccumulator<>();
|
||||
|
||||
GraphAlgorithms.findPaths(g, v1, v10, accumulator, TaskMonitor.DUMMY);
|
||||
|
||||
List<List<TestV>> paths = accumulator.asList();
|
||||
assertEquals(1, paths.size());
|
||||
assertPathExists(paths, v1, v3, v5, v10);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindPathsNew_MultiPaths_LongDeadEnd() throws CancelledException {
|
||||
|
||||
/*
|
||||
v1
|
||||
/ \
|
||||
v2 v3
|
||||
| / | \
|
||||
| v4 v5 v6
|
||||
| | | |
|
||||
| v11 | v7
|
||||
| | | | \
|
||||
| v12 | v8 v9
|
||||
| * | * |
|
||||
\ | /
|
||||
\ | /
|
||||
\ |/
|
||||
v10
|
||||
|
||||
|
||||
Paths:
|
||||
v1, v2, v10
|
||||
v1, v3, v5, v10
|
||||
v1, v3, v6, v7, v9, v10
|
||||
*/
|
||||
|
||||
TestV v1 = vertex(1);
|
||||
TestV v2 = vertex(2);
|
||||
TestV v3 = vertex(3);
|
||||
TestV v4 = vertex(4);
|
||||
TestV v5 = vertex(5);
|
||||
TestV v6 = vertex(6);
|
||||
TestV v7 = vertex(7);
|
||||
TestV v8 = vertex(8);
|
||||
TestV v9 = vertex(9);
|
||||
TestV v10 = vertex(10);
|
||||
TestV v11 = vertex(11);
|
||||
TestV v12 = vertex(12);
|
||||
|
||||
edge(v1, v2);
|
||||
edge(v1, v3);
|
||||
|
||||
edge(v2, v10);
|
||||
|
||||
edge(v3, v4);
|
||||
edge(v3, v5);
|
||||
edge(v3, v6);
|
||||
|
||||
edge(v4, v11);
|
||||
|
||||
edge(v11, v12);
|
||||
|
||||
edge(v5, v10);
|
||||
|
||||
edge(v6, v7);
|
||||
edge(v7, v8);
|
||||
edge(v7, v9);
|
||||
|
||||
edge(v9, v10);
|
||||
|
||||
ListAccumulator<List<TestV>> accumulator = new ListAccumulator<>();
|
||||
|
||||
GraphAlgorithms.findPaths(g, v1, v10, accumulator, TaskMonitor.DUMMY);
|
||||
|
||||
List<List<TestV>> paths = accumulator.asList();
|
||||
assertEquals(3, paths.size());
|
||||
assertPathExists(paths, v1, v2, v10);
|
||||
assertPathExists(paths, v1, v3, v5, v10);
|
||||
assertPathExists(paths, v1, v3, v6, v7, v9, v10);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindPathsNew_MultiPaths() throws CancelledException {
|
||||
|
||||
/*
|
||||
v1
|
||||
/ \
|
||||
v2 v3
|
||||
| / | \
|
||||
| v4 v5 v6
|
||||
| * | |
|
||||
| | v7
|
||||
| | | \
|
||||
| | v8 v9
|
||||
| | * |
|
||||
\ | /
|
||||
\ | /
|
||||
\|/
|
||||
v10
|
||||
|
||||
|
||||
Paths:
|
||||
v1, v2, v10
|
||||
v1, v3, v5, v10
|
||||
v1, v3, v6, v7, v9, v10
|
||||
*/
|
||||
|
||||
TestV v1 = vertex(1);
|
||||
TestV v2 = vertex(2);
|
||||
TestV v3 = vertex(3);
|
||||
TestV v4 = vertex(4);
|
||||
TestV v5 = vertex(5);
|
||||
TestV v6 = vertex(6);
|
||||
TestV v7 = vertex(7);
|
||||
TestV v8 = vertex(8);
|
||||
TestV v9 = vertex(9);
|
||||
TestV v10 = vertex(10);
|
||||
|
||||
edge(v1, v2);
|
||||
edge(v1, v3);
|
||||
|
||||
edge(v2, v10);
|
||||
|
||||
edge(v3, v4);
|
||||
edge(v3, v5);
|
||||
edge(v3, v6);
|
||||
|
||||
edge(v5, v10);
|
||||
|
||||
edge(v6, v7);
|
||||
edge(v7, v8);
|
||||
edge(v7, v9);
|
||||
|
||||
edge(v9, v10);
|
||||
|
||||
ListAccumulator<List<TestV>> accumulator = new ListAccumulator<>();
|
||||
|
||||
GraphAlgorithms.findPaths(g, v1, v10, accumulator, TaskMonitor.DUMMY);
|
||||
|
||||
List<List<TestV>> paths = accumulator.asList();
|
||||
assertEquals(3, paths.size());
|
||||
assertPathExists(paths, v1, v2, v10);
|
||||
assertPathExists(paths, v1, v3, v5, v10);
|
||||
assertPathExists(paths, v1, v3, v6, v7, v9, v10);
|
||||
|
||||
accumulator = new ListAccumulator<>();
|
||||
GraphAlgorithms.findPaths(g, v4, v10, accumulator, TaskMonitor.DUMMY);
|
||||
paths = accumulator.asList();
|
||||
assertTrue(paths.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -1383,7 +1702,7 @@ public class GraphAlgorithmsTest extends AbstractGraphAlgorithmsTest {
|
||||
startMemoryMonitorThread(false);
|
||||
|
||||
g = GraphFactory.createDirectedGraph();
|
||||
TestV[] vertices = generateCompletelyConnectedGraph(30); // this takes a while
|
||||
TestV[] vertices = generateCompletelyConnectedGraph(15);
|
||||
|
||||
int timeout = 250;
|
||||
TimeoutTaskMonitor monitor = TimeoutTaskMonitor.timeoutIn(timeout, TimeUnit.MILLISECONDS);
|
||||
@ -1391,7 +1710,10 @@ public class GraphAlgorithmsTest extends AbstractGraphAlgorithmsTest {
|
||||
TestV start = vertices[0];
|
||||
TestV end = vertices[vertices.length - 1];
|
||||
try {
|
||||
GraphAlgorithms.findPaths(g, start, end, new ListAccumulator<>(), monitor);
|
||||
ListAccumulator<List<TestV>> accumulator = new ListAccumulator<>();
|
||||
GraphAlgorithms.findPaths(g, start, end, accumulator, monitor);
|
||||
|
||||
Msg.debug(this, "Found paths " + accumulator.size());
|
||||
fail("Did not timeout in " + timeout + " ms");
|
||||
}
|
||||
catch (TimeoutException e) {
|
||||
|
@ -0,0 +1,151 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed 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 ghidra.graph;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.awt.event.WindowAdapter;
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.JFrame;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import ghidra.graph.algo.*;
|
||||
import ghidra.graph.algo.viewer.*;
|
||||
import ghidra.util.SystemUtilities;
|
||||
import ghidra.util.datastruct.ListAccumulator;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
|
||||
/**
|
||||
* A tool, written as a junit, that allows the user to run a test and then use the UI to step
|
||||
* through the given graph algorithm.
|
||||
*/
|
||||
public class GraphAlgorithmsVisualDebugger extends AbstractGraphAlgorithmsTest {
|
||||
|
||||
@Override
|
||||
protected GDirectedGraph<TestV, TestE> createGraph() {
|
||||
return GraphFactory.createDirectedGraph();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindPathsNew_MultiPaths_BackFlows_WithUI_IterativeFindPathsAlgorithm()
|
||||
throws CancelledException {
|
||||
|
||||
FindPathsAlgorithm<TestV, TestE> algo = new IterativeFindPathsAlgorithm<>();
|
||||
doTestFindPathsNew_MultiPaths_BackFlows_WithUI(algo);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindPathsNew_MultiPaths_BackFlows_WithUI_RecursiveFindPathsAlgorithm()
|
||||
throws CancelledException {
|
||||
|
||||
FindPathsAlgorithm<TestV, TestE> algo = new RecursiveFindPathsAlgorithm<>();
|
||||
doTestFindPathsNew_MultiPaths_BackFlows_WithUI(algo);
|
||||
}
|
||||
|
||||
private void doTestFindPathsNew_MultiPaths_BackFlows_WithUI(
|
||||
FindPathsAlgorithm<TestV, TestE> algo) throws CancelledException {
|
||||
|
||||
/*
|
||||
--> v1
|
||||
| / \
|
||||
-v2 v3
|
||||
/ | \
|
||||
v4 v5 v6 <--
|
||||
* | | |
|
||||
| v7 |
|
||||
| | \ |
|
||||
| v8 v9 -
|
||||
| *
|
||||
|
|
||||
v10
|
||||
|
||||
|
||||
Paths: v1, v3, v5, v10
|
||||
*/
|
||||
|
||||
TestV v1 = vertex(1);
|
||||
TestV v2 = vertex(2);
|
||||
TestV v3 = vertex(3);
|
||||
TestV v4 = vertex(4);
|
||||
TestV v5 = vertex(5);
|
||||
TestV v6 = vertex(6);
|
||||
TestV v7 = vertex(7);
|
||||
TestV v8 = vertex(8);
|
||||
TestV v9 = vertex(9);
|
||||
TestV v10 = vertex(10);
|
||||
|
||||
edge(v1, v2);
|
||||
edge(v1, v3);
|
||||
|
||||
edge(v2, v1); // back edge
|
||||
|
||||
edge(v3, v4);
|
||||
edge(v3, v5);
|
||||
edge(v3, v6);
|
||||
|
||||
edge(v5, v10);
|
||||
|
||||
edge(v6, v7);
|
||||
|
||||
edge(v7, v8);
|
||||
edge(v7, v9);
|
||||
|
||||
edge(v9, v6); // back edge
|
||||
|
||||
AlgorithmSteppingTaskMonitor steppingMonitor = new AlgorithmSteppingTaskMonitor();
|
||||
steppingMonitor = new AlgorithmSelfSteppingTaskMonitor(500);
|
||||
TestGraphAlgorithmSteppingViewerPanel<TestV, TestE> gp = showViewer(steppingMonitor);
|
||||
|
||||
algo.setStatusListener(gp.getStatusListener());
|
||||
ListAccumulator<List<TestV>> accumulator = new ListAccumulator<>();
|
||||
algo.findPaths(g, v1, v10, accumulator, steppingMonitor);
|
||||
|
||||
//Msg.debug(this, "Total status updates: " + gp.getStatusListener().getTotalStatusChanges());
|
||||
|
||||
steppingMonitor.pause(); // pause this thread to view the final output
|
||||
|
||||
List<List<TestV>> paths = accumulator.asList();
|
||||
assertEquals(1, paths.size());
|
||||
assertPathExists(paths, v1, v3, v5, v10);
|
||||
}
|
||||
|
||||
private TestGraphAlgorithmSteppingViewerPanel<TestV, TestE> showViewer(
|
||||
AlgorithmSteppingTaskMonitor steppingMonitor) {
|
||||
|
||||
String isHeadless = Boolean.toString(false);
|
||||
System.setProperty(SystemUtilities.HEADLESS_PROPERTY, isHeadless);
|
||||
System.setProperty("java.awt.headless", isHeadless);
|
||||
|
||||
JFrame frame = new JFrame("Graph");
|
||||
TestGraphAlgorithmSteppingViewerPanel<TestV, TestE> gp =
|
||||
new TestGraphAlgorithmSteppingViewerPanel<>(g, steppingMonitor);
|
||||
frame.getContentPane().add(gp);
|
||||
frame.setSize(800, 800);
|
||||
frame.setVisible(true);
|
||||
|
||||
frame.addWindowListener(new WindowAdapter() {
|
||||
@Override
|
||||
public void windowClosing(WindowEvent e) {
|
||||
steppingMonitor.cancel();
|
||||
}
|
||||
});
|
||||
|
||||
return gp;
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed 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 ghidra.graph.algo.viewer;
|
||||
|
||||
import ghidra.util.Msg;
|
||||
|
||||
/**
|
||||
* Stepping task monitor that will proceed to the next step after the specified delay
|
||||
*/
|
||||
public class AlgorithmSelfSteppingTaskMonitor extends AlgorithmSteppingTaskMonitor {
|
||||
|
||||
private int stepTime;
|
||||
|
||||
public AlgorithmSelfSteppingTaskMonitor(int stepTime) {
|
||||
this.stepTime = stepTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pause() {
|
||||
|
||||
if (isCancelled()) {
|
||||
return; // no pausing after cancelled
|
||||
}
|
||||
|
||||
notifyStepReady();
|
||||
|
||||
synchronized (this) {
|
||||
|
||||
try {
|
||||
wait(stepTime);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
Msg.debug(this, "Interrupted waiting for next step", e);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed 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 ghidra.graph.algo.viewer;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import ghidra.generic.function.Callback;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitorAdapter;
|
||||
|
||||
/**
|
||||
* Task monitor that will trigger a {@link #wait()} when {@link #checkCanceled()} is called. This
|
||||
* allows clients to watch algorithms as they proceed.
|
||||
*/
|
||||
public class AlgorithmSteppingTaskMonitor extends TaskMonitorAdapter {
|
||||
|
||||
private Set<Callback> stepLisetners = new HashSet<>();
|
||||
|
||||
public AlgorithmSteppingTaskMonitor() {
|
||||
setCancelEnabled(true);
|
||||
}
|
||||
|
||||
public void addStepListener(Callback c) {
|
||||
stepLisetners.add(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
super.cancel();
|
||||
step(); // wake-up any waiting threads
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkCanceled() throws CancelledException {
|
||||
|
||||
super.checkCanceled();
|
||||
|
||||
pause();
|
||||
}
|
||||
|
||||
/**
|
||||
* Causes this monitor to perform at {@link #wait()}. Call {@link #step()} to allow the
|
||||
* client to continue.
|
||||
*/
|
||||
public void pause() {
|
||||
|
||||
if (isCancelled()) {
|
||||
return; // no pausing after cancelled
|
||||
}
|
||||
|
||||
notifyStepReady();
|
||||
|
||||
synchronized (this) {
|
||||
|
||||
try {
|
||||
wait();
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
Msg.debug(this, "Interrupted waiting for next step", e);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void step() {
|
||||
synchronized (this) {
|
||||
notify();
|
||||
}
|
||||
}
|
||||
|
||||
protected void notifyStepReady() {
|
||||
stepLisetners.forEach(l -> l.call());
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed 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 ghidra.graph.algo.viewer;
|
||||
|
||||
import ghidra.graph.viewer.edge.AbstractVisualEdge;
|
||||
|
||||
public class AlgorithmTestSteppingEdge<V>
|
||||
extends AbstractVisualEdge<AlgorithmTestSteppingVertex<V>> {
|
||||
|
||||
AlgorithmTestSteppingEdge(AlgorithmTestSteppingVertex<V> start,
|
||||
AlgorithmTestSteppingVertex<V> end) {
|
||||
super(start, end);
|
||||
}
|
||||
|
||||
// sigh. I could not get this to compile with 'V' type specified
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
@Override
|
||||
public AlgorithmTestSteppingEdge<V> cloneEdge(AlgorithmTestSteppingVertex start,
|
||||
AlgorithmTestSteppingVertex end) {
|
||||
return new AlgorithmTestSteppingEdge<>(start, end);
|
||||
}
|
||||
}
|
@ -0,0 +1,183 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed 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 ghidra.graph.algo.viewer;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.geom.Ellipse2D;
|
||||
import java.awt.geom.Ellipse2D.Double;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import ghidra.graph.algo.GraphAlgorithmStatusListener.STATUS;
|
||||
import ghidra.graph.graphs.AbstractTestVertex;
|
||||
import ghidra.graph.viewer.vertex.VertexShapeProvider;
|
||||
|
||||
public class AlgorithmTestSteppingVertex<V> extends AbstractTestVertex
|
||||
implements VertexShapeProvider {
|
||||
|
||||
private ShapeImage defaultShape;
|
||||
private ShapeImage defaultWithPathShape;
|
||||
private ShapeImage scheduledShape;
|
||||
private ShapeImage exploringShape;
|
||||
private ShapeImage blockedShape;
|
||||
private ShapeImage currentShape;
|
||||
|
||||
private JLabel tempLabel = new JLabel();
|
||||
private V v;
|
||||
private STATUS status = STATUS.WAITING;
|
||||
|
||||
private boolean wasEverInPath;
|
||||
|
||||
protected AlgorithmTestSteppingVertex(V v) {
|
||||
super(v.toString());
|
||||
this.v = v;
|
||||
|
||||
buildShapes();
|
||||
|
||||
tempLabel.setText(v.toString());
|
||||
}
|
||||
|
||||
public void setStatus(STATUS status) {
|
||||
this.status = status;
|
||||
|
||||
ShapeImage si;
|
||||
switch (status) {
|
||||
case BLOCKED:
|
||||
si = blockedShape;
|
||||
if (wasEverInPath) {
|
||||
si = defaultWithPathShape;
|
||||
}
|
||||
break;
|
||||
case EXPLORING:
|
||||
si = exploringShape;
|
||||
break;
|
||||
case SCHEDULED:
|
||||
si = scheduledShape;
|
||||
break;
|
||||
case IN_PATH:
|
||||
si = exploringShape;
|
||||
wasEverInPath = true;
|
||||
break;
|
||||
case WAITING:
|
||||
default:
|
||||
si = defaultShape;
|
||||
if (wasEverInPath) {
|
||||
si = defaultWithPathShape;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
currentShape = si;
|
||||
}
|
||||
|
||||
private void buildShapes() {
|
||||
|
||||
defaultShape = buildCircleShape(Color.LIGHT_GRAY, "default");
|
||||
defaultWithPathShape = buildCircleShape(new Color(192, 216, 65), "default; was in path");
|
||||
scheduledShape = buildCircleShape(new Color(255, 248, 169), "scheduled");
|
||||
exploringShape = buildCircleShape(new Color(0, 147, 0), "exploring");
|
||||
blockedShape = buildCircleShape(new Color(249, 190, 190), "blocked");
|
||||
|
||||
currentShape = defaultShape;
|
||||
}
|
||||
|
||||
private ShapeImage buildCircleShape(Color color, String name) {
|
||||
int w = 50;
|
||||
int h = 50;
|
||||
|
||||
Double circle = new Ellipse2D.Double(0, 0, w, h);
|
||||
|
||||
BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D g2 = (Graphics2D) image.getGraphics();
|
||||
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
|
||||
g2.setColor(color);
|
||||
g2.fill(circle);
|
||||
|
||||
g2.dispose();
|
||||
|
||||
Dimension shapeSize = circle.getBounds().getSize();
|
||||
int x = 50;
|
||||
int y = 0;
|
||||
circle.setFrame(x, y, shapeSize.width, shapeSize.height);
|
||||
|
||||
return new ShapeImage(image, circle, name);
|
||||
}
|
||||
|
||||
V getTestVertex() {
|
||||
return v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JComponent getComponent() {
|
||||
ShapeImage si = getShapeImage();
|
||||
ImageIcon icon = new ImageIcon(si.getImage());
|
||||
tempLabel.setIcon(icon);
|
||||
return tempLabel;
|
||||
}
|
||||
|
||||
private ShapeImage getShapeImage() {
|
||||
return currentShape;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Shape getCompactShape() {
|
||||
return getShapeImage().getShape();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String statusString = status.toString();
|
||||
if (wasEverInPath) {
|
||||
statusString = "";
|
||||
}
|
||||
else if (status == STATUS.BLOCKED) {
|
||||
statusString = "";
|
||||
}
|
||||
|
||||
return v.toString() + " " + statusString;
|
||||
}
|
||||
|
||||
private class ShapeImage {
|
||||
private Image image;
|
||||
private Shape shape;
|
||||
private String shapeName;
|
||||
|
||||
ShapeImage(Image image, Shape shape, String name) {
|
||||
this.image = image;
|
||||
this.shape = shape;
|
||||
this.shapeName = name;
|
||||
}
|
||||
|
||||
Shape getShape() {
|
||||
return shape;
|
||||
}
|
||||
|
||||
Image getImage() {
|
||||
return image;
|
||||
}
|
||||
|
||||
String getName() {
|
||||
return shapeName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getName();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,347 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed 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 ghidra.graph.algo.viewer;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import org.apache.commons.collections4.BidiMap;
|
||||
import org.apache.commons.collections4.bidimap.DualHashBidiMap;
|
||||
|
||||
import edu.uci.ics.jung.visualization.decorators.EdgeShape;
|
||||
import edu.uci.ics.jung.visualization.renderers.Renderer;
|
||||
import ghidra.graph.*;
|
||||
import ghidra.graph.algo.GraphAlgorithmStatusListener;
|
||||
import ghidra.graph.graphs.DefaultVisualGraph;
|
||||
import ghidra.graph.viewer.GraphViewer;
|
||||
import ghidra.graph.viewer.GraphViewerUtils;
|
||||
import ghidra.graph.viewer.layout.AbstractVisualGraphLayout;
|
||||
import ghidra.graph.viewer.layout.GridLocationMap;
|
||||
import ghidra.graph.viewer.options.VisualGraphOptions;
|
||||
import ghidra.graph.viewer.vertex.VisualGraphVertexShapeTransformer;
|
||||
import ghidra.graph.viewer.vertex.VisualVertexRenderer;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.SwingUpdateManager;
|
||||
import resources.ResourceManager;
|
||||
|
||||
public class TestGraphAlgorithmSteppingViewerPanel<V, E extends GEdge<V>> extends JPanel {
|
||||
|
||||
private GraphViewer<AlgorithmTestSteppingVertex<V>, AlgorithmTestSteppingEdge<V>> viewer;
|
||||
private GDirectedGraph<V, E> graph;
|
||||
private BidiMap<V, AlgorithmTestSteppingVertex<V>> vertexLookupMap = new DualHashBidiMap<>();
|
||||
|
||||
private List<BufferedImage> images = new ArrayList<>();
|
||||
private JPanel phasesPanel;
|
||||
private float zoom = .5f;
|
||||
private SwingUpdateManager zoomRebuilder = new SwingUpdateManager(() -> rebuildImages());
|
||||
|
||||
private JPanel buttonPanel;
|
||||
private AlgorithmSteppingTaskMonitor steppingMonitor;
|
||||
private AbstractButton nextButton;
|
||||
private GraphAlgorithmStatusListener<V> algorithmStatusListener =
|
||||
new GraphAlgorithmStatusListener<>() {
|
||||
|
||||
public void finished() {
|
||||
nextButton.setEnabled(true);
|
||||
nextButton.setText("Done");
|
||||
}
|
||||
|
||||
public void statusChanged(V v, STATUS s) {
|
||||
|
||||
totalStatusChanges++;
|
||||
|
||||
AlgorithmTestSteppingVertex<V> vv = vertexLookupMap.get(v);
|
||||
vv.setStatus(s);
|
||||
repaint();
|
||||
|
||||
addCurrentGraphToPhases();
|
||||
}
|
||||
|
||||
private void addCurrentGraphToPhases() {
|
||||
|
||||
Rectangle layoutShape = GraphViewerUtils.getTotalGraphSizeInLayoutSpace(viewer);
|
||||
Rectangle viewShape = GraphViewerUtils.translateRectangleFromLayoutSpaceToViewSpace(
|
||||
viewer, layoutShape);
|
||||
|
||||
int w = viewShape.x + viewShape.width;
|
||||
int h = viewShape.y + viewShape.height;
|
||||
|
||||
BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D g = (Graphics2D) image.getGraphics();
|
||||
g.setColor(Color.WHITE);
|
||||
g.fillRect(0, 0, w, h);
|
||||
|
||||
try {
|
||||
SwingUtilities.invokeAndWait(() -> {
|
||||
viewer.paint(g);
|
||||
});
|
||||
}
|
||||
catch (Exception e) {
|
||||
Msg.debug(this, "Unexpected exception", e);
|
||||
}
|
||||
|
||||
images.add(image);
|
||||
zoomRebuilder.updateLater();
|
||||
}
|
||||
};
|
||||
|
||||
public TestGraphAlgorithmSteppingViewerPanel(GDirectedGraph<V, E> graph,
|
||||
AlgorithmSteppingTaskMonitor steppingMonitor) {
|
||||
this.graph = graph;
|
||||
this.steppingMonitor = steppingMonitor;
|
||||
|
||||
buildGraphViewer();
|
||||
buildPhasesViewer();
|
||||
buildButtons();
|
||||
|
||||
setLayout(new BorderLayout());
|
||||
add(viewer, BorderLayout.CENTER);
|
||||
add(buttonPanel, BorderLayout.SOUTH);
|
||||
|
||||
steppingMonitor.addStepListener(() -> {
|
||||
repaint();
|
||||
nextButton.setEnabled(true);
|
||||
});
|
||||
}
|
||||
|
||||
public GraphAlgorithmStatusListener<V> getStatusListener() {
|
||||
return algorithmStatusListener;
|
||||
}
|
||||
|
||||
private void buildGraphViewer() {
|
||||
TestGraph tvg = new TestGraph();
|
||||
|
||||
Collection<V> vertices = graph.getVertices();
|
||||
for (V v : vertices) {
|
||||
AlgorithmTestSteppingVertex<V> newV = new AlgorithmTestSteppingVertex<>(v);
|
||||
tvg.addVertex(newV);
|
||||
vertexLookupMap.put(v, newV);
|
||||
}
|
||||
|
||||
Collection<E> edges = graph.getEdges();
|
||||
for (E e : edges) {
|
||||
V start = e.getStart();
|
||||
V end = e.getEnd();
|
||||
AlgorithmTestSteppingVertex<V> newStart = vertexLookupMap.get(start);
|
||||
AlgorithmTestSteppingVertex<V> newEnd = vertexLookupMap.get(end);
|
||||
AlgorithmTestSteppingEdge<V> newEdge =
|
||||
new AlgorithmTestSteppingEdge<>(newStart, newEnd);
|
||||
tvg.addEdge(newEdge);
|
||||
}
|
||||
|
||||
TestGraphLayout layout = new TestGraphLayout(tvg);
|
||||
|
||||
tvg.setLayout(layout);
|
||||
viewer = new GraphViewer<>(layout, new Dimension(400, 400));
|
||||
viewer.setBackground(Color.WHITE);
|
||||
viewer.setGraphOptions(new VisualGraphOptions());
|
||||
|
||||
Renderer<AlgorithmTestSteppingVertex<V>, AlgorithmTestSteppingEdge<V>> renderer =
|
||||
viewer.getRenderer();
|
||||
|
||||
// TODO set renderer directly
|
||||
renderer.setVertexRenderer(new VisualVertexRenderer<>());
|
||||
|
||||
// TODO note: this is needed to 1) use shapes and 2) center the vertices
|
||||
VisualGraphVertexShapeTransformer<AlgorithmTestSteppingVertex<V>> shaper =
|
||||
new VisualGraphVertexShapeTransformer<>();
|
||||
viewer.getRenderContext().setVertexShapeTransformer(shaper);
|
||||
|
||||
viewer.getRenderContext().setEdgeShapeTransformer(EdgeShape.line(tvg));
|
||||
|
||||
viewer.getRenderContext().setVertexLabelTransformer(v -> v.toString());
|
||||
}
|
||||
|
||||
private void buildPhasesViewer() {
|
||||
JFrame f = new JFrame("Graph Phases");
|
||||
JPanel parentPanel = new JPanel(new BorderLayout());
|
||||
phasesPanel = new JPanel();
|
||||
|
||||
JPanel zoomPanel = new JPanel();
|
||||
JButton inButton = new JButton("+");
|
||||
inButton.addActionListener(e -> {
|
||||
float newZoom = zoom + .1f;
|
||||
zoom = Math.min(1f, newZoom);
|
||||
zoomRebuilder.update();
|
||||
});
|
||||
JButton outButton = new JButton("-");
|
||||
outButton.addActionListener(e -> {
|
||||
float newZoom = zoom - .1f;
|
||||
zoom = Math.max(0.1f, newZoom);
|
||||
zoomRebuilder.update();
|
||||
});
|
||||
zoomPanel.add(inButton);
|
||||
zoomPanel.add(outButton);
|
||||
|
||||
parentPanel.add(phasesPanel, BorderLayout.CENTER);
|
||||
parentPanel.add(zoomPanel, BorderLayout.SOUTH);
|
||||
|
||||
f.getContentPane().add(parentPanel);
|
||||
|
||||
f.setSize(400, 400);
|
||||
f.setVisible(true);
|
||||
}
|
||||
|
||||
private void rebuildImages() {
|
||||
|
||||
phasesPanel.removeAll();
|
||||
|
||||
double scale = zoom;
|
||||
|
||||
images.forEach(image -> {
|
||||
|
||||
int w = image.getWidth();
|
||||
int h = image.getHeight();
|
||||
double sw = w * scale;
|
||||
double sh = h * scale;
|
||||
Image scaledImage = ResourceManager.createScaledImage(image, (int) sw, (int) sh,
|
||||
Image.SCALE_AREA_AVERAGING);
|
||||
JLabel label = new JLabel(new ImageIcon(scaledImage));
|
||||
phasesPanel.add(label);
|
||||
});
|
||||
|
||||
phasesPanel.invalidate();
|
||||
phasesPanel.getParent().revalidate();
|
||||
phasesPanel.repaint();
|
||||
}
|
||||
|
||||
private void buildButtons() {
|
||||
buttonPanel = new JPanel();
|
||||
|
||||
nextButton = new JButton("Next >>");
|
||||
nextButton.addActionListener(e -> {
|
||||
nextButton.setEnabled(false);
|
||||
steppingMonitor.step();
|
||||
});
|
||||
nextButton.setEnabled(false);
|
||||
|
||||
buttonPanel.add(nextButton);
|
||||
}
|
||||
|
||||
private class TestGraph extends
|
||||
DefaultVisualGraph<AlgorithmTestSteppingVertex<V>, AlgorithmTestSteppingEdge<V>> {
|
||||
|
||||
private TestGraphLayout layout;
|
||||
|
||||
@Override
|
||||
public TestGraphLayout getLayout() {
|
||||
return layout;
|
||||
}
|
||||
|
||||
public void setLayout(TestGraphLayout layout) {
|
||||
this.layout = layout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TestGraph copy() {
|
||||
|
||||
TestGraph newGraph = new TestGraph();
|
||||
|
||||
Collection<AlgorithmTestSteppingVertex<V>> myVertices = getVertices();
|
||||
for (AlgorithmTestSteppingVertex<V> v : myVertices) {
|
||||
newGraph.addVertex(v);
|
||||
}
|
||||
|
||||
Collection<AlgorithmTestSteppingEdge<V>> myEdges = getEdges();
|
||||
for (AlgorithmTestSteppingEdge<V> e : myEdges) {
|
||||
newGraph.addEdge(e);
|
||||
}
|
||||
|
||||
return newGraph;
|
||||
}
|
||||
}
|
||||
|
||||
private class TestGraphLayout extends
|
||||
AbstractVisualGraphLayout<AlgorithmTestSteppingVertex<V>, AlgorithmTestSteppingEdge<V>> {
|
||||
|
||||
protected TestGraphLayout(TestGraph graph) {
|
||||
super(graph);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public VisualGraph<AlgorithmTestSteppingVertex<V>, AlgorithmTestSteppingEdge<V>> getVisualGraph() {
|
||||
return (VisualGraph<AlgorithmTestSteppingVertex<V>, AlgorithmTestSteppingEdge<V>>) getGraph();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isCondensedLayout() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GridLocationMap<AlgorithmTestSteppingVertex<V>, AlgorithmTestSteppingEdge<V>> performInitialGridLayout(
|
||||
VisualGraph<AlgorithmTestSteppingVertex<V>, AlgorithmTestSteppingEdge<V>> g)
|
||||
throws CancelledException {
|
||||
|
||||
GridLocationMap<AlgorithmTestSteppingVertex<V>, AlgorithmTestSteppingEdge<V>> grid =
|
||||
new GridLocationMap<>();
|
||||
|
||||
// sort by name; assume name is just a number
|
||||
List<AlgorithmTestSteppingVertex<V>> sorted = new ArrayList<>(g.getVertices());
|
||||
Collections.sort(sorted, (v1, v2) -> {
|
||||
Integer i1 = Integer.parseInt(v1.getName());
|
||||
Integer i2 = Integer.parseInt(v2.getName());
|
||||
return i1.compareTo(i2);
|
||||
});
|
||||
|
||||
AlgorithmTestSteppingVertex<V> first = sorted.get(0);
|
||||
assignRows(first, g, grid, 1, 1);
|
||||
|
||||
return grid;
|
||||
}
|
||||
|
||||
private void assignRows(AlgorithmTestSteppingVertex<V> v,
|
||||
VisualGraph<AlgorithmTestSteppingVertex<V>, AlgorithmTestSteppingEdge<V>> g,
|
||||
GridLocationMap<AlgorithmTestSteppingVertex<V>, AlgorithmTestSteppingEdge<V>> grid,
|
||||
int row, int col) {
|
||||
|
||||
int existing = grid.row(v);
|
||||
if (existing > 0) {
|
||||
return; // already processed
|
||||
}
|
||||
|
||||
grid.row(v, row);
|
||||
grid.col(v, col);
|
||||
int nextRow = row++;
|
||||
|
||||
Collection<AlgorithmTestSteppingEdge<V>> children = g.getOutEdges(v);
|
||||
int n = children.size();
|
||||
int middle = n / 2;
|
||||
int start = col - middle;
|
||||
int childCol = start;
|
||||
|
||||
for (AlgorithmTestSteppingEdge<V> edge : children) {
|
||||
AlgorithmTestSteppingVertex<V> child = edge.getEnd();
|
||||
assignRows(child, g, grid, nextRow + 1, childCol++);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractVisualGraphLayout<AlgorithmTestSteppingVertex<V>, AlgorithmTestSteppingEdge<V>> createClonedLayout(
|
||||
VisualGraph<AlgorithmTestSteppingVertex<V>, AlgorithmTestSteppingEdge<V>> newGraph) {
|
||||
|
||||
TestGraphLayout newLayout = new TestGraphLayout((TestGraph) newGraph);
|
||||
return newLayout;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -50,6 +50,8 @@ public class TestVisualGraph extends DefaultVisualGraph<AbstractTestVertex, Test
|
||||
newGraph.addEdge(e);
|
||||
}
|
||||
|
||||
newGraph.setLayout(layout);
|
||||
|
||||
return newGraph;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user