Add library tree traversal capabilities to ModuleDef.

Change-Id: I9b33e6bf691c0f58a01abb9a9a0384867d7dbe85
diff --git a/dev/core/src/com/google/gwt/dev/cfg/ModuleDef.java b/dev/core/src/com/google/gwt/dev/cfg/ModuleDef.java
index af2feb6..264abe0 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/ModuleDef.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/ModuleDef.java
@@ -39,9 +39,12 @@
 import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event;
 import com.google.gwt.thirdparty.guava.common.base.Preconditions;
 import com.google.gwt.thirdparty.guava.common.base.Predicates;
+import com.google.gwt.thirdparty.guava.common.collect.HashMultimap;
 import com.google.gwt.thirdparty.guava.common.collect.ImmutableList;
 import com.google.gwt.thirdparty.guava.common.collect.Iterators;
 import com.google.gwt.thirdparty.guava.common.collect.Lists;
+import com.google.gwt.thirdparty.guava.common.collect.Multimap;
+import com.google.gwt.thirdparty.guava.common.collect.Queues;
 import com.google.gwt.thirdparty.guava.common.collect.Sets;
 
 import java.io.File;
@@ -59,6 +62,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Queue;
 import java.util.Set;
 
 /**
@@ -66,7 +70,6 @@
  * XML for unit tests.
  */
 public class ModuleDef {
-
   /**
    * Marks a module in a way that can be used to calculate the effective bounds of a library module
    * in a module tree.
@@ -204,6 +207,17 @@
    */
   private String nameOverride;
 
+
+  /**
+   * The canonical module names of dependency library modules per depending library module.
+   */
+  private Multimap<String, String> directDependentsModuleNamesByModuleName = HashMultimap.create();
+
+  /**
+   * Records the canonical module names for filesets.
+   */
+  private Set<String> filesetModuleNames = Sets.newHashSet();
+
   private final Properties properties = new Properties();
 
   private PathPrefixSet publicPrefixSet = new PathPrefixSet();
@@ -248,6 +262,20 @@
     defaultFilters = new DefaultFilters();
   }
 
+  /**
+   * Register a {@code dependentModuleName} as a directDependent of {@code currentModuleName}
+   */
+  public void addDirectDependent(String currentModuleName, String dependentModuleName) {
+    directDependentsModuleNamesByModuleName.put(currentModuleName, dependentModuleName);
+  }
+
+  /**
+   * Registers a module as a fileset.
+   */
+  public void addFileset(String filesetModuleName) {
+    filesetModuleNames.add(filesetModuleName);
+  }
+
   public synchronized void addEntryPointTypeName(String typeName) {
     if (!attributeIsForTargetLibrary()) {
       return;
@@ -386,6 +414,9 @@
    * modules ModuleType to updates its idea of attribute source.
    */
   public void enterModule(ModuleType moduleType, String canonicalModuleName) {
+    if (moduleType == ModuleType.FILESET) {
+      addFileset(canonicalModuleName);
+    }
     if (monolithic) {
       // When you're monolithic the module tree is all effectively one giant library.
       currentAttributeSource.push(AttributeSource.TARGET_LIBRARY);
@@ -544,6 +575,14 @@
     return compilationState;
   }
 
+  /**
+   * The module names for its direct non fileset dependents.
+   */
+  public Collection<String> getDirectDependencies(String libraryModuleName) {
+    assert !filesetModuleNames.contains(libraryModuleName);
+    return directDependentsModuleNamesByModuleName.get(libraryModuleName);
+  }
+
   public synchronized String[] getEntryPointTypeNames() {
     final int n = entryPointTypeNames.size();
     return entryPointTypeNames.toArray(new String[n]);
@@ -747,6 +786,10 @@
   synchronized void normalize(TreeLogger logger) {
     Event moduleDefNormalize =
         SpeedTracerLogger.start(CompilerEventType.MODULE_DEF, "phase", "normalize");
+
+    // Compute compact dependency graph;
+    computeLibraryDependencyGraph();
+
     // Normalize property providers.
     //
     for (Property current : getProperties()) {
@@ -835,6 +878,47 @@
     }
   }
 
+  /**
+   * Reduce the direct dependency graph to exclude filesets.
+   */
+  private void computeLibraryDependencyGraph() {
+    for (String moduleName : Lists.newArrayList(directDependentsModuleNamesByModuleName.keySet())) {
+      Set<String> libraryModules = Sets.newHashSet();
+      Set<String> filesetsProcessed = Sets.newHashSet();
+
+      // Direct dependents might be libraries or fileset, so add them to a queue of modules
+      // to process.
+      Queue<String> modulesToProcess =
+          Queues.newArrayDeque(directDependentsModuleNamesByModuleName.get(moduleName));
+      while (!modulesToProcess.isEmpty()) {
+        String dependentModuleName = modulesToProcess.poll();
+
+        boolean isLibrary = !filesetModuleNames.contains(dependentModuleName);
+        if (isLibrary) {
+          // Current dependent is not a fileset so it will be in the list of dependents unless it is
+          // the library itself.
+          if (!moduleName.equals(dependentModuleName)) {
+            libraryModules.add(dependentModuleName);
+          }
+          continue;
+        }
+        // Mark the module as processed to avoid an infinite loop.
+        filesetsProcessed.add(dependentModuleName);
+
+        // Get the dependencies of the dependent module under consideration and add all those
+        // that have not been already processed to the queue of modules to process.
+        Set<String> notAlreadyProcessed =
+            Sets.newHashSet(directDependentsModuleNamesByModuleName.get(dependentModuleName));
+        notAlreadyProcessed.removeAll(filesetsProcessed);
+        modulesToProcess.addAll(notAlreadyProcessed);
+      }
+      // Rewrite the dependents with the set just computed.
+      directDependentsModuleNamesByModuleName.replaceValues(moduleName, libraryModules);
+    }
+    // Remove all fileset entries.
+    directDependentsModuleNamesByModuleName.removeAll(filesetModuleNames);
+  }
+
   private synchronized void ensureResourcesScanned() {
     if (resourcesScanned) {
       return;
diff --git a/dev/core/src/com/google/gwt/dev/cfg/ModuleDefSchema.java b/dev/core/src/com/google/gwt/dev/cfg/ModuleDefSchema.java
index 1b6ba0d..cb36293 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/ModuleDefSchema.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/ModuleDefSchema.java
@@ -500,6 +500,7 @@
     @SuppressWarnings("unused") // called reflectively
     protected Schema __inherits_begin(String name)
         throws UnableToCompleteException {
+      moduleDef.addDirectDependent(moduleName, name);
       loader.nestedLoad(logger, name, moduleDef);
       return null;
     }
@@ -1445,7 +1446,8 @@
 
   @SuppressWarnings("unused")
   protected Schema __module_begin(NullableName renameTo, String type) {
-    moduleDef.enterModule(ModuleType.valueOf(type.toUpperCase(Locale.ENGLISH)), moduleName);
+    ModuleType moduleType = ModuleType.valueOf(type.toUpperCase(Locale.ENGLISH));
+    moduleDef.enterModule(moduleType, moduleName);
     return bodySchema;
   }
 
diff --git a/dev/core/test/com/google/gwt/dev/cfg/ModuleDefLoaderTest.java b/dev/core/test/com/google/gwt/dev/cfg/ModuleDefLoaderTest.java
index 3d1083d..ea9712b 100644
--- a/dev/core/test/com/google/gwt/dev/cfg/ModuleDefLoaderTest.java
+++ b/dev/core/test/com/google/gwt/dev/cfg/ModuleDefLoaderTest.java
@@ -22,6 +22,7 @@
 import com.google.gwt.dev.javac.testing.impl.MockResource;
 import com.google.gwt.dev.resource.Resource;
 import com.google.gwt.dev.util.UnitTestTreeLogger;
+import com.google.gwt.thirdparty.guava.common.collect.ImmutableSet;
 import com.google.gwt.thirdparty.guava.common.collect.Lists;
 import com.google.gwt.thirdparty.guava.common.collect.Sets;
 
@@ -99,6 +100,24 @@
   }
 
   /**
+   * Tests that the tree representation for a module is correct.
+   */
+  public void testLibraryTree() throws Exception {
+    TreeLogger logger = TreeLogger.NULL;
+    ModuleDef six = ModuleDefLoader.loadFromClassPath(
+        logger, compilerContext, "com.google.gwt.dev.cfg.testdata.dependents.Six");
+
+    assertTrue(six.getDirectDependencies("com.google.gwt.dev.cfg.testdata.dependents.Six")
+        .equals(ImmutableSet.of("com.google.gwt.dev.cfg.testdata.dependents.Five")));
+    assertTrue("Contains " + six.getDirectDependencies(
+        "com.google.gwt.dev.cfg.testdata.dependents.Five"),
+        six.getDirectDependencies("com.google.gwt.dev.cfg.testdata.dependents.Five")
+        .equals(ImmutableSet.of("com.google.gwt.dev.cfg.testdata.dependents.One",
+            "com.google.gwt.dev.cfg.testdata.dependents.Two",
+            "com.google.gwt.dev.cfg.testdata.dependents.Four")));
+  }
+
+  /**
    * Test of merging multiple modules in the same package space.
    * This exercises the interaction of include, exclude, and skip attributes.
    */
diff --git a/dev/core/test/com/google/gwt/dev/cfg/testdata/dependents/FileSet.gwt.xml b/dev/core/test/com/google/gwt/dev/cfg/testdata/dependents/FileSet.gwt.xml
new file mode 100644
index 0000000..a73cd3c
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/cfg/testdata/dependents/FileSet.gwt.xml
@@ -0,0 +1,3 @@
+<module type="fileset" rename-to="B">
+  <inherits name="com.google.gwt.dev.cfg.testdata.dependents.Two"/>
+</module>
diff --git a/dev/core/test/com/google/gwt/dev/cfg/testdata/dependents/Five.gwt.xml b/dev/core/test/com/google/gwt/dev/cfg/testdata/dependents/Five.gwt.xml
new file mode 100644
index 0000000..be5513f
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/cfg/testdata/dependents/Five.gwt.xml
@@ -0,0 +1,5 @@
+<module rename-to="A">
+  <inherits name="com.google.gwt.dev.cfg.testdata.dependents.FileSet"/>
+  <inherits name="com.google.gwt.dev.cfg.testdata.dependents.One"/>
+  <inherits name="com.google.gwt.dev.cfg.testdata.dependents.Four"/>
+</module>
diff --git a/dev/core/test/com/google/gwt/dev/cfg/testdata/dependents/Four.gwt.xml b/dev/core/test/com/google/gwt/dev/cfg/testdata/dependents/Four.gwt.xml
new file mode 100644
index 0000000..a852bfc
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/cfg/testdata/dependents/Four.gwt.xml
@@ -0,0 +1,3 @@
+<module rename-to="C">
+  <inherits name="com.google.gwt.dev.cfg.testdata.dependents.FileSet"/>
+</module>
diff --git a/dev/core/test/com/google/gwt/dev/cfg/testdata/dependents/One.gwt.xml b/dev/core/test/com/google/gwt/dev/cfg/testdata/dependents/One.gwt.xml
new file mode 100644
index 0000000..ea43b34
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/cfg/testdata/dependents/One.gwt.xml
@@ -0,0 +1,2 @@
+<module rename-to="D">
+</module>
diff --git a/dev/core/test/com/google/gwt/dev/cfg/testdata/dependents/Six.gwt.xml b/dev/core/test/com/google/gwt/dev/cfg/testdata/dependents/Six.gwt.xml
new file mode 100644
index 0000000..a01bc97
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/cfg/testdata/dependents/Six.gwt.xml
@@ -0,0 +1,3 @@
+<module rename-to="E">
+  <inherits name="com.google.gwt.dev.cfg.testdata.dependents.Five"/>
+</module>
diff --git a/dev/core/test/com/google/gwt/dev/cfg/testdata/dependents/Three.gwt.xml b/dev/core/test/com/google/gwt/dev/cfg/testdata/dependents/Three.gwt.xml
new file mode 100644
index 0000000..b374133
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/cfg/testdata/dependents/Three.gwt.xml
@@ -0,0 +1,3 @@
+<module rename-to="F">
+  <inherits name="com.google.gwt.dev.cfg.testdata.dependents.One"/>
+</module>
diff --git a/dev/core/test/com/google/gwt/dev/cfg/testdata/dependents/Two.gwt.xml b/dev/core/test/com/google/gwt/dev/cfg/testdata/dependents/Two.gwt.xml
new file mode 100644
index 0000000..98b235c
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/cfg/testdata/dependents/Two.gwt.xml
@@ -0,0 +1,3 @@
+<module rename-to="G">
+  <inherits name="com.google.gwt.dev.cfg.testdata.dependents.One"/>
+</module>