/* * Copyright 2012 Decebal Suiu * * 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 org.pf4j.util; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Stack; /** * See Wikipedia for more information. * * @author Decebal Suiu */ public class DirectedGraph { /** * The implementation here is basically an adjacency list, but instead * of an array of lists, a Map is used to map each vertex to its list of * adjacent vertices. */ private Map> neighbors = new HashMap<>(); /** * Add a vertex to the graph. Nothing happens if vertex is already in graph. */ public void addVertex(V vertex) { if (containsVertex(vertex)) { return; } neighbors.put(vertex, new ArrayList()); } /** * True if graph contains vertex. */ public boolean containsVertex(V vertex) { return neighbors.containsKey(vertex); } public void removeVertex(V vertex) { neighbors.remove(vertex); } /** * Add an edge to the graph; if either vertex does not exist, it's added. * This implementation allows the creation of multi-edges and self-loops. */ public void addEdge(V from, V to) { addVertex(from); addVertex(to); neighbors.get(from).add(to); } /** * Remove an edge from the graph. Nothing happens if no such edge. * @throws {@link IllegalArgumentException} if either vertex doesn't exist. */ public void removeEdge(V from, V to) { if (!containsVertex(from)) { throw new IllegalArgumentException("Nonexistent vertex " + from); } if (!containsVertex(to)) { throw new IllegalArgumentException("Nonexistent vertex " + to); } neighbors.get(from).remove(to); } public List getNeighbors(V vertex) { return containsVertex(vertex) ? neighbors.get(vertex) : new ArrayList(); } /** * Report (as a Map) the out-degree (the number of tail ends adjacent to a vertex) of each vertex. */ public Map outDegree() { Map result = new HashMap<>(); for (V vertex : neighbors.keySet()) { result.put(vertex, neighbors.get(vertex).size()); } return result; } /** * Report (as a Map) the in-degree (the number of head ends adjacent to a vertex) of each vertex. */ public Map inDegree() { Map result = new HashMap<>(); for (V vertex : neighbors.keySet()) { result.put(vertex, 0); // all in-degrees are 0 } for (V from : neighbors.keySet()) { for (V to : neighbors.get(from)) { result.put(to, result.get(to) + 1); // increment in-degree } } return result; } /** * Report (as a List) the topological sort of the vertices; null for no such sort. * See this for more information. */ public List topologicalSort() { Map degree = inDegree(); // determine all vertices with zero in-degree Stack zeroVertices = new Stack<>(); // stack as good as any here for (V v : degree.keySet()) { if (degree.get(v) == 0) { zeroVertices.push(v); } } // determine the topological order List result = new ArrayList<>(); while (!zeroVertices.isEmpty()) { V vertex = zeroVertices.pop(); // choose a vertex with zero in-degree result.add(vertex); // vertex 'v' is next in topological order // "remove" vertex 'v' by updating its neighbors for (V neighbor : neighbors.get(vertex)) { degree.put(neighbor, degree.get(neighbor) - 1); // remember any vertices that now have zero in-degree if (degree.get(neighbor) == 0) { zeroVertices.push(neighbor); } } } // check that we have used the entire graph (if not, there was a cycle) if (result.size() != neighbors.size()) { return null; } return result; } /** * Report (as a List) the reverse topological sort of the vertices; null for no such sort. */ public List reverseTopologicalSort() { List list = topologicalSort(); if (list == null) { return null; } Collections.reverse(list); return list; } /** * True if graph is a dag (directed acyclic graph). */ public boolean isDag () { return topologicalSort() != null; } /** * String representation of graph. */ @Override public String toString() { StringBuffer sb = new StringBuffer(); for (V vertex : neighbors.keySet()) { sb.append("\n " + vertex + " -> " + neighbors.get(vertex)); } return sb.toString(); } }