/** * Copyright (C) 2009-2013 FoundationDB, LLC * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.foundationdb.util; import com.google.common.base.Objects; import org.jgrapht.DirectedGraph; import org.jgrapht.Graph; import org.jgrapht.alg.CycleDetector; import org.jgrapht.graph.DefaultDirectedGraph; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; import java.util.HashSet; import java.util.List; import java.util.Set; public abstract class DagChecker<T> { protected abstract Set<? extends T> initialNodes(); protected abstract Set<? extends T> nodesFrom(T starting); public boolean isDag() { DirectedGraph<T, Pair> graph = new DefaultDirectedGraph<>(Pair.class); Set<? extends T> initialNodes = initialNodes(); Set<T> knownNodes = new HashSet<>(initialNodes.size() * 10); // just a guess Deque<T> nodePath = new ArrayDeque<>(20); // should be plenty boolean isDag = tryAdd(initialNodes, graph, knownNodes, new CycleDetector<>(graph), nodePath); if (!isDag) { this.badNodes = nodePath; } return isDag; } private boolean tryAdd(Set<? extends T> roots, Graph<T, Pair> graph, Set<T> knownNodes, CycleDetector<T, Pair> cycleDetector, Deque<T> nodePath) { for (T node : roots) { nodePath.addLast(node); graph.addVertex(node); if (knownNodes.add(node)) { Set<? extends T> nodesFrom = nodesFrom(node); for (T from : nodesFrom) { graph.addVertex(from); Pair edge = new Pair(from, node); graph.addEdge(from, node, edge); nodePath.addLast(from); if (cycleDetector.detectCycles()) return false; nodePath.removeLast(); } if (!tryAdd(nodesFrom, graph, knownNodes, cycleDetector, nodePath)) return false; } nodePath.removeLast(); } return true; } public List<T> getBadNodePath() { return badNodes == null ? null : new ArrayList<>(badNodes); } private Deque<T> badNodes = null; private static class Pair { private Pair(Object from, Object to) { this.from = from; this.to = to; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Pair pair = (Pair) o; return Objects.equal(this.from, pair.from) && Objects.equal(this.to, pair.to); } @Override public int hashCode() { int result = from != null ? from.hashCode() : 0; result = 31 * result + (to != null ? to.hashCode() : 0); return result; } private Object from; private Object to; } }