/* * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 org.apache.flex.compiler.internal.projects; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.apache.flex.compiler.common.DependencyType; import org.apache.flex.compiler.common.DependencyTypeSet; import org.apache.flex.compiler.exceptions.CircularDependencyException; import org.apache.flex.compiler.internal.graph.Graph; import org.apache.flex.compiler.internal.graph.GraphEdge; import org.apache.flex.compiler.internal.graph.TopologicalSort; import org.apache.flex.compiler.internal.units.CompilationUnitBase; import org.apache.flex.compiler.internal.units.EmbedCompilationUnit; import org.apache.flex.compiler.internal.units.InvisibleCompilationUnit; import org.apache.flex.compiler.units.ICompilationUnit; import com.google.common.base.Function; import com.google.common.collect.Collections2; /** * Class to track dependencies in a {@link CompilerProject}. * <p> * There are four types of dependency that may exist from one * {@link ICompilationUnit} to another {@link ICompilationUnit}. * <dl> * <dt>Inheritance Dependency</dt> * <dd>If {@link ICompilationUnit} A contains a definition that extends a class * or implements an interface defined in {@link ICompilationUnit} B, then there * is an inheritance dependency from A to B.</dd> * <dt>Signature Dependency</dt> * <dd>If any of: * <ul> * <li>class member variable ( or constant ) type annotation expression</li> * <li>class method return type annotation expression</li> * <li>class method parameter type annotation expression</li> * <li>script variable ( or constant ) type annotation expression</li> * <li>script function return type annotation expression</li> * <li>script function parameter type annotation expression</li> * </ul> * in {@link ICompilationUnit} A refers to a definition from * {@link ICompilationUnit} B, then there is a signature dependency from A to B. * </dd> * <dt>Namespace Dependency</dt> * <dd>If {@link ICompilationUnit} A depends on a namespace definition from * {@link ICompilationUnit} B, then there is a namespace dependency from A to B. * </dd> * </dl> * <dt>Expression Dependency</dt> * <dd>If {@link ICompilationUnit} A contains any reference to * {@link ICompilationUnit} B that does not result in any other type of * dependency, then there exists an expression dependency from A to B.</dd> * </dl> * <p> * The code in this class intends to make adding or updating a dependency edge * very fast and thread safe. */ public final class DependencyGraph { public static final class Dependency implements Comparable<Dependency> { public String qname; public DependencyType type; public Dependency(String qname, DependencyType type) { this.qname = qname; this.type = type; } @Override public int compareTo(Dependency o) { return qname.compareTo(o.qname); } @Override public boolean equals(Object o) { if(o instanceof Dependency) { Dependency other = (Dependency)o; return other.qname.equals(qname); } else { return super.equals(o); } } @Override public int hashCode() { return qname.hashCode(); } } /** * Class to hold information about an edge in the DependencyGraph. */ static final class Edge extends GraphEdge<ICompilationUnit> implements Comparable<Edge> { private Map<String, DependencyTypeSet> dependencies; private DependencyTypeSet dependencySet; /** * @param referencingCompilationUnit * @param declaringCompilationUnit */ private Edge(ICompilationUnit referencingCompilationUnit, ICompilationUnit declaringCompilationUnit) { super(referencingCompilationUnit, declaringCompilationUnit); assert (!(referencingCompilationUnit instanceof InvisibleCompilationUnit)) : "InvisibleCompilationUnit should never have an edge"; assert (!(declaringCompilationUnit instanceof InvisibleCompilationUnit)): "InvisibleCompilationUnit should never have an edge"; this.dependencySet = DependencyTypeSet.noneOf(); this.dependencies = new HashMap<String, DependencyTypeSet>(); } /** */ public boolean getIsInheritanceDependency() { return dependencySet.contains(DependencyType.INHERITANCE); } /** */ public boolean getIsSignatureDependency() { return dependencySet.contains(DependencyType.SIGNATURE); } /** */ public boolean getIsNamespaceDependency() { return dependencySet.contains(DependencyType.NAMESPACE); } /** */ public boolean getIsExpressionDependency() { return dependencySet.contains(DependencyType.EXPRESSION); } /** * @param set A set of dependencies * @return True if any of the union of the parameter set and this Edge's dependencySet is non-null. * */ public boolean typeInSet(DependencyTypeSet set) { for (DependencyType t : set) { if (dependencySet.contains(t)) return true; } return false; } /** * Adds a dependency of a {@link DependencyType} on a definition * with qname to this Edge. * * @param qname The definition qualified name that is depended on * @param types {@link DependencyType}'s to add to this edge. */ private void addDependency(String qname, DependencyTypeSet types) { DependencyTypeSet typeSet = dependencies.get(qname); if(typeSet != null) { DependencyTypeSet newTypeSet = DependencyTypeSet.copyOf(typeSet); newTypeSet.addAll(types); this.dependencies.put(qname, newTypeSet); } else { this.dependencies.put(qname, DependencyTypeSet.copyOf(types)); } dependencySet.addAll(types); } /** * Adds a dependency of a {@link DependencyType} on a definition * with qname to this Edge. * * @param qname The definition qualified name that is depended on * @param type {@link DependencyType} to add to this edge. */ private void addDependency(String qname, DependencyType type) { DependencyTypeSet typeSet = dependencies.get(qname); if (typeSet != null) { DependencyTypeSet newTypeSet = DependencyTypeSet.copyOf(typeSet); newTypeSet.add(type); this.dependencies.put(qname, newTypeSet); } else { this.dependencies.put(qname, DependencyTypeSet.of(type)); } dependencySet.add(type); } /** * Adds an anonymous dependency of a {@link DependencyType} on a definition * to this Edge. * @param type {@link DependencyType}'s to add to this edge. */ private void addDependency(DependencyType type) { dependencySet.add(type); } // Adding toString method for debugging. @Override public String toString() { String result = getFrom().getName() + " -> " + getTo().getName() + " [ "; if (getIsInheritanceDependency()) result += "inheritance "; if (getIsSignatureDependency()) result += "signature "; if (getIsNamespaceDependency()) result += "namespace "; if (getIsExpressionDependency()) result += "expression "; result += "]"; return result; } /** * @return A map of all named dependee qnames of this edge to the * {@link DependencyType} that they depend on. */ public Map<String, DependencyTypeSet> getNamedDependencies() { return this.dependencies; } @Override public int compareTo(Edge edge2) { // First compare the names of the "from" compilation units. int fromCompare = getFrom().getName().compareTo(edge2.getFrom().getName()); if (fromCompare == 0) { // Then compare the names of the "to" compilation units. return getTo().getName().compareTo(edge2.getTo().getName()); } else { return fromCompare; } } public DependencyTypeSet getAllDependencies() { return dependencySet; } } private Edge getEdge(ICompilationUnit referencingCompilationUnit, ICompilationUnit declaringCompilationUnit) { Edge result = graph.getEdge(referencingCompilationUnit, declaringCompilationUnit); if (result == null) { result = new Edge(referencingCompilationUnit, declaringCompilationUnit); graph.setEdge(result); } return result; } /** * Default constructor */ public DependencyGraph() { graph = new Graph<ICompilationUnit, Edge>(); lock = new ReentrantReadWriteLock(); } private final Graph<ICompilationUnit, Edge> graph; private final ReadWriteLock lock; /** * Adds a dependency to the dependency graph. * * @param depender {@link ICompilationUnit} with a reference to a definition * defined by the other {@link ICompilationUnit}. * @param dependee {@link ICompilationUnit} with a definition referred to by * the other {@link ICompilationUnit}. * @param dt The dependency types to add from the "depender" to the "dependee". * @param targetQName A qname of the definition of the dependee that the depender depends on */ public void addDependency(ICompilationUnit depender, ICompilationUnit dependee, DependencyTypeSet dt, String targetQName) { // The compiler does not record self references in the same compilation unit. if (depender == dependee) return; assert !dependee.isInvisible() : "invisible units do not export symbols to the project scope, so nothing should depend one them."; assert (!(depender instanceof InvisibleCompilationUnit)) : "depender should only ever be an InvisibleCompilationUnit delegate, never an InvisibleCompilationUnit"; lock.writeLock().lock(); try { Edge e = getEdge(depender, dependee); e.addDependency(targetQName, dt); } finally { lock.writeLock().unlock(); } } /** * Adds a dependency to the dependency graph. * * @param depender {@link ICompilationUnit} with a reference to a definition * defined by the other {@link ICompilationUnit}. * @param dependee {@link ICompilationUnit} with a definition referred to by * the other {@link ICompilationUnit}. * @param dt The type of dependency to add from the "depender" to the "dependee". * @param targetQName A qname of the definition of the dependee that the depender depends on */ public void addDependency(ICompilationUnit depender, ICompilationUnit dependee, DependencyType dt, String targetQName) { // The compiler does not record self references in the same compilation unit. if (depender == dependee) return; assert !dependee.isInvisible() : "invisible units do not export symbols to the project scope, so nothing should depend one them."; if (depender instanceof InvisibleCompilationUnit) depender = ((InvisibleCompilationUnit)depender).getDelegate(); lock.writeLock().lock(); try { Edge e = getEdge(depender, dependee); e.addDependency(targetQName, dt); } finally { lock.writeLock().unlock(); } } /** * Adds an anonymous dependency to the dependency graph. * * @param depender {@link ICompilationUnit} with a reference to a definition * defined by the other {@link ICompilationUnit}. * @param dependee {@link ICompilationUnit} with a definition referred to by * the other {@link ICompilationUnit}. * @param dt The type of dependency to add from the "depender" to the "dependee". */ public void addDependency(ICompilationUnit depender, ICompilationUnit dependee, DependencyType dt) { // The compiler does not record self references in the same compilation unit. if (depender == dependee) return; assert !dependee.isInvisible() : "invisible units do not export symbols to the project scope, so nothing should depend one them."; if (depender instanceof InvisibleCompilationUnit) depender = ((InvisibleCompilationUnit)depender).getDelegate(); lock.writeLock().lock(); try { Edge e = getEdge(depender, dependee); e.addDependency(dt); } finally { lock.writeLock().unlock(); } } /** * Remove all of the outgoing dependencies on a compilation unit. * * @param cu The compilation to remove dependencies from. */ public void removeDependencies(ICompilationUnit cu) { Set<Edge> edges = getOutgoingEdges(cu); for (final Edge edge : edges) { removeDependency(cu, edge.getTo()); } } /** * Remove a dependency in the dependency graph. * * @param depender {@link ICompilationUnit} with a reference to a definition * defined by the other {@link ICompilationUnit}. * @param dependee {@link ICompilationUnit} with a definition referred to by * the other {@link ICompilationUnit}. */ private void removeDependency(ICompilationUnit depender, ICompilationUnit dependee) { // The compiler does not record self references in the same compilation unit. if (depender == dependee) return; assert !dependee.isInvisible() : "invisible units do not export symbols to the project scope, so nothing should depend one them."; if (depender instanceof InvisibleCompilationUnit) depender = ((InvisibleCompilationUnit)depender).getDelegate(); lock.writeLock().lock(); try { Edge e = getEdge(depender, dependee); graph.removeEdge(e); } finally { lock.writeLock().unlock(); } } /** * Computes the list of all {@link ICompilationUnit}'s that the specified * collection of {@link ICompilationUnit}'s depends directly or indirectly. * {@link ICompilationUnit}'s that contain definitions that extend or * implement definitions from other {@link ICompilationUnit}'s will occur in * the list after the {@link ICompilationUnit}'s that contains the * definitions that are extended or implemented. * * In cases where {@link ICompilationUnit}s have no real dependencies between * them this function employs a lexical sort on the {@link ICompilationUnit}'s * name. * * @param roots Collection of {@link ICompilationUnit}'s that will be in the * returned List along with any {@link ICompilationUnit}'s they depend on * directly or indirectly. * @return List of all {@link ICompilationUnit}'s that the specified * collection of {@link ICompilationUnit}'s depends directly or indirectly. */ public List<ICompilationUnit> topologicalSort(Collection<ICompilationUnit> roots) { return topologicalSort(roots, new Comparator<ICompilationUnit>() { @Override public int compare(ICompilationUnit o1, ICompilationUnit o2) { return o1.getName().compareTo(o2.getName()); } }); } /** * Computes the list of all {@link ICompilationUnit}'s that the specified * collection of {@link ICompilationUnit}'s depends directly or indirectly. * {@link ICompilationUnit}'s that contain definitions that extend or * implement definitions from other {@link ICompilationUnit}'s will occur in * the list after the {@link ICompilationUnit}'s that contains the * definitions that are extended or implemented. * * @param roots Collection of {@link ICompilationUnit}'s that will be in the * returned List along with any {@link ICompilationUnit}'s they depend on * directly or indirectly. * @param comparator The comparator of last resort used to order * {@link ICompilationUnit}s when there are no real dependencies between them. * @return List of all {@link ICompilationUnit}'s that the specified * collection of {@link ICompilationUnit}'s depends directly or indirectly. */ public List<ICompilationUnit> topologicalSort(Collection<ICompilationUnit> roots, final Comparator<ICompilationUnit> comparator) { lock.readLock().lock(); try { final ArrayList<ICompilationUnit> sortedList = new ArrayList<ICompilationUnit>(graph.getVertices().size()); TopologicalSort.IVisitor<ICompilationUnit, Edge> visitor = new TopologicalSort.IVisitor<ICompilationUnit, Edge>() { @Override public void visit(ICompilationUnit v) { assert v != null; // this might mean there was some race condidition... sortedList.add(v); } @Override public boolean isTopologicalEdge(Edge e) { return e.getIsInheritanceDependency(); } @Override public int compare(ICompilationUnit a, ICompilationUnit b) { return comparator.compare(a, b); } }; try { TopologicalSort.sort(graph, roots, visitor); } catch (CircularDependencyException e1) { assert false : "CircularDependencyException"; } return sortedList; } finally { lock.readLock().unlock(); } } /** * Computes the set of {@link ICompilationUnit}'s that should be cleaned * given a collection of {@link ICompilationUnit}'s that are known to need * cleaning. * <p> * This method is not thread safe and must not be called while any * compilation is occurring in any project in the workspace associated with * the specified compilation units. * * @param roots {@link ICompilationUnit}'s that are known to need cleaning * @return Set of {@link ICompilationUnit}'s that should be cleaned. */ public static Set<ICompilationUnit> computeInvalidationSet(Iterable<ICompilationUnit> roots) { HashSet<ICompilationUnit> result = new HashSet<ICompilationUnit>(); LinkedList<Edge> workList = new LinkedList<Edge>(); for (ICompilationUnit unit : roots) { assert unit instanceof CompilationUnitBase; CompilationUnitBase compilationUnit = (CompilationUnitBase)unit; boolean alreadyVisited = !result.add(compilationUnit); if (!alreadyVisited) workList.addAll(compilationUnit.getProject().getDependencyGraph().getIncomingEdges(compilationUnit)); } DependencyTypeSet recursiveInvalidationSet = DependencyTypeSet.of(DependencyType.INHERITANCE, DependencyType.SIGNATURE, DependencyType.NAMESPACE); HashSet<Edge> visitedEdges = new HashSet<Edge>(); while (!workList.isEmpty()) { Edge currentEdge = workList.pop(); if (visitedEdges.add(currentEdge)) { assert currentEdge.getFrom() instanceof CompilationUnitBase; CompilationUnitBase dependentUnit = (CompilationUnitBase)currentEdge.getFrom(); result.add(dependentUnit); if (currentEdge.typeInSet(recursiveInvalidationSet)) workList.addAll(dependentUnit.getProject().getDependencyGraph().getIncomingEdges(dependentUnit)); } } return result; } /** * Add a {@link ICompilationUnit} to the dependency graph. * @param cu {@link ICompilationUnit} to add to the dependency graph */ public void addCompilationUnit(ICompilationUnit cu) { if (cu instanceof InvisibleCompilationUnit) cu = ((InvisibleCompilationUnit)cu).getDelegate(); lock.writeLock().lock(); try { graph.addVertex(cu); } finally { lock.writeLock().unlock(); } } /** * Add a Collection of {@link ICompilationUnit}'s to the dependency graph. * @param c Collection of {@link ICompilationUnit}'s to add to the dependency graph */ public void addCompilationUnits(Collection<ICompilationUnit> c) { Collection<ICompilationUnit> transformed = Collections2.transform(c, new Function<ICompilationUnit, ICompilationUnit>() { @Override public ICompilationUnit apply(ICompilationUnit input) { if (input instanceof InvisibleCompilationUnit) return ((InvisibleCompilationUnit)input).getDelegate(); else return input; }}); lock.writeLock().lock(); try { graph.addVertices(transformed); } finally { lock.writeLock().unlock(); } } /** * Removes an {@link ICompilationUnit} from the dependency graph. */ public void removeCompilationUnit(ICompilationUnit cu) { if (cu instanceof InvisibleCompilationUnit) cu = ((InvisibleCompilationUnit)cu).getDelegate(); lock.writeLock().lock(); try { graph.removeVertex(cu); } finally { lock.writeLock().unlock(); } } Set<Edge> getOutgoingEdges(ICompilationUnit cu) { if (cu instanceof InvisibleCompilationUnit) cu = ((InvisibleCompilationUnit)cu).getDelegate(); lock.readLock().lock(); try { Set<Edge> edges = graph.getOutgoingEdges(cu); return edges; } finally { lock.readLock().unlock(); } } Set<Edge> getIncomingEdges(ICompilationUnit cu) { if (cu instanceof InvisibleCompilationUnit) cu = ((InvisibleCompilationUnit)cu).getDelegate(); lock.readLock().lock(); try { Set<Edge> edges = graph.getIncomingEdges(cu); return edges; } finally { lock.readLock().unlock(); } } /** * Get the Set of {@link ICompilationUnit}'s depended on directly by the specified {@link ICompilationUnit}. * @param cu {@link ICompilationUnit} whose set of direct dependencies will be returned. * @return A new Set of {@link ICompilationUnit}'s that the specified {@link ICompilationUnit} directly depends on. */ public Set<ICompilationUnit> getDirectDependencies(ICompilationUnit cu) { Set<Edge> outgoingEdges = getOutgoingEdges(cu); Set<ICompilationUnit> result = new HashSet<ICompilationUnit>(outgoingEdges.size()); for (Edge e : outgoingEdges) { result.add(e.getTo()); } return result; } /** * Get the Set of {@link ICompilationUnit}'s that directly depended on the specified {@link ICompilationUnit}. * @param cu A compilation unit. * @param types Set of dependency types used to filter the returned set of {@link ICompilationUnit}'s. * @return A new Set of {@link ICompilationUnit}'s that directly depend on the * specified {@link ICompilationUnit}. */ public Set<ICompilationUnit> getDirectReverseDependencies(ICompilationUnit cu, DependencyTypeSet types) { Set<Edge> incomingEdges = getIncomingEdges(cu); Set<ICompilationUnit> result = new HashSet<ICompilationUnit>(incomingEdges.size()); for (Edge e : incomingEdges) { if (e.typeInSet(types)) result.add(e.getFrom()); } return result; } /** * Adds an {@link EmbedCompilationUnit} to the dependency graph. * This is just like adding any other compilation unit, except * for the fact that we may be in the middle of compiling a project. * Thus, when we add an embed compilation unit we grab the dependency graph * write lock. * @param unit The embed compilation unit to be added. */ public void addEmbedCompilationUnit(EmbedCompilationUnit unit) { assert !unit.isInvisible(); lock.writeLock().lock(); try { addCompilationUnit(unit); } finally { lock.writeLock().unlock(); } } /** * @param unit An {@link ICompilationUnit} to be checked * @return True if the {@link ICompilationUnit} unit exists in this {@link DependencyGraph} */ public boolean contains(ICompilationUnit unit) { return graph.getVertices().contains(unit); } /** * Finds the named dependencies between two compilation unit * @param from The depender {@link ICompilationUnit} * @param to The dependee {@link ICompilationUnit} * @return A copied non-synchronous {@link Map} from definition qname {@link String} to a {@link DependencyTypeSet}, * representing the dependencies between two {@link ICompilationUnit} */ public Map<String, DependencyTypeSet> getDependencySet(ICompilationUnit from, ICompilationUnit to) { assert !to.isInvisible() : "invisible compilation units must not be in the dependency graph"; return new HashMap<String, DependencyTypeSet>(getEdge(from, to).getNamedDependencies()); } /** * Finds the dependencies between two {@link ICompilationUnit}s * @param from The depender {@link ICompilationUnit} * @param to The dependee {@link ICompilationUnit} * @return A copy of an {@link DependencyTypeSet} that is active between the two compilation units */ public DependencyTypeSet getDependencyTypes(ICompilationUnit from, ICompilationUnit to) { assert !to.isInvisible() : "invisible compilation units must not be in the dependency graph"; return DependencyTypeSet.copyOf(getEdge(from, to).getAllDependencies()); } /** * @return An unmodifiable view of all the compilation units in the dependency graph. */ public Collection<ICompilationUnit> getCompilationUnits() { return graph.getVertices(); } }