This patch updates the following:
i) Ensures that generated units, if they are the same, are not compiled again.
Does this in a resource-efficient way: without holding on to the jdt structures
(significant memory overhead) and without signficant CPU overhead. Adds test
cases for confirming the refresh behavior.
ii) cleans up the implementation of how TypeOracle tracks ParameterizedTypes,
ArrayTypes, and wild-card types by using appropriate variants of apache's
AbstractReferenceMap.Adds manual tests for verifying what different variants
do. Fixes parameterizedTypes map to use explicit objects keys that enforce
identity and don't have the problems associated with generating unique string
keys.
iii) Cleans up the invalidator code that invalidates a unit if it refers a
'invalid' unit.
iv) Adds the code for handling binary references. Adds test cases for checking
the handling of binary references.
Patch by: amitmanjhi, scottb (pair prog)
Review by: scottb, amitmanjhi
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@5048 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/core/ext/typeinfo/JDelegatingClassType.java b/dev/core/src/com/google/gwt/core/ext/typeinfo/JDelegatingClassType.java
index 3f3e0d4..cbb866a 100644
--- a/dev/core/src/com/google/gwt/core/ext/typeinfo/JDelegatingClassType.java
+++ b/dev/core/src/com/google/gwt/core/ext/typeinfo/JDelegatingClassType.java
@@ -201,11 +201,6 @@
}
@Override
- public int hashCode() {
- return baseType.hashCode();
- }
-
- @Override
public boolean isAbstract() {
return baseType.isAbstract();
}
diff --git a/dev/core/src/com/google/gwt/core/ext/typeinfo/JRealClassType.java b/dev/core/src/com/google/gwt/core/ext/typeinfo/JRealClassType.java
index 26c4ab0..d9fbb76 100644
--- a/dev/core/src/com/google/gwt/core/ext/typeinfo/JRealClassType.java
+++ b/dev/core/src/com/google/gwt/core/ext/typeinfo/JRealClassType.java
@@ -393,6 +393,13 @@
return null;
}
+ /**
+ * TODO: solve this better.
+ */
+ public void resurrect() {
+ oracle.resurrect(this);
+ }
+
@Override
public void setSuperclass(JClassType type) {
assert (type != null);
diff --git a/dev/core/src/com/google/gwt/core/ext/typeinfo/JType.java b/dev/core/src/com/google/gwt/core/ext/typeinfo/JType.java
index f5d3cea..15bc0bd 100644
--- a/dev/core/src/com/google/gwt/core/ext/typeinfo/JType.java
+++ b/dev/core/src/com/google/gwt/core/ext/typeinfo/JType.java
@@ -20,6 +20,14 @@
*/
public abstract class JType {
+ /**
+ * All types use identity for comparison.
+ */
+ @Override
+ public final boolean equals(Object obj) {
+ return super.equals(obj);
+ }
+
public abstract JType getErasedType();
public abstract String getJNISignature();
@@ -37,6 +45,14 @@
public abstract String getSimpleSourceName();
/**
+ * All types use identity for comparison.
+ */
+ @Override
+ public final int hashCode() {
+ return super.hashCode();
+ }
+
+ /**
* Returns this instance if it is a annotation or <code>null</code> if it is
* not.
*
@@ -70,7 +86,7 @@
// TODO: Rename this to isGeneric
public abstract JGenericType isGenericType();
-
+
public abstract JClassType isInterface();
public abstract JParameterizedType isParameterized();
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 0de6e96..a18b85e 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
@@ -17,9 +17,15 @@
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JWildcardType.BoundType;
import com.google.gwt.dev.generator.GenUtil;
+import com.google.gwt.dev.jjs.InternalCompilerException;
import com.google.gwt.dev.shell.JsValueGlue;
+import org.apache.commons.collections.map.AbstractReferenceMap;
+import org.apache.commons.collections.map.ReferenceIdentityMap;
+import org.apache.commons.collections.map.ReferenceMap;
+
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
@@ -29,7 +35,6 @@
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;
@@ -57,6 +62,62 @@
* </p>
*/
public class TypeOracle {
+
+ private static class ParameterizedTypeKey {
+ private final JClassType enclosingType;
+ private final JGenericType genericType;
+ private final JClassType[] typeArgs;
+
+ public ParameterizedTypeKey(JGenericType genericType,
+ JClassType enclosingType, JClassType[] typeArgs) {
+ this.genericType = genericType;
+ this.enclosingType = enclosingType;
+ this.typeArgs = typeArgs;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof ParameterizedTypeKey)) {
+ return false;
+ }
+ ParameterizedTypeKey other = (ParameterizedTypeKey) obj;
+ return genericType == other.genericType
+ && enclosingType == other.enclosingType
+ && Arrays.equals(typeArgs, other.typeArgs);
+ }
+
+ @Override
+ public int hashCode() {
+ return 29 * genericType.hashCode() + 17
+ * ((enclosingType == null) ? 0 : enclosingType.hashCode())
+ + Arrays.hashCode(typeArgs);
+ }
+ }
+
+ private static class WildCardKey {
+ private final BoundType boundType;
+ private final JClassType typeBound;
+
+ public WildCardKey(BoundType boundType, JClassType typeBound) {
+ this.boundType = boundType;
+ this.typeBound = typeBound;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof WildCardKey)) {
+ return false;
+ }
+ WildCardKey other = (WildCardKey) obj;
+ return boundType == other.boundType && typeBound == other.typeBound;
+ }
+
+ @Override
+ public int hashCode() {
+ return 29 * typeBound.hashCode() + boundType.hashCode();
+ }
+ }
+
/**
* A reserved metadata tag to indicates that a field type, method return type
* or method parameter type is intended to be parameterized. Note that
@@ -138,57 +199,27 @@
}
/**
- * 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<JRealClassType> invalidTypes) {
- if (type instanceof JParameterizedType) {
- JParameterizedType parameterizedType = (JParameterizedType) type;
- if (isInvalidatedTypeRecursive(parameterizedType.getBaseType(),
- 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);
- }
- }
-
- /**
* A map of fully-qualify source names (ie, use "." rather than "$" for nested
* classes) to JRealClassTypes.
*/
private final Map<String, JRealClassType> allTypes = new HashMap<String, JRealClassType>();
- private final Map<JType, JArrayType> arrayTypes = new IdentityHashMap<JType, JArrayType>();
-
- /**
- * A set of invalidated types queued up to be removed on the next
- * {@link #reset()}.
- */
- private final Set<JRealClassType> invalidatedTypes = new HashSet<JRealClassType>();
+ @SuppressWarnings("unchecked")
+ private final Map<JType, JArrayType> arrayTypes = new ReferenceIdentityMap(
+ AbstractReferenceMap.WEAK, AbstractReferenceMap.WEAK, true);
private JClassType javaLangObject;
+ /**
+ * Maps SingleJsoImpl interfaces to the implementing JSO subtype.
+ */
+ private final Map<JClassType, JClassType> jsoSingleImpls = new IdentityHashMap<JClassType, JClassType>();
+
private final Map<String, JPackage> packages = new HashMap<String, JPackage>();
- private final Map<String, List<JParameterizedType>> parameterizedTypes = new HashMap<String, List<JParameterizedType>>();
+ @SuppressWarnings("unchecked")
+ private final Map<ParameterizedTypeKey, JParameterizedType> parameterizedTypes = new ReferenceMap(
+ AbstractReferenceMap.HARD, AbstractReferenceMap.WEAK, true);
/**
* A list of recently-added types that will be fully initialized on the next
@@ -198,12 +229,11 @@
private int reloadCount = 0;
- private final Map<String, List<JWildcardType>> wildcardTypes = new HashMap<String, List<JWildcardType>>();
+ private JWildcardType unboundWildCardType;
- /**
- * Maps SingleJsoImpl interfaces to the implementing JSO subtype.
- */
- private final Map<JClassType, JClassType> jsoSingleImpls = new IdentityHashMap<JClassType, JClassType>();
+ @SuppressWarnings("unchecked")
+ private final Map<WildCardKey, JWildcardType> wildcardTypes = new ReferenceMap(
+ AbstractReferenceMap.HARD, AbstractReferenceMap.WEAK, true);
public TypeOracle() {
// Always create the default package.
@@ -260,7 +290,7 @@
public void finish(TreeLogger logger) {
JClassType[] newTypes = recentTypes.toArray(NO_JCLASSES);
computeHierarchyRelationships(newTypes);
- computeSingleJsoImplData(logger, newTypes);
+ computeSingleJsoImplData(newTypes);
consumeTypeArgMetaData(logger, newTypes);
recentTypes.clear();
}
@@ -356,8 +386,11 @@
*/
public JParameterizedType getParameterizedType(JGenericType genericType,
JClassType enclosingType, JClassType[] typeArgs) {
- if (genericType == null) {
- throw new NullPointerException("genericType");
+ ParameterizedTypeKey key = new ParameterizedTypeKey(genericType,
+ enclosingType, typeArgs);
+ JParameterizedType result = parameterizedTypes.get(key);
+ if (result != null) {
+ return result;
}
if (genericType.isMemberType() && !genericType.isStatic()) {
@@ -392,29 +425,9 @@
// TODO: validate that the type arguments satisfy the generic type parameter
// bounds if any were specified
- // Uses the generated string signature to intern parameterized types.
- //
- JParameterizedType parameterized = new JParameterizedType(genericType,
- enclosingType, typeArgs);
-
- // TODO: parameterized qualified source name does not account for the type
- // args of the enclosing type
- String sig = parameterized.getParameterizedQualifiedSourceName();
- List<JParameterizedType> candidates = parameterizedTypes.get(sig);
- if (candidates == null) {
- candidates = new ArrayList<JParameterizedType>();
- parameterizedTypes.put(sig, candidates);
- } else {
- for (JParameterizedType candidate : candidates) {
- if (candidate.hasTypeArgs(typeArgs)) {
- return candidate;
- }
- }
- }
-
- candidates.add(parameterized);
-
- return parameterized;
+ result = new JParameterizedType(genericType, enclosingType, typeArgs);
+ parameterizedTypes.put(key, result);
+ return result;
}
/**
@@ -502,23 +515,25 @@
public JWildcardType getWildcardType(JWildcardType.BoundType boundType,
JClassType typeBound) {
- JWildcardType wildcardType = new JWildcardType(boundType, typeBound);
- String sig = wildcardType.getQualifiedSourceName();
- List<JWildcardType> candidates = wildcardTypes.get(sig);
- if (candidates == null) {
- candidates = new ArrayList<JWildcardType>();
- wildcardTypes.put(sig, candidates);
- } else {
- for (JWildcardType candidate : candidates) {
- if (candidate.boundsMatch(wildcardType)) {
- return candidate;
- }
+ // Special fast case for <? extends Object>
+ // TODO(amitmanjhi): make sure this actually does speed things up!
+ if (typeBound == getJavaLangObject() && boundType == BoundType.UNBOUND) {
+ if (unboundWildCardType == null) {
+ unboundWildCardType = new JWildcardType(boundType, typeBound);
}
+ return unboundWildCardType;
+ }
+ // End special case / todo.
+
+ WildCardKey key = new WildCardKey(boundType, typeBound);
+ JWildcardType result = wildcardTypes.get(key);
+ if (result != null) {
+ return result;
}
- candidates.add(wildcardType);
-
- return wildcardType;
+ result = new JWildcardType(boundType, typeBound);
+ wildcardTypes.put(key, result);
+ return result;
}
/**
@@ -559,11 +574,7 @@
*/
public void reset() {
recentTypes.clear();
- if (!invalidatedTypes.isEmpty()) {
- invalidateTypes(invalidatedTypes);
- invalidatedTypes.clear();
- ++reloadCount;
- }
+ ++reloadCount;
}
/**
@@ -632,7 +643,11 @@
}
void invalidate(JRealClassType realClassType) {
- invalidatedTypes.add(realClassType);
+ removeType(realClassType);
+ }
+
+ void resurrect(JRealClassType realClassType) {
+ resurrectType(realClassType);
}
private void computeHierarchyRelationships(JClassType[] types) {
@@ -647,7 +662,7 @@
/**
* Updates the list of jsoSingleImpl types from recently-added types.
*/
- private void computeSingleJsoImplData(TreeLogger logger, JClassType[] newTypes) {
+ private void computeSingleJsoImplData(JClassType... newTypes) {
JClassType jsoType = findType(JsValueGlue.JSO_CLASS);
if (jsoType == null) {
return;
@@ -687,8 +702,7 @@
} else if (type.isAssignableTo(previousType)) {
// Do nothing
} else {
- // This should have been taken care of by JSORetrictionsChecker
- logger.log(TreeLogger.ERROR,
+ throw new InternalCompilerException(
"Already seen an implementing JSO subtype ("
+ previousType.getName() + ") for interface ("
+ intf.getName() + ") while examining newly-added type ("
@@ -874,12 +888,6 @@
return resultingType;
}
- private void invalidateTypes(Set<JRealClassType> invalidTypes) {
- removeInvalidatedArrayTypes(invalidTypes);
- removeInvalidatedParameterizedTypes(invalidTypes);
- removeTypes(invalidTypes);
- }
-
private JType parseImpl(String type) throws NotFoundException,
ParseException, BadTypeArgsException {
if (type.endsWith("[]")) {
@@ -1023,56 +1031,47 @@
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<JRealClassType> invalidTypes) {
- arrayTypes.keySet().removeAll(invalidTypes);
- }
+ private void removeSingleJsoImplData(JClassType... types) {
+ JClassType jsoType = findType(JsValueGlue.JSO_CLASS);
+ if (jsoType == null) {
+ return;
+ }
- /**
- * Remove any parameterized type that was invalidated because either its raw
- * type or any one of its type arguments was invalidated.
- *
- * @param invalidTypes set of types known to have been invalidated
- */
- private void removeInvalidatedParameterizedTypes(
- Set<JRealClassType> invalidTypes) {
- Iterator<List<JParameterizedType>> listIterator = parameterizedTypes.values().iterator();
-
- while (listIterator.hasNext()) {
- List<JParameterizedType> list = listIterator.next();
- Iterator<JParameterizedType> typeIterator = list.iterator();
- while (typeIterator.hasNext()) {
- JType type = typeIterator.next();
- if (isInvalidatedTypeRecursive(type, invalidTypes)) {
- typeIterator.remove();
+ for (JClassType type : types) {
+ if (!jsoType.isAssignableFrom(type)) {
+ continue;
+ }
+ for (JClassType intf : JClassType.getFlattenedSuperTypeHierarchy(type)) {
+ if (jsoSingleImpls.get(intf) == type) {
+ jsoSingleImpls.remove(intf);
}
}
}
}
/**
- * Removes the specified types from the type oracle.
- *
- * @param invalidTypes set of types to remove
+ * Removes the specified type from the type oracle.
*/
- private void removeTypes(Set<JRealClassType> invalidTypes) {
- for (Iterator<JRealClassType> iter = invalidTypes.iterator(); iter.hasNext();) {
- JRealClassType classType = iter.next();
- String fqcn = classType.getQualifiedSourceName();
- allTypes.remove(fqcn);
- jsoSingleImpls.remove(classType);
- JPackage pkg = classType.getPackage();
- if (pkg != null) {
- pkg.remove(classType);
- }
-
- classType.removeFromSupertypes();
-
- iter.remove();
+ private void removeType(JRealClassType invalidType) {
+ allTypes.remove(invalidType.getQualifiedSourceName());
+ JPackage pkg = invalidType.getPackage();
+ if (pkg != null) {
+ pkg.remove(invalidType);
}
+ invalidType.removeFromSupertypes();
+ removeSingleJsoImplData(invalidType);
+ }
+
+ /**
+ * Restore the specific type from the type oracle.
+ */
+ private void resurrectType(JRealClassType type) {
+ allTypes.put(type.getQualifiedSourceName(), type);
+ JPackage pkg = type.getPackage();
+ if (pkg != null) {
+ pkg.addType(type);
+ }
+ type.notifySuperTypes();
+ computeSingleJsoImplData(type);
}
}
diff --git a/dev/core/src/com/google/gwt/dev/javac/CompilationState.java b/dev/core/src/com/google/gwt/dev/javac/CompilationState.java
index d9c710a..9cfa89d 100644
--- a/dev/core/src/com/google/gwt/dev/javac/CompilationState.java
+++ b/dev/core/src/com/google/gwt/dev/javac/CompilationState.java
@@ -22,6 +22,7 @@
import com.google.gwt.dev.js.ast.JsProgram;
import com.google.gwt.dev.util.PerfLogger;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -36,17 +37,33 @@
*/
public class CompilationState {
+ private static Set<CompilationUnit> concatSet(Collection<CompilationUnit> a,
+ Collection<CompilationUnit> b) {
+ Set<CompilationUnit> result = new HashSet<CompilationUnit>(a.size()
+ + b.size());
+ result.addAll(a);
+ result.addAll(b);
+ return result;
+ }
+
private static void markSurvivorsChecked(Set<CompilationUnit> units) {
for (CompilationUnit unit : units) {
- if (unit.getState() == State.COMPILED) {
- unit.setState(State.CHECKED);
+ if (unit.getState() == State.COMPILED
+ || unit.getState() == State.GRAVEYARD) {
+ unit.setChecked();
}
}
}
protected final Map<String, CompilationUnit> unitMap = new HashMap<String, CompilationUnit>();
+ /**
+ * Generated units become graveyardUnits when refresh is hit. Package
+ * protected for testing.
+ */
+ Map<String, CompilationUnit> graveyardUnits;
private Set<JavaSourceFile> cachedSourceFiles = Collections.emptySet();
+
/**
* Classes mapped by binary name.
*/
@@ -67,6 +84,8 @@
*/
private Set<CompilationUnit> exposedUnits = Collections.emptySet();
+ private CompilationUnitInvalidator.InvalidatorState invalidatorState = new CompilationUnitInvalidator.InvalidatorState();
+
/**
* Recreated on refresh, allows incremental compiles.
*/
@@ -82,8 +101,6 @@
*/
private final JavaSourceOracle sourceOracle;
- private CompilationUnitInvalidator.InvalidatorState invalidatorState = new CompilationUnitInvalidator.InvalidatorState();
-
/**
* Construct a new {@link CompilationState}.
*
@@ -95,20 +112,18 @@
refresh(logger);
}
- @SuppressWarnings("unchecked")
+ /**
+ * The method processes generatedCompilationUnits and adds them to the
+ * TypeOracle, using graveyardUnits wherever possible.
+ */
public void addGeneratedCompilationUnits(TreeLogger logger,
Set<? extends CompilationUnit> generatedCups) {
logger = logger.branch(TreeLogger.DEBUG, "Adding '" + generatedCups.size()
+ "' new generated units");
- for (CompilationUnit unit : generatedCups) {
- String typeName = unit.getTypeName();
- assert (!unitMap.containsKey(typeName));
- unitMap.put(typeName, unit);
- }
- updateExposedUnits();
- compile(logger, (Set<CompilationUnit>) generatedCups);
- mediator.addNewUnits(logger, (Set<CompilationUnit>) generatedCups);
- markSurvivorsChecked((Set<CompilationUnit>) generatedCups);
+ Map<String, CompilationUnit> usefulGraveyardUnits = getUsefulGraveyardUnits(generatedCups);
+ logger.log(TreeLogger.DEBUG, "Using " + usefulGraveyardUnits.values()
+ + " units from graveyard");
+ addGeneratedCompilationUnits(logger, generatedCups, usefulGraveyardUnits);
}
/**
@@ -153,18 +168,29 @@
/**
* Synchronize against the source oracle to check for added/removed/updated
* units. Updated units are invalidated, and any units depending on changed
- * units are also invalidated. All generated units are removed.
- *
- * TODO: something more optimal with generated files?
+ * units are also invalidated. All generated units are moved to GRAVEYARD.
*/
public void refresh(TreeLogger logger) {
logger = logger.branch(TreeLogger.DEBUG, "Refreshing module from source");
+ /*
+ * Clear out existing graveyard units that were never regenerated during the
+ * current refresh cycle.
+ *
+ * TODO: we could possibly <i>not</i> clear this out entirely, but it would
+ * be slightly more complicated.
+ */
+ graveyardUnits = new HashMap<String, CompilationUnit>();
// Always remove all generated compilation units.
for (Iterator<CompilationUnit> it = unitMap.values().iterator(); it.hasNext();) {
CompilationUnit unit = it.next();
if (unit.isGenerated()) {
- unit.setState(State.FRESH);
+ if (unit.getState() == State.CHECKED) {
+ unit.setGraveyard();
+ graveyardUnits.put(unit.getTypeName(), unit);
+ } else {
+ unit.setFresh();
+ }
it.remove();
}
}
@@ -173,8 +199,11 @@
updateExposedUnits();
// Don't log about invalidated units via refresh.
+ Set<CompilationUnit> allUnitsPlusGraveyard = concatSet(
+ getCompilationUnits(), graveyardUnits.values());
CompilationUnitInvalidator.invalidateUnitsWithInvalidRefs(TreeLogger.NULL,
- getCompilationUnits());
+ allUnitsPlusGraveyard);
+ removeInvalidatedGraveyardUnits(graveyardUnits);
/*
* Only retain state for units marked as CHECKED; because CHECKED units
@@ -190,16 +219,94 @@
invalidatorState.retainAll(toRetain);
jdtCompiler = new JdtCompiler();
- compile(logger, getCompilationUnits());
+ compile(logger, getCompilationUnits(),
+ Collections.<CompilationUnit> emptySet());
mediator.refresh(logger, getCompilationUnits());
markSurvivorsChecked(getCompilationUnits());
}
/**
+ * This method processes generatedCups using usefulGraveyardUnits wherever
+ * possible.
+ */
+ void addGeneratedCompilationUnits(TreeLogger logger,
+ Set<? extends CompilationUnit> newGeneratedCups,
+ Map<String, CompilationUnit> usefulGraveyardUnitsMap) {
+
+ Set<CompilationUnit> usefulGeneratedCups = new HashSet<CompilationUnit>();
+ for (CompilationUnit newGeneratedUnit : newGeneratedCups) {
+ String typeName = newGeneratedUnit.getTypeName();
+ CompilationUnit oldGeneratedUnit = usefulGraveyardUnitsMap.get(typeName);
+ if (oldGeneratedUnit != null) {
+ usefulGeneratedCups.add(oldGeneratedUnit);
+ unitMap.put(typeName, oldGeneratedUnit);
+ } else {
+ usefulGeneratedCups.add(newGeneratedUnit);
+ unitMap.put(typeName, newGeneratedUnit);
+ }
+ }
+ assert (newGeneratedCups.size() == usefulGeneratedCups.size());
+ for (CompilationUnit generatedUnit : usefulGeneratedCups) {
+ unitMap.put(generatedUnit.getTypeName(), generatedUnit);
+ }
+ updateExposedUnits();
+
+ compile(logger, usefulGeneratedCups, getCompilationUnits());
+ mediator.addNewUnits(logger, usefulGeneratedCups);
+ markSurvivorsChecked(usefulGeneratedCups);
+ }
+
+ /**
+ * Given a set of generatedCups, returns the useful graveyardUnits that do not
+ * need to be compiled. Additionally, updates the graveyardUnits by removing
+ * units that are either stale or depend on some other stale unit.
+ */
+ Map<String, CompilationUnit> getUsefulGraveyardUnits(
+ Set<? extends CompilationUnit> generatedCups) {
+ boolean anyGraveyardUnitsWereInvalidated = false;
+ Map<String, CompilationUnit> usefulGraveyardUnits = new HashMap<String, CompilationUnit>();
+ for (CompilationUnit unit : generatedCups) {
+ String typeName = unit.getTypeName();
+ assert (!unitMap.containsKey(typeName));
+ CompilationUnit graveyardUnit = graveyardUnits.remove(typeName);
+ if (graveyardUnit != null) {
+ assert graveyardUnit.getState() == State.GRAVEYARD;
+ if (unit.getSource().equals(graveyardUnit.getSource())) {
+ usefulGraveyardUnits.put(typeName, graveyardUnit);
+ } else {
+ // The old unit is invalidated.
+ anyGraveyardUnitsWereInvalidated = true;
+ graveyardUnit.setFresh();
+ }
+ }
+ }
+
+ assert Collections.disjoint(usefulGraveyardUnits.values(),
+ graveyardUnits.values());
+
+ /*
+ * If any units became fresh, we need to ensure that any units that might
+ * refer to that unit get invalidated.
+ */
+ if (anyGraveyardUnitsWereInvalidated) {
+ /* Remove units that refer the graveyard units marked FRESH */
+ Set<CompilationUnit> allGraveyardUnits = concatSet(
+ graveyardUnits.values(), usefulGraveyardUnits.values());
+ CompilationUnitInvalidator.invalidateUnitsWithInvalidRefs(
+ TreeLogger.NULL, allGraveyardUnits, exposedUnits);
+
+ removeInvalidatedGraveyardUnits(graveyardUnits);
+ removeInvalidatedGraveyardUnits(usefulGraveyardUnits);
+ }
+ return usefulGraveyardUnits;
+ }
+
+ /**
* Compile units and update their internal state. Invalidate any units with
* compile errors.
*/
- private void compile(TreeLogger logger, Set<CompilationUnit> newUnits) {
+ private void compile(TreeLogger logger, Set<CompilationUnit> newUnits,
+ Set<CompilationUnit> existingUnits) {
PerfLogger.start("CompilationState.compile");
if (jdtCompiler.doCompile(newUnits)) {
logger = logger.branch(TreeLogger.DEBUG,
@@ -219,7 +326,7 @@
if (anyErrors) {
CompilationUnitInvalidator.invalidateUnitsWithInvalidRefs(logger,
- newUnits);
+ newUnits, existingUnits);
}
JsniCollector.collectJsniMethods(logger, newUnits, new JsProgram());
@@ -263,7 +370,7 @@
CompilationUnit unit = it.next();
SourceFileCompilationUnit sourceFileUnit = (SourceFileCompilationUnit) unit;
if (!unchanged.contains(sourceFileUnit.getSourceFile())) {
- unit.setState(State.FRESH);
+ unit.setFresh();
it.remove();
}
}
@@ -273,12 +380,27 @@
String typeName = newSourceFile.getTypeName();
assert (!unitMap.containsKey(typeName));
unitMap.put(typeName, new SourceFileCompilationUnit(newSourceFile));
+ // invalid a graveyard unit, if a new unit has the same type.
+ CompilationUnit graveyardUnit = graveyardUnits.remove(typeName);
+ if (graveyardUnit != null) {
+ graveyardUnit.setFresh();
+ }
}
// Record the update.
cachedSourceFiles = newSourceFiles;
}
+ private void removeInvalidatedGraveyardUnits(
+ Map<String, CompilationUnit> tempUnits) {
+ for (Iterator<CompilationUnit> it = tempUnits.values().iterator(); it.hasNext();) {
+ CompilationUnit graveyardUnit = it.next();
+ if (graveyardUnit.getState() != State.GRAVEYARD) {
+ it.remove();
+ }
+ }
+ }
+
private void updateExposedUnits() {
exposedUnits = Collections.unmodifiableSet(new HashSet<CompilationUnit>(
unitMap.values()));
diff --git a/dev/core/src/com/google/gwt/dev/javac/CompilationUnit.java b/dev/core/src/com/google/gwt/dev/javac/CompilationUnit.java
index e3c2f19..eb0b054 100644
--- a/dev/core/src/com/google/gwt/dev/javac/CompilationUnit.java
+++ b/dev/core/src/com/google/gwt/dev/javac/CompilationUnit.java
@@ -26,7 +26,9 @@
import org.eclipse.jdt.core.compiler.CategorizedProblem;
import org.eclipse.jdt.internal.compiler.ASTVisitor;
import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.Expression;
import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
+import org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.BlockScope;
import org.eclipse.jdt.internal.compiler.lookup.ClassScope;
import org.eclipse.jdt.internal.compiler.lookup.CompilationUnitScope;
@@ -204,7 +206,14 @@
* been added to the module's TypeOracle, as well as byte code, JSNI
* methods, and all other final state.
*/
- CHECKED
+ CHECKED,
+ /**
+ * A CHECKED generated unit enters this state at the start of a refresh. If
+ * a generator generates the same unit with identical source, the unit is
+ * immediately promoted to CHECKED, bypassing costly compilation,
+ * validation, and TypeOracle building.
+ */
+ GRAVEYARD,
}
private class FindTypesInCud extends ASTVisitor {
@@ -250,10 +259,21 @@
}
}
- private static Set<String> computeFileNameRefs(CompilationUnitDeclaration cud) {
+ private static Set<String> computeFileNameRefs(
+ CompilationUnitDeclaration cud,
+ final Map<String, String> binaryTypeToSourceFileMap) {
final Set<String> result = new HashSet<String>();
cud.traverse(new TypeRefVisitor() {
@Override
+ protected void onBinaryTypeRef(BinaryTypeBinding referencedType,
+ CompilationUnitDeclaration unitOfReferrer, Expression expression) {
+ String fileName = binaryTypeToSourceFileMap.get(String.valueOf(referencedType.getFileName()));
+ if (fileName != null) {
+ result.add(fileName);
+ }
+ }
+
+ @Override
protected void onTypeRef(SourceTypeBinding referencedType,
CompilationUnitDeclaration unitOfReferrer) {
// Map the referenced type to the target compilation unit file.
@@ -378,7 +398,8 @@
* Returns <code>true</code> if this unit is compiled and valid.
*/
public boolean isCompiled() {
- return state == State.COMPILED || state == State.CHECKED;
+ return state == State.COMPILED || state == State.CHECKED
+ || state == State.GRAVEYARD;
}
public boolean isError() {
@@ -404,6 +425,13 @@
return getDisplayLocation();
}
+ /*
+ * A unit that will not be resurrected without being re-compiled.
+ */
+ public boolean willNotBeResurrected() {
+ return state == State.FRESH || state == State.ERROR;
+ }
+
/**
* Called when this unit no longer needs to keep an internal cache of its
* source.
@@ -433,9 +461,6 @@
}
Set<String> getFileNameRefs() {
- if (fileNameRefs == null) {
- fileNameRefs = computeFileNameRefs(cud);
- }
return fileNameRefs;
}
@@ -451,52 +476,55 @@
return state;
}
+ void setChecked() {
+ dumpSource();
+ assert cud != null || state == State.GRAVEYARD;
+ for (CompiledClass compiledClass : getCompiledClasses()) {
+ compiledClass.checked();
+ }
+ cud = null;
+ state = State.CHECKED;
+ }
+
/**
* Sets the compiled JDT AST for this unit.
*/
- void setJdtCud(CompilationUnitDeclaration cud) {
+ void setCompiled(CompilationUnitDeclaration cud,
+ Map<String, String> binaryTypeToSourceFileMap) {
assert (state == State.FRESH || state == State.ERROR);
this.cud = cud;
+ fileNameRefs = computeFileNameRefs(cud, binaryTypeToSourceFileMap);
state = State.COMPILED;
}
+ void setError() {
+ dumpSource();
+ this.errors = cud.compilationResult().getErrors();
+ invalidate();
+ state = State.ERROR;
+ }
+
+ void setFresh() {
+ dumpSource();
+ this.errors = null;
+ invalidate();
+ state = State.FRESH;
+ }
+
+ void setGraveyard() {
+ assert (state == State.CHECKED);
+ if (exposedCompiledClasses != null) {
+ for (CompiledClass compiledClass : exposedCompiledClasses) {
+ compiledClass.graveyard();
+ }
+ }
+ state = State.GRAVEYARD;
+ }
+
void setJsniMethods(List<JsniMethod> jsniMethods) {
this.jsniMethods = Collections.unmodifiableList(jsniMethods);
}
- /**
- * Changes the compilation unit's internal state.
- */
- void setState(State newState) {
- assert (newState != State.COMPILED);
- if (state == newState) {
- return;
- }
- state = newState;
-
- dumpSource();
- switch (newState) {
- case CHECKED:
- // Must cache before we destroy the cud.
- assert (cud != null);
- getFileNameRefs();
- for (CompiledClass compiledClass : getCompiledClasses()) {
- compiledClass.checked();
- }
- cud = null;
- break;
-
- case ERROR:
- this.errors = cud.compilationResult().getErrors();
- invalidate();
- break;
- case FRESH:
- this.errors = null;
- invalidate();
- break;
- }
- }
-
private List<String> getJdtClassNames(String topLevelClass) {
List<String> classNames = new ArrayList<String>();
for (CompiledClass cc : getCompiledClasses()) {
@@ -540,5 +568,4 @@
}
return CompilingClassLoader.isClassnameGenerated(cc.getBinaryName());
}
-
}
diff --git a/dev/core/src/com/google/gwt/dev/javac/CompilationUnitInvalidator.java b/dev/core/src/com/google/gwt/dev/javac/CompilationUnitInvalidator.java
index 270e504..0b86ed9 100644
--- a/dev/core/src/com/google/gwt/dev/javac/CompilationUnitInvalidator.java
+++ b/dev/core/src/com/google/gwt/dev/javac/CompilationUnitInvalidator.java
@@ -26,6 +26,7 @@
import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
@@ -116,7 +117,7 @@
// Invalidate the unit if there are errors.
if (result.hasErrors()) {
- unit.setState(State.ERROR);
+ unit.setError();
anyRemoved = true;
}
}
@@ -125,35 +126,66 @@
return anyRemoved;
}
+ /**
+ * Invalidate any units that contain either a) references to non-compiled
+ * units or b) references to unknown units.
+ *
+ * @param logger
+ * @param unitsToCheck to units that might be invalid, this should be a closed
+ * set that reference each other
+ */
public static void invalidateUnitsWithInvalidRefs(TreeLogger logger,
- Set<CompilationUnit> units) {
+ Set<CompilationUnit> unitsToCheck) {
+ invalidateUnitsWithInvalidRefs(logger, unitsToCheck,
+ Collections.<CompilationUnit> emptySet());
+ }
+
+ /**
+ * Invalidate any units that contain either a) references to non-compiled
+ * units or b) references to unknown units.
+ *
+ * @param logger
+ * @param unitsToCheck to units that might be invalid, these will be the only
+ * units we will invalidate
+ * @param knownUnits a set of reference units (may contain invalid units) (set
+ * may be empty)
+ */
+ public static void invalidateUnitsWithInvalidRefs(TreeLogger logger,
+ Set<CompilationUnit> unitsToCheck, Set<CompilationUnit> knownUnits) {
logger = logger.branch(TreeLogger.TRACE, "Removing invalidated units");
- // Assume all compiled units are valid at first.
- boolean changed;
- Set<CompilationUnit> validUnits = new HashSet<CompilationUnit>();
- for (CompilationUnit unit : units) {
+ Set<String> knownValidRefs = new HashSet<String>();
+ for (CompilationUnit unit : knownUnits) {
if (unit.isCompiled()) {
- validUnits.add(unit);
+ knownValidRefs.add(unit.getDisplayLocation());
}
}
+
+ // Assume all compiled units are valid at first.
+ Set<CompilationUnit> currentlyValidUnitsToCheck = new HashSet<CompilationUnit>();
+ for (CompilationUnit unit : unitsToCheck) {
+ if (unit.isCompiled()) {
+ currentlyValidUnitsToCheck.add(unit);
+ knownValidRefs.add(unit.getDisplayLocation());
+ }
+ }
+
+ boolean changed;
do {
changed = false;
- Set<String> validRefs = new HashSet<String>();
- for (CompilationUnit unit : validUnits) {
- validRefs.add(unit.getDisplayLocation());
- }
- for (Iterator<CompilationUnit> it = validUnits.iterator(); it.hasNext();) {
- CompilationUnit unit = it.next();
+ for (Iterator<CompilationUnit> it = currentlyValidUnitsToCheck.iterator(); it.hasNext();) {
+ CompilationUnit currentlyValidUnitToCheck = it.next();
TreeLogger branch = null;
- for (String ref : unit.getFileNameRefs()) {
- if (!validRefs.contains(ref)) {
+ for (String ref : currentlyValidUnitToCheck.getFileNameRefs()) {
+ if (!knownValidRefs.contains(ref)) {
if (branch == null) {
branch = logger.branch(TreeLogger.WARN, "Compilation unit '"
- + unit + "' is removed due to invalid reference(s):");
+ + currentlyValidUnitToCheck
+ + "' is removed due to invalid reference(s):");
it.remove();
+ knownValidRefs.remove(currentlyValidUnitToCheck.getDisplayLocation());
+ currentlyValidUnitToCheck.setFresh();
changed = true;
- unit.setState(State.FRESH);
}
branch.log(TreeLogger.WARN, ref);
}
diff --git a/dev/core/src/com/google/gwt/dev/javac/CompiledClass.java b/dev/core/src/com/google/gwt/dev/javac/CompiledClass.java
index 29ad532..f3f1eec 100644
--- a/dev/core/src/com/google/gwt/dev/javac/CompiledClass.java
+++ b/dev/core/src/com/google/gwt/dev/javac/CompiledClass.java
@@ -60,6 +60,7 @@
// The state below is transient.
private NameEnvironmentAnswer nameEnvironmentAnswer;
private JRealClassType realClassType;
+
// Can be killed after parent is CHECKED.
private TypeDeclaration typeDeclaration;
@@ -152,6 +153,10 @@
return typeDeclaration;
}
+ void graveyard() {
+ realClassType.invalidate();
+ }
+
void invalidate() {
nameEnvironmentAnswer = null;
typeDeclaration = null;
diff --git a/dev/core/src/com/google/gwt/dev/javac/JdtCompiler.java b/dev/core/src/com/google/gwt/dev/javac/JdtCompiler.java
index 6bb5875..a82b705 100644
--- a/dev/core/src/com/google/gwt/dev/javac/JdtCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/javac/JdtCompiler.java
@@ -102,7 +102,7 @@
ICompilationUnit icu = cud.compilationResult().compilationUnit;
CompilationUnitAdapter adapter = (CompilationUnitAdapter) icu;
CompilationUnit unit = adapter.getUnit();
- unit.setJdtCud(cud);
+ unit.setCompiled(cud, binaryTypesRefs);
if (!cud.compilationResult().hasErrors()) {
recordBinaryTypes(unit.getCompiledClasses());
}
@@ -233,6 +233,11 @@
*/
private final Map<String, CompiledClass> binaryTypes = new HashMap<String, CompiledClass>();
+ /**
+ * Maps dotted binary names the containing unit location.
+ */
+ private final Map<String, String> binaryTypesRefs = new HashMap<String, String>();
+
private final CompilerImpl compiler = new CompilerImpl();
private final Set<String> notPackages = new HashSet<String>();
@@ -287,6 +292,8 @@
private void recordBinaryTypes(Set<CompiledClass> compiledClasses) {
for (CompiledClass compiledClass : compiledClasses) {
binaryTypes.put(compiledClass.getBinaryName(), compiledClass);
+ binaryTypesRefs.put(compiledClass.getBinaryName(),
+ compiledClass.getUnit().getDisplayLocation());
}
}
diff --git a/dev/core/src/com/google/gwt/dev/javac/TypeOracleMediator.java b/dev/core/src/com/google/gwt/dev/javac/TypeOracleMediator.java
index 5c6ee23..9649749 100644
--- a/dev/core/src/com/google/gwt/dev/javac/TypeOracleMediator.java
+++ b/dev/core/src/com/google/gwt/dev/javac/TypeOracleMediator.java
@@ -38,6 +38,7 @@
import com.google.gwt.core.ext.typeinfo.JTypeParameter;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.core.ext.typeinfo.JWildcardType.BoundType;
+import com.google.gwt.dev.javac.CompilationUnit.State;
import com.google.gwt.dev.javac.impl.Shared;
import com.google.gwt.dev.util.Empty;
@@ -366,22 +367,34 @@
public void addNewUnits(TreeLogger logger, Set<CompilationUnit> units) {
// Perform a shallow pass to establish identity for new and old types.
for (CompilationUnit unit : units) {
- if (!unit.isCompiled()) {
- continue;
- }
- Set<CompiledClass> compiledClasses = unit.getCompiledClasses();
- for (CompiledClass compiledClass : compiledClasses) {
- JRealClassType type = compiledClass.getRealClassType();
- if (type == null) {
- type = createType(compiledClass);
- }
- binaryMapper.put(compiledClass.getBinaryName(), type);
+ switch (unit.getState()) {
+ case GRAVEYARD:
+ for (CompiledClass compiledClass : unit.getCompiledClasses()) {
+ JRealClassType type = compiledClass.getRealClassType();
+ assert (type != null);
+ type.resurrect();
+ binaryMapper.put(compiledClass.getBinaryName(), type);
+ }
+ break;
+ case COMPILED:
+ for (CompiledClass compiledClass : unit.getCompiledClasses()) {
+ JRealClassType type = createType(compiledClass);
+ binaryMapper.put(compiledClass.getBinaryName(), type);
+ }
+ break;
+ case CHECKED:
+ for (CompiledClass compiledClass : unit.getCompiledClasses()) {
+ JRealClassType type = compiledClass.getRealClassType();
+ assert (type != null);
+ binaryMapper.put(compiledClass.getBinaryName(), type);
+ }
+ break;
}
}
// Perform a deep pass to resolve all new types in terms of our types.
for (CompilationUnit unit : units) {
- if (!unit.isCompiled()) {
+ if (unit.getState() != State.COMPILED) {
continue;
}
TreeLogger cudLogger = logger.branch(TreeLogger.SPAM,
diff --git a/dev/core/test/com/google/gwt/dev/javac/CompilationStateTest.java b/dev/core/test/com/google/gwt/dev/javac/CompilationStateTest.java
index 6c1a9ad..d0fdef0 100644
--- a/dev/core/test/com/google/gwt/dev/javac/CompilationStateTest.java
+++ b/dev/core/test/com/google/gwt/dev/javac/CompilationStateTest.java
@@ -17,6 +17,7 @@
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.dev.javac.CompilationUnit.State;
+import com.google.gwt.dev.javac.impl.JavaResourceBase;
import com.google.gwt.dev.javac.impl.MockJavaSourceFile;
import com.google.gwt.dev.javac.impl.SourceFileCompilationUnit;
import com.google.gwt.dev.util.log.AbstractTreeLogger;
@@ -26,6 +27,7 @@
import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
@@ -54,9 +56,8 @@
AbstractTreeLogger logger = new PrintWriterTreeLogger();
logger.setMaxDetail(TreeLogger.ALL);
return logger;
- } else {
- return TreeLogger.NULL;
}
+ return TreeLogger.NULL;
}
private MockJavaSourceOracle oracle = new MockJavaSourceOracle(
@@ -69,7 +70,8 @@
validateCompilationState();
// Add a unit and ensure it shows up.
- addGeneratedUnits(JavaSourceCodeBase.FOO);
+ state.addGeneratedCompilationUnits(createTreeLogger(),
+ getCompilationUnits(JavaSourceCodeBase.FOO));
validateCompilationState(JavaSourceCodeBase.FOO.getTypeName());
// Ensure it disappears after a refresh.
@@ -77,6 +79,16 @@
validateCompilationState();
}
+ /* test that a generated unit, if unchanged, is reused */
+ public void testCaching() {
+ testCaching(JavaSourceCodeBase.FOO);
+ }
+
+ /* test that mutiple generated units, if unchanged, are reused */
+ public void testCachingOfMultipleUnits() {
+ testCaching(JavaSourceCodeBase.BAR, JavaSourceCodeBase.FOO);
+ }
+
public void testCompileError() {
oracle.add(JavaSourceCodeBase.BAR);
state.refresh(createTreeLogger());
@@ -93,13 +105,15 @@
public void testCompileWithGeneratedUnits() {
assertUnitsChecked(state.getCompilationUnits());
- addGeneratedUnits(JavaSourceCodeBase.FOO);
+ state.addGeneratedCompilationUnits(createTreeLogger(),
+ getCompilationUnits(JavaSourceCodeBase.FOO));
assertUnitsChecked(state.getCompilationUnits());
}
public void testCompileWithGeneratedUnitsError() {
assertUnitsChecked(state.getCompilationUnits());
- addGeneratedUnits(JavaSourceCodeBase.BAR);
+ state.addGeneratedCompilationUnits(createTreeLogger(),
+ getCompilationUnits(JavaSourceCodeBase.BAR));
CompilationUnit badUnit = state.getCompilationUnitMap().get(
JavaSourceCodeBase.BAR.getTypeName());
@@ -111,10 +125,100 @@
assertUnitsChecked(goodUnits);
}
+ public void testCompileWithGeneratedUnitsErrorAndDepedentGeneratedUnit() {
+ assertUnitsChecked(state.getCompilationUnits());
+ MockJavaSourceFile badFoo = new MockJavaSourceFile(JavaResourceBase.FOO) {
+ @Override
+ public String readSource() {
+ return super.readSource() + "\ncompilation error LOL!";
+ }
+ };
+ state.addGeneratedCompilationUnits(createTreeLogger(), getCompilationUnits(
+ badFoo, JavaSourceCodeBase.BAR));
+
+ CompilationUnit badUnit = state.getCompilationUnitMap().get(
+ badFoo.getTypeName());
+ assertSame(State.ERROR, badUnit.getState());
+ CompilationUnit invalidUnit = state.getCompilationUnitMap().get(
+ JavaSourceCodeBase.BAR.getTypeName());
+ assertSame(State.FRESH, invalidUnit.getState());
+
+ Set<CompilationUnit> goodUnits = new HashSet<CompilationUnit>(
+ state.getCompilationUnits());
+ goodUnits.remove(badUnit);
+ goodUnits.remove(invalidUnit);
+ assertUnitsChecked(goodUnits);
+ }
+
+ /*
+ * test if things work correctly when a generated unit can't be reused, but
+ * another generated unit it depends on can be reused
+ */
+ public void testComplexCacheInvalidation() {
+ Set<CompilationUnit> modifiedUnits = getCompilationUnits(JavaSourceCodeBase.FOO);
+ modifiedUnits.addAll(getModifiedCompilationUnits(JavaSourceCodeBase.BAR));
+ Set<String> reusedTypes = new HashSet<String>();
+ reusedTypes.add(JavaSourceCodeBase.FOO.getTypeName());
+ testCachingOverMultipleRefreshes(getCompilationUnits(
+ JavaSourceCodeBase.FOO, JavaSourceCodeBase.BAR), modifiedUnits,
+ reusedTypes, 1);
+ }
+
public void testInitialization() {
assertUnitsChecked(state.getCompilationUnits());
}
+ public void testInvalidation() {
+ testCachingOverMultipleRefreshes(
+ getCompilationUnits(JavaSourceCodeBase.FOO),
+ getModifiedCompilationUnits(JavaSourceCodeBase.FOO),
+ Collections.<String> emptySet(), 1);
+ }
+
+ public void testInvalidationOfMultipleUnits() {
+ testCachingOverMultipleRefreshes(getCompilationUnits(
+ JavaSourceCodeBase.BAR, JavaSourceCodeBase.FOO),
+ getModifiedCompilationUnits(JavaSourceCodeBase.BAR,
+ JavaSourceCodeBase.FOO), Collections.<String> emptySet(), 2);
+ }
+
+ /*
+ * Steps: (i) Check compilation state. (ii) Add generated units. (iii) Change
+ * unit in source oracle. (iv) Refresh oracle. (v) Add same generated units.
+ * (v) Check that there is no reuse.
+ */
+ public void testInvalidationWhenSourceUnitsChange() {
+ validateCompilationState();
+ oracle.add(JavaSourceCodeBase.FOO);
+ state.refresh(createTreeLogger());
+
+ // add generated units
+ Set<CompilationUnit> generatedCups = getCompilationUnits(JavaSourceCodeBase.BAR);
+ Map<String, CompilationUnit> usefulUnits = state.getUsefulGraveyardUnits(generatedCups);
+ assertEquals(0, usefulUnits.size());
+ state.addGeneratedCompilationUnits(createTreeLogger(), generatedCups,
+ usefulUnits);
+ assertUnitsChecked(state.getCompilationUnits());
+
+ // change unit in source oracle
+ oracle.replace(new MockJavaSourceFile(JavaSourceCodeBase.FOO) {
+ @Override
+ public String readSource() {
+ return JavaSourceCodeBase.FOO.readSource() + "\n";
+ }
+ });
+ state.refresh(createTreeLogger());
+
+ /*
+ * Add same generated units. Verify that the generated units are not used.
+ */
+ usefulUnits = state.getUsefulGraveyardUnits(generatedCups);
+ assertEquals(0, usefulUnits.size());
+ state.addGeneratedCompilationUnits(createTreeLogger(), generatedCups,
+ usefulUnits);
+ assertUnitsChecked(state.getCompilationUnits());
+ }
+
public void testSourceOracleAdd() {
validateCompilationState();
@@ -165,7 +269,17 @@
validateCompilationState();
}
- private void addGeneratedUnits(JavaSourceFile... sourceFiles) {
+ /* test if generatedUnits that depend on stale generatedUnits are invalidated */
+ public void testTransitiveInvalidation() {
+ Set<CompilationUnit> modifiedUnits = getModifiedCompilationUnits(JavaSourceCodeBase.FOO);
+ modifiedUnits.addAll(getCompilationUnits(JavaSourceCodeBase.BAR));
+ testCachingOverMultipleRefreshes(getCompilationUnits(
+ JavaSourceCodeBase.BAR, JavaSourceCodeBase.FOO), modifiedUnits,
+ Collections.<String> emptySet(), 2);
+ }
+
+ private Set<CompilationUnit> getCompilationUnits(
+ JavaSourceFile... sourceFiles) {
Set<CompilationUnit> units = new HashSet<CompilationUnit>();
for (JavaSourceFile sourceFile : sourceFiles) {
units.add(new SourceFileCompilationUnit(sourceFile) {
@@ -175,7 +289,112 @@
}
});
}
- state.addGeneratedCompilationUnits(createTreeLogger(), units);
+ return units;
+ }
+
+ private Set<CompilationUnit> getModifiedCompilationUnits(
+ JavaSourceFile... sourceFiles) {
+ Set<CompilationUnit> units = new HashSet<CompilationUnit>();
+ for (JavaSourceFile sourceFile : sourceFiles) {
+ units.add(new SourceFileCompilationUnit(sourceFile) {
+ /* modified the source */
+ @Override
+ public String getSource() {
+ return super.getSource() + "\n";
+ }
+
+ @Override
+ public boolean isGenerated() {
+ return true;
+ }
+ });
+ }
+ return units;
+ }
+
+ private void testCaching(JavaSourceFile... files) {
+ Set<String> reusedTypes = new HashSet<String>();
+ for (JavaSourceFile file : files) {
+ reusedTypes.add(file.getTypeName());
+ }
+ testCachingOverMultipleRefreshes(getCompilationUnits(files),
+ getCompilationUnits(files), reusedTypes, 0);
+ }
+
+ /**
+ * Test caching logic for generated units during refreshes. Steps:
+ * <ol>
+ * <li>Verify that there were no generated units before</li>
+ * <li>Add 'initialSet' generatedUnits over a refresh cycle</li>
+ * <li>Add 'updatedSet' generatedUnits over a refresh cycle</li>
+ * <li>Add 'updatedSet' generatedUnits over the second refresh cycle</li>
+ * </ol>
+ *
+ * @param initialSet CompilationUnits that are generated the first time.
+ * @param updatedSet CompilationUnits that are generated the next time.
+ * @param reusedTypes Main type of the units that can be reused between the
+ * initialSet and updatedSet.
+ * @param numInvalidated Number of types invalidated from graveyardUnits.
+ */
+ private void testCachingOverMultipleRefreshes(
+ Set<CompilationUnit> initialSet, Set<CompilationUnit> updatedSet,
+ Set<String> reusedTypes, int numInvalidated) {
+
+ // verify that there were no generated units before.
+ state.refresh(createTreeLogger());
+ assertEquals(0, state.graveyardUnits.size());
+
+ // add 'updatedSet' generatedUnits over the first refresh cycle.
+ testCachingOverSingleRefresh(new HashSet<CompilationUnit>(initialSet), 0,
+ Collections.<String> emptySet(), 0);
+
+ // add 'updatedSet' generatedUnits over the second refresh cycle.
+ testCachingOverSingleRefresh(new HashSet<CompilationUnit>(updatedSet),
+ initialSet.size(), reusedTypes, numInvalidated);
+
+ // add 'updatedSet' generatedUnits over the third refresh cycle.
+ reusedTypes = new HashSet<String>();
+ for (CompilationUnit unit : updatedSet) {
+ reusedTypes.add(unit.getTypeName());
+ }
+ testCachingOverSingleRefresh(new HashSet<CompilationUnit>(updatedSet),
+ updatedSet.size(), reusedTypes, 0);
+ }
+
+ /**
+ * Steps:
+ * <ol>
+ * <li>Check graveyardUnits before refresh. assert size is 0.</li>
+ * <li>Refresh. assert size is 'graveyardUnitsSize'.</li>
+ * <li>Add generated cups. Confirm that the 'reusedTypes' and
+ * 'numInvalidated' match.</li>
+ * </ol>
+ *
+ * @param generatedCups generated CompilationUnits to be added.
+ * @param graveyardUnitsSize initial expected size of graveyard units.
+ * @param reusedTypes Main type of the units that can be reused between the
+ * initialSet and updatedSet.
+ * @param numInvalidated Number of types invalidated from graveyardUnits.
+ */
+ private void testCachingOverSingleRefresh(Set<CompilationUnit> generatedCups,
+ int graveyardUnitsSize, Set<String> reusedTypes, int numInvalidated) {
+ assertEquals(0, state.graveyardUnits.size());
+
+ assertUnitsChecked(state.getCompilationUnits());
+ state.refresh(createTreeLogger());
+ assertEquals(graveyardUnitsSize, state.graveyardUnits.size());
+
+ int initialSize = state.graveyardUnits.size();
+ Map<String, CompilationUnit> usefulUnits = state.getUsefulGraveyardUnits(generatedCups);
+ assertEquals(reusedTypes.size(), usefulUnits.size());
+ for (String typeName : reusedTypes) {
+ assertNotNull(usefulUnits.get(typeName));
+ }
+ assertEquals(numInvalidated, initialSize - reusedTypes.size()
+ - state.graveyardUnits.size());
+ state.addGeneratedCompilationUnits(createTreeLogger(), generatedCups,
+ usefulUnits);
+ assertUnitsChecked(state.getCompilationUnits());
}
private void validateCompilationState(String... generatedTypeNames) {
diff --git a/dev/core/test/com/google/gwt/dev/javac/CompilationUnitFileReferenceTest.java b/dev/core/test/com/google/gwt/dev/javac/CompilationUnitFileReferenceTest.java
new file mode 100644
index 0000000..90f119a
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/javac/CompilationUnitFileReferenceTest.java
@@ -0,0 +1,282 @@
+/*
+ * Copyright 2009 Google Inc.
+ *
+ * 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.gwt.dev.javac;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.dev.javac.impl.JavaResourceBase;
+import com.google.gwt.dev.javac.impl.MockJavaResource;
+import com.google.gwt.dev.javac.impl.MockJavaSourceFile;
+import com.google.gwt.dev.javac.impl.SourceFileCompilationUnit;
+import com.google.gwt.dev.resource.Resource;
+import com.google.gwt.dev.util.log.AbstractTreeLogger;
+import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
+
+import junit.framework.TestCase;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Test if all fileReferences in a compilationUnit are recorded correctly.
+ */
+public class CompilationUnitFileReferenceTest extends TestCase {
+
+ public static final MockJavaResource MEMBER_INNER_SUBCLASS = new MockJavaResource(
+ "test.OuterSubclass") {
+ @Override
+ protected CharSequence getContent() {
+ StringBuffer code = new StringBuffer();
+ code.append("package test;\n");
+ code.append("public class OuterSubclass extends Outer {\n");
+ code.append(" public String value() { return \"OuterSubclass\"; }\n");
+ code.append(" public class MemberInnerSubclass extends MemberInner {\n");
+ code.append(" public String value() { return \"MemberInnerSubclass\"; }\n");
+ code.append(" }\n");
+ code.append("}\n");
+ return code;
+ }
+ };
+
+ public static final MockJavaResource OUTER = new MockJavaResource(
+ "test.Outer") {
+ @Override
+ protected CharSequence getContent() {
+ StringBuffer code = new StringBuffer();
+ code.append("package test;\n");
+ code.append("public class Outer {\n");
+ code.append(" public String value() { return \"Outer\"; }\n");
+ code.append(" public static class StaticInner {\n");
+ code.append(" public String value() { return \"StaticInner\"; }\n");
+ code.append(" }\n");
+ code.append(" public class MemberInner {\n");
+ code.append(" public String value() { return \"MemberInner\"; }\n");
+ code.append(" }\n");
+ code.append("}\n");
+ return code;
+ }
+ };
+
+ public static final MockJavaResource STATIC_INNER_SUBCLASS = new MockJavaResource(
+ "test.StaticInnerSubclass") {
+ @Override
+ protected CharSequence getContent() {
+ StringBuffer code = new StringBuffer();
+ code.append("package test;\n");
+ code.append("public class StaticInnerSubclass extends Outer.StaticInner {\n");
+ code.append(" public String value() { return \"StaticInnerSubclass\"; }\n");
+ code.append("}\n");
+ return code;
+ }
+ };
+
+ public static final MockJavaResource TOP = new MockJavaResource("test.Top") {
+ @Override
+ protected CharSequence getContent() {
+ StringBuffer code = new StringBuffer();
+ code.append("package test;\n");
+ code.append("public class Top {\n");
+ code.append(" public String value() { return \"Top\"; }\n");
+ code.append("}\n");
+ code.append("class Top2 extends Top {\n");
+ code.append(" public String value() { return \"Top2\"; }\n");
+ code.append("}\n");
+ return code;
+ }
+ };
+
+ public static final MockJavaResource TOP3 = new MockJavaResource("test.Top3") {
+ @Override
+ protected CharSequence getContent() {
+ StringBuffer code = new StringBuffer();
+ code.append("package test;\n");
+ code.append("public class Top3 extends Top2 {\n");
+ code.append(" public String value() { return \"Top3\"; }\n");
+ code.append("}\n");
+ return code;
+ }
+ };
+
+ /**
+ * This map contains the hand-computed set of references we expect each of the
+ * test compilation units to have. The actual set of references computed by
+ * {@link CompilationState} will be checked against this set.
+ */
+ private static final Map<String, Set<String>> EXPECTED_DEPENDENCIES = new HashMap<String, Set<String>>();
+
+ static {
+ // Setup EXPECTED_DEPENDENCIES with hand-computed data.
+ initializeExpectedDependency(JavaResourceBase.FOO, JavaResourceBase.STRING);
+ initializeExpectedDependency(JavaResourceBase.BAR, JavaResourceBase.STRING,
+ JavaResourceBase.FOO);
+
+ // TOP has a self-reference
+ initializeExpectedDependency(TOP, JavaResourceBase.STRING, TOP);
+ initializeExpectedDependency(TOP3, JavaResourceBase.STRING, TOP);
+
+ initializeExpectedDependency(OUTER, JavaResourceBase.STRING);
+ initializeExpectedDependency(MEMBER_INNER_SUBCLASS,
+ JavaResourceBase.STRING, OUTER);
+
+ initializeExpectedDependency(OUTER, JavaResourceBase.STRING);
+ initializeExpectedDependency(STATIC_INNER_SUBCLASS,
+ JavaResourceBase.STRING, OUTER);
+ }
+
+ /**
+ * Tweak this if you want to see the log output.
+ */
+ private static TreeLogger createTreeLogger() {
+ boolean reallyLog = false;
+ if (reallyLog) {
+ AbstractTreeLogger logger = new PrintWriterTreeLogger();
+ logger.setMaxDetail(TreeLogger.ALL);
+ return logger;
+ }
+ return TreeLogger.NULL;
+ }
+
+ private static void initializeExpectedDependency(Resource source,
+ Resource... targets) {
+ Set<String> targetSet = new HashSet<String>();
+ for (Resource target : targets) {
+ targetSet.add(target.getLocation());
+ }
+ EXPECTED_DEPENDENCIES.put(source.getLocation(), targetSet);
+ }
+
+ private MockJavaSourceOracle oracle = new MockJavaSourceOracle(
+ JavaSourceCodeBase.getStandardResources());
+
+ private CompilationState state = new CompilationState(createTreeLogger(),
+ oracle);
+
+ public void testBinaryBindingsWithMemberInnerClass() {
+ testBinaryBindings(OUTER, MEMBER_INNER_SUBCLASS);
+ }
+
+ public void testBinaryBindingsWithMultipleTopLevelClasses() {
+ testBinaryBindings(TOP, TOP3);
+ }
+
+ public void testBinaryBindingsWithSimpleUnits() {
+ testBinaryBindings(JavaSourceCodeBase.FOO, JavaSourceCodeBase.BAR);
+ }
+
+ public void testBinaryBindingsWithStaticInnerClass() {
+ testBinaryBindings(OUTER, STATIC_INNER_SUBCLASS);
+ }
+
+ public void testSourceBindingsWithMemberInnerClass() {
+ testSourceBindings(OUTER, MEMBER_INNER_SUBCLASS);
+ }
+
+ public void testSourceBindingsWithMultipleTopLevelClasses() {
+ testSourceBindings(TOP, TOP3);
+ }
+
+ public void testSourceBindingsWithSimpleUnits() {
+ testSourceBindings(JavaSourceCodeBase.FOO, JavaSourceCodeBase.BAR);
+ }
+
+ public void testSourceBindingsWithStaticInnerClass() {
+ testSourceBindings(OUTER, STATIC_INNER_SUBCLASS);
+ }
+
+ public void testWithGeneratedUnits() {
+ state.addGeneratedCompilationUnits(createTreeLogger(),
+ copyAsGeneratedUnits(JavaSourceCodeBase.BAR, JavaSourceCodeBase.FOO));
+ assertRefsMatchExpectedRefs(JavaSourceCodeBase.BAR, JavaSourceCodeBase.FOO);
+ }
+
+ public void testWithMixedUnits() {
+ oracle.add(JavaSourceCodeBase.FOO);
+ state.refresh(createTreeLogger());
+ state.addGeneratedCompilationUnits(createTreeLogger(),
+ copyAsGeneratedUnits(JavaSourceCodeBase.BAR));
+ assertRefsMatchExpectedRefs(JavaSourceCodeBase.BAR, JavaSourceCodeBase.FOO);
+ }
+
+ private void assertRefsMatchExpectedRefs(JavaSourceFile... files) {
+ for (JavaSourceFile sourceFile : files) {
+ Set<String> sourceFileRefs = state.getCompilationUnitMap().get(
+ sourceFile.getTypeName()).getFileNameRefs();
+ Set<String> expectedSourceFileRefs = EXPECTED_DEPENDENCIES.get(sourceFile.getLocation());
+ assertEquals(expectedSourceFileRefs, sourceFileRefs);
+ }
+ }
+
+ /**
+ * Returns copies of units as generated units for testing interactions with
+ * generated units.
+ */
+ private Set<CompilationUnit> copyAsGeneratedUnits(
+ JavaSourceFile... sourceFiles) {
+ Set<CompilationUnit> units = new HashSet<CompilationUnit>();
+ for (JavaSourceFile sourceFile : sourceFiles) {
+ units.add(new SourceFileCompilationUnit(sourceFile) {
+ @Override
+ public boolean isGenerated() {
+ return true;
+ }
+ });
+ }
+ return units;
+ }
+
+ /**
+ * Independently compiles each file in order to force each subsequent unit to
+ * have only binary references to the previous unit(s). This tests the binary
+ * reference matching in {@link CompilationState}.
+ */
+ private void testBinaryBindings(JavaSourceFile... files) {
+ for (JavaSourceFile sourceFile : files) {
+ oracle.add(sourceFile);
+ state.refresh(createTreeLogger());
+ }
+ assertRefsMatchExpectedRefs(files);
+ }
+
+ private void testBinaryBindings(MockJavaResource... resources) {
+ JavaSourceFile[] files = new JavaSourceFile[resources.length];
+ for (int i = 0; i < resources.length; ++i) {
+ files[i] = new MockJavaSourceFile(resources[i]);
+ }
+ testBinaryBindings(files);
+ }
+
+ /**
+ * Compiles all files together so that all units will have source references
+ * to each other. This tests the source reference matching in
+ * {@link CompilationState}.
+ */
+ private void testSourceBindings(JavaSourceFile... files) {
+ for (JavaSourceFile sourceFile : files) {
+ oracle.add(sourceFile);
+ }
+ state.refresh(createTreeLogger());
+ assertRefsMatchExpectedRefs(files);
+ }
+
+ private void testSourceBindings(MockJavaResource... resources) {
+ JavaSourceFile[] files = new JavaSourceFile[resources.length];
+ for (int i = 0; i < resources.length; ++i) {
+ files[i] = new MockJavaSourceFile(resources[i]);
+ }
+ testSourceBindings(files);
+ }
+}
diff --git a/dev/core/test/com/google/gwt/dev/javac/JavaCompilationSuite.java b/dev/core/test/com/google/gwt/dev/javac/JavaCompilationSuite.java
index eb14456..f391487 100644
--- a/dev/core/test/com/google/gwt/dev/javac/JavaCompilationSuite.java
+++ b/dev/core/test/com/google/gwt/dev/javac/JavaCompilationSuite.java
@@ -30,6 +30,7 @@
suite.addTestSuite(BinaryTypeReferenceRestrictionsCheckerTest.class);
suite.addTestSuite(CompilationStateTest.class);
+ suite.addTestSuite(CompilationUnitFileReferenceTest.class);
suite.addTestSuite(GWTProblemTest.class);
suite.addTestSuite(JdtBehaviorTest.class);
suite.addTestSuite(JdtCompilerTest.class);
diff --git a/dev/core/test/com/google/gwt/dev/javac/TypeOracleMediatorTest.java b/dev/core/test/com/google/gwt/dev/javac/TypeOracleMediatorTest.java
index fee4d53..d2a3687 100644
--- a/dev/core/test/com/google/gwt/dev/javac/TypeOracleMediatorTest.java
+++ b/dev/core/test/com/google/gwt/dev/javac/TypeOracleMediatorTest.java
@@ -16,6 +16,7 @@
package com.google.gwt.dev.javac;
import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JArrayType;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JField;
@@ -25,13 +26,16 @@
import com.google.gwt.core.ext.typeinfo.NotFoundException;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.core.ext.typeinfo.TypeOracleException;
-import com.google.gwt.dev.javac.CompilationUnit.State;
import com.google.gwt.dev.javac.impl.Shared;
import com.google.gwt.dev.util.log.AbstractTreeLogger;
import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
import junit.framework.TestCase;
+import org.apache.commons.collections.map.AbstractReferenceMap;
+import org.apache.commons.collections.map.ReferenceMap;
+
+import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
@@ -49,10 +53,10 @@
}
}
+ public abstract void check(JClassType type) throws NotFoundException;
+
@Override
public abstract String getSource();
-
- public abstract void check(JClassType type) throws NotFoundException;
}
private static void assertEqualArraysUnordered(Object[] expected,
@@ -787,6 +791,49 @@
}
}
+ /**
+ * Tests which variant of AbstractRefrenceMap we want to store the map for
+ * parameterizedTypes, arrayTypes, and wildCardTypes in TypeOracle. Note: this
+ * test is manual because gc can be unreliable.
+ */
+ @SuppressWarnings("unchecked")
+ public void manualTestAbstractRefrenceMap() {
+
+ /*
+ * with a HARD -> WEAK map, verify that the entry remains if there is no
+ * reference to key, but is deleted when the reference to value is gone
+ */
+ Map<Integer, Integer> simpleMap = new ReferenceMap(
+ AbstractReferenceMap.HARD, AbstractReferenceMap.WEAK, true);
+ Integer bar = new Integer(42);
+ simpleMap.put(new Integer(32), bar);
+ Runtime.getRuntime().gc();
+ assertEquals(1, simpleMap.size());
+ bar = null;
+ Runtime.getRuntime().gc();
+ assertEquals(0, simpleMap.size());
+
+ /*
+ * with a WEAK -> WEAK map, verify that the entry is gone if there are no
+ * references to either the key or the value.
+ */
+ simpleMap = new ReferenceMap(AbstractReferenceMap.WEAK,
+ AbstractReferenceMap.WEAK, true);
+ Map<Integer, Integer> reverseMap = new ReferenceMap(
+ AbstractReferenceMap.WEAK, AbstractReferenceMap.WEAK, true);
+ Integer foo = new Integer(32);
+ bar = new Integer(42);
+ simpleMap.put(foo, bar);
+ reverseMap.put(bar, foo);
+ Runtime.getRuntime().gc();
+ assertEquals(1, simpleMap.size());
+ assertEquals(1, reverseMap.size());
+ bar = null;
+ Runtime.getRuntime().gc();
+ assertEquals(0, simpleMap.size());
+ assertEquals(0, reverseMap.size());
+ }
+
public void testAssignable() throws TypeOracleException {
units.add(CU_Object);
units.add(CU_Assignable);
@@ -943,7 +990,7 @@
* Invalidate CU_GenericList and simulate a refresh. This should cause
* anything that depends on GenericList to be rebuilt by the type oracle.
*/
- CU_GenericList.setState(State.FRESH);
+ CU_GenericList.setFresh();
compileAndRefresh();
assertNotSame(genericListType.getQualifiedSourceName() + "; ",
@@ -1008,8 +1055,8 @@
}
@Override
- void setState(State newState) {
- super.setState(newState);
+ void setFresh() {
+ super.setFresh();
source = "This will cause a syntax error.";
}
};
@@ -1040,7 +1087,7 @@
addCompilationUnit("test.refresh.with.errors.BadClass", sb);
// Now this cup should cause errors.
- unitThatWillGoBad.setState(State.FRESH);
+ unitThatWillGoBad.setFresh();
compileAndRefresh();