From c0d915b33d6ee2961544c900b3c42a17da93591a Mon Sep 17 00:00:00 2001 From: kohsuke Date: Mon, 11 Jan 2010 16:48:23 +0000 Subject: [PATCH] added code to check cycles in a directed graph git-svn-id: https://hudson.dev.java.net/svn/hudson/trunk/hudson/main@25681 71c3de6d-444a-0410-be80-ed276b4c234a --- .../java/hudson/util/CyclicGraphDetector.java | 62 ++++++++++++++ .../hudson/util/CyclicGraphDetectorTest.java | 83 +++++++++++++++++++ 2 files changed, 145 insertions(+) create mode 100644 core/src/main/java/hudson/util/CyclicGraphDetector.java create mode 100644 core/src/test/java/hudson/util/CyclicGraphDetectorTest.java diff --git a/core/src/main/java/hudson/util/CyclicGraphDetector.java b/core/src/main/java/hudson/util/CyclicGraphDetector.java new file mode 100644 index 0000000000..e52d87d31b --- /dev/null +++ b/core/src/main/java/hudson/util/CyclicGraphDetector.java @@ -0,0 +1,62 @@ +package hudson.util; + +import hudson.Util; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.Stack; + +/** + * Traverses a directed graph and if it contains any cycle, throw an exception. + * + * @author Kohsuke Kawaguchi + */ +public abstract class CyclicGraphDetector { + private final Set visited = new HashSet(); + private final Set visiting = new HashSet(); + private final Stack path = new Stack(); + + public void run(Iterable allNodes) throws CycleDetectedException { + for (N n : allNodes) + visit(n); + } + + /** + * List up edges from the given node (by listing nodes that those edges point to.) + * + * @return + * Never null. + */ + protected abstract Iterable getEdges(N n); + + private void visit(N p) throws CycleDetectedException { + if (!visited.add(p)) return; + + visiting.add(p); + path.push(p); + for (N q : getEdges(p)) { + if (q==null) continue; // ignore unresolved references + if (visiting.contains(q)) + detectedCycle(q); + visit(q); + } + visiting.remove(p); + path.pop(); + } + + private void detectedCycle(N q) throws CycleDetectedException { + int i = path.indexOf(q); + path.push(q); + throw new CycleDetectedException(path.subList(i, path.size())); + } + + public static final class CycleDetectedException extends Exception { + public final List cycle; + + public CycleDetectedException(List cycle) { + super("Cycle detected: "+Util.join(cycle," -> ")); + this.cycle = cycle; + } + } +} diff --git a/core/src/test/java/hudson/util/CyclicGraphDetectorTest.java b/core/src/test/java/hudson/util/CyclicGraphDetectorTest.java new file mode 100644 index 0000000000..9a12ba070f --- /dev/null +++ b/core/src/test/java/hudson/util/CyclicGraphDetectorTest.java @@ -0,0 +1,83 @@ +package hudson.util; + +import hudson.util.CyclicGraphDetector.CycleDetectedException; +import junit.framework.TestCase; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * @author Kohsuke Kawaguchi + */ +public class CyclicGraphDetectorTest extends TestCase { + private class Edge { + String src,dst; + + private Edge(String src, String dst) { + this.src = src; + this.dst = dst; + } + } + + private class Graph extends ArrayList { + Graph e(String src, String dst) { + add(new Edge(src,dst)); + return this; + } + + Set nodes() { + Set nodes = new LinkedHashSet(); + for (Edge e : this) { + nodes.add(e.src); + nodes.add(e.dst); + } + return nodes; + } + + Set edges(String from) { + Set edges = new LinkedHashSet(); + for (Edge e : this) { + if (e.src.equals(from)) + edges.add(e.dst); + } + return edges; + } + + /** + * Performs a cycle check. + */ + void check() throws Exception { + new CyclicGraphDetector() { + protected Set getEdges(String s) { + return edges(s); + } + }.run(nodes()); + } + + void mustContainCycle(String... members) throws Exception { + try { + check(); + fail("Cycle expected"); + } catch (CycleDetectedException e) { + String msg = "Expected cycle of " + Arrays.asList(members) + " but found " + e.cycle; + for (String s : members) + assertTrue(msg, e.cycle.contains(s)); + } + } + } + + public void testCycle() throws Exception { + new Graph().e("A","B").e("B","C").e("C","A").mustContainCycle("A","B","C"); + } + + public void testCycle2() throws Exception { + new Graph().e("A","B").e("B","C").e("C","C").mustContainCycle("C"); + } + + public void testCycle3() throws Exception { + new Graph().e("A","B").e("B","C").e("C","D").e("B","E").e("E","D").e("E","A").mustContainCycle("A","B","E"); + } + +} -- GitLab