/* *************************************************************************************** * Copyright (C) 2006 EsperTech, Inc. All rights reserved. * * http://www.espertech.com/esper * * http://www.espertech.com * * ---------------------------------------------------------------------------------- * * The software in this package is published under the terms of the GPL license * * a copy of which has been included with this distribution in the license.txt file. * *************************************************************************************** */ package com.espertech.esper.util; import java.io.PrintWriter; import java.io.StringWriter; import java.util.*; /** * Model of dependency of lookup, in which one stream supplies values for lookup in another stream. */ public class DependencyGraph { private final int numStreams; private final boolean allowDependencySame; private final Map<Integer, SortedSet<Integer>> dependencies; /** * Ctor. * * @param numStreams - number of streams * @param allowDependencySame - allow same-dependency stream */ public DependencyGraph(int numStreams, boolean allowDependencySame) { this.numStreams = numStreams; this.allowDependencySame = allowDependencySame; dependencies = new HashMap<Integer, SortedSet<Integer>>(); } /** * Returns the number of streams. * * @return number of streams */ public int getNumStreams() { return numStreams; } public String toString() { StringWriter buf = new StringWriter(); PrintWriter writer = new PrintWriter(buf); int count = 0; for (Map.Entry<Integer, SortedSet<Integer>> entry : dependencies.entrySet()) { count++; writer.println("Entry " + count + ": from=" + entry.getKey() + " to=" + entry.getValue()); } return buf.toString(); } /** * Adds dependencies that a target may have on required streams. * * @param target the stream having dependencies on one or more other streams * @param requiredStreams the streams that the target stream has a dependency on */ public void addDependency(int target, SortedSet<Integer> requiredStreams) { if (requiredStreams.contains(target)) { throw new IllegalArgumentException("Dependency between same streams is not allowed for stream " + target); } Set<Integer> toSet = dependencies.get(target); if (toSet != null) { throw new IllegalArgumentException("Dependencies from stream " + target + " already in collection"); } dependencies.put(target, requiredStreams); } /** * Adds a single dependency of target on a required streams. * * @param target the stream having dependencies on one or more other streams * @param from a single required streams that the target stream has a dependency on */ public void addDependency(int target, int from) { if (target == from && !allowDependencySame) { throw new IllegalArgumentException("Dependency between same streams is not allowed for stream " + target); } SortedSet<Integer> toSet = dependencies.get(target); if (toSet == null) { toSet = new TreeSet<Integer>(); dependencies.put(target, toSet); } toSet.add(from); } /** * Returns true if the stream asked for has a dependency. * * @param stream to check dependency for * @return true if a dependency exist, false if not */ public boolean hasDependency(int stream) { SortedSet<Integer> dep = dependencies.get(stream); if (dep != null) { return !dep.isEmpty(); } return false; } /** * Returns the set of dependent streams for a given stream. * * @param stream to return dependent streams for * @return set of stream numbers of stream providing properties */ public Set<Integer> getDependenciesForStream(int stream) { SortedSet<Integer> dep = dependencies.get(stream); if (dep != null) { return dep; } return Collections.emptySet(); } /** * Returns a map of stream number and the streams dependencies. * * @return map of dependencies */ public Map<Integer, SortedSet<Integer>> getDependencies() { return dependencies; } /** * Returns a set of stream numbers that are the root dependencies, i.e. the dependencies with the deepest graph. * * @return set of stream number of streams */ public Set<Integer> getRootNodes() { Set<Integer> rootNodes = new HashSet<Integer>(); for (int i = 0; i < numStreams; i++) { boolean found = false; for (Map.Entry<Integer, SortedSet<Integer>> entry : dependencies.entrySet()) { if (entry.getValue().contains(i)) { found = true; break; } } if (!found) { rootNodes.add(i); } } return rootNodes; } /** * Return the root nodes ignoring the nodes provided. * * @param ignoreList nodes to be ignored * @return root nodes */ public Set<Integer> getRootNodes(Set<Integer> ignoreList) { Set<Integer> rootNodes = new HashSet<Integer>(); for (int i = 0; i < numStreams; i++) { if (ignoreList.contains(i)) { continue; } boolean found = false; for (Map.Entry<Integer, SortedSet<Integer>> entry : dependencies.entrySet()) { if (entry.getValue().contains(i)) { if (!ignoreList.contains(entry.getKey())) { found = true; break; } } } if (!found) { rootNodes.add(i); } } return rootNodes; } /** * Returns any circular dependency as a stack of stream numbers, or null if none exist. * * @return circular dependency stack */ public Stack<Integer> getFirstCircularDependency() { for (int i = 0; i < numStreams; i++) { Stack<Integer> deepDependencies = new Stack<Integer>(); deepDependencies.push(i); boolean isCircular = recursiveDeepDepends(deepDependencies, i); if (isCircular) { return deepDependencies; } } return null; } private boolean recursiveDeepDepends(Stack<Integer> deepDependencies, int currentStream) { Set<Integer> required = dependencies.get(currentStream); if (required == null) { return false; } for (Integer stream : required) { if (deepDependencies.contains(stream)) { return true; } deepDependencies.push(stream); boolean isDeep = recursiveDeepDepends(deepDependencies, stream); if (isDeep) { return true; } deepDependencies.pop(); } return false; } /** * Check if the given stream has any dependencies, direct or indirect, to any of the streams that are not in the ignore list. * * @param ignoreList ignore list * @param navigableStream to-stream * @return indicator whether there is an unsatisfied dependency */ public boolean hasUnsatisfiedDependency(int navigableStream, Set<Integer> ignoreList) { Set<Integer> deepDependencies = new HashSet<Integer>(); recursivePopulateDependencies(navigableStream, deepDependencies); for (int dependency : deepDependencies) { if (!ignoreList.contains(dependency)) { return true; } } return false; } private void recursivePopulateDependencies(int navigableStream, Set<Integer> deepDependencies) { Set<Integer> dependencies = getDependenciesForStream(navigableStream); deepDependencies.addAll(dependencies); for (int dependency : dependencies) { recursivePopulateDependencies(dependency, deepDependencies); } } }