This patch addresses Issue 711.  There were several problems which would result in an invalid hosted mode cache.  Specifically:

    * Transitive dependency calculations were incomplete
    * JClassType.getSubclasses() would return JClassTypes that had been invalidated
    * gwt.typeArgs were not considered in the class dependency information 

Review by: scottb, hcc



git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@481 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/core/ext/typeinfo/JClassType.java b/dev/core/src/com/google/gwt/core/ext/typeinfo/JClassType.java
index afc7647..1e11306 100644
--- a/dev/core/src/com/google/gwt/core/ext/typeinfo/JClassType.java
+++ b/dev/core/src/com/google/gwt/core/ext/typeinfo/JClassType.java
@@ -515,6 +515,13 @@
     notifySuperTypesOf(this);
   }
 
+  /**
+   * Removes references to this instance from all of its super types.
+   */
+  void removeFromSupertypes() {
+    removeSubtype(this);
+  }
+
   private void acceptSubtype(JClassType me) {
     allSubtypes.add(me);
     notifySuperTypesOf(me);
@@ -540,4 +547,18 @@
       intf.acceptSubtype(me);
     }
   }
+
+  private void removeSubtype(JClassType me) {
+    allSubtypes.remove(me);
+
+    if (superclass != null) {
+      superclass.removeSubtype(me);
+    }
+
+    for (int i = 0, n = interfaces.size(); i < n; ++i) {
+      JClassType intf = (JClassType) interfaces.get(i);
+
+      intf.removeSubtype(me);
+    }
+  }
 }
diff --git a/dev/core/src/com/google/gwt/core/ext/typeinfo/TypeOracle.java b/dev/core/src/com/google/gwt/core/ext/typeinfo/TypeOracle.java
index ff471b6..ac87419 100644
--- a/dev/core/src/com/google/gwt/core/ext/typeinfo/TypeOracle.java
+++ b/dev/core/src/com/google/gwt/core/ext/typeinfo/TypeOracle.java
@@ -24,6 +24,7 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -132,6 +133,38 @@
     return (String[]) strings.toArray(NO_STRINGS);
   }
 
+  /**
+   * Returns true if the type has been invalidated because it is in the set of
+   * invalid types or if it is a parameterized type and either it raw type or
+   * any one of its type args has been invalidated.
+   * 
+   * @param type type to check
+   * @param invalidTypes set of type known to be invalid
+   * @return true if the type has been invalidated
+   */
+  private static boolean isInvalidatedTypeRecursive(JType type, Set invalidTypes) {
+    if (type instanceof JParameterizedType) {
+      JParameterizedType parameterizedType = (JParameterizedType) type;
+      if (isInvalidatedTypeRecursive(parameterizedType.getRawType(),
+          invalidTypes)) {
+        return true;
+      }
+
+      JType[] typeArgs = parameterizedType.getTypeArgs();
+      for (int i = 0; i < typeArgs.length; ++i) {
+        JType typeArg = typeArgs[i];
+
+        if (isInvalidatedTypeRecursive(typeArg, invalidTypes)) {
+          return true;
+        }
+      }
+
+      return false;
+    } else {
+      return invalidTypes.contains(type);
+    }
+  }
+
   private final Map arrayTypes = new IdentityHashMap();
 
   private JClassType javaLangObject;
@@ -467,19 +500,31 @@
     reloadCount++;
   }
 
-  Set invalidateTypesInCompilationUnit(CompilationUnitProvider cup) {
+  /**
+   * Note, this method is called reflectively from the
+   * {@link CacheManager#invalidateOnRefresh(TypeOracle)}
+   * 
+   * @param cup compilation unit whose types will be invalidated
+   */
+  void invalidateTypesInCompilationUnit(CompilationUnitProvider cup) {
     Set invalidTypes = new HashSet();
     JClassType[] types = (JClassType[]) typesByCup.get(cup);
     if (types == null) {
-      return invalidTypes;
+      return;
     }
+
     for (int i = 0; i < types.length; i++) {
-      JPackage jp = types[i].getPackage();
-      invalidTypes.add(types[i].getQualifiedSourceName());
-      jp.remove(types[i]);
+      JClassType classTypeToInvalidate = types[i];
+      invalidTypes.add(classTypeToInvalidate);
     }
+
     typesByCup.remove(cup);
-    return invalidTypes;
+
+    removeInvalidatedArrayTypes(invalidTypes);
+
+    removeInvalidatedParameterizedTypes(invalidTypes);
+
+    removeTypes(invalidTypes);
   }
 
   void recordTypeInCompilationUnit(CompilationUnitProvider cup, JClassType type) {
@@ -772,4 +817,50 @@
     }
     return parameterizedType;
   }
+
+  /**
+   * Remove any array type whose leaf type has been invalidated.
+   * 
+   * @param invalidTypes set of types that have been invalidated.
+   */
+  private void removeInvalidatedArrayTypes(Set invalidTypes) {
+    arrayTypes.keySet().removeAll(invalidTypes);
+  }
+
+  /**
+   * Remove any parameterized type that was invalidated because either its raw
+   * type or any one of its type arguements was invalidated.
+   * 
+   * @param invalidTypes set of types known to have been invalidated
+   */
+  private void removeInvalidatedParameterizedTypes(Set invalidTypes) {
+    Iterator iter = parameterizedTypes.values().iterator();
+
+    while (iter.hasNext()) {
+      JType type = (JType) iter.next();
+
+      if (isInvalidatedTypeRecursive(type, invalidTypes)) {
+        iter.remove();
+      }
+    }
+  }
+
+  /**
+   * Removes the specified types from the type oracle.
+   * 
+   * @param invalidTypes set of types to remove
+   */
+  private void removeTypes(Set invalidTypes) {
+    Iterator iter = invalidTypes.iterator();
+
+    while (iter.hasNext()) {
+      JClassType classType = (JClassType) iter.next();
+      JPackage pkg = classType.getPackage();
+      if (pkg != null) {
+        pkg.remove(classType);
+      }
+
+      classType.removeFromSupertypes();
+    }
+  }
 }
diff --git a/dev/core/src/com/google/gwt/dev/jdt/CacheManager.java b/dev/core/src/com/google/gwt/dev/jdt/CacheManager.java
index 8af2f0f..ae39226 100644
--- a/dev/core/src/com/google/gwt/dev/jdt/CacheManager.java
+++ b/dev/core/src/com/google/gwt/dev/jdt/CacheManager.java
@@ -18,6 +18,7 @@
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.typeinfo.CompilationUnitProvider;
+import com.google.gwt.core.ext.typeinfo.HasMetaData;
 import com.google.gwt.core.ext.typeinfo.JClassType;
 import com.google.gwt.core.ext.typeinfo.JType;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
@@ -27,7 +28,13 @@
 import com.google.gwt.dev.util.Util;
 
 import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.Javadoc;
+import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration;
 import org.eclipse.jdt.internal.compiler.env.ICompilationUnit;
+import org.eclipse.jdt.internal.compiler.impl.ReferenceContext;
+import org.eclipse.jdt.internal.compiler.lookup.ClassScope;
+import org.eclipse.jdt.internal.compiler.lookup.MethodScope;
 import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding;
 
 import java.io.File;
@@ -51,6 +58,7 @@
  * reflected correctly on reload.
  */
 public class CacheManager {
+
   /**
    * Maps SourceTypeBindings to their associated types.
    */
@@ -89,6 +97,7 @@
       if (!map.containsKey(dependeeFilename)) {
         map.put(dependeeFilename, new HashSet());
       }
+
       get(dependeeFilename).add(dependerFilename);
     }
 
@@ -113,7 +122,7 @@
       queue.add(filename);
       while (true) {
         finished.add(current);
-        Set children = get(filename);
+        Set children = get(current);
         if (children != null) {
           for (Iterator iter = children.iterator(); iter.hasNext();) {
             String child = (String) iter.next();
@@ -133,6 +142,149 @@
   }
 
   /**
+   * Visit all of the CUDs and extract dependencies. This visitor handles
+   * explicit TypeRefs via the onTypeRef method AND it also deals with the
+   * gwt.typeArgs annotation.
+   * 
+   * <ol>
+   * <li>Extract the list of type names from the gwt.typeArgs annotation</li>
+   * <li>For each type name, locate the CUD that defines it</li>
+   * <li>Add the referenced CUD as a dependency</li>
+   * </ol>
+   */
+  private final class DependencyVisitor extends TypeRefVisitor {
+    private final Dependencies dependencies;
+
+    private DependencyVisitor(Dependencies dependencies) {
+      this.dependencies = dependencies;
+    }
+
+    public void endVisit(FieldDeclaration fieldDeclaration,
+        final MethodScope scope) {
+      extractDependenciesFromTypeArgs(fieldDeclaration.javadoc,
+          scope.referenceContext(), true);
+    }
+
+    public void endVisit(MethodDeclaration methodDeclaration, ClassScope scope) {
+      extractDependenciesFromTypeArgs(methodDeclaration.javadoc,
+          scope.referenceContext(), false);
+    }
+
+    protected void onTypeRef(SourceTypeBinding referencedType,
+        CompilationUnitDeclaration unitOfReferrer) {
+      // If the referenced type belongs to a compilation unit that
+      // was changed, then the unit in which it
+      // is referenced must also be treated as changed.
+      //
+      String dependeeFilename = String.valueOf(referencedType.getFileName());
+      String dependerFilename = String.valueOf(unitOfReferrer.getFileName());
+
+      dependencies.add(dependerFilename, dependeeFilename);
+    }
+
+    private String combine(String[] strings, int startIndex) {
+      StringBuffer sb = new StringBuffer();
+      for (int i = startIndex; i < strings.length; i++) {
+        String s = strings[i];
+        sb.append(s);
+      }
+      return sb.toString();
+    }
+
+    /**
+     * Extracts additional dependencies based on the gwt.typeArgs annotation.
+     * This is not detected by JDT so we need to do it here. We do not perform
+     * as strict a parse as the TypeOracle would do.
+     * 
+     * @param javadoc javadoc text
+     * @param scope scope that contains the definition
+     * @param isField true if the javadoc is associated with a field
+     */
+    private void extractDependenciesFromTypeArgs(Javadoc javadoc,
+        final ReferenceContext scope, final boolean isField) {
+      if (javadoc == null) {
+        return;
+      }
+      final char[] source = scope.compilationResult().compilationUnit.getContents();
+
+      TypeOracleBuilder.parseMetaDataTags(source, new HasMetaData() {
+        public void addMetaData(String tagName, String[] values) {
+          assert (values != null);
+
+          if (!TypeOracle.TAG_TYPEARGS.equals(tagName)) {
+            // don't care about non gwt.typeArgs
+            return;
+          }
+
+          if (values.length == 0) {
+            return;
+          }
+
+          Set typeNames = new HashSet();
+
+          /*
+           * if the first element starts with a "<" then we assume that no
+           * parameter name was specified
+           */
+          int startIndex = 1;
+          if (values[0].trim().startsWith("<")) {
+            startIndex = 0;
+          }
+
+          extractTypeNamesFromTypeArg(combine(values, startIndex), typeNames);
+
+          Iterator it = typeNames.iterator();
+          while (it.hasNext()) {
+            String typeName = (String) it.next();
+
+            try {
+              ICompilationUnit compilationUnit = astCompiler.getCompilationUnitForType(
+                  TreeLogger.NULL, typeName);
+
+              String dependeeFilename = String.valueOf(compilationUnit.getFileName());
+              String dependerFilename = String.valueOf(scope.compilationResult().compilationUnit.getFileName());
+
+              dependencies.add(dependerFilename, dependeeFilename);
+
+            } catch (UnableToCompleteException e) {
+              // Purposely ignored
+            }
+          }
+        }
+
+        public String[][] getMetaData(String tagName) {
+          return null;
+        }
+
+        public String[] getMetaDataTags() {
+          return null;
+        }
+      }, javadoc);
+    }
+
+    /**
+     * Extracts the type names referenced from a gwt.typeArgs annotation and
+     * adds them to the set of type names.
+     * 
+     * @param typeArg a string containing the type args as the user entered them
+     * @param typeNames the set of type names referenced in the typeArgs string
+     */
+    private void extractTypeNamesFromTypeArg(String typeArg, Set typeNames) {
+      // Remove all whitespace
+      typeArg.replaceAll("\\\\s", "");
+      
+      // Remove anything that is not a raw type name
+      String[] typeArgs = typeArg.split("[\\[\\]<>,]");
+
+      for (int i = 0; i < typeArgs.length; ++i) {
+        if (typeArgs[i].length() > 0) {
+          typeNames.add(typeArgs[i]);
+        }
+      }
+    }
+  }
+
+  /**
    * Caches information using a directory, with an in memory cache to prevent
    * unneeded disk access.
    */
@@ -558,26 +710,15 @@
   void addDependentsToChangedFiles() {
     final Dependencies dependencies = new Dependencies();
 
-    // induction
-    TypeRefVisitor trv = new TypeRefVisitor() {
-      protected void onTypeRef(SourceTypeBinding referencedType,
-          CompilationUnitDeclaration unitOfReferrer) {
-        // If the referenced type belongs to a compilation unit that
-        // was changed, then the unit in which it
-        // is referenced must also be treated as changed.
-        //
-        String referencedFn = String.valueOf(referencedType.getFileName());
-        CompilationUnitDeclaration referencedCup = (CompilationUnitDeclaration) cudsByFileName.get(referencedFn);
-        String fileName = String.valueOf(unitOfReferrer.getFileName());
-        dependencies.add(fileName, referencedFn);
-      };
-    };
+    DependencyVisitor trv = new DependencyVisitor(dependencies);
+
     // Find references to type in units that aren't any longer valid.
     //
     for (Iterator iter = cudsByFileName.values().iterator(); iter.hasNext();) {
       CompilationUnitDeclaration cud = (CompilationUnitDeclaration) iter.next();
       cud.traverse(trv, cud.scope);
     }
+
     Set toTraverse = new HashSet(changedFiles);
     for (Iterator iter = toTraverse.iterator(); iter.hasNext();) {
       String fileName = (String) iter.next();
@@ -679,6 +820,7 @@
     if (!isTypeOracleBuilderFirstTime()) {
       addVolatileFiles(changedFiles);
       addDependentsToChangedFiles();
+
       for (Iterator iter = changedFiles.iterator(); iter.hasNext();) {
         String location = (String) iter.next();
         CompilationUnitProvider cup = (CompilationUnitProvider) getCupsByLocation().get(
diff --git a/dev/core/src/com/google/gwt/dev/jdt/TypeOracleBuilder.java b/dev/core/src/com/google/gwt/dev/jdt/TypeOracleBuilder.java
index 7d3c0da..0ed9896 100644
--- a/dev/core/src/com/google/gwt/dev/jdt/TypeOracleBuilder.java
+++ b/dev/core/src/com/google/gwt/dev/jdt/TypeOracleBuilder.java
@@ -94,6 +94,93 @@
 
   private static final Pattern PATTERN_WHITESPACE = Pattern.compile("\\s");
 
+  static boolean parseMetaDataTags(char[] unitSource, HasMetaData hasMetaData,
+      Javadoc javadoc) {
+
+    int start = javadoc.sourceStart;
+    int end = javadoc.sourceEnd;
+    char[] comment = CharOperation.subarray(unitSource, start, end + 1);
+    BufferedReader reader = new BufferedReader(new CharArrayReader(comment));
+    String activeTag = null;
+    final List tagValues = new ArrayList();
+    try {
+      String line = reader.readLine();
+      boolean firstLine = true;
+      while (line != null) {
+        if (firstLine) {
+          firstLine = false;
+          int commentStart = line.indexOf("/**");
+          if (commentStart == -1) {
+            // Malformed.
+            return false;
+          }
+          line = line.substring(commentStart + 3);
+        }
+
+        String[] tokens = PATTERN_WHITESPACE.split(line);
+        boolean canIgnoreStar = true;
+        for (int i = 0; i < tokens.length; i++) {
+          String token = tokens[i];
+
+          // Check for the end.
+          //
+          if (token.endsWith("*/")) {
+            token = token.substring(0, token.length() - 2);
+          }
+
+          // Check for an ignored leading star.
+          //
+          if (canIgnoreStar && token.startsWith("*")) {
+            token = token.substring(1);
+            canIgnoreStar = false;
+          }
+
+          // Decide what to do with whatever is left.
+          //
+          if (token.length() > 0) {
+            canIgnoreStar = false;
+            if (token.startsWith("@")) {
+              // A new tag has been introduced.
+              // Subsequent tokens will attach to it.
+              // Make sure we finish the previously active tag before moving on.
+              //
+              if (activeTag != null) {
+                finishTag(hasMetaData, activeTag, tagValues);
+              }
+              activeTag = token.substring(1);
+            } else if (activeTag != null) {
+              // Attach this item to the active tag.
+              //
+              tagValues.add(token);
+            } else {
+              // Just ignore it.
+              //
+            }
+          }
+        }
+
+        line = reader.readLine();
+      }
+    } catch (IOException e) {
+      return false;
+    }
+
+    // To catch the last batch of values, if any.
+    //
+    finishTag(hasMetaData, activeTag, tagValues);
+    return true;
+  }
+
+  private static void finishTag(HasMetaData hasMetaData, String tagName,
+      List tagValues) {
+    // Add the values even if the list is empty, because the presence of the
+    // tag itself might be important.
+    // 
+    String[] values = (String[]) tagValues.toArray(Empty.STRINGS);
+    hasMetaData.addMetaData(tagName, values);
+    tagValues.clear();
+  }
+
   private static void removeInfectedUnits(final TreeLogger logger,
       final Map cudsByFileName) {
 
@@ -368,15 +455,6 @@
     return oracle;
   }
 
-  private void finishTag(HasMetaData hasMetaData, String tagName, List tagValues) {
-    // Add the values even if the list is empty, because the presence of the
-    // tag itself might be important.
-    //
-    String[] values = (String[]) tagValues.toArray(Empty.STRINGS);
-    hasMetaData.addMetaData(tagName, values);
-    tagValues.clear();
-  }
-
   private CompilationUnitProvider getCup(TypeDeclaration typeDecl) {
     ICompilationUnit icu = typeDecl.compilationResult.compilationUnit;
     ICompilationUnitAdapter icua = (ICompilationUnitAdapter) icu;
@@ -404,83 +482,6 @@
     }
   }
 
-  private boolean parseMetaDataTags(char[] unitSource, HasMetaData hasMetaData,
-      Javadoc javadoc) {
-
-    int start = javadoc.sourceStart;
-    int end = javadoc.sourceEnd;
-    char[] comment = CharOperation.subarray(unitSource, start, end + 1);
-    BufferedReader reader = new BufferedReader(new CharArrayReader(comment));
-    String activeTag = null;
-    final List tagValues = new ArrayList();
-    try {
-      String line = reader.readLine();
-      boolean firstLine = true;
-      while (line != null) {
-        if (firstLine) {
-          firstLine = false;
-          int commentStart = line.indexOf("/**");
-          if (commentStart == -1) {
-            // Malformed.
-            return false;
-          }
-          line = line.substring(commentStart + 3);
-        }
-
-        String[] tokens = PATTERN_WHITESPACE.split(line);
-        boolean canIgnoreStar = true;
-        for (int i = 0; i < tokens.length; i++) {
-          String token = tokens[i];
-
-          // Check for the end.
-          //
-          if (token.endsWith("*/")) {
-            token = token.substring(0, token.length() - 2);
-          }
-
-          // Check for an ignored leading star.
-          //
-          if (canIgnoreStar && token.startsWith("*")) {
-            token = token.substring(1);
-            canIgnoreStar = false;
-          }
-
-          // Decide what to do with whatever is left.
-          //
-          if (token.length() > 0) {
-            canIgnoreStar = false;
-            if (token.startsWith("@")) {
-              // A new tag has been introduced.
-              // Subsequent tokens will attach to it.
-              // Make sure we finish the previously active tag before moving on.
-              //
-              if (activeTag != null) {
-                finishTag(hasMetaData, activeTag, tagValues);
-              }
-              activeTag = token.substring(1);
-            } else if (activeTag != null) {
-              // Attach this item to the active tag.
-              //
-              tagValues.add(token);
-            } else {
-              // Just ignore it.
-              //
-            }
-          }
-        }
-
-        line = reader.readLine();
-      }
-    } catch (IOException e) {
-      return false;
-    }
-
-    // To catch the last batch of values, if any.
-    //
-    finishTag(hasMetaData, activeTag, tagValues);
-    return true;
-  }
-
   /**
    * Maps a TypeDeclaration into a JClassType.
    */