/* * Copyright 2014 Google Inc. All rights reserved. * * 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 com.google.acai; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Multimap; import com.google.common.collect.Sets; import java.util.ArrayDeque; import java.util.Queue; import java.util.Set; /** * Internal helper methods for managing {@code TestingService} dependencies which have been declared * using the {@code DependsOn} annotation. */ class Dependencies { /** * Returns a valid execution order for {@code testingServices} based on any {@link DependsOn} * annotations present. * * @throws IllegalArgumentException if the dependency graph contains a cycle */ static ImmutableList<TestingService> inOrder(Set<TestingService> testingServices) { return topologicalSorting(buildDependencyGraph(testingServices)); } /** * Returns a topological sorting. * * <p>Algorithm due to Kahn, Arthur B. (1962), Topological sorting of large networks, * Communications of the ACM 5 (11): 558–562, <a * href="http://dl.acm.org/citation.cfm?doid=368996.369025">doi:10.1145/368996.369025</a>. */ private static ImmutableList<TestingService> topologicalSorting( DirectedGraph<TestingService> dependencyGraph) { Queue<TestingService> rootVertices = new ArrayDeque<>(dependencyGraph.getRootVertices()); ImmutableList.Builder<TestingService> ordered = ImmutableList.builder(); while (!rootVertices.isEmpty()) { TestingService vertex = rootVertices.remove(); ordered.add(vertex); for (TestingService successor : dependencyGraph.getSuccessors(vertex)) { dependencyGraph.removeEdge(vertex, successor); if (dependencyGraph.isRootVertex(successor)) { rootVertices.add(successor); } } } if (dependencyGraph.hasEdges()) { throw new IllegalArgumentException("Cycle exists in @DependsOn dependencies."); } return ordered.build(); } /** Returns a directed graph representing the dependencies of {@code testingServices}. */ private static DirectedGraph<TestingService> buildDependencyGraph( Set<TestingService> testingServices) { DirectedGraph<TestingService> dependencyGraph = new DirectedGraph<>(testingServices); Multimap<Class<? extends TestingService>, TestingService> servicesByClass = HashMultimap.create(); for (TestingService testingService : testingServices) { servicesByClass.put(testingService.getClass(), testingService); } for (TestingService testingService : testingServices) { for (TestingService dependency : getDependencies(testingService, servicesByClass)) { dependencyGraph.addEdge(dependency, testingService); } } return dependencyGraph; } /** Returns the set of services which {@code testingService} depends upon. */ private static ImmutableSet<TestingService> getDependencies( TestingService testingService, Multimap<Class<? extends TestingService>, TestingService> servicesByClass) { if (!testingService.getClass().isAnnotationPresent(DependsOn.class)) { return ImmutableSet.of(); } ImmutableSet.Builder<TestingService> dependencies = ImmutableSet.builder(); DependsOn dependsOn = testingService.getClass().getAnnotation(DependsOn.class); for (Class<? extends TestingService> serviceClass : dependsOn.value()) { dependencies.addAll(servicesByClass.get(serviceClass)); } return dependencies.build(); } /** * Simple representation of a directed graph. * * <p>The set of vertices in the graph is immutable but edges may be added and removed from an * instance. */ private static class DirectedGraph<T> { private final Multimap<T, T> successors = HashMultimap.create(); private final Multimap<T, T> predecessors = HashMultimap.create(); private final ImmutableSet<T> vertices; /** Initializes a graph containing {@code vertices}. */ DirectedGraph(Set<T> vertices) { this.vertices = ImmutableSet.copyOf(vertices); } /** Adds a directed edge from {@code tail} to {@code head}. */ void addEdge(T tail, T head) { successors.put(tail, head); predecessors.put(head, tail); } /** Removes the directed edge from {@code tail} to {@code head}. */ void removeEdge(T tail, T head) { checkArgument(successors.remove(tail, head), "Attempt to remove non-existent edge"); checkState(predecessors.remove(head, tail), "Graph state was invalid."); } /** Returns the set of vertices which are not the tail of any directed edge. */ Set<T> getRootVertices() { return Sets.difference(vertices, predecessors.keySet()); } /** Returns true if there are no directed edges whose tail is {@code vertex}. */ boolean isRootVertex(T vertex) { checkArgument(vertices.contains(vertex)); return !predecessors.containsKey(vertex); } /** Returns true if the graph has any edges. */ boolean hasEdges() { return !successors.isEmpty(); } /** Returns the set of vertices who are at the head of an edge whose tail is {@code vertex}. */ ImmutableSet<T> getSuccessors(T vertex) { return ImmutableSet.copyOf(successors.get(vertex)); } } }