/***************************************************************** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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.apache.cayenne.di.spi; import org.apache.cayenne.di.DIRuntimeException; import java.util.*; /** * The implementation here is basically an adjacency list, but a {@link Map} is * used to map each vertex to its list of adjacent vertices. * * @param <V> * A type of a vertex. * @since 4.0 */ class DIGraph<V> { /** * {@link LinkedHashMap} is used for supporting insertion order. */ private Map<V, List<V>> neighbors = new LinkedHashMap<>(); public DIGraph() { } /** * Add a vertex to the graph. Nothing happens if vertex is already in graph. */ public void add(V vertex) { if (neighbors.containsKey(vertex)) { return; } neighbors.put(vertex, new ArrayList<V>()); } /** * Add vertexes to the graph. */ public void addAll(Collection<V> vertexes) { for (V vertex : vertexes) { this.add(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 add(V from, V to) { this.add(from); this.add(to); neighbors.get(from).add(to); } /** * True iff graph contains vertex. */ public boolean contains(V vertex) { return neighbors.containsKey(vertex); } /** * Remove an edge from the graph. Nothing happens if no such edge. * * @throws IllegalArgumentException * if either vertex doesn't exist. */ public void remove(V from, V to) { if (!(this.contains(from) && this.contains(to))) throw new IllegalArgumentException("Nonexistent vertex"); neighbors.get(from).remove(to); } /** * Return (as a Map) the out-degree of each vertex. */ public Map<V, Integer> outDegree() { Map<V, Integer> result = new LinkedHashMap<>(); for (Map.Entry<V, List<V>> entry : neighbors.entrySet()) { result.put(entry.getKey(), entry.getValue().size()); } return result; } /** * Return (as a Map) the in-degree of each vertex. */ public Map<V, Integer> inDegree() { Map<V, Integer> result = new LinkedHashMap<>(); for (V v : neighbors.keySet()) { result.put(v, 0); } for (V from : neighbors.keySet()) { for (V to : neighbors.get(from)) { result.put(to, result.get(to) + 1); } } return result; } /** * Return (as a List) the topological sort of the vertices. Throws an exception if cycles are detected. */ public List<V> topSort() { Map<V, Integer> degree = inDegree(); Deque<V> zeroDegree = new ArrayDeque<>(); LinkedList<V> result = new LinkedList<>(); for (Map.Entry<V, Integer> entry : degree.entrySet()) { if (entry.getValue() == 0) { zeroDegree.push(entry.getKey()); } } while (!zeroDegree.isEmpty()) { V v = zeroDegree.pop(); result.push(v); for (V neighbor : neighbors.get(v)) { degree.put(neighbor, degree.get(neighbor) - 1); if (degree.get(neighbor) == 0) { zeroDegree.push(neighbor); } } } // Check that we have used the entire graph (if not, there was a cycle) if (result.size() != neighbors.size()) { throw new DIRuntimeException("Dependency cycle detected in DI container"); } return result; } /** * String representation of graph. */ public String toString() { StringBuffer s = new StringBuffer(); for (Map.Entry<V, List<V>> entry : neighbors.entrySet()) { s.append("\n " + entry.getKey() + " -> " + entry.getValue()); } return s.toString(); } public int size() { return neighbors.size(); } }