/** * Compute the transitive closure of the universe, then breadth-first search from the argument * towards the universe while staying within the transitive closure. */ @Override public <T> Set<T> eval(QueryEnvironment<T> env, QueryExpression expression, List<Argument> args) throws QueryException { Set<T> universeValue = args.get(0).getExpression().eval(env); Set<T> argumentValue = args.get(1).getExpression().eval(env); int depthBound = args.size() > 2 ? args.get(2).getInteger() : Integer.MAX_VALUE; env.buildTransitiveClosure(expression, universeValue, Integer.MAX_VALUE); Set<T> visited = new LinkedHashSet<>(); Set<T> reachableFromUniverse = env.getTransitiveClosure(universeValue); Collection<T> current = argumentValue; // We need to iterate depthBound + 1 times. for (int i = 0; i <= depthBound; i++) { List<T> next = new ArrayList<>(); for (T node : current) { if (!reachableFromUniverse.contains(node)) { // Traversed outside the transitive closure of the universe. continue; } if (!visited.add(node)) { // Already visited; if we see a node in a later round, then we don't need to visit it // again, because the depth at which we see it at must be greater than or equal to the // last visit. continue; } next.addAll(env.getReverseDeps(node)); } if (next.isEmpty()) { // Exit when there are no more nodes to visit. break; } current = next; } return visited; }