All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.javascript.jscomp.deps.ClosureSortedDependencies Maven / Gradle / Ivy

/*
 * Copyright 2010 The Closure Compiler Authors.
 *
 * 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.javascript.jscomp.deps;

import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.HashMultiset;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import com.google.common.collect.Multiset;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.Set;

/**
 * A sorted list of inputs with dependency information.
 * 

* Uses a stable topological sort to make sure that an * input always comes after its dependencies. *

* Also exposes other information about the inputs, like which inputs * do not provide symbols. * * @author [email protected] (Nick Santos) */ public final class ClosureSortedDependencies implements SortedDependencies { private final List inputs; // A topologically sorted list of the inputs. private final List sortedList; // A list of all the inputs that do not have provides. private final List noProvides; private final Map provideMap = new HashMap<>(); public ClosureSortedDependencies(List inputs) throws CircularDependencyException { this.inputs = new ArrayList<>(inputs); noProvides = new ArrayList<>(); // Collect all symbols provided in these files. for (INPUT input : inputs) { Collection currentProvides = input.getProvides(); if (currentProvides.isEmpty()) { noProvides.add(input); } for (String provide : currentProvides) { provideMap.put(provide, input); } } // Get the direct dependencies. final Multimap deps = HashMultimap.create(); for (INPUT input : inputs) { for (String req : input.getRequires()) { INPUT dep = provideMap.get(req); if (dep != null && dep != input) { deps.put(input, dep); } } } // Sort the inputs by sucking in 0-in-degree nodes until we're done. sortedList = topologicalStableSort(inputs, deps); // The dependency graph of inputs has a cycle iff sortedList is a proper // subset of inputs. Also, it has a cycle iff the subgraph // (inputs - sortedList) has a cycle. It's fairly easy to prove this // by the lemma that a graph has a cycle iff it has a subgraph where // no nodes have out-degree 0. I'll leave the proof of this as an exercise // to the reader. if (sortedList.size() < inputs.size()) { List subGraph = new ArrayList<>(inputs); subGraph.removeAll(sortedList); throw new CircularDependencyException( cycleToString(findCycle(subGraph, deps))); } } @Override public INPUT getInputProviding(String symbol) throws MissingProvideException { if (provideMap.containsKey(symbol)) { return provideMap.get(symbol); } throw new MissingProvideException(symbol); } @Override public INPUT maybeGetInputProviding(String symbol) { return provideMap.get(symbol); } /** * Returns the first circular dependency found. Expressed as a list of * items in reverse dependency order (the second element depends on the * first, etc.). */ private List findCycle( List subGraph, Multimap deps) { return findCycle(subGraph.get(0), new HashSet<>(subGraph), deps, new HashSet()); } private List findCycle( INPUT current, Set subGraph, Multimap deps, Set covered) { if (covered.add(current)) { List cycle = findCycle( findRequireInSubGraphOrFail(current, subGraph), subGraph, deps, covered); if (current == cycle.get(0)) { return cycle; } else if (cycle.size() == 1 && cycle.get(0) != current) { cycle.add(current); // Don't add the input to the list if the cycle has closed already. } else if (cycle.get(0) != current && cycle.get(0) != cycle.get(cycle.size() - 1)) { if (cycle.get(cycle.size() - 1) != current) { cycle.add(current); } } return cycle; } else { // Explicitly use the add() method, to prevent a generics constructor // warning that is dumb. The condition it's protecting is // obscure, and I think people have proposed that it be removed. List cycle = new ArrayList<>(); cycle.add(current); return cycle; } } private INPUT findRequireInSubGraphOrFail(INPUT input, Set subGraph) { for (String symbol : input.getRequires()) { INPUT candidate = provideMap.get(symbol); if (subGraph.contains(candidate)) { return candidate; } } throw new IllegalStateException("no require found in subgraph"); } /** * @param cycle A cycle in reverse-dependency order. */ private String cycleToString(List cycle) { List symbols = new ArrayList<>(); for (int i = cycle.size() - 1; i >= 0; i--) { symbols.add(cycle.get(i).getProvides().iterator().next()); } symbols.add(symbols.get(0)); return Joiner.on(" -> ").join(symbols); } @Override public List getSortedList() { return Collections.unmodifiableList(sortedList); } /** * Gets all the dependencies of the given roots. The inputs must be returned * in a stable order. In other words, if A comes before B, and A does not * transitively depend on B, then A must also come before B in the returned * list. */ @Override public List getSortedDependenciesOf(List roots) { return getDependenciesOf(roots, true); } @Override public List getDependenciesOf(List roots, boolean sorted) { Preconditions.checkArgument(inputs.containsAll(roots)); Set included = new HashSet<>(); Deque worklist = new ArrayDeque<>(roots); while (!worklist.isEmpty()) { INPUT current = worklist.pop(); if (included.add(current)) { for (String req : current.getRequires()) { INPUT dep = provideMap.get(req); if (dep != null) { worklist.add(dep); } } } } ImmutableList.Builder builder = ImmutableList.builder(); for (INPUT current : (sorted ? sortedList : inputs)) { if (included.contains(current)) { builder.add(current); } } return builder.build(); } @Override public List getInputsWithoutProvides() { return Collections.unmodifiableList(noProvides); } private static List topologicalStableSort( List items, Multimap deps) { if (items.isEmpty()) { // Priority queue blows up if we give it a size of 0. Since we need // to special case this either way, just bail out. return new ArrayList<>(); } final Map originalIndex = new HashMap<>(); for (int i = 0; i < items.size(); i++) { originalIndex.put(items.get(i), i); } PriorityQueue inDegreeZero = new PriorityQueue<>(items.size(), new Comparator() { @Override public int compare(T a, T b) { return originalIndex.get(a).intValue() - originalIndex.get(b).intValue(); } }); List result = new ArrayList<>(); Multiset inDegree = HashMultiset.create(); Multimap reverseDeps = ArrayListMultimap.create(); Multimaps.invertFrom(deps, reverseDeps); // First, add all the inputs with in-degree 0. for (T item : items) { Collection itemDeps = deps.get(item); inDegree.add(item, itemDeps.size()); if (itemDeps.isEmpty()) { inDegreeZero.add(item); } } // Then, iterate to a fixed point over the reverse dependency graph. while (!inDegreeZero.isEmpty()) { T item = inDegreeZero.remove(); result.add(item); for (T inWaiting : reverseDeps.get(item)) { inDegree.remove(inWaiting, 1); if (inDegree.count(inWaiting) == 0) { inDegreeZero.add(inWaiting); } } } return result; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy