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.
*/