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

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

Go to download

Closure Compiler is a JavaScript optimizing compiler. It parses your JavaScript, analyzes it, removes dead code and rewrites and minimizes what's left. It also checks syntax, variable references, and types, and warns about common JavaScript pitfalls. It is used in many of Google's JavaScript apps, including Gmail, Google Web Search, Google Maps, and Google Docs.

There is a newer version: v20240317
Show newest version
/*
 * 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.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import com.google.common.collect.Multiset;
import com.google.common.collect.Sets;

import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Deque;
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 class 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 = Maps.newHashMap();

  public SortedDependencies(List inputs)
      throws CircularDependencyException {
    this.inputs = Lists.newArrayList(inputs);
    noProvides = Lists.newArrayList();

    // 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) {
          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 = Lists.newArrayList(inputs);
      subGraph.removeAll(sortedList);

      throw new CircularDependencyException(
          cycleToString(findCycle(subGraph, deps)));
    }
  }

  /**
   * Return the input that gives us the given symbol.
   * @throws MissingProvideException An exception if there is no
   *     input for this symbol.
   */
  public INPUT getInputProviding(String symbol)
      throws MissingProvideException {
    if (provideMap.containsKey(symbol)) {
      return provideMap.get(symbol);
    }
    throw new MissingProvideException(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), Sets.newHashSet(subGraph),
        deps, Sets.newHashSet());
  }

  private List findCycle(
      INPUT current, Set subGraph, Multimap deps,
      Set covered) {
    if (covered.add(current)) {
      List cycle = findCycle(
          findRequireInSubGraphOrFail(current, subGraph),
          subGraph, deps, covered);

      // Don't add the input to the list if the cycle has closed already.
      if (cycle.get(0) != cycle.get(cycle.size() - 1)) {
        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 = Lists.newArrayList();
      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 = Lists.newArrayList();
    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);
  }

  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.
   */
  public List getSortedDependenciesOf(List roots) {
    Preconditions.checkArgument(inputs.containsAll(roots));
    Set included = Sets.newHashSet();
    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 : sortedList) {
      if (included.contains(current)) {
        builder.add(current);
      }
    }
    return builder.build();
  }

  public List getInputsWithoutProvides() {
    return Collections.unmodifiableList(noProvides);
  }

  private static  List topologicalStableSort(
      List items, Multimap deps) {
    final Map originalIndex = Maps.newHashMap();
    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 = Lists.newArrayList();

    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;
  }

  public static class CircularDependencyException extends Exception {
    CircularDependencyException(String message) {
      super(message);
    }
  }

  public static class MissingProvideException extends Exception {
    MissingProvideException(String provide) {
      super(provide);
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy