/******************************************************************************* * * Copyright (c) 2004-2010 Oracle Corporation. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * * *******************************************************************************/ 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<N> { private final Set<N> visited = new HashSet<N>(); private final Set<N> visiting = new HashSet<N>(); private final Stack<N> path = new Stack<N>(); public void run(Iterable<? extends N> 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<? extends N> 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 { //TODO: review and check whether we can do it private public final List cycle; public List getCycle() { return cycle; } public CycleDetectedException(List cycle) { super("Cycle detected: " + Util.join(cycle, " -> ")); this.cycle = cycle; } } }