package org.smoothbuild.parse; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.smoothbuild.cli.Console; import org.smoothbuild.lang.function.Functions; import org.smoothbuild.lang.function.base.Name; import com.google.common.collect.ImmutableList; /** * Sorts functions so all dependencies of each function are placed before that * function in returned list. Detects cycles in dependency graph. */ public class DependencySorter { public static List<Name> sortDependencies(Functions functions, Map<Name, Set<Dependency>> dependencies, Console console) { Worker worker = new Worker(functions, dependencies, console); worker.work(); return worker.result(); } private static class Worker { private final Map<Name, Set<Dependency>> notSorted; private final Set<Name> reachableNames; private final List<Name> sorted; private final DependencyStack stack; private final Console console; public Worker(Functions functions, Map<Name, Set<Dependency>> dependencies, Console console) { this.console = console; this.notSorted = new HashMap<>(dependencies); this.reachableNames = new HashSet<>(functions.names()); this.sorted = new ArrayList<>(dependencies.size()); this.stack = new DependencyStack(); } public void work() { while (!notSorted.isEmpty() || !stack.isEmpty()) { if (stack.isEmpty()) { stack.push(removeNext(notSorted)); } processStackTop(); } } private void processStackTop() { DependencyStackElem stackTop = stack.peek(); Dependency missing = findUnreachableDependency(reachableNames, stackTop.dependencies()); if (missing == null) { addStackTopToSorted(); } else { stackTop.setMissing(missing); Set<Dependency> next = notSorted.remove(missing.functionName()); if (next == null) { // DependencyCollector made sure that all dependency exists so the // only possibility at this point is that missing dependency is on // stack and we have cycle in call graph. stack.reportAndThrowCycleException(console); } else { stack.push(new DependencyStackElem(missing.functionName(), next)); } } } private void addStackTopToSorted() { Name name = stack.pop().name(); sorted.add(name); reachableNames.add(name); } public List<Name> result() { return ImmutableList.copyOf(sorted); } private Dependency findUnreachableDependency(Set<Name> reachableNames, Set<Dependency> dependencies) { for (Dependency dependency : dependencies) { if (!reachableNames.contains(dependency.functionName())) { return dependency; } } return null; } private DependencyStackElem removeNext(Map<Name, Set<Dependency>> dependencies) { Iterator<Entry<Name, Set<Dependency>>> it = dependencies.entrySet().iterator(); Entry<Name, Set<Dependency>> element = it.next(); it.remove(); return new DependencyStackElem(element.getKey(), element.getValue()); } } }