FLAMING SWORD OF DEATH

Review by: bruce (TBR)



git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@2742 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java
index a480c17..a6dee8e 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java
@@ -176,14 +176,12 @@
     properties = Collections.unmodifiableSortedSet(mutableProperties);
 
     for (Script script : module.getScripts()) {
-      artifacts.add(new StandardScriptReference(script.getSrc(),
-          module.findPublicFile(script.getSrc())));
+      artifacts.add(new StandardScriptReference(script.getSrc()));
       logger.log(TreeLogger.SPAM, "Added script " + script.getSrc(), null);
     }
 
     for (String style : module.getStyles()) {
-      artifacts.add(new StandardStylesheetReference(style,
-          module.findPublicFile(style)));
+      artifacts.add(new StandardStylesheetReference(style));
       logger.log(TreeLogger.SPAM, "Added style " + style, null);
     }
 
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardPublicResource.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardPublicResource.java
index 913580c..401b1b8 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardPublicResource.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardPublicResource.java
@@ -18,30 +18,24 @@
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.linker.PublicResource;
+import com.google.gwt.dev.resource.Resource;
 
-import java.io.IOException;
 import java.io.InputStream;
-import java.net.URL;
 
 /**
  * The standard implementation of {@link PublicResource}.
  */
 public class StandardPublicResource extends PublicResource {
-  private final URL url;
+  private final Resource resource;
 
-  public StandardPublicResource(String partialPath, URL url) {
+  public StandardPublicResource(String partialPath, Resource resource) {
     super(StandardLinkerContext.class, partialPath);
-    this.url = url;
+    this.resource = resource;
   }
 
   @Override
   public InputStream getContents(TreeLogger logger)
       throws UnableToCompleteException {
-    try {
-      return url.openStream();
-    } catch (IOException e) {
-      logger.log(TreeLogger.ERROR, "Unable to open file", e);
-      throw new UnableToCompleteException();
-    }
+    return resource.openContents();
   }
 }
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardScriptReference.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardScriptReference.java
index 2410e12..79cc00a 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardScriptReference.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardScriptReference.java
@@ -17,18 +17,12 @@
 
 import com.google.gwt.core.ext.linker.ScriptReference;
 
-import java.net.URL;
-
 /**
  * The standard implementation of {@link ScriptReference}.
  */
 public class StandardScriptReference extends ScriptReference {
 
-  /**
-   * Might use <code>url</code>someday.
-   */
-  @SuppressWarnings("unused")
-  public StandardScriptReference(String src, URL url) {
+  public StandardScriptReference(String src) {
     super(StandardLinkerContext.class, src);
   }
 }
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardStylesheetReference.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardStylesheetReference.java
index 3e0540b..49211fa 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardStylesheetReference.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardStylesheetReference.java
@@ -17,18 +17,12 @@
 
 import com.google.gwt.core.ext.linker.StylesheetReference;
 
-import java.net.URL;
-
 /**
  * The standard implementation of {@link StylesheetReference}.
  */
 public class StandardStylesheetReference extends StylesheetReference {
 
-  /**
-   * Might use <code>url</code>someday.
-   */
-  @SuppressWarnings("unused")
-  public StandardStylesheetReference(String src, URL url) {
+  public StandardStylesheetReference(String src) {
     super(StandardLinkerContext.class, src);
   }
 }
diff --git a/dev/core/src/com/google/gwt/core/ext/typeinfo/CompilationUnitProvider.java b/dev/core/src/com/google/gwt/core/ext/typeinfo/CompilationUnitProvider.java
deleted file mode 100644
index 4d2aa8a..0000000
--- a/dev/core/src/com/google/gwt/core/ext/typeinfo/CompilationUnitProvider.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2008 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.core.ext.typeinfo;
-
-import com.google.gwt.core.ext.UnableToCompleteException;
-
-import java.util.Comparator;
-
-/**
- * Provides information about a single compilation unit on demand.
- */
-public interface CompilationUnitProvider {
-
-  Comparator<CompilationUnitProvider> LOCATION_COMPARATOR = new Comparator<CompilationUnitProvider>() {
-    public int compare(CompilationUnitProvider cups1,
-        CompilationUnitProvider cups2) {
-      String loc1 = cups1.getLocation();
-      String loc2 = cups2.getLocation();
-      return loc1.compareTo(loc2);
-    }
-  };
-
-  long getLastModified() throws UnableToCompleteException;
-
-  String getLocation();
-
-  /**
-   * Returns the, not <code>null</code>, name of the top level public type.
-   * 
-   */
-  String getMainTypeName();
-
-  String getPackageName();
-
-  char[] getSource() throws UnableToCompleteException;
-
-  boolean isTransient();
-}
diff --git a/dev/core/src/com/google/gwt/core/ext/typeinfo/JAbstractMethod.java b/dev/core/src/com/google/gwt/core/ext/typeinfo/JAbstractMethod.java
index 8d931c6..aa54e64 100644
--- a/dev/core/src/com/google/gwt/core/ext/typeinfo/JAbstractMethod.java
+++ b/dev/core/src/com/google/gwt/core/ext/typeinfo/JAbstractMethod.java
@@ -28,14 +28,6 @@
 
   private final Annotations annotations;
 
-  private int bodyEnd;
-
-  private int bodyStart;
-
-  private final int declEnd;
-
-  private final int declStart;
-
   private boolean isVarArgs = false;
 
   private final HasMetaData metaData = new MetaData();
@@ -52,10 +44,6 @@
 
   JAbstractMethod(JAbstractMethod srcMethod) {
     this.annotations = new Annotations(srcMethod.annotations);
-    this.bodyEnd = srcMethod.bodyEnd;
-    this.bodyStart = srcMethod.bodyStart;
-    this.declEnd = srcMethod.declEnd;
-    this.declStart = srcMethod.declStart;
     this.isVarArgs = srcMethod.isVarArgs;
     MetaData.copy(this, srcMethod.metaData);
     this.modifierBits = srcMethod.modifierBits;
@@ -63,21 +51,16 @@
   }
 
   // Only the builder can construct
-  JAbstractMethod(String name, int declStart, int declEnd, int bodyStart,
-      int bodyEnd,
+  JAbstractMethod(String name,
       Map<Class<? extends Annotation>, Annotation> declaredAnnotations,
       JTypeParameter[] jtypeParameters) {
     this.name = name;
-    this.declStart = declStart;
-    this.declEnd = declEnd;
-    this.bodyStart = bodyStart;
-    this.bodyEnd = bodyEnd;
     annotations = new Annotations();
     annotations.addAnnotations(declaredAnnotations);
-    
+
     if (jtypeParameters != null) {
       for (JTypeParameter jtypeParameter : jtypeParameters) {
-        addTypeParameter(jtypeParameter);            
+        addTypeParameter(jtypeParameter);
       }
     }
   }
@@ -108,22 +91,6 @@
     return annotations.getAnnotation(annotationClass);
   }
 
-  public int getBodyEnd() {
-    return bodyEnd;
-  }
-
-  public int getBodyStart() {
-    return bodyStart;
-  }
-
-  public int getDeclEnd() {
-    return declEnd;
-  }
-
-  public int getDeclStart() {
-    return declStart;
-  }
-
   /**
    * Gets the type in which this method or constructor was declared.
    */
@@ -281,8 +248,8 @@
     }
     return true;
   }
-  
+
   private void addTypeParameter(JTypeParameter typeParameter) {
     typeParams.add(typeParameter);
-  }      
+  }
 }
diff --git a/dev/core/src/com/google/gwt/core/ext/typeinfo/JAnnotationMethod.java b/dev/core/src/com/google/gwt/core/ext/typeinfo/JAnnotationMethod.java
index 4b74e4f..4896191 100644
--- a/dev/core/src/com/google/gwt/core/ext/typeinfo/JAnnotationMethod.java
+++ b/dev/core/src/com/google/gwt/core/ext/typeinfo/JAnnotationMethod.java
@@ -29,11 +29,9 @@
   private final Object defaultValue;
 
   public JAnnotationMethod(JClassType enclosingType, String name,
-      int declStart, int declEnd, int bodyStart, int bodyEnd,
       Object defaultValue,
       Map<Class<? extends Annotation>, Annotation> declaredAnnotations) {
-    super(enclosingType, name, declStart, declEnd, bodyStart, bodyEnd,
-        declaredAnnotations, null);
+    super(enclosingType, name, declaredAnnotations, null);
     this.defaultValue = defaultValue;
   }
 
diff --git a/dev/core/src/com/google/gwt/core/ext/typeinfo/JAnnotationType.java b/dev/core/src/com/google/gwt/core/ext/typeinfo/JAnnotationType.java
index fbec74e..912e20e 100644
--- a/dev/core/src/com/google/gwt/core/ext/typeinfo/JAnnotationType.java
+++ b/dev/core/src/com/google/gwt/core/ext/typeinfo/JAnnotationType.java
@@ -22,12 +22,11 @@
  */
 public class JAnnotationType extends JRealClassType {
 
-  public JAnnotationType(TypeOracle oracle, CompilationUnitProvider cup,
-      JPackage declaringPackage, JClassType enclosingType, boolean isLocalType,
-      String name, int declStart, int declEnd, int bodyStart, int bodyEnd,
+  public JAnnotationType(TypeOracle oracle, JPackage declaringPackage,
+      JClassType enclosingType, boolean isLocalType, String name,
       boolean isInterface) {
-    super(oracle, cup, declaringPackage, enclosingType, isLocalType, name,
-        declStart, declEnd, bodyStart, bodyEnd, isInterface);
+    super(oracle, declaringPackage, enclosingType, isLocalType, name,
+        isInterface);
   }
 
   @Override
diff --git a/dev/core/src/com/google/gwt/core/ext/typeinfo/JArrayType.java b/dev/core/src/com/google/gwt/core/ext/typeinfo/JArrayType.java
index f66cd10..f238f41 100644
--- a/dev/core/src/com/google/gwt/core/ext/typeinfo/JArrayType.java
+++ b/dev/core/src/com/google/gwt/core/ext/typeinfo/JArrayType.java
@@ -15,8 +15,6 @@
  */
 package com.google.gwt.core.ext.typeinfo;
 
-import com.google.gwt.core.ext.UnableToCompleteException;
-
 import java.lang.annotation.Annotation;
 import java.util.Map;
 
@@ -82,21 +80,6 @@
     return null;
   }
 
-  @Override
-  public int getBodyEnd() {
-    return 0;
-  }
-
-  @Override
-  public int getBodyStart() {
-    return 0;
-  }
-
-  @Override
-  public CompilationUnitProvider getCompilationUnit() {
-    return null;
-  }
-
   public JType getComponentType() {
     return componentType;
   }
@@ -113,16 +96,6 @@
   }
 
   @Override
-  public int getDeclEnd() {
-    return 0;
-  }
-
-  @Override
-  public int getDeclStart() {
-    return 0;
-  }
-
-  @Override
   public JClassType getEnclosingType() {
     return null;
   }
@@ -272,20 +245,6 @@
   }
 
   @Override
-  public String getTypeHash() throws UnableToCompleteException {
-    JType leafType = getLeafType();
-    JClassType leafClassType = leafType.isClassOrInterface();
-    if (leafClassType != null) {
-      return leafClassType.getTypeHash();
-    }
-
-    // Arrays of primitive types should have a stable hash
-    assert (leafType.isPrimitive() != null);
-
-    return leafType.getQualifiedSourceName();
-  }
-
-  @Override
   public boolean isAbstract() {
     return false;
   }
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 569e25d..eeb18ce 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
@@ -15,8 +15,6 @@
  */
 package com.google.gwt.core.ext.typeinfo;
 
-import com.google.gwt.core.ext.UnableToCompleteException;
-
 import java.lang.annotation.Annotation;
 import java.util.HashSet;
 import java.util.Map;
@@ -338,21 +336,11 @@
   public abstract <T extends Annotation> T getAnnotation(
       Class<T> annotationClass);
 
-  public abstract int getBodyEnd();
-
-  public abstract int getBodyStart();
-
-  public abstract CompilationUnitProvider getCompilationUnit();
-
   public abstract JConstructor getConstructor(JType[] paramTypes)
       throws NotFoundException;
 
   public abstract JConstructor[] getConstructors();
 
-  public abstract int getDeclEnd();
-
-  public abstract int getDeclStart();
-
   public abstract JClassType getEnclosingType();
 
   public abstract JClassType getErasedType();
@@ -412,8 +400,6 @@
 
   public abstract JClassType getSuperclass();
 
-  public abstract String getTypeHash() throws UnableToCompleteException;
-
   public abstract boolean isAbstract();
 
   public abstract boolean isAnnotationPresent(
diff --git a/dev/core/src/com/google/gwt/core/ext/typeinfo/JConstructor.java b/dev/core/src/com/google/gwt/core/ext/typeinfo/JConstructor.java
index a52b4e1..5767abc 100644
--- a/dev/core/src/com/google/gwt/core/ext/typeinfo/JConstructor.java
+++ b/dev/core/src/com/google/gwt/core/ext/typeinfo/JConstructor.java
@@ -24,18 +24,15 @@
 public class JConstructor extends JAbstractMethod {
   private final JClassType enclosingType;
 
-  public JConstructor(JClassType enclosingType, String name, int declStart,
-      int declEnd, int bodyStart, int bodyEnd) {
-    this(enclosingType, name, declStart, declEnd, bodyStart, bodyEnd, null, null);
+  public JConstructor(JClassType enclosingType, String name) {
+    this(enclosingType, name, null, null);
   }
 
-  public JConstructor(JClassType enclosingType, String name, int declStart,
-      int declEnd, int bodyStart, int bodyEnd,
+  public JConstructor(JClassType enclosingType, String name,
       Map<Class<? extends Annotation>, Annotation> declaredAnnotations,
       JTypeParameter[] jtypeParameters) {
-    super(name, declStart, declEnd, bodyStart, bodyEnd, declaredAnnotations, 
-        jtypeParameters);
-    
+    super(name, declaredAnnotations, jtypeParameters);
+
     this.enclosingType = enclosingType;
     enclosingType.addConstructor(this);
   }
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 ad16306..3f3e0d4 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
@@ -15,8 +15,6 @@
  */
 package com.google.gwt.core.ext.typeinfo;
 
-import com.google.gwt.core.ext.UnableToCompleteException;
-
 import java.lang.annotation.Annotation;
 import java.util.Map;
 
@@ -83,21 +81,6 @@
     return baseType;
   }
 
-  @Override
-  public int getBodyEnd() {
-    return baseType.getBodyEnd();
-  }
-
-  @Override
-  public int getBodyStart() {
-    return baseType.getBodyStart();
-  }
-
-  @Override
-  public CompilationUnitProvider getCompilationUnit() {
-    return baseType.getCompilationUnit();
-  }
-
   /**
    * Delegating types generally cannot be constructed.
    */
@@ -116,16 +99,6 @@
   }
 
   @Override
-  public int getDeclEnd() {
-    return baseType.getDeclEnd();
-  }
-
-  @Override
-  public int getDeclStart() {
-    return baseType.getDeclStart();
-  }
-
-  @Override
   public JClassType getEnclosingType() {
     // TODO this can be wrong if the enclosing type is a parameterized type. For
     // example, if a generic class has a non-static generic inner class.
@@ -228,11 +201,6 @@
   }
 
   @Override
-  public String getTypeHash() throws UnableToCompleteException {
-    return baseType.getTypeHash();
-  }
-
-  @Override
   public int hashCode() {
     return baseType.hashCode();
   }
diff --git a/dev/core/src/com/google/gwt/core/ext/typeinfo/JEnumType.java b/dev/core/src/com/google/gwt/core/ext/typeinfo/JEnumType.java
index 6097392..7989c03 100644
--- a/dev/core/src/com/google/gwt/core/ext/typeinfo/JEnumType.java
+++ b/dev/core/src/com/google/gwt/core/ext/typeinfo/JEnumType.java
@@ -37,12 +37,11 @@
    * @param bodyEnd
    * @param isInterface
    */
-  public JEnumType(TypeOracle oracle, CompilationUnitProvider cup,
-      JPackage declaringPackage, JClassType enclosingType, boolean isLocalType,
-      String name, int declStart, int declEnd, int bodyStart, int bodyEnd,
+  public JEnumType(TypeOracle oracle, JPackage declaringPackage,
+      JClassType enclosingType, boolean isLocalType, String name,
       boolean isInterface) {
-    super(oracle, cup, declaringPackage, enclosingType, isLocalType, name,
-        declStart, declEnd, bodyStart, bodyEnd, isInterface);
+    super(oracle, declaringPackage, enclosingType, isLocalType, name,
+        isInterface);
   }
 
   /**
diff --git a/dev/core/src/com/google/gwt/core/ext/typeinfo/JGenericType.java b/dev/core/src/com/google/gwt/core/ext/typeinfo/JGenericType.java
index d6a2173..a1286dc 100644
--- a/dev/core/src/com/google/gwt/core/ext/typeinfo/JGenericType.java
+++ b/dev/core/src/com/google/gwt/core/ext/typeinfo/JGenericType.java
@@ -27,16 +27,15 @@
 
   private final List<JTypeParameter> typeParams = new ArrayList<JTypeParameter>();
 
-  public JGenericType(TypeOracle oracle, CompilationUnitProvider cup,
-      JPackage declaringPackage, JClassType enclosingType, boolean isLocalType,
-      String name, int declStart, int declEnd, int bodyStart, int bodyEnd,
+  public JGenericType(TypeOracle oracle, JPackage declaringPackage,
+      JClassType enclosingType, boolean isLocalType, String name,
       boolean isInterface, JTypeParameter[] jtypeParameters) {
-    super(oracle, cup, declaringPackage, enclosingType, isLocalType, name,
-        declStart, declEnd, bodyStart, bodyEnd, isInterface);
-    
-     if (jtypeParameters != null) {
+    super(oracle, declaringPackage, enclosingType, isLocalType, name,
+        isInterface);
+
+    if (jtypeParameters != null) {
       for (JTypeParameter jtypeParameter : jtypeParameters) {
-        addTypeParameter(jtypeParameter);            
+        addTypeParameter(jtypeParameter);
       }
     }
   }
diff --git a/dev/core/src/com/google/gwt/core/ext/typeinfo/JMethod.java b/dev/core/src/com/google/gwt/core/ext/typeinfo/JMethod.java
index 3bcdb7d..8de3b43 100644
--- a/dev/core/src/com/google/gwt/core/ext/typeinfo/JMethod.java
+++ b/dev/core/src/com/google/gwt/core/ext/typeinfo/JMethod.java
@@ -27,18 +27,14 @@
 
   private JType returnType;
 
-  public JMethod(JClassType enclosingType, String name, int declStart,
-      int declEnd, int bodyStart, int bodyEnd) {
-    this(enclosingType, name, declStart, declEnd, bodyStart, bodyEnd, null,
-        null);
+  public JMethod(JClassType enclosingType, String name) {
+    this(enclosingType, name, null, null);
   }
 
-  public JMethod(JClassType enclosingType, String name, int declStart,
-      int declEnd, int bodyStart, int bodyEnd,
+  public JMethod(JClassType enclosingType, String name,
       Map<Class<? extends Annotation>, Annotation> declaredAnnotations,
       JTypeParameter[] jtypeParameters) {
-    super(name, declStart, declEnd, bodyStart, bodyEnd, declaredAnnotations,
-        jtypeParameters);
+    super(name, declaredAnnotations, jtypeParameters);
     this.enclosingType = enclosingType;
     enclosingType.addMethod(this);
   }
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 be91bc0..13329a8 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
@@ -15,10 +15,6 @@
  */
 package com.google.gwt.core.ext.typeinfo;
 
-import com.google.gwt.core.ext.UnableToCompleteException;
-import com.google.gwt.dev.util.Util;
-
-import java.io.UnsupportedEncodingException;
 import java.lang.annotation.Annotation;
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -35,18 +31,8 @@
 
   private final Annotations annotations = new Annotations();
 
-  private final int bodyEnd;
-
-  private final int bodyStart;
-
-  private final CompilationUnitProvider cup;
-
   private final JPackage declaringPackage;
 
-  private final int declEnd;
-
-  private final int declStart;
-
   private final JClassType enclosingType;
 
   private final List<JClassType> interfaces = new ArrayList<JClassType>();
@@ -55,8 +41,6 @@
 
   private final boolean isLocalType;
 
-  private String lazyHash;
-
   private String lazyQualifiedName;
 
   private final AbstractMembers members = new Members(this);
@@ -73,21 +57,14 @@
 
   private JClassType superclass;
 
-  public JRealClassType(TypeOracle oracle, CompilationUnitProvider cup,
-      JPackage declaringPackage, JClassType enclosingType, boolean isLocalType,
-      String name, int declStart, int declEnd, int bodyStart, int bodyEnd,
+  public JRealClassType(TypeOracle oracle, JPackage declaringPackage,
+      JClassType enclosingType, boolean isLocalType, String name,
       boolean isInterface) {
-    oracle.recordTypeInCompilationUnit(cup, this);
     this.oracle = oracle;
-    this.cup = cup;
     this.declaringPackage = declaringPackage;
     this.enclosingType = enclosingType;
     this.isLocalType = isLocalType;
     this.name = name;
-    this.declStart = declStart;
-    this.declEnd = declEnd;
-    this.bodyStart = bodyStart;
-    this.bodyEnd = bodyEnd;
     this.isInterface = isInterface;
     if (enclosingType == null) {
       // Add myself to my package.
@@ -155,18 +132,6 @@
     return annotations.getAnnotation(annotationClass);
   }
 
-  public int getBodyEnd() {
-    return bodyEnd;
-  }
-
-  public int getBodyStart() {
-    return bodyStart;
-  }
-
-  public CompilationUnitProvider getCompilationUnit() {
-    return cup;
-  }
-
   @Override
   public JConstructor getConstructor(JType[] paramTypes)
       throws NotFoundException {
@@ -178,14 +143,6 @@
     return members.getConstructors();
   }
 
-  public int getDeclEnd() {
-    return declEnd;
-  }
-
-  public int getDeclStart() {
-    return declStart;
-  }
-
   public JClassType getEnclosingType() {
     return enclosingType;
   }
@@ -298,19 +255,11 @@
     return superclass;
   }
 
-  public String getTypeHash() throws UnableToCompleteException {
-    if (lazyHash == null) {
-      char[] source = cup.getSource();
-      int length = declEnd - declStart + 1;
-      String s = new String(source, declStart, length);
-      try {
-        lazyHash = Util.computeStrongName(s.getBytes(Util.DEFAULT_ENCODING));
-      } catch (UnsupportedEncodingException e) {
-        // Details, details...
-        throw new UnableToCompleteException();
-      }
-    }
-    return lazyHash;
+  /**
+   * TODO: solve this better.
+   */
+  public void invalidate() {
+    oracle.invalidate(this);
   }
 
   public boolean isAbstract() {
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 8105085..fdc7a64 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
@@ -143,7 +143,7 @@
    * @return true if the type has been invalidated
    */
   private static boolean isInvalidatedTypeRecursive(JType type,
-      Set<JClassType> invalidTypes) {
+      Set<JRealClassType> invalidTypes) {
     if (type instanceof JParameterizedType) {
       JParameterizedType parameterizedType = (JParameterizedType) type;
       if (isInvalidatedTypeRecursive(parameterizedType.getBaseType(),
@@ -168,6 +168,8 @@
 
   private final Map<JType, JArrayType> arrayTypes = new IdentityHashMap<JType, JArrayType>();
 
+  private final Set<JRealClassType> invalidatedTypes = new HashSet<JRealClassType>();
+
   private JClassType javaLangObject;
 
   private final Map<String, JPackage> packages = new HashMap<String, JPackage>();
@@ -176,8 +178,6 @@
 
   private int reloadCount = 0;
 
-  private final Map<CompilationUnitProvider, JClassType[]> typesByCup = new IdentityHashMap<CompilationUnitProvider, JClassType[]>();
-
   private final Map<String, List<JWildcardType>> wildcardTypes = new HashMap<String, List<JWildcardType>>();
 
   public TypeOracle() {
@@ -267,6 +267,7 @@
    * <code>java.lang.Object</code>.
    */
   public JClassType getJavaLangObject() {
+    assert javaLangObject != null;
     return javaLangObject;
   }
 
@@ -347,7 +348,7 @@
         throw new IllegalArgumentException("Generic type '"
             + genericType.getParameterizedQualifiedSourceName()
             + "' is a non-static member type, but the enclosing type '"
-            + enclosingType.getQualifiedSourceName() 
+            + enclosingType.getQualifiedSourceName()
             + "' is not a parameterized or raw type");
       }
     }
@@ -459,22 +460,13 @@
       JPackage pkg = pkgs[i];
       JClassType[] types = pkg.getTypes();
       for (int j = 0; j < types.length; j++) {
-        JClassType type = types[j];
+        JRealClassType type = (JRealClassType) types[j];
         buildAllTypesImpl(allTypes, type);
       }
     }
     return allTypes.toArray(NO_JCLASSES);
   }
 
-  public JClassType[] getTypesInCompilationUnit(CompilationUnitProvider cup) {
-    JClassType[] types = typesByCup.get(cup);
-    if (types != null) {
-      return types;
-    } else {
-      return NO_JCLASSES;
-    }
-  }
-
   public JWildcardType getWildcardType(JWildcardType.BoundType boundType,
       JClassType typeBound) {
     JWildcardType wildcardType = new JWildcardType(boundType, typeBound);
@@ -528,6 +520,42 @@
   }
 
   /**
+   * Updates relationships within this type oracle. Should be called after any
+   * changes are made.
+   * 
+   * <p>
+   * Throws <code>TypeOracleException</code> thrown if fundamental baseline
+   * correctness criteria are violated, most notably the absence of
+   * "java.lang.Object"
+   * </p>
+   * 
+   * TODO: make this not public.
+   */
+  public void refresh(TreeLogger logger) throws NotFoundException {
+    if (javaLangObject == null) {
+      javaLangObject = findType("java.lang.Object");
+      if (javaLangObject == null) {
+        throw new NotFoundException("java.lang.Object");
+      }
+    }
+    computeHierarchyRelationships();
+    consumeTypeArgMetaData(logger);
+  }
+
+  /**
+   * Removes all types that are no longer valid.
+   * 
+   * TODO: make not public?
+   */
+  public void removeInvalidatedTypes() {
+    if (!invalidatedTypes.isEmpty()) {
+      invalidateTypes(invalidatedTypes);
+      invalidatedTypes.clear();
+      ++reloadCount;
+    }
+  }
+
+  /**
    * Convenience method to sort class types in a consistent way. Note that the
    * order is subject to change and is intended to generate an "aesthetically
    * pleasing" order rather than a computationally reliable order.
@@ -586,77 +614,16 @@
     });
   }
 
-  void incrementReloadCount() {
-    reloadCount++;
+  void invalidate(JRealClassType realClassType) {
+    invalidatedTypes.add(realClassType);
   }
 
-  /**
-   * Note, this method is called reflectively from the
-   * {@link com.google.gwt.dev.jdt.CacheManager#invalidateOnRefresh(TypeOracle)}.
-   * 
-   * @param cup compilation unit whose types will be invalidated
-   */
-  void invalidateTypesInCompilationUnit(CompilationUnitProvider cup) {
-    Set<JClassType> invalidTypes = new HashSet<JClassType>();
-    JClassType[] types = typesByCup.get(cup);
-    if (types == null) {
-      return;
-    }
-
-    for (int i = 0; i < types.length; i++) {
-      JClassType classTypeToInvalidate = types[i];
-      invalidTypes.add(classTypeToInvalidate);
-    }
-
-    typesByCup.remove(cup);
-
-    removeInvalidatedArrayTypes(invalidTypes);
-
-    removeInvalidatedParameterizedTypes(invalidTypes);
-
-    removeTypes(invalidTypes);
-  }
-
-  void recordTypeInCompilationUnit(CompilationUnitProvider cup, JClassType type) {
-    JClassType[] types = typesByCup.get(cup);
-    if (types == null) {
-      types = new JClassType[] {type};
-    } else {
-      JClassType[] temp = new JClassType[types.length + 1];
-      System.arraycopy(types, 0, temp, 0, types.length);
-      temp[types.length] = type;
-      types = temp;
-    }
-    typesByCup.put(cup, types);
-  }
-
-  /**
-   * Updates relationships within this type oracle. Should be called after any
-   * changes are made.
-   * 
-   * <p>
-   * Throws <code>TypeOracleException</code> thrown if fundamental baseline
-   * correctness criteria are violated, most notably the absence of
-   * "java.lang.Object"
-   * </p>
-   */
-  void refresh(TreeLogger logger) throws NotFoundException {
-    if (javaLangObject == null) {
-      javaLangObject = findType("java.lang.Object");
-      if (javaLangObject == null) {
-        throw new NotFoundException("java.lang.Object");
-      }
-    }
-    computeHierarchyRelationships();
-    consumeTypeArgMetaData(logger);
-  }
-
-  private void buildAllTypesImpl(Set<JClassType> allTypes, JClassType type) {
+  private void buildAllTypesImpl(Set<JClassType> allTypes, JRealClassType type) {
     boolean didAdd = allTypes.add(type);
     assert (didAdd);
     JClassType[] nestedTypes = type.getNestedTypes();
     for (int i = 0; i < nestedTypes.length; i++) {
-      JClassType nestedType = nestedTypes[i];
+      JRealClassType nestedType = (JRealClassType) nestedTypes[i];
       buildAllTypesImpl(allTypes, nestedType);
     }
   }
@@ -843,6 +810,12 @@
     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("[]")) {
@@ -991,7 +964,7 @@
    * 
    * @param invalidTypes set of types that have been invalidated.
    */
-  private void removeInvalidatedArrayTypes(Set<JClassType> invalidTypes) {
+  private void removeInvalidatedArrayTypes(Set<JRealClassType> invalidTypes) {
     arrayTypes.keySet().removeAll(invalidTypes);
   }
 
@@ -1001,7 +974,8 @@
    * 
    * @param invalidTypes set of types known to have been invalidated
    */
-  private void removeInvalidatedParameterizedTypes(Set<JClassType> invalidTypes) {
+  private void removeInvalidatedParameterizedTypes(
+      Set<JRealClassType> invalidTypes) {
     Iterator<List<JParameterizedType>> listIterator = parameterizedTypes.values().iterator();
 
     while (listIterator.hasNext()) {
@@ -1021,10 +995,8 @@
    * 
    * @param invalidTypes set of types to remove
    */
-  private void removeTypes(Set<JClassType> invalidTypes) {
-    Iterator<JClassType> iter = invalidTypes.iterator();
-
-    while (iter.hasNext()) {
+  private void removeTypes(Set<JRealClassType> invalidTypes) {
+    for (Iterator<JRealClassType> iter = invalidTypes.iterator(); iter.hasNext();) {
       JClassType classType = iter.next();
       JPackage pkg = classType.getPackage();
       if (pkg != null) {
@@ -1032,6 +1004,7 @@
       }
 
       classType.removeFromSupertypes();
+      iter.remove();
     }
   }
 }
diff --git a/dev/core/src/com/google/gwt/dev/GWTCompiler.java b/dev/core/src/com/google/gwt/dev/GWTCompiler.java
index a83801e..7505c9f 100644
--- a/dev/core/src/com/google/gwt/dev/GWTCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/GWTCompiler.java
@@ -22,9 +22,7 @@
 import com.google.gwt.core.ext.linker.SelectionProperty;
 import com.google.gwt.core.ext.linker.impl.StandardCompilationResult;
 import com.google.gwt.core.ext.linker.impl.StandardLinkerContext;
-import com.google.gwt.core.ext.typeinfo.CompilationUnitProvider;
 import com.google.gwt.core.ext.typeinfo.JClassType;
-import com.google.gwt.core.ext.typeinfo.TypeOracle;
 import com.google.gwt.dev.cfg.ModuleDef;
 import com.google.gwt.dev.cfg.ModuleDefLoader;
 import com.google.gwt.dev.cfg.Properties;
@@ -32,9 +30,9 @@
 import com.google.gwt.dev.cfg.PropertyPermutations;
 import com.google.gwt.dev.cfg.Rules;
 import com.google.gwt.dev.cfg.StaticPropertyOracle;
-import com.google.gwt.dev.jdt.CacheManager;
+import com.google.gwt.dev.javac.CompilationState;
+import com.google.gwt.dev.javac.CompilationUnit;
 import com.google.gwt.dev.jdt.RebindPermutationOracle;
-import com.google.gwt.dev.jdt.StandardSourceOracle;
 import com.google.gwt.dev.jdt.WebModeCompilerFrontEnd;
 import com.google.gwt.dev.jjs.JJSOptions;
 import com.google.gwt.dev.jjs.JavaToJavaScriptCompiler;
@@ -122,8 +120,8 @@
     private final Map<String, String> cache = new HashMap<String, String>();
 
     public CompilationRebindOracle(ArtifactSet generatorArtifacts) {
-      super(typeOracle, propOracle, module, rules, genDir,
-          generatorResourcesDir, cacheManager, generatorArtifacts);
+      super(compilationState, propOracle, module, rules, genDir,
+          generatorResourcesDir, generatorArtifacts);
     }
 
     /**
@@ -146,31 +144,8 @@
         String msg = "Rebind answer for '" + in + "' found in cache " + out;
         logger.log(TreeLogger.DEBUG, msg, null);
       }
-
-      if (recordDecision(in, out)) {
-        List<JClassType> genTypes = generatedTypesByResultTypeName.get(out);
-        if (genTypes != null) {
-          for (JClassType genType : genTypes) {
-            String sourceHash = genType.getTypeHash();
-            String genTypeName = genType.getQualifiedSourceName();
-            recordGeneratedTypeHash(genTypeName, sourceHash);
-          }
-        }
-      }
-
       return out;
     }
-
-    @SuppressWarnings("unused")
-    protected boolean recordDecision(String in, String out) {
-      // TODO(bobv): consider caching compilations again?
-      return false;
-    }
-
-    @SuppressWarnings("unused")
-    protected void recordGeneratedTypeHash(String typeName, String sourceHash) {
-      // TODO(bobv): consider caching compilations again?
-    }
   }
 
   private class DistillerRebindPermutationOracle implements
@@ -179,9 +154,8 @@
     private final StandardRebindOracle rebindOracle;
 
     public DistillerRebindPermutationOracle(ArtifactSet generatorArtifacts) {
-      rebindOracle = new StandardRebindOracle(typeOracle, propOracle, module,
-          rules, genDir, generatorResourcesDir, cacheManager,
-          generatorArtifacts) {
+      rebindOracle = new StandardRebindOracle(compilationState, propOracle,
+          module, rules, genDir, generatorResourcesDir, generatorArtifacts) {
 
         /**
          * Record generated types.
@@ -246,20 +220,7 @@
     System.exit(1);
   }
 
-  /**
-   * Returns the fully-qualified main type name of a compilation unit.
-   */
-  private static String makeTypeName(CompilationUnitProvider cup) {
-    if (cup.getPackageName().length() > 0) {
-      return cup.getPackageName() + "." + cup.getMainTypeName();
-    } else {
-      return cup.getMainTypeName();
-    }
-  }
-
-  private final CacheManager cacheManager;
-
-  private File generatorResourcesDir;
+  private CompilationState compilationState;
 
   private String[] declEntryPts;
 
@@ -267,6 +228,8 @@
 
   private Map<String, List<JClassType>> generatedTypesByResultTypeName = new HashMap<String, List<JClassType>>();
 
+  private File generatorResourcesDir;
+
   private JavaToJavaScriptCompiler jjs;
 
   private JJSOptions jjsOptions = new JJSOptions();
@@ -289,17 +252,9 @@
 
   private Rules rules;
 
-  private StandardSourceOracle sourceOracle;
-
-  private TypeOracle typeOracle;
-
   private boolean useGuiLogger;
 
   public GWTCompiler() {
-    this(null);
-  }
-
-  public GWTCompiler(CacheManager cacheManager) {
     registerHandler(new ArgHandlerLogLevel() {
       @Override
       public void setLogLevel(Type level) {
@@ -344,13 +299,12 @@
     });
 
     registerHandler(new ArgHandlerValidateOnlyFlag());
-
-    this.cacheManager = cacheManager;
   }
 
   public void distill(TreeLogger logger, ModuleDef moduleDef)
       throws UnableToCompleteException {
     this.module = moduleDef;
+    this.compilationState = moduleDef.getCompilationState();
 
     // Set up all the initial state.
     checkModule(logger);
@@ -369,18 +323,20 @@
     Util.recursiveDelete(generatorResourcesDir, true);
     generatorResourcesDir.mkdirs();
 
+    // TODO: All JDT checks now before even building TypeOracle?
+    compilationState.compile(logger);
+
     rules = module.getRules();
-    typeOracle = module.getTypeOracle(logger);
-    sourceOracle = new StandardSourceOracle(typeOracle);
     if (jjsOptions.isValidateOnly()) {
+      // TODO: revisit this.. do we even need to run JJS?
       logger.log(TreeLogger.INFO, "Validating compilation " + module.getName(),
           null);
       // Pretend that every single compilation unit is an entry point.
-      CompilationUnitProvider[] compilationUnits = module.getCompilationUnits();
-      declEntryPts = new String[compilationUnits.length];
-      for (int i = 0; i < compilationUnits.length; ++i) {
-        CompilationUnitProvider cup = compilationUnits[i];
-        declEntryPts[i] = makeTypeName(cup);
+      Set<CompilationUnit> compilationUnits = compilationState.getCompilationUnits();
+      declEntryPts = new String[compilationUnits.size()];
+      int i = 0;
+      for (CompilationUnit unit : compilationUnits) {
+        declEntryPts[i++] = unit.getTypeName();
       }
     } else {
       logger.log(TreeLogger.INFO, "Compiling module " + module.getName(), null);
@@ -393,7 +349,7 @@
     properties = module.getProperties();
     perms = new PropertyPermutations(properties);
     WebModeCompilerFrontEnd frontEnd = new WebModeCompilerFrontEnd(
-        sourceOracle, rebindPermOracle);
+        compilationState, rebindPermOracle);
     jjs = new JavaToJavaScriptCompiler(logger, frontEnd, declEntryPts,
         jjsOptions);
 
diff --git a/dev/core/src/com/google/gwt/dev/GWTShell.java b/dev/core/src/com/google/gwt/dev/GWTShell.java
index f22c3ef..61ba6ad 100644
--- a/dev/core/src/com/google/gwt/dev/GWTShell.java
+++ b/dev/core/src/com/google/gwt/dev/GWTShell.java
@@ -160,27 +160,6 @@
   }
 
   /**
-   * handles the -saveJsni command line flag.
-   */
-  protected class ArgHandlerSaveJsni extends ArgHandlerFlag {
-    @Override
-    public String getPurpose() {
-      return "Save generated JSNI source in the supplied gen directory (if any)";
-    }
-
-    @Override
-    public String getTag() {
-      return "-saveJsni";
-    }
-
-    @Override
-    public boolean setFlag() {
-      saveJsni = true;
-      return true;
-    }
-  }
-
-  /**
    * Handles the list of startup urls that can be passed on the command line.
    */
   protected class ArgHandlerStartupURLs extends ArgHandlerExtra {
@@ -403,8 +382,6 @@
 
   private boolean runTomcat = true;
 
-  private boolean saveJsni = false;
-
   private boolean started;
 
   private final List<String> startupUrls = new ArrayList<String>();
@@ -442,8 +419,6 @@
       }
     });
 
-    registerHandler(new ArgHandlerSaveJsni());
-
     if (!noURLs) {
       registerHandler(new ArgHandlerStartupURLs());
     }
@@ -644,7 +619,7 @@
    */
   protected void compile(TreeLogger logger, ModuleDef moduleDef)
       throws UnableToCompleteException {
-    GWTCompiler compiler = new GWTCompiler(moduleDef.getCacheManager());
+    GWTCompiler compiler = new GWTCompiler();
     compiler.setCompilerOptions(jjsOptions);
     compiler.setGenDir(genDir);
     compiler.setOutDir(outDir);
@@ -668,7 +643,7 @@
       TreeLogger logger, TypeOracle typeOracle, ModuleDef moduleDef,
       File genDir, File shellDir) {
     return new ShellModuleSpaceHost(logger, typeOracle, moduleDef, genDir,
-        shellDir, saveJsni);
+        shellDir);
   }
 
   /**
diff --git a/dev/core/src/com/google/gwt/dev/cfg/ModuleDef.java b/dev/core/src/com/google/gwt/dev/cfg/ModuleDef.java
index e716af6..9cddd07 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/ModuleDef.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/ModuleDef.java
@@ -20,22 +20,23 @@
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.linker.LinkerOrder;
 import com.google.gwt.core.ext.linker.LinkerOrder.Order;
-import com.google.gwt.core.ext.typeinfo.CompilationUnitProvider;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
-import com.google.gwt.dev.jdt.CacheManager;
-import com.google.gwt.dev.jdt.TypeOracleBuilder;
-import com.google.gwt.dev.jdt.URLCompilationUnitProvider;
+import com.google.gwt.dev.javac.CompilationState;
+import com.google.gwt.dev.javac.JavaSourceFile;
+import com.google.gwt.dev.javac.JavaSourceOracle;
+import com.google.gwt.dev.javac.impl.JavaSourceOracleImpl;
+import com.google.gwt.dev.resource.Resource;
+import com.google.gwt.dev.resource.impl.PathPrefix;
+import com.google.gwt.dev.resource.impl.PathPrefixSet;
+import com.google.gwt.dev.resource.impl.ResourceFilter;
+import com.google.gwt.dev.resource.impl.ResourceOracleImpl;
 import com.google.gwt.dev.util.Empty;
-import com.google.gwt.dev.util.FileOracle;
-import com.google.gwt.dev.util.FileOracleFactory;
 import com.google.gwt.dev.util.PerfLogger;
 import com.google.gwt.dev.util.Util;
-import com.google.gwt.dev.util.FileOracleFactory.FileFilter;
 
 import org.apache.tools.ant.types.ZipScanner;
 
 import java.io.File;
-import java.net.URL;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Comparator;
@@ -84,22 +85,17 @@
 
   private Class<? extends Linker> activePrimaryLinker;
 
-  private final ArrayList<URLCompilationUnitProvider> allCups = new ArrayList<URLCompilationUnitProvider>();
-
-  private final Set<String> alreadySeenFiles = new HashSet<String>();
-
-  private final CacheManager cacheManager = new CacheManager(".gwt-cache",
-      new TypeOracle());
-
-  private CompilationUnitProvider[] cups = new CompilationUnitProvider[0];
+  private CompilationState compilationState;
 
   private final List<String> entryPointTypeNames = new ArrayList<String>();
 
   private final Set<File> gwtXmlFiles = new HashSet<File>();
 
-  private FileOracle lazyPublicOracle;
+  private JavaSourceOracle lazyJavaSourceOracle;
 
-  private FileOracle lazySourceOracle;
+  private ResourceOracleImpl lazyPublicOracle;
+
+  private ResourceOracleImpl lazySourceOracle;
 
   private TypeOracle lazyTypeOracle;
 
@@ -117,7 +113,7 @@
 
   private final Properties properties = new Properties();
 
-  private final FileOracleFactory publicPathEntries = new FileOracleFactory();
+  private PathPrefixSet publicPrefixSet = new PathPrefixSet();
 
   private final Rules rules = new Rules();
 
@@ -125,7 +121,7 @@
 
   private final Map<String, String> servletClassNamesByPath = new HashMap<String, String>();
 
-  private final FileOracleFactory sourcePathEntries = new FileOracleFactory();
+  private PathPrefixSet sourcePrefixSet = new PathPrefixSet();
 
   private final Styles styles = new Styles();
 
@@ -164,12 +160,11 @@
     final ZipScanner scanner = getScanner(includeList, excludeList,
         defaultExcludes, caseSensitive);
 
-    // index from this package down
-    publicPathEntries.addRootPackage(publicPackage, new FileFilter() {
-      public boolean accept(String name) {
-        return scanner.match(name);
+    publicPrefixSet.add(new PathPrefix(publicPackage, new ResourceFilter() {
+      public boolean allows(String path) {
+        return scanner.match(path);
       }
-    });
+    }, true));
   }
 
   public void addSourcePackage(String sourcePackage, String[] includeList,
@@ -195,17 +190,15 @@
     final ZipScanner scanner = getScanner(includeList, excludeList,
         defaultExcludes, caseSensitive);
 
-    final FileFilter sourceFileFilter = new FileFilter() {
-      public boolean accept(String name) {
-        return scanner.match(name);
+    ResourceFilter sourceFileFilter = new ResourceFilter() {
+      public boolean allows(String path) {
+        return path.endsWith(".java") && scanner.match(path);
       }
     };
 
-    if (isSuperSource) {
-      sourcePathEntries.addRootPackage(sourcePackage, sourceFileFilter);
-    } else {
-      sourcePathEntries.addPackage(sourcePackage, sourceFileFilter);
-    }
+    PathPrefix pathPrefix = new PathPrefix(sourcePackage, sourceFileFilter,
+        isSuperSource);
+    sourcePrefixSet.add(pathPrefix);
   }
 
   public void addSuperSourcePackage(String superSourcePackage,
@@ -223,13 +216,12 @@
     linkerTypesByName.put(name, linker);
   }
 
-  public synchronized URL findPublicFile(String partialPath) {
-    return lazyPublicOracle.find(partialPath);
+  public synchronized Resource findPublicFile(String partialPath) {
+    return lazyPublicOracle.getResourceMap().get(partialPath);
   }
 
   public synchronized String findServletForPath(String actual) {
     // Walk in backwards sorted order to find the longest path match first.
-    //
     Set<Entry<String, String>> entrySet = servletClassNamesByPath.entrySet();
     Entry<String, String>[] entries = Util.toArray(Entry.class, entrySet);
     Arrays.sort(entries, REV_NAME_CMP);
@@ -257,11 +249,7 @@
   }
 
   public String[] getAllPublicFiles() {
-    return lazyPublicOracle.getAllFiles();
-  }
-
-  public CacheManager getCacheManager() {
-    return cacheManager;
+    return lazyPublicOracle.getPathNames().toArray(Empty.STRINGS);
   }
 
   /**
@@ -272,8 +260,8 @@
     return name;
   }
 
-  public synchronized CompilationUnitProvider[] getCompilationUnits() {
-    return cups;
+  public CompilationState getCompilationState() {
+    return compilationState;
   }
 
   public synchronized String[] getEntryPointTypeNames() {
@@ -332,7 +320,8 @@
   public synchronized TypeOracle getTypeOracle(TreeLogger logger)
       throws UnableToCompleteException {
     if (lazyTypeOracle == null) {
-      lazyTypeOracle = createTypeOracle(logger);
+      lazyTypeOracle = compilationState.getTypeOracle();
+      updateTypeOracle(logger);
     }
     return lazyTypeOracle;
   }
@@ -363,11 +352,17 @@
   public synchronized void refresh(TreeLogger logger)
       throws UnableToCompleteException {
     PerfLogger.start("ModuleDef.refresh");
-    cacheManager.invalidateVolatileFiles();
-    normalize(logger);
-    lazyTypeOracle = createTypeOracle(logger);
-    Util.invokeInaccessableMethod(TypeOracle.class, "incrementReloadCount",
-        new Class[] {}, lazyTypeOracle, new Object[] {});
+
+    // Refresh the public path.
+    lazyPublicOracle.refresh(logger);
+
+    // Refreshes source internally.
+    compilationState.refresh();
+
+    // Refresh type oracle if needed.
+    if (lazyTypeOracle != null) {
+      updateTypeOracle(logger);
+    }
     PerfLogger.end();
   }
 
@@ -388,8 +383,8 @@
    * @param partialPath
    * @return
    */
-  synchronized URL findSourceFile(String partialPath) {
-    return lazySourceOracle.find(partialPath);
+  synchronized JavaSourceFile findSourceFile(String partialPath) {
+    return lazyJavaSourceOracle.getSourceMap().get(partialPath);
   }
 
   /**
@@ -424,111 +419,32 @@
       }
     }
 
-    // Create the source path.
-    //
-    TreeLogger branch = Messages.SOURCE_PATH_LOCATIONS.branch(logger, null);
-    lazySourceOracle = sourcePathEntries.create(branch);
+    // Create the public path.
+    TreeLogger branch = Messages.PUBLIC_PATH_LOCATIONS.branch(logger, null);
+    // lazyPublicOracle = publicPathEntries.create(branch);
+    if (lazyPublicOracle == null) {
+      lazyPublicOracle = new ResourceOracleImpl(branch);
+      lazyPublicOracle.setPathPrefixes(publicPrefixSet);
+    }
+    lazyPublicOracle.refresh(branch);
 
-    if (lazySourceOracle.isEmpty()) {
+    // Create the source path.
+    branch = Messages.SOURCE_PATH_LOCATIONS.branch(logger, null);
+    lazySourceOracle = new ResourceOracleImpl(branch);
+    lazySourceOracle.setPathPrefixes(sourcePrefixSet);
+    lazySourceOracle.refresh(branch);
+    if (lazySourceOracle.getResources().isEmpty()) {
       branch.log(TreeLogger.WARN,
           "No source path entries; expect subsequent failures", null);
-    } else {
-      // Create the CUPs
-      String[] allFiles = lazySourceOracle.getAllFiles();
-      Set<String> files = new HashSet<String>();
-      files.addAll(Arrays.asList(allFiles));
-      files.removeAll(alreadySeenFiles);
-      for (Iterator<String> iter = files.iterator(); iter.hasNext();) {
-        String fileName = iter.next();
-        int pos = fileName.lastIndexOf('/');
-        String packageName;
-        if (pos >= 0) {
-          packageName = fileName.substring(0, pos);
-          packageName = packageName.replace('/', '.');
-        } else {
-          packageName = "";
-        }
-        URL url = lazySourceOracle.find(fileName);
-        allCups.add(new URLCompilationUnitProvider(url, packageName));
-      }
-      alreadySeenFiles.addAll(files);
-      this.cups = allCups.toArray(this.cups);
     }
+    lazyJavaSourceOracle = new JavaSourceOracleImpl(lazySourceOracle);
 
-    // Create the public path.
-    //
-    branch = Messages.PUBLIC_PATH_LOCATIONS.branch(logger, null);
-    lazyPublicOracle = publicPathEntries.create(branch);
+    // Create the compilation state.
+    compilationState = new CompilationState(lazyJavaSourceOracle);
 
     PerfLogger.end();
   }
 
-  private TypeOracle createTypeOracle(TreeLogger logger)
-      throws UnableToCompleteException {
-
-    TypeOracle newTypeOracle = null;
-
-    try {
-      String msg = "Analyzing source in module '" + getName() + "'";
-      TreeLogger branch = logger.branch(TreeLogger.TRACE, msg, null);
-      long before = System.currentTimeMillis();
-      TypeOracleBuilder builder = new TypeOracleBuilder(getCacheManager());
-      CompilationUnitProvider[] currentCups = getCompilationUnits();
-      Arrays.sort(currentCups, CompilationUnitProvider.LOCATION_COMPARATOR);
-
-      TreeLogger subBranch = null;
-      if (branch.isLoggable(TreeLogger.DEBUG)) {
-        subBranch = branch.branch(TreeLogger.DEBUG,
-            "Adding compilation units...", null);
-      }
-
-      for (int i = 0; i < currentCups.length; i++) {
-        CompilationUnitProvider cup = currentCups[i];
-        if (subBranch != null) {
-          subBranch.log(TreeLogger.DEBUG, cup.getLocation(), null);
-        }
-        builder.addCompilationUnit(currentCups[i]);
-      }
-
-      if (lazyTypeOracle != null) {
-        cacheManager.invalidateOnRefresh(lazyTypeOracle);
-      }
-
-      newTypeOracle = builder.build(branch);
-      long after = System.currentTimeMillis();
-      branch.log(TreeLogger.TRACE, "Finished in " + (after - before) + " ms",
-          null);
-    } catch (UnableToCompleteException e) {
-      logger.log(TreeLogger.ERROR, "Failed to complete analysis", null);
-      throw new UnableToCompleteException();
-    }
-
-    // Sanity check the seed types and don't even start it they're missing.
-    //
-    boolean seedTypesMissing = false;
-    if (newTypeOracle.findType("java.lang.Object") == null) {
-      Util.logMissingTypeErrorWithHints(logger, "java.lang.Object");
-      seedTypesMissing = true;
-    } else {
-      TreeLogger branch = logger.branch(TreeLogger.TRACE,
-          "Finding entry point classes", null);
-      String[] typeNames = getEntryPointTypeNames();
-      for (int i = 0; i < typeNames.length; i++) {
-        String typeName = typeNames[i];
-        if (newTypeOracle.findType(typeName) == null) {
-          Util.logMissingTypeErrorWithHints(branch, typeName);
-          seedTypesMissing = true;
-        }
-      }
-    }
-
-    if (seedTypesMissing) {
-      throw new UnableToCompleteException();
-    }
-
-    return newTypeOracle;
-  }
-
   private ZipScanner getScanner(String[] includeList, String[] excludeList,
       boolean defaultExcludes, boolean caseSensitive) {
     /*
@@ -552,4 +468,37 @@
     return scanner;
   }
 
+  private void updateTypeOracle(TreeLogger logger)
+      throws UnableToCompleteException {
+    PerfLogger.start("ModuleDef.updateTypeOracle");
+    TreeLogger branch = logger.branch(TreeLogger.TRACE,
+        "Compiling Java source files in module '" + getName() + "'");
+    compilationState.compile(branch);
+    PerfLogger.end();
+
+    TypeOracle typeOracle = compilationState.getTypeOracle();
+
+    // Sanity check the seed types and don't even start it they're missing.
+    boolean seedTypesMissing = false;
+    if (typeOracle.findType("java.lang.Object") == null) {
+      Util.logMissingTypeErrorWithHints(logger, "java.lang.Object");
+      seedTypesMissing = true;
+    } else {
+      branch = logger.branch(TreeLogger.TRACE, "Finding entry point classes",
+          null);
+      String[] typeNames = getEntryPointTypeNames();
+      for (int i = 0; i < typeNames.length; i++) {
+        String typeName = typeNames[i];
+        if (typeOracle.findType(typeName) == null) {
+          Util.logMissingTypeErrorWithHints(branch, typeName);
+          seedTypesMissing = true;
+        }
+      }
+    }
+
+    if (seedTypesMissing) {
+      throw new UnableToCompleteException();
+    }
+  }
+
 }
diff --git a/dev/core/src/com/google/gwt/dev/cfg/ModuleDefLoader.java b/dev/core/src/com/google/gwt/dev/cfg/ModuleDefLoader.java
index 5cb3086..f61e01c 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/ModuleDefLoader.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/ModuleDefLoader.java
@@ -101,7 +101,7 @@
    */
   public static ModuleDef loadFromClassPath(TreeLogger logger, String moduleName)
       throws UnableToCompleteException {
-    return loadFromClassPath(logger, moduleName, true);
+    return loadFromClassPath(logger, moduleName, false);
   }
 
   /**
diff --git a/dev/core/src/com/google/gwt/dev/cfg/PublicOracle.java b/dev/core/src/com/google/gwt/dev/cfg/PublicOracle.java
index 3709608..c88f852 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/PublicOracle.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/PublicOracle.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007 Google Inc.
+ * Copyright 2008 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
@@ -15,7 +15,7 @@
  */
 package com.google.gwt.dev.cfg;
 
-import java.net.URL;
+import com.google.gwt.dev.resource.Resource;
 
 /**
  * Abstracts the process of querying for public files.
@@ -28,7 +28,7 @@
    * @param partialPath a file path relative to the root of any public package
    * @return the url of the file, or <code>null</code> if no such file exists
    */
-  URL findPublicFile(String partialPath);
+  Resource findPublicFile(String partialPath);
 
   /**
    * Returns all available public files.
diff --git a/dev/core/src/com/google/gwt/dev/jdt/AnnotationProxyFactory.java b/dev/core/src/com/google/gwt/dev/javac/AnnotationProxyFactory.java
similarity index 98%
rename from dev/core/src/com/google/gwt/dev/jdt/AnnotationProxyFactory.java
rename to dev/core/src/com/google/gwt/dev/javac/AnnotationProxyFactory.java
index d3ae321..28627e7 100644
--- a/dev/core/src/com/google/gwt/dev/jdt/AnnotationProxyFactory.java
+++ b/dev/core/src/com/google/gwt/dev/javac/AnnotationProxyFactory.java
@@ -13,7 +13,7 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.dev.jdt;
+package com.google.gwt.dev.javac;
 
 import java.lang.annotation.Annotation;
 import java.lang.reflect.InvocationHandler;
@@ -284,7 +284,7 @@
     AnnotationProxyInvocationHandler annotationInvocationHandler = new AnnotationProxyInvocationHandler(
         identifierToValue, annotationClass);
     Annotation proxy = (Annotation) Proxy.newProxyInstance(
-        AnnotationProxyFactory.class.getClassLoader(), new Class<?>[] {
+        Thread.currentThread().getContextClassLoader(), new Class<?>[] {
             java.lang.annotation.Annotation.class, annotationClass},
         annotationInvocationHandler);
     annotationInvocationHandler.setProxy(proxy);
diff --git a/dev/core/src/com/google/gwt/dev/jdt/BinaryTypeReferenceRestrictionsChecker.java b/dev/core/src/com/google/gwt/dev/javac/BinaryTypeReferenceRestrictionsChecker.java
similarity index 85%
rename from dev/core/src/com/google/gwt/dev/jdt/BinaryTypeReferenceRestrictionsChecker.java
rename to dev/core/src/com/google/gwt/dev/javac/BinaryTypeReferenceRestrictionsChecker.java
index 8d4382a..3fc0ba4 100644
--- a/dev/core/src/com/google/gwt/dev/jdt/BinaryTypeReferenceRestrictionsChecker.java
+++ b/dev/core/src/com/google/gwt/dev/javac/BinaryTypeReferenceRestrictionsChecker.java
@@ -13,7 +13,9 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.dev.jdt;
+package com.google.gwt.dev.javac;
+
+import com.google.gwt.dev.jdt.TypeRefVisitor;
 
 import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
 import org.eclipse.jdt.internal.compiler.ast.Expression;
@@ -33,13 +35,13 @@
  * Check a {@link CompilationUnitDeclaration} for references to binary types
  * outside the context of an annotation.
  */
-class BinaryTypeReferenceRestrictionsChecker {
+public class BinaryTypeReferenceRestrictionsChecker {
   /**
    * Records the location from which a {@link BinaryTypeBinding} is referenced.
    */
   static class BinaryTypeReferenceSite {
-    private final Expression expression;
     private final BinaryTypeBinding binaryTypeBinding;
+    private final Expression expression;
 
     BinaryTypeReferenceSite(Expression expression,
         BinaryTypeBinding binaryTypeBinding) {
@@ -107,17 +109,24 @@
    * is reported against the {@link CompilationUnitDeclaration} for the first
    * instance of each unique {@link BinaryTypeBinding}.
    */
-  public static void check(CompilationUnitDeclaration cud) {
-    List<BinaryTypeReferenceSite> binaryTypeReferenceSites = findInvalidBinaryTypeReferenceSites(cud);
-    Set<BinaryTypeBinding> invalidBindaryTypeBindings = new HashSet<BinaryTypeBinding>();
+  public static void check(CompilationUnitDeclaration cud,
+      Set<String> validBinaryTypeNames) {
+    List<BinaryTypeReferenceSite> binaryTypeReferenceSites = findAllBinaryTypeReferenceSites(cud);
+    Set<BinaryTypeBinding> alreadySeenTypeBindings = new HashSet<BinaryTypeBinding>();
 
     for (BinaryTypeReferenceSite binaryTypeReferenceSite : binaryTypeReferenceSites) {
       BinaryTypeBinding binaryTypeBinding = binaryTypeReferenceSite.getBinaryTypeBinding();
-      if (invalidBindaryTypeBindings.contains(binaryTypeBinding)) {
+      if (alreadySeenTypeBindings.contains(binaryTypeBinding)) {
         continue;
       }
-      invalidBindaryTypeBindings.add(binaryTypeBinding);
+      alreadySeenTypeBindings.add(binaryTypeBinding);
 
+      String binaryName = String.valueOf(binaryTypeBinding.constantPoolName());
+      if (validBinaryTypeNames.contains(binaryName)) {
+        // This binary name is valid; it is a reference to a unit that was
+        // compiled in a previous JDT run.
+        continue;
+      }
       String qualifiedTypeName = binaryTypeBinding.debugName();
       String error = formatBinaryTypeRefErrorMessage(qualifiedTypeName);
 
@@ -127,7 +136,7 @@
     }
   }
 
-  static List<BinaryTypeReferenceSite> findInvalidBinaryTypeReferenceSites(
+  static List<BinaryTypeReferenceSite> findAllBinaryTypeReferenceSites(
       CompilationUnitDeclaration cud) {
     List<BinaryTypeReferenceSite> binaryTypeReferenceSites = new ArrayList<BinaryTypeReferenceSite>();
     BinaryTypeReferenceVisitor binaryTypeReferenceVisitor = new BinaryTypeReferenceVisitor(
diff --git a/dev/core/src/com/google/gwt/dev/javac/CompilationState.java b/dev/core/src/com/google/gwt/dev/javac/CompilationState.java
new file mode 100644
index 0000000..bdf9c2c
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/javac/CompilationState.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2008 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.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.dev.javac.CompilationUnit.State;
+import com.google.gwt.dev.javac.impl.SourceFileCompilationUnit;
+import com.google.gwt.dev.js.ast.JsProgram;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Encapsulates the state of active compilation units in a particular module.
+ * State is accumulated throughout the life cycle of the containing module and
+ * may be invalidated at certain times and recomputed.
+ */
+public class CompilationState {
+  protected final Map<String, CompilationUnit> unitMap = new HashMap<String, CompilationUnit>();
+  private Set<JavaSourceFile> cachedSourceFiles = Collections.emptySet();
+  private Map<String, CompiledClass> exposedClassFileMap = null;
+  private final Map<String, CompilationUnit> exposedUnitMap = Collections.unmodifiableMap(unitMap);
+  private Set<CompilationUnit> exposedUnits = Collections.emptySet();
+  private final TypeOracleMediator mediator = new TypeOracleMediator();
+  private final JavaSourceOracle sourceOracle;
+
+  /**
+   * Construct a new {@link CompilationState}.
+   * 
+   * @param sourceOracle an oracle used to retrieve source code and check for
+   *          changes in the underlying source code base
+   */
+  public CompilationState(JavaSourceOracle sourceOracle) {
+    this.sourceOracle = sourceOracle;
+    refresh();
+  }
+
+  public void addGeneratedCompilationUnit(CompilationUnit unit) {
+    String typeName = unit.getTypeName();
+    assert (!unitMap.containsKey(typeName));
+    unitMap.put(typeName, unit);
+    updateExposedUnits();
+  }
+
+  /**
+   * Compile all units and updates all internal state. Invalidate any units with
+   * compile errors.
+   */
+  public void compile(TreeLogger logger) throws UnableToCompleteException {
+    JdtCompiler.compile(getCompilationUnits());
+    CompilationUnitInvalidator.validateCompilationUnits(getCompilationUnits(),
+        getClassFileMap());
+
+    // TODO: Move into validation & log errors?
+    JsniCollector.collectJsniMethods(logger, getCompilationUnits(),
+        new JsProgram());
+
+    CompilationUnitInvalidator.invalidateUnitsWithErrors(logger,
+        getCompilationUnits());
+
+    mediator.refresh(logger, getCompilationUnits());
+
+    // Any surviving units are now checked.
+    for (CompilationUnit unit : getCompilationUnits()) {
+      if (unit.getState() == State.COMPILED) {
+        unit.setState(State.CHECKED);
+      }
+    }
+
+    updateExposedUnits();
+  }
+
+  /**
+   * Returns a map of all compiled classes by binary name.
+   */
+  public Map<String, CompiledClass> getClassFileMap() {
+    if (exposedClassFileMap == null) {
+      HashMap<String, CompiledClass> classFileMap = new HashMap<String, CompiledClass>();
+      for (CompilationUnit unit : getCompilationUnits()) {
+        if (unit.isCompiled()) {
+          for (CompiledClass compiledClass : unit.getCompiledClasses()) {
+            classFileMap.put(compiledClass.getBinaryName(), compiledClass);
+          }
+        }
+      }
+      exposedClassFileMap = Collections.unmodifiableMap(classFileMap);
+    }
+    return exposedClassFileMap;
+  }
+
+  /**
+   * Returns an unmodifiable view of the set of compilation units, mapped by the
+   * main type's qualified source name.
+   */
+  public Map<String, CompilationUnit> getCompilationUnitMap() {
+    return exposedUnitMap;
+  }
+
+  /**
+   * Returns an unmodifiable view of the set of compilation units.
+   */
+  public Set<CompilationUnit> getCompilationUnits() {
+    return exposedUnits;
+  }
+
+  public TypeOracle getTypeOracle() {
+    return mediator.getTypeOracle();
+  }
+
+  /**
+   * 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?
+   */
+  public void refresh() {
+    // 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);
+        it.remove();
+      }
+    }
+
+    refreshFromSourceOracle();
+    // Don't log about invalidated units via refresh.
+    CompilationUnitInvalidator.invalidateUnitsWithInvalidRefs(TreeLogger.NULL,
+        getCompilationUnits());
+    updateExposedUnits();
+  }
+
+  private void refreshFromSourceOracle() {
+    // See if the source oracle has changed.
+    Set<JavaSourceFile> newSourceFiles = sourceOracle.getSourceFiles();
+    if (cachedSourceFiles == newSourceFiles) {
+      return;
+    }
+
+    // Divide resources into changed and unchanged.
+    Set<JavaSourceFile> unchanged = new HashSet<JavaSourceFile>(
+        cachedSourceFiles);
+    unchanged.retainAll(newSourceFiles);
+
+    Set<JavaSourceFile> changed = new HashSet<JavaSourceFile>(newSourceFiles);
+    changed.removeAll(unchanged);
+
+    // First remove any stale units.
+    for (Iterator<CompilationUnit> it = unitMap.values().iterator(); it.hasNext();) {
+      CompilationUnit unit = it.next();
+      SourceFileCompilationUnit sourceFileUnit = (SourceFileCompilationUnit) unit;
+      if (!unchanged.contains(sourceFileUnit.getSourceFile())) {
+        unit.setState(State.FRESH);
+        it.remove();
+      }
+    }
+
+    // Then add any new source files.
+    for (JavaSourceFile newSourceFile : changed) {
+      String typeName = newSourceFile.getTypeName();
+      assert (!unitMap.containsKey(typeName));
+      unitMap.put(typeName, new SourceFileCompilationUnit(newSourceFile));
+    }
+
+    // Record the update.
+    cachedSourceFiles = newSourceFiles;
+  }
+
+  private void updateExposedUnits() {
+    exposedUnits = Collections.unmodifiableSet(new HashSet<CompilationUnit>(
+        unitMap.values()));
+    exposedClassFileMap = null;
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/javac/CompilationUnit.java b/dev/core/src/com/google/gwt/dev/javac/CompilationUnit.java
new file mode 100644
index 0000000..e4e723a
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/javac/CompilationUnit.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright 2008 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.dev.jdt.TypeRefVisitor;
+
+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.TypeDeclaration;
+import org.eclipse.jdt.internal.compiler.lookup.BlockScope;
+import org.eclipse.jdt.internal.compiler.lookup.ClassScope;
+import org.eclipse.jdt.internal.compiler.lookup.CompilationUnitScope;
+import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Encapsulates the state of a single active compilation unit in a particular
+ * module. State is accumulated throughout the life cycle of the containing
+ * module and may be invalidated at certain times and recomputed.
+ */
+public abstract class CompilationUnit {
+
+  enum State {
+    COMPILED, CHECKED, ERROR, FRESH
+  }
+
+  private class FindTypesInCud extends ASTVisitor {
+    Map<SourceTypeBinding, CompiledClass> map = new IdentityHashMap<SourceTypeBinding, CompiledClass>();
+
+    public Set<CompiledClass> getClasses() {
+      return new HashSet<CompiledClass>(map.values());
+    }
+
+    @Override
+    public boolean visit(TypeDeclaration typeDecl, BlockScope scope) {
+      CompiledClass enclosingClass = map.get(typeDecl.binding.enclosingType());
+      assert (enclosingClass != null);
+      /*
+       * Weird case: if JDT determines that this local class is totally
+       * uninstantiable, it won't bother allocating a local name.
+       */
+      if (typeDecl.binding.constantPoolName() != null) {
+        CompiledClass newClass = new CompiledClass(typeDecl, enclosingClass);
+        map.put(typeDecl.binding, newClass);
+      }
+      return true;
+    }
+
+    @Override
+    public boolean visit(TypeDeclaration typeDecl, ClassScope scope) {
+      CompiledClass enclosingClass = map.get(typeDecl.binding.enclosingType());
+      assert (enclosingClass != null);
+      CompiledClass newClass = new CompiledClass(typeDecl, enclosingClass);
+      map.put(typeDecl.binding, newClass);
+      return true;
+    }
+
+    @Override
+    public boolean visit(TypeDeclaration typeDecl, CompilationUnitScope scope) {
+      assert (typeDecl.binding.enclosingType() == null);
+      CompiledClass newClass = new CompiledClass(typeDecl, null);
+      map.put(typeDecl.binding, newClass);
+      return true;
+    }
+  }
+
+  private static Set<String> computeFileNameRefs(CompilationUnitDeclaration cud) {
+    final Set<String> result = new HashSet<String>();
+    cud.traverse(new TypeRefVisitor() {
+      @Override
+      protected void onTypeRef(SourceTypeBinding referencedType,
+          CompilationUnitDeclaration unitOfReferrer) {
+        // Map the referenced type to the target compilation unit file.
+        result.add(String.valueOf(referencedType.getFileName()));
+      }
+    }, cud.scope);
+    return result;
+  }
+
+  private CompilationUnitDeclaration cud;
+  private CategorizedProblem[] errors;
+  private Set<CompiledClass> exposedCompiledClasses;
+  private Set<String> fileNameRefs;
+  private State state = State.FRESH;
+
+  /**
+   * Overridden to finalize; always returns object identity.
+   */
+  @Override
+  public final boolean equals(Object obj) {
+    return super.equals(obj);
+  }
+
+  /**
+   * Returns the user-relevant location of the source file. No programmatic
+   * assumptions should be made about the return value.
+   */
+  public abstract String getDisplayLocation();
+
+  /**
+   * Returns the source code for this unit.
+   */
+  public abstract String getSource();
+
+  /**
+   * Returns the fully-qualified name of the top level public type.
+   */
+  public abstract String getTypeName();
+
+  /**
+   * Overridden to finalize; always returns identity hash code.
+   */
+  @Override
+  public final int hashCode() {
+    return super.hashCode();
+  }
+
+  /**
+   * Returns <code>true</code> if this unit is compiled and valid.
+   */
+  public boolean isCompiled() {
+    return state == State.COMPILED || state == State.CHECKED;
+  }
+
+  /**
+   * Returns <code>true</code> if this unit was generated by a
+   * {@link com.google.gwt.core.ext.Generator}.
+   */
+  public abstract boolean isGenerated();
+
+  /**
+   * Overridden to finalize; always returns {@link #getDisplayLocation()}.
+   */
+  public final String toString() {
+    return getDisplayLocation();
+  }
+
+  /**
+   * Called when this unit no longer needs to keep an internal cache of its
+   * source.
+   */
+  protected void dumpSource() {
+  }
+
+  /**
+   * If compiled, returns all contained classes; otherwise returns
+   * <code>null</code>.
+   */
+  Set<CompiledClass> getCompiledClasses() {
+    if (!isCompiled()) {
+      return null;
+    }
+    if (exposedCompiledClasses == null) {
+      FindTypesInCud typeFinder = new FindTypesInCud();
+      cud.traverse(typeFinder, cud.scope);
+      Set<CompiledClass> compiledClasses = typeFinder.getClasses();
+      exposedCompiledClasses = Collections.unmodifiableSet(compiledClasses);
+    }
+    return exposedCompiledClasses;
+  }
+
+  CategorizedProblem[] getErrors() {
+    return errors;
+  }
+
+  Set<String> getFileNameRefs() {
+    if (fileNameRefs == null) {
+      fileNameRefs = computeFileNameRefs(cud);
+    }
+    return fileNameRefs;
+  }
+
+  /**
+   * If compiled, returns the JDT compilation unit declaration; otherwise
+   * <code>null</code>.
+   */
+  CompilationUnitDeclaration getJdtCud() {
+    return cud;
+  }
+
+  State getState() {
+    return state;
+  }
+
+  /**
+   * Sets the compiled JDT AST for this unit.
+   */
+  void setJdtCud(CompilationUnitDeclaration cud) {
+    this.cud = cud;
+    state = State.COMPILED;
+  }
+
+  /**
+   * 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;
+    }
+  }
+
+  /**
+   * Removes all accumulated state associated with compilation.
+   */
+  private void invalidate() {
+    cud = null;
+    fileNameRefs = null;
+    if (exposedCompiledClasses != null) {
+      for (CompiledClass compiledClass : exposedCompiledClasses) {
+        compiledClass.invalidate();
+      }
+      exposedCompiledClasses = null;
+    }
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/javac/CompilationUnitInvalidator.java b/dev/core/src/com/google/gwt/dev/javac/CompilationUnitInvalidator.java
new file mode 100644
index 0000000..0a670ed
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/javac/CompilationUnitInvalidator.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2008 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.core.ext.TreeLogger.HelpInfo;
+import com.google.gwt.dev.javac.CompilationUnit.State;
+import com.google.gwt.dev.util.Util;
+
+import org.eclipse.jdt.core.compiler.CategorizedProblem;
+import org.eclipse.jdt.internal.compiler.CompilationResult;
+import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.Map.Entry;
+
+/**
+ * Helper class to invalidate units in a set based on errors or references to
+ * other invalidate units.
+ * 
+ * TODO: {@link ClassFileReader#hasStructuralChanges(byte[])} could help us
+ * optimize this process!
+ */
+public class CompilationUnitInvalidator {
+
+  public static void invalidateUnitsWithErrors(TreeLogger logger,
+      Set<CompilationUnit> units) {
+    logger = logger.branch(TreeLogger.TRACE, "Removing units with errors");
+    // Start by removing units with a known problem.
+    boolean anyRemoved = false;
+    for (CompilationUnit unit : units) {
+      CompilationUnitDeclaration cud = unit.getJdtCud();
+      if (cud == null) {
+        continue;
+      }
+      CompilationResult result = cud.compilationResult();
+      if (result.hasErrors()) {
+        anyRemoved = true;
+
+        // TODO: defer this until later?
+        TreeLogger branch = logger.branch(TreeLogger.ERROR, "Errors in '"
+            + unit.getDisplayLocation() + "'", null);
+
+        for (CategorizedProblem error : result.getErrors()) {
+          // Append 'Line #: msg' to the error message.
+          StringBuffer msgBuf = new StringBuffer();
+          int line = error.getSourceLineNumber();
+          if (line > 0) {
+            msgBuf.append("Line ");
+            msgBuf.append(line);
+            msgBuf.append(": ");
+          }
+          msgBuf.append(error.getMessage());
+
+          HelpInfo helpInfo = null;
+          if (error instanceof GWTProblem) {
+            GWTProblem gwtProblem = (GWTProblem) error;
+            helpInfo = gwtProblem.getHelpInfo();
+          }
+          branch.log(TreeLogger.ERROR, msgBuf.toString(), null, helpInfo);
+        }
+
+        Util.maybeDumpSource(branch, unit.getDisplayLocation(),
+            unit.getSource(), unit.getTypeName());
+
+        // TODO: hold onto errors?
+        unit.setState(State.ERROR);
+      }
+    }
+
+    if (anyRemoved) {
+      // Then removing anything else that won't compile as a result.
+      invalidateUnitsWithInvalidRefs(logger, units);
+    }
+  }
+
+  public static void invalidateUnitsWithInvalidRefs(TreeLogger logger,
+      Set<CompilationUnit> units) {
+    logger = logger.branch(TreeLogger.TRACE, "Removing invalidate units");
+
+    // Map all units by file name.
+    Map<String, CompilationUnit> unitsByFileName = new HashMap<String, CompilationUnit>();
+    for (CompilationUnit unit : units) {
+      unitsByFileName.put(unit.getDisplayLocation(), unit);
+    }
+    // First, compute a map from all targets all referents.
+    Map<CompilationUnit, Set<CompilationUnit>> refTargetToReferents = new HashMap<CompilationUnit, Set<CompilationUnit>>();
+    for (CompilationUnit referentUnit : units) {
+      if (referentUnit.isCompiled()) {
+        Set<String> fileNameRefs = referentUnit.getFileNameRefs();
+        for (String fileNameRef : fileNameRefs) {
+          CompilationUnit targetUnit = unitsByFileName.get(fileNameRef);
+          if (targetUnit != null) {
+            Set<CompilationUnit> referents = refTargetToReferents.get(targetUnit);
+            if (referents == null) {
+              referents = new HashSet<CompilationUnit>();
+              refTargetToReferents.put(targetUnit, referents);
+            }
+            // Add myself as a referent.
+            referents.add(referentUnit);
+          }
+        }
+      }
+    }
+
+    // Now use the map to transitively blow away invalid units.
+    for (Entry<CompilationUnit, Set<CompilationUnit>> entry : refTargetToReferents.entrySet()) {
+      CompilationUnit maybeInvalidUnit = entry.getKey();
+      if (!maybeInvalidUnit.isCompiled()) {
+        // Invalidate all dependent units.
+        Set<CompilationUnit> invalidReferentUnits = entry.getValue();
+        TreeLogger branch = logger.branch(TreeLogger.TRACE,
+            "Compilation unit '" + maybeInvalidUnit + "' is invalid");
+        State why = maybeInvalidUnit.getState();
+        for (CompilationUnit invalidReferentUnit : invalidReferentUnits) {
+          if (invalidReferentUnit.isCompiled()) {
+            // Set it to the same state as the unit it depends on.
+            invalidReferentUnit.setState(why);
+            branch.log(TreeLogger.TRACE, "Removing dependent unit '"
+                + invalidReferentUnit + "'");
+          }
+        }
+      }
+    }
+  }
+
+  public static void validateCompilationUnits(Set<CompilationUnit> units,
+      Map<String, CompiledClass> compiledClasses) {
+    for (CompilationUnit unit : units) {
+      if (unit.getState() != State.CHECKED) {
+        CompilationUnitDeclaration jdtCud = unit.getJdtCud();
+        JSORestrictionsChecker.check(jdtCud);
+        LongFromJSNIChecker.check(jdtCud);
+        BinaryTypeReferenceRestrictionsChecker.check(jdtCud,
+            compiledClasses.keySet());
+      }
+    }
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/javac/CompiledClass.java b/dev/core/src/com/google/gwt/dev/javac/CompiledClass.java
new file mode 100644
index 0000000..f3e1080
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/javac/CompiledClass.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2008 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.typeinfo.JRealClassType;
+import com.google.gwt.dev.javac.impl.Shared;
+
+import org.eclipse.jdt.core.compiler.CharOperation;
+import org.eclipse.jdt.internal.compiler.ClassFile;
+import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
+import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
+import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException;
+import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer;
+import org.eclipse.jdt.internal.compiler.lookup.LocalTypeBinding;
+import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Encapsulates the state of a single compiled class file.
+ */
+public final class CompiledClass {
+
+  private static ClassFile getClassFile(TypeDeclaration typeDecl,
+      String binaryName) {
+    for (ClassFile tryClassFile : typeDecl.compilationResult().getClassFiles()) {
+      char[] tryBinaryName = CharOperation.concatWith(
+          tryClassFile.getCompoundName(), '/');
+      if (binaryName.equals(String.valueOf(tryBinaryName))) {
+        return tryClassFile;
+      }
+    }
+    assert false;
+    return null;
+  }
+
+  private static String getPackagePrefix(String packageName) {
+    return packageName.length() > 0 ? packageName + "." : "";
+  }
+
+  protected final String binaryName;
+  protected final byte[] bytes;
+  protected final CompiledClass enclosingClass;
+  protected final String location;
+  protected final String packageName;
+  protected final String sourceName;
+
+  // The state below is transient.
+  private List<JsniMethod> jsniMethods;
+  private NameEnvironmentAnswer nameEnvironmentAnswer;
+  private JRealClassType realClassType;
+  // Can be killed after parent is CHECKED.
+  private TypeDeclaration typeDeclaration;
+
+  CompiledClass(TypeDeclaration typeDeclaration, CompiledClass enclosingClass) {
+    SourceTypeBinding binding = typeDeclaration.binding;
+    this.typeDeclaration = typeDeclaration;
+    this.enclosingClass = enclosingClass;
+    this.binaryName = CharOperation.charToString(binding.constantPoolName());
+    this.packageName = Shared.getPackageNameFromBinary(binaryName);
+    if (binding instanceof LocalTypeBinding) {
+      // The source name of a local type must be determined from binary.
+      String qualifiedName = binaryName.replace('/', '.');
+      this.sourceName = qualifiedName.replace('$', '.');
+    } else {
+      this.sourceName = getPackagePrefix(packageName)
+          + String.valueOf(binding.qualifiedSourceName());
+    }
+    ClassFile classFile = getClassFile(typeDeclaration, binaryName);
+    this.bytes = classFile.getBytes();
+    this.location = String.valueOf(classFile.fileName());
+  }
+
+  /**
+   * Returns the binary class name, e.g. {@code java/util/Map$Entry}.
+   */
+  public String getBinaryName() {
+    return binaryName;
+  }
+
+  /**
+   * Returns the bytes of the compiled class.
+   */
+  public byte[] getBytes() {
+    return bytes;
+  }
+
+  public CompiledClass getEnclosingClass() {
+    return enclosingClass;
+  }
+
+  public List<JsniMethod> getJsniMethods() {
+    return jsniMethods;
+  }
+
+  /**
+   * Returns the enclosing package, e.g. {@code java.util}.
+   */
+  public String getPackageName() {
+    return packageName;
+  }
+
+  /**
+   * Returns the qualified source name, e.g. {@code java.util.Map.Entry}.
+   */
+  public String getSourceName() {
+    return sourceName;
+  }
+
+  @Override
+  public String toString() {
+    return binaryName;
+  }
+
+  /**
+   * All checking is done, free up internal state.
+   */
+  void checked() {
+    this.typeDeclaration = null;
+  }
+
+  NameEnvironmentAnswer getNameEnvironmentAnswer() {
+    if (nameEnvironmentAnswer == null) {
+      try {
+        ClassFileReader cfr = new ClassFileReader(bytes, location.toCharArray());
+        nameEnvironmentAnswer = new NameEnvironmentAnswer(cfr, null);
+      } catch (ClassFormatException e) {
+        throw new RuntimeException("Unexpectedly unable to parse class file", e);
+      }
+    }
+    return nameEnvironmentAnswer;
+  }
+
+  JRealClassType getRealClassType() {
+    return realClassType;
+  }
+
+  TypeDeclaration getTypeDeclaration() {
+    return typeDeclaration;
+  }
+
+  void invalidate() {
+    nameEnvironmentAnswer = null;
+    typeDeclaration = null;
+    jsniMethods = null;
+    if (realClassType != null) {
+      realClassType.invalidate();
+      realClassType = null;
+    }
+  }
+
+  void setJsniMethods(List<JsniMethod> jsniMethods) {
+    this.jsniMethods = Collections.unmodifiableList(jsniMethods);
+  }
+
+  void setRealClassType(JRealClassType realClassType) {
+    this.realClassType = realClassType;
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/jdt/GWTProblem.java b/dev/core/src/com/google/gwt/dev/javac/GWTProblem.java
similarity index 94%
rename from dev/core/src/com/google/gwt/dev/jdt/GWTProblem.java
rename to dev/core/src/com/google/gwt/dev/javac/GWTProblem.java
index b61688e..645b6b7 100644
--- a/dev/core/src/com/google/gwt/dev/jdt/GWTProblem.java
+++ b/dev/core/src/com/google/gwt/dev/javac/GWTProblem.java
@@ -13,7 +13,7 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.dev.jdt;
+package com.google.gwt.dev.javac;
 
 import com.google.gwt.core.ext.TreeLogger.HelpInfo;
 
@@ -30,7 +30,7 @@
  */
 public class GWTProblem extends DefaultProblem {
 
-  static void recordInCud(ASTNode node, CompilationUnitDeclaration cud,
+  public static void recordInCud(ASTNode node, CompilationUnitDeclaration cud,
       String message, HelpInfo helpInfo) {
     CompilationResult compResult = cud.compilationResult();
     int[] lineEnds = compResult.getLineSeparatorPositions();
diff --git a/dev/core/src/com/google/gwt/dev/jdt/JSORestrictionsChecker.java b/dev/core/src/com/google/gwt/dev/javac/JSORestrictionsChecker.java
similarity index 60%
rename from dev/core/src/com/google/gwt/dev/jdt/JSORestrictionsChecker.java
rename to dev/core/src/com/google/gwt/dev/javac/JSORestrictionsChecker.java
index 43e3df3..b6c7390 100644
--- a/dev/core/src/com/google/gwt/dev/jdt/JSORestrictionsChecker.java
+++ b/dev/core/src/com/google/gwt/dev/javac/JSORestrictionsChecker.java
@@ -13,12 +13,10 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.dev.jdt;
+package com.google.gwt.dev.javac;
 
-import com.google.gwt.dev.shell.JsValueGlue;
 import com.google.gwt.dev.util.InstalledHelpInfo;
 
-import org.eclipse.jdt.core.compiler.CharOperation;
 import org.eclipse.jdt.internal.compiler.ASTVisitor;
 import org.eclipse.jdt.internal.compiler.ast.ASTNode;
 import org.eclipse.jdt.internal.compiler.ast.AllocationExpression;
@@ -33,9 +31,10 @@
 import org.eclipse.jdt.internal.compiler.lookup.CompilationUnitScope;
 import org.eclipse.jdt.internal.compiler.lookup.MethodScope;
 import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
-import org.eclipse.jdt.internal.compiler.lookup.Scope;
 import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
 
+import java.util.Stack;
+
 /**
  * Check a compilation unit for violations of
  * {@link com.google.gwt.core.client.JavaScriptObject JavaScriptObject} (JSO)
@@ -58,100 +57,134 @@
  * Any violations found are attached as errors on the
  * CompilationUnitDeclaration.
  */
-class JSORestrictionsChecker {
+public class JSORestrictionsChecker {
 
   private class JSORestrictionsVisitor extends ASTVisitor implements
       ClassFileConstants {
 
+    private final Stack<Boolean> isJsoStack = new Stack<Boolean>();
+
     @Override
     public void endVisit(AllocationExpression exp, BlockScope scope) {
-      if (exp.type != null && isJSOSubclass(exp.type.resolveType(scope))) {
+      // Anywhere an allocation occurs is wrong.
+      if (exp.type != null && isJsoSubclass(exp.type.resolveType(scope))) {
         errorOn(exp, ERR_NEW_JSO);
       }
     }
 
     @Override
     public void endVisit(ConstructorDeclaration meth, ClassScope scope) {
-      if (isForJSOSubclass(scope)) {
-        if ((meth.arguments != null) && (meth.arguments.length > 0)) {
-          errorOn(meth, ERR_CONSTRUCTOR_WITH_PARAMETERS);
-        }
-        if ((meth.modifiers & AccProtected) == 0) {
-          errorOn(meth, ERR_NONPROTECTED_CONSTRUCTOR);
-        }
-        if (meth.statements != null && meth.statements.length > 0) {
-          errorOn(meth, ERR_NONEMPTY_CONSTRUCTOR);
-        }
+      if (!isJso()) {
+        return;
+      }
+      if ((meth.arguments != null) && (meth.arguments.length > 0)) {
+        errorOn(meth, ERR_CONSTRUCTOR_WITH_PARAMETERS);
+      }
+      if ((meth.modifiers & AccProtected) == 0) {
+        errorOn(meth, ERR_NONPROTECTED_CONSTRUCTOR);
+      }
+      if (meth.statements != null && meth.statements.length > 0) {
+        errorOn(meth, ERR_NONEMPTY_CONSTRUCTOR);
       }
     }
 
     @Override
     public void endVisit(FieldDeclaration field, MethodScope scope) {
-      if (isForJSOSubclass(scope)) {
-        if (!field.isStatic()) {
-          errorOn(field, ERR_INSTANCE_FIELD);
-        }
+      if (!isJso()) {
+        return;
+      }
+      if (!field.isStatic()) {
+        errorOn(field, ERR_INSTANCE_FIELD);
       }
     }
 
     @Override
     public void endVisit(MethodDeclaration meth, ClassScope scope) {
-      if (isForJSOSubclass(scope)) {
-        if ((meth.modifiers & (AccFinal | AccPrivate | AccStatic)) == 0) {
-          // The method's modifiers allow it to be overridden. Make
-          // one final check to see if the surrounding class is final.
-          if ((meth.scope == null)
-              || !meth.scope.enclosingSourceType().isFinal()) {
-            errorOn(meth, ERR_INSTANCE_METHOD_NONFINAL);
-          }
+      if (!isJso()) {
+        return;
+      }
+      if ((meth.modifiers & (AccFinal | AccPrivate | AccStatic)) == 0) {
+        // The method's modifiers allow it to be overridden. Make
+        // one final check to see if the surrounding class is final.
+        if ((meth.scope == null) || !meth.scope.enclosingSourceType().isFinal()) {
+          errorOn(meth, ERR_INSTANCE_METHOD_NONFINAL);
         }
+      }
 
-        // Should not have to check isStatic() here, but isOverriding() appears
-        // to be set for static methods.
-        if (!meth.isStatic()
-            && (meth.binding != null && meth.binding.isOverriding())) {
-          errorOn(meth, ERR_OVERRIDDEN_METHOD);
-        }
+      // Should not have to check isStatic() here, but isOverriding() appears
+      // to be set for static methods.
+      if (!meth.isStatic()
+          && (meth.binding != null && meth.binding.isOverriding())) {
+        errorOn(meth, ERR_OVERRIDDEN_METHOD);
       }
     }
 
     @Override
     public void endVisit(TypeDeclaration type, BlockScope scope) {
-      checkType(type);
+      popIsJso();
     }
 
     @Override
     public void endVisit(TypeDeclaration type, ClassScope scope) {
-      checkType(type);
+      popIsJso();
     }
 
     @Override
     public void endVisit(TypeDeclaration type, CompilationUnitScope scope) {
-      checkType(type);
+      popIsJso();
     }
 
-    private void checkType(TypeDeclaration type) {
-      if (isJSOSubclass(type)) {
-        if (type.enclosingType != null && !type.binding.isStatic()) {
-          errorOn(type, ERR_IS_NONSTATIC_NESTED);
-        }
+    @Override
+    public boolean visit(TypeDeclaration type, BlockScope scope) {
+      pushIsJso(checkType(type));
+      return true;
+    }
 
-        ReferenceBinding[] interfaces = type.binding.superInterfaces();
-        if (interfaces != null) {
-          for (ReferenceBinding interf : interfaces) {
-            if (interf.methods() != null && interf.methods().length > 0) {
-              String intfName = String.copyValueOf(interf.shortReadableName());
-              errorOn(type, errInterfaceWithMethods(intfName));
-            }
+    @Override
+    public boolean visit(TypeDeclaration type, ClassScope scope) {
+      pushIsJso(checkType(type));
+      return true;
+    }
+
+    @Override
+    public boolean visit(TypeDeclaration type, CompilationUnitScope scope) {
+      pushIsJso(checkType(type));
+      return true;
+    }
+
+    private boolean checkType(TypeDeclaration type) {
+      if (!isJsoSubclass(type.binding)) {
+        return false;
+      }
+      if (type.enclosingType != null && !type.binding.isStatic()) {
+        errorOn(type, ERR_IS_NONSTATIC_NESTED);
+      }
+
+      ReferenceBinding[] interfaces = type.binding.superInterfaces();
+      if (interfaces != null) {
+        for (ReferenceBinding interf : interfaces) {
+          if (interf.methods() != null && interf.methods().length > 0) {
+            String intfName = String.copyValueOf(interf.shortReadableName());
+            errorOn(type, errInterfaceWithMethods(intfName));
           }
         }
       }
+      return true;
+    }
+
+    private boolean isJso() {
+      return isJsoStack.peek();
+    }
+
+    private void popIsJso() {
+      isJsoStack.pop();
+    }
+
+    private void pushIsJso(boolean isJso) {
+      isJsoStack.push(isJso);
     }
   }
 
-  protected static final char[][] JSO_CLASS_CHARS = CharOperation.splitOn('.',
-      JsValueGlue.JSO_CLASS.toCharArray());
-
   static final String ERR_CONSTRUCTOR_WITH_PARAMETERS = "Constructors must not have parameters in subclasses of JavaScriptObject";
   static final String ERR_INSTANCE_FIELD = "Instance fields cannot be used in subclasses of JavaScriptObject";
   static final String ERR_INSTANCE_METHOD_NONFINAL = "Instance methods must be 'final' in non-final subclasses of JavaScriptObject";
@@ -160,6 +193,7 @@
   static final String ERR_NONEMPTY_CONSTRUCTOR = "Constructors must be totally empty in subclasses of JavaScriptObject";
   static final String ERR_NONPROTECTED_CONSTRUCTOR = "Constructors must be 'protected' in subclasses of JavaScriptObject";
   static final String ERR_OVERRIDDEN_METHOD = "Methods cannot be overridden in JavaScriptObject subclasses";
+  static final String JSO_CLASS = "com/google/gwt/core/client/JavaScriptObject";
 
   /**
    * Checks an entire
@@ -167,13 +201,7 @@
    * 
    */
   public static void check(CompilationUnitDeclaration cud) {
-    TypeBinding jsoType = cud.scope.environment().getType(JSO_CLASS_CHARS);
-    if (jsoType == null) {
-      // JavaScriptObject not available; do nothing
-      return;
-    }
-
-    JSORestrictionsChecker checker = new JSORestrictionsChecker(cud, jsoType);
+    JSORestrictionsChecker checker = new JSORestrictionsChecker(cud);
     checker.check();
   }
 
@@ -184,40 +212,30 @@
 
   private final CompilationUnitDeclaration cud;
 
-  /**
-   * The type of the GWT JavaScriptObject class. Cannot be null.
-   */
-  private final TypeBinding jsoType;
-
-  private JSORestrictionsChecker(CompilationUnitDeclaration cud,
-      TypeBinding jsoType) {
-    assert jsoType != null;
+  private JSORestrictionsChecker(CompilationUnitDeclaration cud) {
     this.cud = cud;
-    this.jsoType = jsoType;
   }
 
   private void check() {
     cud.traverse(new JSORestrictionsVisitor(), cud.scope);
   }
 
-  private TypeBinding classType(Scope scope) {
-    return scope.classScope().referenceType().binding;
-  }
-
   private void errorOn(ASTNode node, String error) {
     GWTProblem.recordInCud(node, cud, error, new InstalledHelpInfo(
         "jsoRestrictions.html"));
   }
 
-  private boolean isForJSOSubclass(Scope scope) {
-    return isJSOSubclass(classType(scope));
-  }
-
-  private boolean isJSOSubclass(TypeBinding typeBinding) {
-    return typeBinding.isCompatibleWith(jsoType) && typeBinding != jsoType;
-  }
-
-  private boolean isJSOSubclass(TypeDeclaration typeDecl) {
-    return isJSOSubclass(typeDecl.binding);
+  private boolean isJsoSubclass(TypeBinding typeBinding) {
+    if (!(typeBinding instanceof ReferenceBinding)) {
+      return false;
+    }
+    ReferenceBinding binding = (ReferenceBinding) typeBinding;
+    while (binding.superclass() != null) {
+      if (JSO_CLASS.equals(String.valueOf(binding.superclass().constantPoolName()))) {
+        return true;
+      }
+      binding = binding.superclass();
+    }
+    return false;
   }
 }
diff --git a/dev/core/src/com/google/gwt/dev/javac/JavaSourceFile.java b/dev/core/src/com/google/gwt/dev/javac/JavaSourceFile.java
new file mode 100644
index 0000000..f73c2da
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/javac/JavaSourceFile.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2008 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;
+
+/**
+ * Provides information about a single Java source file.
+ */
+public abstract class JavaSourceFile {
+
+  /**
+   * Overridden to finalize; always returns object identity.
+   */
+  @Override
+  public final boolean equals(Object obj) {
+    return super.equals(obj);
+  }
+
+  /**
+   * Returns the user-relevant location of the source file. No programmatic
+   * assumptions should be made about the return value.
+   */
+  public abstract String getLocation();
+
+  /**
+   * Returns the name of the package.
+   */
+  public abstract String getPackageName();
+
+  /**
+   * Returns the unqualified name of the top level public type.
+   */
+  public abstract String getShortName();
+
+  /**
+   * Returns the fully-qualified name of the top level public type.
+   */
+  public abstract String getTypeName();
+
+  /**
+   * Overridden to finalize; always returns identity hash code.
+   */
+  @Override
+  public final int hashCode() {
+    return super.hashCode();
+  }
+
+  /**
+   * Returns the Java code contained in this source file. May return
+   * <code>null</code> if this {@link JavaSourceFile} has been invalidated by
+   * its containing {@link JavaSourceOracle}. This method may be expensive as
+   * the implementor is generally not required to cache the results.
+   */
+  public abstract String readSource();
+
+  /**
+   * Overridden to finalize; always returns {@link #getLocation()}.
+   */
+  public final String toString() {
+    return getLocation();
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/javac/JavaSourceOracle.java b/dev/core/src/com/google/gwt/dev/javac/JavaSourceOracle.java
new file mode 100644
index 0000000..12d5841
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/javac/JavaSourceOracle.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2006 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 java.util.Map;
+import java.util.Set;
+
+/**
+ * An unmodifiable view of a module's Java source tree.
+ * 
+ * <p>
+ * The identity of the returned sets and maps will change exactly when the
+ * underlying module is refreshed.
+ * </p>
+ * 
+ * <p>
+ * Even when the identity of a returned set changes, the identity of any
+ * contained {@link JavaSourceFile} values is guaranteed to differ from a
+ * previous result exactly when that particular source file becomes invalid.
+ * </p>
+ * 
+ * <p>
+ * A source file could become invalid for various reasons, including:
+ * <ul>
+ * <li>the underlying file was deleted or modified</li>
+ * <li>another file with the same logical name superceded it on the classpath</li>
+ * <li>the underlying module changed to exclude this file or supercede it with
+ * another file</li>
+ * </ul>
+ * </p>
+ * 
+ * <p>
+ * After a refresh, a client can reliably detect changes by checking which of
+ * its cached source files is still contained in the new result of
+ * {@link #getSourceFiles()}.
+ * </p>
+ */
+public interface JavaSourceOracle {
+
+  /**
+   * Returns an unmodifiable set of fully-qualified class names with constant
+   * lookup time.
+   */
+  Set<String> getClassNames();
+
+  /**
+   * Returns an unmodifiable set of unique source files with constant lookup
+   * time.
+   */
+  Set<JavaSourceFile> getSourceFiles();
+
+  /**
+   * Returns an unmodifiable map of fully-qualified class name to source file.
+   */
+  Map<String, JavaSourceFile> getSourceMap();
+}
diff --git a/dev/core/src/com/google/gwt/dev/javac/JdtCompiler.java b/dev/core/src/com/google/gwt/dev/javac/JdtCompiler.java
new file mode 100644
index 0000000..831b46a
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/javac/JdtCompiler.java
@@ -0,0 +1,279 @@
+/*
+ * Copyright 2008 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.dev.javac.impl.Shared;
+import com.google.gwt.dev.util.PerfLogger;
+import com.google.gwt.util.tools.Utility;
+
+import org.eclipse.jdt.core.compiler.CharOperation;
+import org.eclipse.jdt.internal.compiler.CompilationResult;
+import org.eclipse.jdt.internal.compiler.Compiler;
+import org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies;
+import org.eclipse.jdt.internal.compiler.ICompilerRequestor;
+import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
+import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
+import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException;
+import org.eclipse.jdt.internal.compiler.env.ICompilationUnit;
+import org.eclipse.jdt.internal.compiler.env.INameEnvironment;
+import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer;
+import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
+import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Manages the process of compiling {@link CompilationUnit}s.
+ */
+public class JdtCompiler {
+
+  /**
+   * Adapts a {@link CompilationUnit} for a JDT compile.
+   */
+  public static class CompilationUnitAdapter implements ICompilationUnit {
+
+    private final CompilationUnit unit;
+
+    public CompilationUnitAdapter(CompilationUnit unit) {
+      this.unit = unit;
+    }
+
+    public char[] getContents() {
+      return unit.getSource().toString().toCharArray();
+    }
+
+    public char[] getFileName() {
+      return unit.getDisplayLocation().toCharArray();
+    }
+
+    public char[] getMainTypeName() {
+      return Shared.getShortName(unit.getTypeName()).toCharArray();
+    }
+
+    public char[][] getPackageName() {
+      String packageName = Shared.getPackageName(unit.getTypeName());
+      return CharOperation.splitOn('.', packageName.toCharArray());
+    }
+
+    public CompilationUnit getUnit() {
+      return unit;
+    }
+
+    @Override
+    public String toString() {
+      return unit.toString();
+    }
+  }
+  private class CompilerImpl extends Compiler {
+
+    public CompilerImpl() {
+      super(new INameEnvironmentImpl(),
+          DefaultErrorHandlingPolicies.proceedWithAllProblems(),
+          getCompilerOptions(), new ICompilerRequestorImpl(),
+          new DefaultProblemFactory(Locale.getDefault()));
+    }
+
+    @Override
+    public void process(CompilationUnitDeclaration cud, int i) {
+      // TODO: not always generate bytecode eagerly?
+      super.process(cud, i);
+      ICompilationUnit icu = cud.compilationResult().compilationUnit;
+      CompilationUnitAdapter adapter = (CompilationUnitAdapter) icu;
+      adapter.getUnit().setJdtCud(cud);
+    }
+  }
+
+  /**
+   * Hook point to accept results.
+   */
+  private class ICompilerRequestorImpl implements ICompilerRequestor {
+    public void acceptResult(CompilationResult result) {
+    }
+  }
+
+  /**
+   * How JDT receives files from the environment.
+   */
+  private class INameEnvironmentImpl implements INameEnvironment {
+    public void cleanup() {
+    }
+
+    public NameEnvironmentAnswer findType(char[] type, char[][] pkg) {
+      return findType(CharOperation.arrayConcat(pkg, type));
+    }
+
+    public NameEnvironmentAnswer findType(char[][] compoundTypeName) {
+      char[] binaryNameChars = CharOperation.concatWith(compoundTypeName, '.');
+      String binaryName = String.valueOf(binaryNameChars);
+      CompiledClass compiledClass = binaryTypes.get(binaryName);
+      if (compiledClass != null) {
+        return compiledClass.getNameEnvironmentAnswer();
+      }
+      if (isPackage(binaryName)) {
+        return null;
+      }
+      try {
+        // Check for binary-only annotations.
+        Class.forName(binaryName, false, getClassLoader());
+        String resourcePath = binaryName.replace('.', '/') + ".class";
+        URL resource = getClassLoader().getResource(resourcePath);
+        InputStream openStream = resource.openStream();
+        try {
+          ClassFileReader cfr = ClassFileReader.read(openStream,
+              resource.toExternalForm(), true);
+          return new NameEnvironmentAnswer(cfr, null);
+        } finally {
+          Utility.close(openStream);
+        }
+      } catch (NoClassDefFoundError e) {
+      } catch (ClassNotFoundException e) {
+      } catch (ClassFormatException e) {
+      } catch (IOException e) {
+      }
+      return null;
+    }
+
+    public boolean isPackage(char[][] parentPkg, char[] pkg) {
+      final char[] pathChars = CharOperation.concatWith(parentPkg, pkg, '.');
+      String packageName = String.valueOf(pathChars);
+      return isPackage(packageName);
+    }
+
+    private ClassLoader getClassLoader() {
+      return Thread.currentThread().getContextClassLoader();
+    }
+
+    private boolean isPackage(String packageName) {
+      // Include class loader check for binary-only annotations.
+      if (packages.contains(packageName)) {
+        return true;
+      }
+      if (notPackages.contains(packageName)) {
+        return false;
+      }
+      String resourceName = packageName.replace('.', '/') + '/';
+      if (getClassLoader().getResource(resourceName) != null) {
+        addPackages(packageName);
+        return true;
+      } else {
+        notPackages.add(packageName);
+        return false;
+      }
+    }
+  }
+
+  /**
+   * Compiles the given set of units. The units will be internally modified to
+   * reflect the results of compilation.
+   */
+  public static void compile(Collection<CompilationUnit> units) {
+    PerfLogger.start("JdtCompiler.compile");
+    new JdtCompiler().doCompile(units);
+    PerfLogger.end();
+  }
+
+  private static CompilerOptions getCompilerOptions() {
+    Map<String, String> settings = new HashMap<String, String>();
+    settings.put(CompilerOptions.OPTION_LineNumberAttribute,
+        CompilerOptions.GENERATE);
+    settings.put(CompilerOptions.OPTION_SourceFileAttribute,
+        CompilerOptions.GENERATE);
+    /*
+     * Tricks like "boolean stopHere = true;" depend on this setting to work in
+     * hosted mode. In web mode, our compiler should optimize them out once we
+     * do real data flow.
+     */
+    settings.put(CompilerOptions.OPTION_PreserveUnusedLocal,
+        CompilerOptions.PRESERVE);
+    settings.put(CompilerOptions.OPTION_ReportDeprecation,
+        CompilerOptions.IGNORE);
+    settings.put(CompilerOptions.OPTION_LocalVariableAttribute,
+        CompilerOptions.GENERATE);
+    settings.put(CompilerOptions.OPTION_Compliance, CompilerOptions.VERSION_1_5);
+    settings.put(CompilerOptions.OPTION_Source, CompilerOptions.VERSION_1_5);
+    settings.put(CompilerOptions.OPTION_TargetPlatform,
+        CompilerOptions.VERSION_1_5);
+
+    // This is needed by TypeOracleBuilder to parse metadata.
+    settings.put(CompilerOptions.OPTION_DocCommentSupport,
+        CompilerOptions.ENABLED);
+    return new CompilerOptions(settings);
+  }
+
+  private final List<CompilationUnit> activeUnits = new ArrayList<CompilationUnit>();
+
+  /**
+   * Maps dotted binary names to compiled classes.
+   */
+  private final Map<String, CompiledClass> binaryTypes = new HashMap<String, CompiledClass>();
+
+  private final CompilerImpl compiler = new CompilerImpl();
+
+  private final Set<String> notPackages = new HashSet<String>();
+
+  private final Set<String> packages = new HashSet<String>();
+
+  /**
+   * Not externally instantiable.
+   */
+  private JdtCompiler() {
+  }
+
+  private void addPackages(String packageName) {
+    while (true) {
+      packages.add(String.valueOf(packageName));
+      int pos = packageName.lastIndexOf('.');
+      if (pos > 0) {
+        packageName = packageName.substring(0, pos);
+      } else {
+        packages.add("");
+        break;
+      }
+    }
+  }
+
+  private void doCompile(Collection<CompilationUnit> units) {
+    List<ICompilationUnit> icus = new ArrayList<ICompilationUnit>();
+    for (CompilationUnit unit : units) {
+      String packageName = Shared.getPackageName(unit.getTypeName());
+      addPackages(packageName);
+      Set<CompiledClass> compiledClasses = unit.getCompiledClasses();
+      if (compiledClasses == null) {
+        icus.add(new CompilationUnitAdapter(unit));
+        activeUnits.add(unit);
+      } else {
+        for (CompiledClass compiledClass : compiledClasses) {
+          binaryTypes.put(compiledClass.getBinaryName().replace('/', '.'),
+              compiledClass);
+        }
+      }
+    }
+    if (!icus.isEmpty()) {
+      compiler.compile(icus.toArray(new ICompilationUnit[icus.size()]));
+    }
+  }
+
+}
diff --git a/dev/core/src/com/google/gwt/dev/javac/JsniCollector.java b/dev/core/src/com/google/gwt/dev/javac/JsniCollector.java
new file mode 100644
index 0000000..6acae2c
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/javac/JsniCollector.java
@@ -0,0 +1,300 @@
+/*
+ * Copyright 2008 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.CompilationUnit.State;
+import com.google.gwt.dev.js.JsParser;
+import com.google.gwt.dev.js.JsParserException;
+import com.google.gwt.dev.js.JsParserException.SourceDetail;
+import com.google.gwt.dev.js.ast.JsExprStmt;
+import com.google.gwt.dev.js.ast.JsFunction;
+import com.google.gwt.dev.js.ast.JsProgram;
+import com.google.gwt.dev.js.ast.JsStatement;
+import com.google.gwt.dev.util.Empty;
+import com.google.gwt.dev.util.Jsni;
+
+import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
+import org.eclipse.jdt.internal.compiler.ast.Argument;
+import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
+import org.eclipse.jdt.internal.compiler.util.Util;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Adapts compilation units containing JSNI-accessible code by rewriting the
+ * source.
+ */
+public class JsniCollector {
+
+  /**
+   * Represents a logical interval of text.
+   */
+  public static class Interval {
+    public final int end;
+    public final int start;
+
+    public Interval(int start, int end) {
+      this.start = start;
+      this.end = end;
+    }
+  }
+
+  private static final class JsniMethodImpl extends JsniMethod {
+    private JsFunction func;
+    private final int line;
+    private final String location;
+    private final String name;
+    private final String[] paramNames;
+    private final String source;
+    private final JsProgram program;
+
+    private JsniMethodImpl(String name, String source, String[] paramNames,
+        int line, String location, JsProgram program) {
+      this.name = name;
+      this.source = source;
+      this.paramNames = paramNames;
+      this.line = line;
+      this.location = location;
+      this.program = program;
+    }
+
+    @Override
+    public JsFunction function(TreeLogger logger) {
+      if (func == null) {
+        func = parseAsAnonymousFunction(logger, program, source, paramNames,
+            location, line);
+      }
+      return func;
+    }
+
+    public int line() {
+      return line;
+    }
+
+    public String location() {
+      return location;
+    }
+
+    public String name() {
+      return name;
+    }
+
+    public String[] paramNames() {
+      return paramNames;
+    }
+
+    public String source() {
+      return source;
+    }
+
+    @Override
+    public String toString() {
+      StringBuffer sb = new StringBuffer();
+      sb.append("function ");
+      sb.append(name);
+      sb.append('(');
+      boolean first = true;
+      for (String paramName : paramNames) {
+        if (first) {
+          first = false;
+        } else {
+          sb.append(", ");
+        }
+        sb.append(paramName);
+      }
+      sb.append("} {\n");
+      sb.append(source);
+      sb.append("}\n");
+      return sb.toString();
+    }
+  }
+
+  public static void collectJsniMethods(TreeLogger logger,
+      Set<CompilationUnit> units, JsProgram program) {
+    for (CompilationUnit unit : units) {
+      if (unit.getState() == State.COMPILED) {
+        String loc = unit.getDisplayLocation();
+        String source = unit.getSource();
+        for (CompiledClass compiledClass : unit.getCompiledClasses()) {
+          assert compiledClass.getJsniMethods() == null;
+          collectJsniMethods(logger, loc, source, compiledClass, program);
+        }
+      }
+    }
+  }
+
+  /**
+   * TODO: log real errors, replacing GenerateJavaScriptAST?
+   */
+  private static void collectJsniMethods(TreeLogger logger, String loc,
+      String source, CompiledClass compiledClass, JsProgram program) {
+    TypeDeclaration typeDecl = compiledClass.getTypeDeclaration();
+    int[] lineEnds = typeDecl.compilationResult.getLineSeparatorPositions();
+    List<JsniMethod> jsniMethods = new ArrayList<JsniMethod>();
+    String enclosingType = compiledClass.getBinaryName().replace('/', '.');
+    AbstractMethodDeclaration[] methods = typeDecl.methods;
+    if (methods != null) {
+      for (AbstractMethodDeclaration method : methods) {
+        if (!method.isNative()) {
+          continue;
+        }
+        Interval interval = findJsniSource(source, method);
+        if (interval == null) {
+          String msg = "No JavaScript body found for native method '" + method
+              + "' in type '" + compiledClass.getSourceName() + "'";
+          logger.log(TreeLogger.ERROR, msg, null);
+          continue;
+        }
+
+        String js = source.substring(interval.start, interval.end);
+        int startLine = Util.getLineNumber(interval.start, lineEnds, 0,
+            lineEnds.length - 1);
+        String jsniSignature = getJsniSignature(enclosingType, method);
+        String[] paramNames = getParamNames(method);
+
+        jsniMethods.add(new JsniMethodImpl(jsniSignature, js, paramNames,
+            startLine, loc, program));
+      }
+    }
+    compiledClass.setJsniMethods(jsniMethods);
+  }
+
+  private static Interval findJsniSource(String source,
+      AbstractMethodDeclaration method) {
+    assert (method.isNative());
+    int bodyStart = method.bodyStart;
+    int bodyEnd = method.bodyEnd;
+    String js = source.substring(bodyStart, bodyEnd + 1);
+
+    int jsniStart = js.indexOf(Jsni.JSNI_BLOCK_START);
+    if (jsniStart == -1) {
+      return null;
+    }
+
+    int jsniEnd = js.indexOf(Jsni.JSNI_BLOCK_END, jsniStart);
+    if (jsniEnd == -1) {
+      // Suspicious, but maybe this is just a weird comment, so let it slide.
+      //
+      return null;
+    }
+
+    int srcStart = bodyStart + jsniStart + Jsni.JSNI_BLOCK_START.length();
+    int srcEnd = bodyStart + jsniEnd;
+    return new Interval(srcStart, srcEnd);
+  }
+
+  /**
+   * Gets a unique name for this method and its signature (this is used to
+   * determine whether one method overrides another).
+   */
+  private static String getJsniSignature(String enclosingType,
+      AbstractMethodDeclaration method) {
+    return '@' + enclosingType + "::" + getMemberSignature(method);
+  }
+
+  /**
+   * Gets a unique name for this method and its signature (this is used to
+   * determine whether one method overrides another).
+   */
+  private static String getMemberSignature(AbstractMethodDeclaration method) {
+    String name = String.valueOf(method.selector);
+    StringBuilder sb = new StringBuilder();
+    sb.append(name);
+    sb.append("(");
+    if (method.arguments != null) {
+      for (Argument param : method.arguments) {
+        sb.append(param.binding.type.signature());
+      }
+    }
+    sb.append(")");
+    return sb.toString();
+  }
+
+  private static String[] getParamNames(AbstractMethodDeclaration method) {
+    if (method.arguments != null) {
+      String[] paramNames = new String[method.arguments.length];
+      for (int i = 0; i < paramNames.length; ++i) {
+        paramNames[i] = String.valueOf(method.arguments[i].name);
+      }
+      return paramNames;
+    }
+    return Empty.STRINGS;
+  }
+
+  /**
+   * TODO: rip out problem reporting code from BuildTypeMap and attach errors to
+   * the compilation units.
+   */
+  private static JsFunction parseAsAnonymousFunction(TreeLogger logger,
+      JsProgram program, String js, String[] paramNames, String location,
+      int startLine) {
+
+    // Wrap the code in an anonymous function and parse it.
+    StringReader r;
+    {
+      StringBuilder sb = new StringBuilder();
+      sb.append("function (");
+      boolean first = true;
+      for (String paramName : paramNames) {
+        if (first) {
+          first = false;
+        } else {
+          sb.append(',');
+        }
+        sb.append(paramName);
+      }
+      sb.append(") {");
+      sb.append(js);
+      sb.append('}');
+      r = new StringReader(sb.toString());
+    }
+
+    try {
+      List<JsStatement> stmts = new JsParser().parse(program.getScope(), r,
+          startLine);
+
+      return (JsFunction) ((JsExprStmt) stmts.get(0)).getExpression();
+    } catch (IOException e) {
+      // Should never happen.
+      throw new RuntimeException(e);
+    } catch (JsParserException e) {
+      SourceDetail dtl = e.getSourceDetail();
+      if (dtl != null) {
+        StringBuilder sb = new StringBuilder();
+        sb.append(location);
+        sb.append("(");
+        sb.append(dtl.getLine());
+        sb.append(", ");
+        sb.append(dtl.getLineOffset());
+        sb.append("): ");
+        sb.append(e.getMessage());
+        logger.log(TreeLogger.ERROR, sb.toString(), e);
+        return null;
+      } else {
+        logger.log(TreeLogger.ERROR, "Error parsing JSNI source", e);
+        return null;
+      }
+    }
+  }
+
+  private JsniCollector() {
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/javac/JsniMethod.java b/dev/core/src/com/google/gwt/dev/javac/JsniMethod.java
new file mode 100644
index 0000000..d45a460
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/javac/JsniMethod.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2008 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.js.ast.JsFunction;
+
+/**
+ * Represents a single JsniMethod in a compiled class file.
+ */
+public abstract class JsniMethod {
+  /**
+   * If non-null, an anonymous function containing the parameters and body of
+   * this JSNI method.
+   */
+  public abstract JsFunction function(TreeLogger logger);
+
+  /**
+   * Starting line number of the method.
+   */
+  public abstract int line();
+
+  /**
+   * Location of the containing compilation unit.
+   */
+  public abstract String location();
+
+  /**
+   * The mangled method name (a jsni signature).
+   */
+  public abstract String name();
+
+  /**
+   * The parameter names.
+   */
+  public abstract String[] paramNames();
+
+  /**
+   * The script body.
+   */
+  public abstract String source();
+}
diff --git a/dev/core/src/com/google/gwt/dev/jdt/LongFromJSNIChecker.java b/dev/core/src/com/google/gwt/dev/javac/LongFromJSNIChecker.java
similarity index 98%
rename from dev/core/src/com/google/gwt/dev/jdt/LongFromJSNIChecker.java
rename to dev/core/src/com/google/gwt/dev/javac/LongFromJSNIChecker.java
index e404c8c..fa49fd6 100644
--- a/dev/core/src/com/google/gwt/dev/jdt/LongFromJSNIChecker.java
+++ b/dev/core/src/com/google/gwt/dev/javac/LongFromJSNIChecker.java
@@ -13,9 +13,10 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.dev.jdt;
+package com.google.gwt.dev.javac;
 
 import com.google.gwt.core.client.UnsafeNativeLong;
+import com.google.gwt.dev.jdt.FindJsniRefVisitor;
 import com.google.gwt.dev.util.InstalledHelpInfo;
 import com.google.gwt.dev.util.JsniRef;
 
diff --git a/dev/core/src/com/google/gwt/dev/jdt/TypeOracleBuilder.java b/dev/core/src/com/google/gwt/dev/javac/TypeOracleMediator.java
similarity index 69%
rename from dev/core/src/com/google/gwt/dev/jdt/TypeOracleBuilder.java
rename to dev/core/src/com/google/gwt/dev/javac/TypeOracleMediator.java
index 0af0fac..a58513c 100644
--- a/dev/core/src/com/google/gwt/dev/jdt/TypeOracleBuilder.java
+++ b/dev/core/src/com/google/gwt/dev/javac/TypeOracleMediator.java
@@ -13,11 +13,10 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.dev.jdt;
+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.CompilationUnitProvider;
 import com.google.gwt.core.ext.typeinfo.HasMetaData;
 import com.google.gwt.core.ext.typeinfo.HasTypeParameters;
 import com.google.gwt.core.ext.typeinfo.JAbstractMethod;
@@ -38,17 +37,14 @@
 import com.google.gwt.core.ext.typeinfo.JRealClassType;
 import com.google.gwt.core.ext.typeinfo.JType;
 import com.google.gwt.core.ext.typeinfo.JTypeParameter;
+import com.google.gwt.core.ext.typeinfo.NotFoundException;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
 import com.google.gwt.core.ext.typeinfo.JWildcardType.BoundType;
-import com.google.gwt.dev.jdt.CacheManager.Mapper;
+import com.google.gwt.dev.javac.impl.Shared;
 import com.google.gwt.dev.util.Empty;
 import com.google.gwt.dev.util.PerfLogger;
-import com.google.gwt.dev.util.Util;
 
 import org.eclipse.jdt.core.compiler.CharOperation;
-import org.eclipse.jdt.core.compiler.IProblem;
-import org.eclipse.jdt.internal.compiler.ASTVisitor;
-import org.eclipse.jdt.internal.compiler.CompilationResult;
 import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration;
 import org.eclipse.jdt.internal.compiler.ast.AbstractVariableDeclaration;
 import org.eclipse.jdt.internal.compiler.ast.Annotation;
@@ -57,7 +53,6 @@
 import org.eclipse.jdt.internal.compiler.ast.ArrayInitializer;
 import org.eclipse.jdt.internal.compiler.ast.ClassLiteralAccess;
 import org.eclipse.jdt.internal.compiler.ast.Clinit;
-import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
 import org.eclipse.jdt.internal.compiler.ast.Expression;
 import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
 import org.eclipse.jdt.internal.compiler.ast.Initializer;
@@ -69,13 +64,10 @@
 import org.eclipse.jdt.internal.compiler.ast.TypeParameter;
 import org.eclipse.jdt.internal.compiler.ast.TypeReference;
 import org.eclipse.jdt.internal.compiler.ast.Wildcard;
-import org.eclipse.jdt.internal.compiler.env.ICompilationUnit;
 import org.eclipse.jdt.internal.compiler.impl.Constant;
 import org.eclipse.jdt.internal.compiler.lookup.ArrayBinding;
 import org.eclipse.jdt.internal.compiler.lookup.BaseTypeBinding;
 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;
 import org.eclipse.jdt.internal.compiler.lookup.LocalTypeBinding;
 import org.eclipse.jdt.internal.compiler.lookup.MethodBinding;
@@ -89,40 +81,26 @@
 import org.eclipse.jdt.internal.compiler.lookup.WildcardBinding;
 
 import java.io.BufferedReader;
-import java.io.CharArrayReader;
-import java.io.File;
 import java.io.IOException;
+import java.io.StringReader;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.reflect.Array;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.Iterator;
+import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.TreeMap;
 import java.util.regex.Pattern;
 
 /**
- * Builds a {@link com.google.gwt.core.ext.typeinfo.TypeOracle} from a set of
- * compilation units.
- * <p>
- * For example,
- * 
- * <pre>
- * TypeOracleBuilder b = new TypeOracleBuilder();
- * b.addCompilationUnit(unit1);
- * b.addCompilationUnit(unit2);
- * b.addCompilationUnit(unit3);
- * b.excludePackage(&quot;example.pkg&quot;);
- * TypeOracle oracle = b.build(logger);
- * JClassType[] allTypes = oracle.getTypes();
- * </pre>
+ * Builds or rebuilds a {@link com.google.gwt.core.ext.typeinfo.TypeOracle} from
+ * a set of compilation units.
  */
-public class TypeOracleBuilder {
+public class TypeOracleMediator {
+
   private static final JClassType[] NO_JCLASSES = new JClassType[0];
   private static final Pattern PATTERN_WHITESPACE = Pattern.compile("\\s");
 
@@ -160,22 +138,23 @@
     return classType.getQualifiedSourceName();
   }
 
-  static boolean parseMetaDataTags(char[] unitSource, HasMetaData hasMetaData,
+  static boolean parseMetaDataTags(String unitSource, HasMetaData hasMetaData,
       Javadoc javadoc) {
 
     int start = javadoc.sourceStart;
     int end = javadoc.sourceEnd;
-    char[] comment = CharOperation.subarray(unitSource, start, end + 1);
-    if (comment == null) {
-      comment = new char[0];
+    if (start < 0 || end > unitSource.length() || start > end) {
+      // Invalid.
+      return false;
     }
-    BufferedReader reader = new BufferedReader(new CharArrayReader(comment));
+    String comment = unitSource.substring(start, end + 1);
+    BufferedReader reader = new BufferedReader(new StringReader(
+        comment.toString()));
     String activeTag = null;
     final List<String> tagValues = new ArrayList<String>();
     try {
-      String line = reader.readLine();
       boolean firstLine = true;
-      while (line != null) {
+      for (String line = reader.readLine(); line != null; line = reader.readLine()) {
         if (firstLine) {
           firstLine = false;
           int commentStart = line.indexOf("/**");
@@ -186,26 +165,27 @@
           line = line.substring(commentStart + 3);
         }
 
+        if (activeTag == null && line.indexOf('@') < 0) {
+          continue;
+        }
+
         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("@")) {
@@ -227,8 +207,6 @@
             }
           }
         }
-
-        line = reader.readLine();
       }
     } catch (IOException e) {
       return false;
@@ -304,14 +282,6 @@
     return retentionPolicy;
   }
 
-  private static boolean isAnnotation(TypeDeclaration typeDecl) {
-    if (TypeDeclaration.kind(typeDecl.modifiers) == TypeDeclaration.ANNOTATION_TYPE_DECL) {
-      return true;
-    } else {
-      return false;
-    }
-  }
-
   /**
    * Returns <code>true</code> if this name is the special package-info type
    * name.
@@ -326,6 +296,11 @@
   private static boolean maybeGeneric(TypeDeclaration typeDecl,
       JClassType enclosingType) {
 
+    if (typeDecl.typeParameters != null) {
+      // Definitely generic since it has type parameters.
+      return true;
+    }
+
     if (enclosingType != null && enclosingType.isGenericType() != null) {
       if (!typeDecl.binding.isStatic()) {
         /*
@@ -351,11 +326,6 @@
       }
     }
 
-    if (typeDecl.typeParameters != null) {
-      // Definitely generic since it has type parameters.
-      return true;
-    }
-
     return false;
   }
 
@@ -364,309 +334,72 @@
     return new HashMap<Class<? extends java.lang.annotation.Annotation>, java.lang.annotation.Annotation>();
   }
 
-  private static void removeInfectedUnits(final TreeLogger logger,
-      final Map<String, CompilationUnitDeclaration> changedCudsByFileName,
-      final Map<String, CompilationUnitDeclaration> unchangedCudsByFileName) {
+  private final Map<String, JRealClassType> binaryMapper = new HashMap<String, JRealClassType>();
+  private final Map<SourceTypeBinding, JRealClassType> sourceMapper = new IdentityHashMap<SourceTypeBinding, JRealClassType>();
+  private final Map<TypeVariableBinding, JTypeParameter> tvMapper = new IdentityHashMap<TypeVariableBinding, JTypeParameter>();
+  private final TypeOracle typeOracle = new TypeOracle();
+  private final Set<JRealClassType> unresolvedTypes = new HashSet<JRealClassType>();
 
-    final Set<String> pendingRemovals = new HashSet<String>();
-    TypeRefVisitor trv = new TypeRefVisitor() {
-      @Override
-      protected void onTypeRef(SourceTypeBinding referencedType,
-          CompilationUnitDeclaration unitOfReferrer) {
-        // If the referenced type belongs to a compilation unit that is
-        // not in the list of valid units, then the unit in which it
-        // is referenced must also be removed.
-        //
-        String referencedFn = String.valueOf(referencedType.getFileName());
-        if (!unchangedCudsByFileName.containsKey(referencedFn)
-            && !changedCudsByFileName.containsKey(referencedFn)) {
-          // This is a referenced to a bad or non-existent unit.
-          // So, remove the referrer's unit if it hasn't been already.
-          //
-          String referrerFn = String.valueOf(unitOfReferrer.getFileName());
-          if (changedCudsByFileName.containsKey(referrerFn)
-              && !pendingRemovals.contains(referrerFn)) {
-            TreeLogger branch = logger.branch(TreeLogger.TRACE,
-                "Cascaded removal of compilation unit '" + referrerFn + "'",
-                null);
-            final String badTypeName = CharOperation.toString(referencedType.compoundName);
-            branch.branch(TreeLogger.TRACE,
-                "Due to reference to unavailable type: " + badTypeName, null);
-            pendingRemovals.add(referrerFn);
-          }
-        }
-      }
-    };
-
-    do {
-      // Perform any pending removals.
-      //
-      for (Iterator<String> iter = pendingRemovals.iterator(); iter.hasNext();) {
-        String fnToRemove = iter.next();
-        Object removed = changedCudsByFileName.remove(fnToRemove);
-        assert (removed != null);
-      }
-
-      // Start fresh for this iteration.
-      //
-      pendingRemovals.clear();
-
-      // Find references to type in units that aren't valid.
-      //
-      for (Iterator<CompilationUnitDeclaration> iter = changedCudsByFileName.values().iterator(); iter.hasNext();) {
-        CompilationUnitDeclaration cud = iter.next();
-        cud.traverse(trv, cud.scope);
-      }
-    } while (!pendingRemovals.isEmpty());
+  public TypeOracle getTypeOracle() {
+    return typeOracle;
   }
 
-  private static void removeUnitsWithErrors(TreeLogger logger,
-      Map<String, CompilationUnitDeclaration> changedCudsByFileName,
-      Map<String, CompilationUnitDeclaration> unchangedCudsByFileName) {
-    // Start by removing units with a known problem.
-    //
-    boolean anyRemoved = false;
-    for (Iterator<CompilationUnitDeclaration> iter = changedCudsByFileName.values().iterator(); iter.hasNext();) {
-      CompilationUnitDeclaration cud = iter.next();
-      CompilationResult result = cud.compilationResult;
-      IProblem[] errors = result.getErrors();
-      if (errors != null && errors.length > 0) {
-        anyRemoved = true;
-        iter.remove();
-
-        String fileName = CharOperation.charToString(cud.getFileName());
-        char[] source = cud.compilationResult.compilationUnit.getContents();
-        Util.maybeDumpSource(logger, fileName, source,
-            String.valueOf(cud.getMainTypeName()));
-        logger.log(TreeLogger.TRACE, "Removing problematic compilation unit '"
-            + fileName + "'", null);
-      }
-    }
-
-    if (anyRemoved) {
-      // Then removing anything else that won't compile as a result.
-      //
-      removeInfectedUnits(logger, changedCudsByFileName,
-          unchangedCudsByFileName);
-    }
-  }
-
-  private final CacheManager cacheManager;
-
-  /**
-   * Constructs a default instance, with a default cacheManager. This is not to
-   * be used in Hosted Mode, as caching will then not work.
-   */
-  public TypeOracleBuilder() {
-    cacheManager = new CacheManager();
-  }
-
-  /**
-   * Constructs an instance from the supplied cacheManager, using the
-   * <code>TypeOracle</code> contained therein. This is to be used in Hosted
-   * Mode, so that caching will work, assuming the cacheManager has a cache
-   * directory.
-   */
-  public TypeOracleBuilder(CacheManager cacheManager) {
-    this.cacheManager = cacheManager;
-  }
-
-  /**
-   * Constructs an instance from the supplied typeOracle, with a cacheManager
-   * using the same typeOracle. This is not to be used in Hosted Mode, as
-   * caching will then not work.
-   */
-  public TypeOracleBuilder(TypeOracle typeOracle) {
-    cacheManager = new CacheManager(typeOracle);
-  }
-
-  /**
-   * Includes the specified logical compilation unit into the set of units this
-   * builder will parse and analyze. If a previous compilation unit was
-   * specified in the same location, it will be replaced if it is older.
-   */
-  public void addCompilationUnit(CompilationUnitProvider cup)
+  public void refresh(TreeLogger logger, Set<CompilationUnit> units)
       throws UnableToCompleteException {
-    cacheManager.addCompilationUnit(cup);
-  }
+    PerfLogger.start("TypeOracleMediator.refresh");
+    typeOracle.removeInvalidatedTypes();
+    binaryMapper.clear();
+    sourceMapper.clear();
+    tvMapper.clear();
+    unresolvedTypes.clear();
 
-  public TypeOracle build(final TreeLogger logger)
-      throws UnableToCompleteException {
-    PerfLogger.start("TypeOracleBuilder.build");
-
-    Set<CompilationUnitProvider> addedCups = cacheManager.getAddedCups();
-    TypeOracle oracle = cacheManager.getTypeOracle();
-    // Make a copy that we can sort.
-    //
-    for (Iterator<CompilationUnitProvider> iter = addedCups.iterator(); iter.hasNext();) {
-      CompilationUnitProvider cup = iter.next();
-      String location = cup.getLocation();
-      // TODO: Delegate to cup in order to check for deletification.
-      if (!((location.indexOf("http://") != -1) || (location.indexOf("ftp://") != -1))) {
-        location = Util.findFileName(location);
-        if (!(new File(location).exists() || cup.isTransient())) {
-          iter.remove();
-          logger.log(
-              TreeLogger.TRACE,
-              "The file "
-                  + location
-                  + " was removed by the user.  All types therein are now unavailable.",
-              null);
+    // Perform a shallow pass to establish identity for new and old types.
+    PerfLogger.start("TypeOracleMediator.refresh (shallow)");
+    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);
       }
     }
-    CompilationUnitProvider[] cups = Util.toArray(
-        CompilationUnitProvider.class, addedCups);
-    Arrays.sort(cups, CompilationUnitProvider.LOCATION_COMPARATOR);
-
-    // Make sure we can find the java.lang.Object compilation unit.
-    //
-    boolean foundJavaLangPackage = oracle.findPackage("java.lang") != null;
-
-    // Adapt to JDT idioms.
-    //
-    ICompilationUnit[] units = new ICompilationUnit[cups.length];
-    for (int i = 0; i < cups.length; i++) {
-      if (!foundJavaLangPackage && cups[i].getPackageName().equals("java.lang")) {
-        foundJavaLangPackage = true;
-      }
-      units[i] = cacheManager.findUnitForCup(cups[i]);
-    }
-
-    // Error if no java.lang.
-    if (!foundJavaLangPackage) {
-      Util.logMissingTypeErrorWithHints(logger, "java.lang.Object");
-      throw new UnableToCompleteException();
-    }
-
-    PerfLogger.start("TypeOracleBuilder.build (compile)");
-    /*
-     * The assumption is that getting the changed CUDs will include anything
-     * that transitively depends on the changed CUDs.
-     */
-    CompilationUnitDeclaration[] cuds = cacheManager.getAstCompiler().getChangedCompilationUnitDeclarations(
-        logger, units);
     PerfLogger.end();
 
-    // Build a list that makes it easy to remove problems.
-    //
-    final Map<String, CompilationUnitDeclaration> unchangedCudsByFileName = new TreeMap<String, CompilationUnitDeclaration>();
-    unchangedCudsByFileName.putAll(cacheManager.getCudsByFileName());
-    final Map<String, CompilationUnitDeclaration> changedCudsByFileName = new TreeMap<String, CompilationUnitDeclaration>();
-    for (int i = 0; i < cuds.length; i++) {
-      CompilationUnitDeclaration cud = cuds[i];
-      String fileName = String.valueOf(cud.getFileName());
-      changedCudsByFileName.put(fileName, cud);
-      /*
-       * The CacheManager's cuds by file name may include the changed CUDs from
-       * a previous refresh. So we remove them here to ensure that our sets do
-       * not overlap.
-       */
-      unchangedCudsByFileName.remove(fileName);
-    }
-
-    /*
-     * Note that the CacheManager can easily end up with bad CUDs if the newly
-     * compiled files have errors. However, this does not impact the TypeOracle.
-     */
-    cacheManager.getCudsByFileName().putAll(changedCudsByFileName);
-
-    /*
-     * At this point changedCudsByFileName contains only the CUDs which needed
-     * to be recompiled and unchangedCudsByFileName contains only the CUDs which
-     * were not recompiled. Now we can scan changedCudsByFileName an remove any
-     * CUDs which have errors and any CUDs that are infected by those errors.
-     */
-    removeUnitsWithErrors(logger, changedCudsByFileName,
-        unchangedCudsByFileName);
-
-    // Perform a shallow pass to establish identity for new types.
-    //
-    final CacheManager.Mapper identityMapper = cacheManager.getIdentityMapper();
-    for (Iterator<CompilationUnitDeclaration> iter = changedCudsByFileName.values().iterator(); iter.hasNext();) {
-      CompilationUnitDeclaration cud = iter.next();
-
-      cud.traverse(new ASTVisitor() {
-        @Override
-        public boolean visit(TypeDeclaration typeDecl, BlockScope scope) {
-          JClassType enclosingType = identityMapper.get(typeDecl.binding.enclosingType());
-          processType(typeDecl, enclosingType, true);
-          return true;
-        }
-
-        @Override
-        public boolean visit(TypeDeclaration typeDecl, ClassScope scope) {
-          JClassType enclosingType = identityMapper.get(typeDecl.binding.enclosingType());
-          processType(typeDecl, enclosingType, false);
-          return true;
-        }
-
-        @Override
-        public boolean visit(TypeDeclaration typeDecl,
-            CompilationUnitScope scope) {
-          processType(typeDecl, null, false);
-          return true;
-        }
-
-      }, cud.scope);
-    }
-
-    // Perform a deep pass to resolve all types in terms of our types.
-    //
-    for (Iterator<CompilationUnitDeclaration> iter = changedCudsByFileName.values().iterator(); iter.hasNext();) {
-      CompilationUnitDeclaration cud = iter.next();
-      String loc = String.valueOf(cud.getFileName());
-      String processing = "Processing types in compilation unit: " + loc;
-      final TreeLogger cudLogger = logger.branch(TreeLogger.SPAM, processing,
-          null);
-      final char[] source = cud.compilationResult.compilationUnit.getContents();
-
-      cud.traverse(new ASTVisitor() {
-        @Override
-        public boolean visit(TypeDeclaration typeDecl, BlockScope scope) {
-          if (!resolveTypeDeclaration(cudLogger, source, typeDecl)) {
-            String name = String.valueOf(typeDecl.binding.readableName());
-            String msg = "Unexpectedly unable to fully resolve type " + name;
-            logger.log(TreeLogger.WARN, msg, null);
+    // Perform a deep pass to resolve all new types in terms of our types.
+    PerfLogger.start("TypeOracleMediator.refresh (deep)");
+    for (CompilationUnit unit : units) {
+      if (!unit.isCompiled()) {
+        continue;
+      }
+      TreeLogger cudLogger = logger.branch(TreeLogger.SPAM,
+          "Processing types in compilation unit: " + unit.getDisplayLocation());
+      Set<CompiledClass> compiledClasses = unit.getCompiledClasses();
+      for (CompiledClass compiledClass : compiledClasses) {
+        if (unresolvedTypes.contains(compiledClass.getRealClassType())) {
+          TypeDeclaration typeDeclaration = compiledClass.getTypeDeclaration();
+          if (!resolveTypeDeclaration(cudLogger, unit.getSource(), typeDeclaration)) {
+            logger.log(TreeLogger.WARN,
+                "Unexpectedly unable to fully resolve type "
+                    + compiledClass.getSourceName());
           }
-          return true;
         }
-
-        @Override
-        public boolean visit(TypeDeclaration typeDecl, ClassScope scope) {
-          if (!resolveTypeDeclaration(cudLogger, source, typeDecl)) {
-            String name = String.valueOf(typeDecl.binding.readableName());
-            String msg = "Unexpectedly unable to fully resolve type " + name;
-            logger.log(TreeLogger.WARN, msg, null);
-          }
-          return true;
-        }
-
-        @Override
-        public boolean visit(TypeDeclaration typeDecl,
-            CompilationUnitScope scope) {
-          if (!resolveTypeDeclaration(cudLogger, source, typeDecl)) {
-            String name = String.valueOf(typeDecl.binding.readableName());
-            String msg = "Unexpectedly unable to fully resolve type " + name;
-            logger.log(TreeLogger.WARN, msg, null);
-          }
-          return true;
-        }
-      }, cud.scope);
+      }
     }
-    Util.invokeInaccessableMethod(TypeOracle.class, "refresh",
-        new Class[] {TreeLogger.class}, oracle, new Object[] {logger});
-
     PerfLogger.end();
 
-    return oracle;
-  }
+    try {
+      typeOracle.refresh(logger);
+    } catch (NotFoundException e) {
+      // TODO
+      e.printStackTrace();
+    }
 
-  /**
-   * Used for testing purposes only.
-   */
-  final CacheManager getCacheManager() {
-    return cacheManager;
+    PerfLogger.end();
   }
 
   private Object createAnnotationInstance(TreeLogger logger,
@@ -710,6 +443,86 @@
     return AnnotationProxyFactory.create(clazz, identifierToValue);
   }
 
+  private JRealClassType createType(CompiledClass compiledClass) {
+    JRealClassType realClassType = compiledClass.getRealClassType();
+    if (realClassType == null) {
+      JRealClassType enclosingType = null;
+      CompiledClass enclosingClass = compiledClass.getEnclosingClass();
+      if (enclosingClass != null) {
+        enclosingType = enclosingClass.getRealClassType();
+        if (enclosingType == null) {
+          enclosingType = createType(enclosingClass);
+        }
+      }
+      realClassType = createType(compiledClass, enclosingType);
+      if (realClassType != null) {
+        unresolvedTypes.add(realClassType);
+        sourceMapper.put(compiledClass.getTypeDeclaration().binding,
+            realClassType);
+        compiledClass.setRealClassType(realClassType);
+      }
+    }
+    return realClassType;
+  }
+
+  /**
+   * Maps a TypeDeclaration into a JRealClassType. If the TypeDeclaration has
+   * TypeParameters (i.e, it is a generic type or method), then the
+   * TypeParameters are mapped into JTypeParameters.
+   */
+  private JRealClassType createType(CompiledClass compiledClass,
+      JRealClassType enclosingType) {
+    TypeDeclaration typeDecl = compiledClass.getTypeDeclaration();
+    SourceTypeBinding binding = typeDecl.binding;
+    assert (binding.constantPoolName() != null);
+
+    String qname = compiledClass.getSourceName();
+    String className = Shared.getShortName(qname);
+    String jpkgName = compiledClass.getPackageName();
+    JPackage pkg = typeOracle.getOrCreatePackage(jpkgName);
+    boolean isLocalType = binding instanceof LocalTypeBinding;
+    boolean isIntf = TypeDeclaration.kind(typeDecl.modifiers) == TypeDeclaration.INTERFACE_DECL;
+    boolean isAnnotation = TypeDeclaration.kind(typeDecl.modifiers) == TypeDeclaration.ANNOTATION_TYPE_DECL;
+
+    JRealClassType resultType;
+    if (isAnnotation) {
+      resultType = new JAnnotationType(typeOracle, pkg, enclosingType,
+          isLocalType, className, isIntf);
+    } else if (maybeGeneric(typeDecl, enclosingType)) {
+      // Go through and create declarations for each of the type parameters on
+      // the generic class or method
+      JTypeParameter[] jtypeParameters = declareTypeParameters(typeDecl.typeParameters);
+
+      JGenericType jgenericType = new JGenericType(typeOracle, pkg,
+          enclosingType, isLocalType, className, isIntf, jtypeParameters);
+
+      resultType = jgenericType;
+    } else if (binding.isEnum()) {
+      resultType = new JEnumType(typeOracle, pkg, enclosingType, isLocalType,
+          className, isIntf);
+    } else {
+      resultType = new JRealClassType(typeOracle, pkg, enclosingType,
+          isLocalType, className, isIntf);
+    }
+
+    /*
+     * Declare type parameters for all methods; we must do this during the first
+     * pass.
+     */
+    if (typeDecl.methods != null) {
+      for (AbstractMethodDeclaration method : typeDecl.methods) {
+        declareTypeParameters(method.typeParameters());
+      }
+    }
+
+    /*
+     * Add modifiers since these are needed for
+     * TypeOracle.getParameterizedType's error checking code.
+     */
+    resultType.addModifierBits(Shared.bindingToModifierBits(binding));
+    return resultType;
+  }
+
   private JClassType[] createTypeParameterBounds(TreeLogger logger,
       TypeVariableBinding tvBinding) {
     TypeBinding firstBound = tvBinding.firstBound;
@@ -782,12 +595,10 @@
     }
 
     JTypeParameter[] jtypeParamArray = new JTypeParameter[typeParameters.length];
-    Mapper identityMapper = cacheManager.getIdentityMapper();
-
-    for (int i = 0; i < typeParameters.length; i++) {
+    for (int i = 0; i < typeParameters.length; ++i) {
       TypeParameter typeParam = typeParameters[i];
       jtypeParamArray[i] = new JTypeParameter(String.valueOf(typeParam.name), i);
-      identityMapper.put(typeParam.binding, jtypeParamArray[i]);
+      tvMapper.put(typeParam.binding, jtypeParamArray[i]);
     }
 
     return jtypeParamArray;
@@ -935,7 +746,7 @@
         String className = String.valueOf(resolvedType.constantPoolName());
         className = className.replace('/', '.');
         return Class.forName(className, false,
-            TypeOracleBuilder.class.getClassLoader());
+            Thread.currentThread().getContextClassLoader());
       } catch (ClassNotFoundException e) {
         logger.log(TreeLogger.ERROR, "", e);
         return null;
@@ -969,17 +780,6 @@
     }
   }
 
-  private CompilationUnitProvider getCup(TypeDeclaration typeDecl) {
-    ICompilationUnit icu = typeDecl.compilationResult.compilationUnit;
-    ICompilationUnitAdapter icua = (ICompilationUnitAdapter) icu;
-    return icua.getCompilationUnitProvider();
-  }
-
-  private String getPackage(TypeDeclaration typeDecl) {
-    final char[][] pkgParts = typeDecl.compilationResult.compilationUnit.getPackageName();
-    return String.valueOf(CharOperation.concatWith(pkgParts, '.'));
-  }
-
   /**
    * Returns the qualified name of the binding, excluding any type parameter
    * information.
@@ -999,101 +799,9 @@
     }
 
     qualifiedName = qualifiedName.replace('$', '.');
-
     return qualifiedName;
   }
 
-  private String getSimpleName(TypeDeclaration typeDecl) {
-    return String.valueOf(typeDecl.name);
-  }
-
-  private boolean isInterface(TypeDeclaration typeDecl) {
-    if (TypeDeclaration.kind(typeDecl.modifiers) == TypeDeclaration.INTERFACE_DECL) {
-      return true;
-    } else {
-      return false;
-    }
-  }
-
-  /**
-   * Maps a TypeDeclaration into a JClassType. If the TypeDeclaration has
-   * TypeParameters (i.e, it is a generic type or method), then the
-   * TypeParameters are mapped into JTypeParameters by
-   * {@link TypeOracleBuilder#declareTypeParameters(org.eclipse.jdt.internal.compiler.ast.TypeParameter[])}
-   */
-  private void processType(TypeDeclaration typeDecl, JClassType jenclosingType,
-      boolean isLocalType) {
-    TypeOracle oracle = cacheManager.getTypeOracle();
-
-    // Create our version of the type structure unless it already exists in the
-    // type oracle.
-    //
-    SourceTypeBinding binding = typeDecl.binding;
-    if (binding.constantPoolName() == null) {
-      /*
-       * Weird case: if JDT determines that this local class is totally
-       * uninstantiable, it won't bother allocating a local name.
-       */
-      return;
-    }
-
-    String qname = getQualifiedName(binding);
-    String className;
-    if (binding instanceof LocalTypeBinding) {
-      className = qname.substring(qname.lastIndexOf('.') + 1);
-    } else {
-      className = getSimpleName(typeDecl);
-    }
-
-    if (oracle.findType(qname) != null) {
-      return;
-    }
-
-    String jpkgName = getPackage(typeDecl);
-    JPackage pkg = oracle.getOrCreatePackage(jpkgName);
-    final boolean jclassIsIntf = isInterface(typeDecl);
-    boolean jclassIsAnnonation = isAnnotation(typeDecl);
-    CompilationUnitProvider cup = getCup(typeDecl);
-
-    int declStart = typeDecl.declarationSourceStart;
-    int declEnd = typeDecl.declarationSourceEnd;
-    int bodyStart = typeDecl.bodyStart;
-    int bodyEnd = typeDecl.bodyEnd;
-
-    JRealClassType jrealClassType;
-    if (jclassIsAnnonation) {
-      jrealClassType = new JAnnotationType(oracle, cup, pkg, jenclosingType,
-          isLocalType, className, declStart, declEnd, bodyStart, bodyEnd,
-          jclassIsIntf);
-    } else if (maybeGeneric(typeDecl, jenclosingType)) {
-      // Go through and create declarations for each of the type parameters on
-      // the generic class or method
-      JTypeParameter[] jtypeParameters = declareTypeParameters(typeDecl.typeParameters);
-
-      JGenericType jgenericType = new JGenericType(oracle, cup, pkg,
-          jenclosingType, isLocalType, className, declStart, declEnd,
-          bodyStart, bodyEnd, jclassIsIntf, jtypeParameters);
-
-      jrealClassType = jgenericType;
-    } else if (binding.isEnum()) {
-      jrealClassType = new JEnumType(oracle, cup, pkg, jenclosingType,
-          isLocalType, className, declStart, declEnd, bodyStart, bodyEnd,
-          jclassIsIntf);
-    } else {
-      jrealClassType = new JRealClassType(oracle, cup, pkg, jenclosingType,
-          isLocalType, className, declStart, declEnd, bodyStart, bodyEnd,
-          jclassIsIntf);
-    }
-
-    /*
-     * Add modifiers since these are needed for
-     * TypeOracle.getParameterizedType's error checking code.
-     */
-    jrealClassType.addModifierBits(Shared.bindingToModifierBits(binding));
-
-    cacheManager.setTypeForBinding(binding, jrealClassType);
-  }
-
   private boolean resolveAnnotation(
       TreeLogger logger,
       Annotation jannotation,
@@ -1146,7 +854,6 @@
     }
 
     genericElement.getTypeParameters()[ordinal].setBounds(jbounds);
-
     return true;
   }
 
@@ -1163,7 +870,7 @@
     return true;
   }
 
-  private boolean resolveField(TreeLogger logger, char[] unitSource,
+  private boolean resolveField(TreeLogger logger, String unitSource,
       JClassType enclosingType, FieldDeclaration jfield) {
 
     if (jfield instanceof Initializer) {
@@ -1213,7 +920,7 @@
     return true;
   }
 
-  private boolean resolveFields(TreeLogger logger, char[] unitSource,
+  private boolean resolveFields(TreeLogger logger, String unitSource,
       JClassType type, FieldDeclaration[] jfields) {
     if (jfields != null) {
       for (int i = 0; i < jfields.length; i++) {
@@ -1226,7 +933,7 @@
     return true;
   }
 
-  private boolean resolveMethod(TreeLogger logger, char[] unitSource,
+  private boolean resolveMethod(TreeLogger logger, String unitSource,
       JClassType enclosingType, AbstractMethodDeclaration jmethod) {
 
     if (jmethod instanceof Clinit) {
@@ -1235,10 +942,6 @@
       return true;
     }
 
-    int declStart = jmethod.declarationSourceStart;
-    int declEnd = jmethod.declarationSourceEnd;
-    int bodyStart = jmethod.bodyStart;
-    int bodyEnd = jmethod.bodyEnd;
     String name = getMethodName(enclosingType, jmethod);
 
     // Try to resolve annotations, ignore any that fail.
@@ -1250,11 +953,11 @@
     // Declare the type parameters. We will pass them into the constructors for
     // JConstructor/JMethod/JAnnotatedMethod. Then, we'll do a second pass to
     // resolve the bounds on each JTypeParameter object.
-    JTypeParameter[] jtypeParameters = declareTypeParameters(jmethod.typeParameters());
+    JTypeParameter[] jtypeParameters = resolveTypeParameters(jmethod.typeParameters());
 
     if (jmethod.isConstructor()) {
-      method = new JConstructor(enclosingType, name, declStart, declEnd,
-          bodyStart, bodyEnd, declaredAnnotations, jtypeParameters);
+      method = new JConstructor(enclosingType, name, declaredAnnotations,
+          jtypeParameters);
       // Do a second pass to resolve the bounds on each JTypeParameter.
       if (!resolveBoundsForTypeParameters(logger, method,
           jmethod.typeParameters())) {
@@ -1269,11 +972,11 @@
               annotationMethod.returnType.resolvedType,
               annotationMethod.defaultValue);
         }
-        method = new JAnnotationMethod(enclosingType, name, declStart, declEnd,
-            bodyStart, bodyEnd, defaultValue, declaredAnnotations);
+        method = new JAnnotationMethod(enclosingType, name, defaultValue,
+            declaredAnnotations);
       } else {
-        method = new JMethod(enclosingType, name, declStart, declEnd,
-            bodyStart, bodyEnd, declaredAnnotations, jtypeParameters);
+        method = new JMethod(enclosingType, name, declaredAnnotations,
+            jtypeParameters);
       }
 
       // Do a second pass to resolve the bounds on each JTypeParameter.
@@ -1328,7 +1031,7 @@
     return true;
   }
 
-  private boolean resolveMethods(TreeLogger logger, char[] unitSource,
+  private boolean resolveMethods(TreeLogger logger, String unitSource,
       JClassType type, AbstractMethodDeclaration[] jmethods) {
     if (jmethods != null) {
       for (int i = 0; i < jmethods.length; i++) {
@@ -1344,9 +1047,8 @@
   private boolean resolvePackage(TreeLogger logger, TypeDeclaration jclass) {
     SourceTypeBinding binding = jclass.binding;
 
-    TypeOracle oracle = cacheManager.getTypeOracle();
     String packageName = String.valueOf(binding.fPackage.readableName());
-    JPackage pkg = oracle.getOrCreatePackage(packageName);
+    JPackage pkg = typeOracle.getOrCreatePackage(packageName);
     assert (pkg != null);
 
     CompilationUnitScope cus = (CompilationUnitScope) jclass.scope.parent;
@@ -1358,7 +1060,6 @@
         declaredAnnotations);
 
     pkg.addAnnotations(declaredAnnotations);
-
     return true;
   }
 
@@ -1426,9 +1127,7 @@
   }
 
   private JType resolveType(TreeLogger logger, TypeBinding binding) {
-    TypeOracle oracle = cacheManager.getTypeOracle();
     // Check for primitives.
-    //
     if (binding instanceof BaseTypeBinding) {
       switch (binding.id) {
         case TypeIds.T_boolean:
@@ -1470,10 +1169,10 @@
       // oracle we're assimilating into.
       //
       String typeName = getQualifiedName(referenceBinding);
-      JType resolvedType = oracle.findType(typeName);
+      JType resolvedType = typeOracle.findType(typeName);
       if (resolvedType == null) {
         // Otherwise, it should be something we've mapped during this build.
-        resolvedType = cacheManager.getTypeForBinding(referenceBinding);
+        resolvedType = sourceMapper.get(referenceBinding);
       }
 
       if (resolvedType != null) {
@@ -1486,6 +1185,15 @@
       }
     }
 
+    if (binding instanceof BinaryTypeBinding) {
+      // Try a binary lookup.
+      String binaryName = String.valueOf(binding.constantPoolName());
+      JRealClassType realClassType = binaryMapper.get(binaryName);
+      if (realClassType != null) {
+        return realClassType;
+      }
+    }
+
     // Check for an array.
     //
     if (binding instanceof ArrayBinding) {
@@ -1501,7 +1209,7 @@
           // By using the oracle to intern, we guarantee correct identity
           // mapping of lazily-created array types.
           //
-          resolvedType = oracle.getArrayType(resolvedType);
+          resolvedType = typeOracle.getArrayType(resolvedType);
         }
         return resolvedType;
       } else {
@@ -1552,7 +1260,7 @@
 
       if (!failed) {
         if (resolveType.isGenericType() != null) {
-          return oracle.getParameterizedType(resolveType.isGenericType(),
+          return typeOracle.getParameterizedType(resolveType.isGenericType(),
               enclosingType, typeArguments);
         } else {
           /*
@@ -1570,7 +1278,7 @@
 
     if (binding instanceof TypeVariableBinding) {
       TypeVariableBinding tvBinding = (TypeVariableBinding) binding;
-      JTypeParameter typeParameter = (JTypeParameter) cacheManager.getTypeForBinding(tvBinding);
+      JTypeParameter typeParameter = tvMapper.get(tvBinding);
       if (typeParameter != null) {
         return typeParameter;
       }
@@ -1610,7 +1318,7 @@
       }
 
       if (boundType != null) {
-        return oracle.getWildcardType(boundType, typeBound);
+        return typeOracle.getWildcardType(boundType, typeBound);
       }
 
       // Fall-through to failure
@@ -1630,16 +1338,10 @@
     return null;
   }
 
-  private boolean resolveTypeDeclaration(TreeLogger logger, char[] unitSource,
+  private boolean resolveTypeDeclaration(TreeLogger logger, String unitSource,
       TypeDeclaration clazz) {
     SourceTypeBinding binding = clazz.binding;
-    if (binding.constantPoolName() == null) {
-      /*
-       * Weird case: if JDT determines that this local class is totally
-       * uninstantiable, it won't bother allocating a local name.
-       */
-      return true;
-    }
+    assert (binding.constantPoolName() != null);
 
     String qname = String.valueOf(binding.qualifiedSourceName());
     logger = logger.branch(TreeLogger.SPAM, "Found type '" + qname + "'", null);
@@ -1682,11 +1384,11 @@
     //
     if (jtype.isInterface() == null) {
       ReferenceBinding superclassRef = binding.superclass;
+      assert superclassRef != null
+          || "java.lang.Object".equals(jtype.getQualifiedSourceName());
       if (superclassRef != null) {
         JClassType jsuperClass = (JClassType) resolveType(logger, superclassRef);
-        if (jsuperClass == null) {
-          return false;
-        }
+        assert jsuperClass != null;
         jtype.setSuperclass(jsuperClass);
       }
     }
@@ -1738,4 +1440,29 @@
 
     return true;
   }
+
+  /**
+   * Declares TypeParameters declared on a JGenericType or a JAbstractMethod by
+   * mapping the TypeParameters into JTypeParameters. <p/> This mapping has to
+   * be done on the first pass through the AST in order to handle declarations
+   * of the form: <<C exends GenericClass<T>, T extends SimpleClass> <p/> JDT
+   * already knows that GenericClass<T> is a parameterized type with a type
+   * argument of <T extends SimpleClass>. Therefore, in order to resolve
+   * GenericClass<T>, we must have knowledge of <T extends SimpleClass>. <p/>
+   * By calling this method on the first pass through the AST, a JTypeParameter
+   * for <T extends SimpleClass> will be created.
+   */
+  private JTypeParameter[] resolveTypeParameters(TypeParameter[] typeParameters) {
+    if (typeParameters == null || typeParameters.length == 0) {
+      return null;
+    }
+
+    JTypeParameter[] jtypeParamArray = new JTypeParameter[typeParameters.length];
+    for (int i = 0; i < typeParameters.length; ++i) {
+      jtypeParamArray[i] = tvMapper.get(typeParameters[i].binding);
+      assert jtypeParamArray[i] != null;
+    }
+
+    return jtypeParamArray;
+  }
 }
diff --git a/dev/core/src/com/google/gwt/dev/javac/impl/FileCompilationUnit.java b/dev/core/src/com/google/gwt/dev/javac/impl/FileCompilationUnit.java
new file mode 100644
index 0000000..3194633
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/javac/impl/FileCompilationUnit.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2008 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.impl;
+
+import com.google.gwt.dev.javac.CompilationUnit;
+import com.google.gwt.dev.util.Util;
+
+import java.io.File;
+
+/**
+ * A compilation unit based on a file.
+ */
+public final class FileCompilationUnit extends CompilationUnit {
+  private final File file;
+  private final String typeName;
+
+  public FileCompilationUnit(File file, String packageName) {
+    this.file = file;
+    String fileName = file.getName();
+    assert fileName.endsWith(".java");
+    fileName = fileName.substring(0, fileName.length() - 5);
+    if (packageName.length() == 0) {
+      this.typeName = fileName;
+    } else {
+      this.typeName = packageName + '.' + fileName;
+    }
+  }
+
+  @Override
+  public String getDisplayLocation() {
+    return file.getAbsolutePath();
+  }
+
+  @Override
+  public String getSource() {
+    return Util.readFileAsString(file);
+  }
+
+  @Override
+  public String getTypeName() {
+    return typeName;
+  }
+
+  @Override
+  public boolean isGenerated() {
+    return false;
+  }
+}
\ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/dev/javac/impl/JavaSourceOracleImpl.java b/dev/core/src/com/google/gwt/dev/javac/impl/JavaSourceOracleImpl.java
new file mode 100644
index 0000000..820c214
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/javac/impl/JavaSourceOracleImpl.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright 2008 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.impl;
+
+import com.google.gwt.dev.javac.JavaSourceFile;
+import com.google.gwt.dev.javac.JavaSourceOracle;
+import com.google.gwt.dev.resource.Resource;
+import com.google.gwt.dev.resource.ResourceOracle;
+import com.google.gwt.dev.util.Util;
+
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Implements {@link JavaSourceOracle} on top of a {@link ResourceOracle}.
+ */
+public class JavaSourceOracleImpl implements JavaSourceOracle {
+
+  private static class JavaSourceFileImpl extends JavaSourceFile {
+
+    private final String location;
+    private final String name;
+    private final String packageName;
+    private Resource resource;
+    private final String shortName;
+
+    public JavaSourceFileImpl(Resource resource) {
+      this.resource = resource;
+      location = resource.getLocation();
+      String path = resource.getPath();
+      assert (path.endsWith(".java"));
+      path = path.substring(0, path.lastIndexOf('.'));
+      name = path.replace('/', '.');
+      int pos = name.lastIndexOf('.');
+      if (pos < 0) {
+        shortName = name;
+        packageName = "";
+      } else {
+        shortName = name.substring(pos + 1);
+        packageName = name.substring(0, pos);
+      }
+    }
+
+    @Override
+    public String getLocation() {
+      return location;
+    }
+
+    @Override
+    public String getPackageName() {
+      return packageName;
+    }
+
+    @Override
+    public String getShortName() {
+      return shortName;
+    }
+
+    @Override
+    public String getTypeName() {
+      return name;
+    }
+
+    @Override
+    public String readSource() {
+      if (resource != null) {
+        InputStream contents = resource.openContents();
+        return Util.readStreamAsString(contents);
+      }
+      return null;
+    }
+
+    Resource getResource() {
+      return resource;
+    }
+
+    void invalidate() {
+      resource = null;
+    }
+  }
+
+  /**
+   * The last resource set returned by my oracle.
+   */
+  private Set<Resource> cachedResources = Collections.emptySet();
+
+  /**
+   * An unmodifiable set of exposedClassNames to return to a client.
+   */
+  private Set<String> exposedClassNames = Collections.emptySet();
+
+  /**
+   * An unmodifiable set of exposedSourceFiles to return to a client.
+   */
+  private Set<JavaSourceFile> exposedSourceFiles = Collections.emptySet();
+
+  /**
+   * An unmodifiable source map to return to a client.
+   */
+  private Map<String, JavaSourceFile> exposedSourceMap = Collections.emptyMap();
+
+  /**
+   * My resource oracle.
+   */
+  private final ResourceOracle oracle;
+
+  /**
+   * My internal set of source files.
+   */
+  private final Set<JavaSourceFileImpl> sourceFiles = new HashSet<JavaSourceFileImpl>();
+
+  public JavaSourceOracleImpl(ResourceOracle oracle) {
+    this.oracle = oracle;
+  }
+
+  public Set<String> getClassNames() {
+    refresh();
+    return exposedClassNames;
+  }
+
+  public Set<JavaSourceFile> getSourceFiles() {
+    refresh();
+    return exposedSourceFiles;
+  }
+
+  public Map<String, JavaSourceFile> getSourceMap() {
+    refresh();
+    return exposedSourceMap;
+  }
+
+  private void refresh() {
+    Set<Resource> newResources = oracle.getResources();
+    if (newResources == cachedResources) {
+      // We're up to date.
+      return;
+    }
+
+    // Divide resources into changed and unchanged.
+    Set<Resource> unchanged = new HashSet<Resource>(cachedResources);
+    unchanged.retainAll(newResources);
+
+    Set<Resource> changed = new HashSet<Resource>(newResources);
+    changed.removeAll(unchanged);
+
+    // First remove any stale source files.
+    for (Iterator<JavaSourceFileImpl> it = sourceFiles.iterator(); it.hasNext();) {
+      JavaSourceFileImpl sourceFile = it.next();
+      if (!unchanged.contains(sourceFile.getResource())) {
+        sourceFile.invalidate();
+        it.remove();
+      }
+    }
+
+    // Then add any new source files.
+    for (Resource newResource : changed) {
+      sourceFiles.add(new JavaSourceFileImpl(newResource));
+    }
+
+    // Finally rebuild the unmodifiable views.
+    Map<String, JavaSourceFile> sourceMap = new HashMap<String, JavaSourceFile>();
+    for (JavaSourceFileImpl sourceFile : sourceFiles) {
+      sourceMap.put(sourceFile.getTypeName(), sourceFile);
+    }
+    exposedSourceMap = Collections.unmodifiableMap(sourceMap);
+    exposedClassNames = Collections.unmodifiableSet(sourceMap.keySet());
+    HashSet<JavaSourceFile> sourceFilesConstantLookup = new HashSet<JavaSourceFile>(
+        sourceMap.values());
+    exposedSourceFiles = Collections.unmodifiableSet(sourceFilesConstantLookup);
+
+    // Record the update.
+    cachedResources = newResources;
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/javac/impl/Shared.java b/dev/core/src/com/google/gwt/dev/javac/impl/Shared.java
new file mode 100644
index 0000000..3cf1613
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/javac/impl/Shared.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright 2008 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.impl;
+
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JConstructor;
+import com.google.gwt.core.ext.typeinfo.JField;
+import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.gwt.core.ext.typeinfo.JPackage;
+import com.google.gwt.core.ext.typeinfo.JParameter;
+import com.google.gwt.core.ext.typeinfo.JType;
+import com.google.gwt.dev.util.Util;
+import com.google.gwt.util.tools.Utility;
+
+import org.eclipse.jdt.internal.compiler.lookup.FieldBinding;
+import org.eclipse.jdt.internal.compiler.lookup.MethodBinding;
+import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A grab bag of utility functions useful for dealing with java files.
+ */
+public class Shared {
+
+  public static final int MOD_ABSTRACT = 0x00000001;
+  public static final int MOD_FINAL = 0x00000002;
+  public static final int MOD_NATIVE = 0x00000004;
+  public static final int MOD_PRIVATE = 0x00000008;
+  public static final int MOD_PROTECTED = 0x00000010;
+  public static final int MOD_PUBLIC = 0x00000020;
+  public static final int MOD_STATIC = 0x00000040;
+  public static final int MOD_TRANSIENT = 0x00000080;
+  public static final int MOD_VOLATILE = 0x00000100;
+  public static final JClassType[] NO_JCLASSES = new JClassType[0];
+  public static final JConstructor[] NO_JCTORS = new JConstructor[0];
+  public static final JField[] NO_JFIELDS = new JField[0];
+  public static final JMethod[] NO_JMETHODS = new JMethod[0];
+  public static final JPackage[] NO_JPACKAGES = new JPackage[0];
+  public static final JParameter[] NO_JPARAMS = new JParameter[0];
+  public static final JType[] NO_JTYPES = new JType[0];
+  public static final String[][] NO_STRING_ARR_ARR = new String[0][];
+  public static final String[] NO_STRINGS = new String[0];
+
+  public static int bindingToModifierBits(FieldBinding binding) {
+    int bits = 0;
+    bits |= (binding.isPublic() ? MOD_PUBLIC : 0);
+    bits |= (binding.isPrivate() ? MOD_PRIVATE : 0);
+    bits |= (binding.isProtected() ? MOD_PROTECTED : 0);
+    bits |= (binding.isStatic() ? MOD_STATIC : 0);
+    bits |= (binding.isTransient() ? MOD_TRANSIENT : 0);
+    bits |= (binding.isFinal() ? MOD_FINAL : 0);
+    bits |= (binding.isVolatile() ? MOD_VOLATILE : 0);
+    return bits;
+  }
+
+  public static int bindingToModifierBits(MethodBinding binding) {
+    int bits = 0;
+    bits |= (binding.isPublic() ? MOD_PUBLIC : 0);
+    bits |= (binding.isPrivate() ? MOD_PRIVATE : 0);
+    bits |= (binding.isProtected() ? MOD_PROTECTED : 0);
+    bits |= (binding.isStatic() ? MOD_STATIC : 0);
+    bits |= (binding.isFinal() ? MOD_FINAL : 0);
+    bits |= (binding.isNative() ? MOD_NATIVE : 0);
+    bits |= (binding.isAbstract() ? MOD_ABSTRACT : 0);
+    return bits;
+  }
+
+  public static int bindingToModifierBits(ReferenceBinding binding) {
+    int bits = 0;
+    bits |= (binding.isPublic() ? MOD_PUBLIC : 0);
+    bits |= (binding.isPrivate() ? MOD_PRIVATE : 0);
+    bits |= (binding.isProtected() ? MOD_PROTECTED : 0);
+    bits |= (binding.isStatic() ? MOD_STATIC : 0);
+    bits |= (binding.isFinal() ? MOD_FINAL : 0);
+    bits |= (binding.isAbstract() ? MOD_ABSTRACT : 0);
+    return bits;
+  }
+
+  public static String getPackageName(String qualifiedTypeName) {
+    int pos = qualifiedTypeName.lastIndexOf('.');
+    return (pos < 0) ? "" : qualifiedTypeName.substring(0, pos);
+  }
+
+  public static String getPackageNameFromBinary(String binaryName) {
+    int pos = binaryName.lastIndexOf('/');
+    return (pos < 0) ? "" : binaryName.substring(0, pos).replace('/', '.');
+  }
+
+  public static String getShortName(String qualifiedTypeName) {
+    int pos = qualifiedTypeName.lastIndexOf('.');
+    return (pos < 0) ? qualifiedTypeName : qualifiedTypeName.substring(pos + 1);
+  }
+
+  public static String makeTypeName(String packageName, String shortName) {
+    if (packageName.length() == 0) {
+      return shortName;
+    } else {
+      return packageName + '.' + shortName;
+    }
+  }
+
+  public static String readContent(InputStream content) {
+    try {
+      ByteArrayOutputStream out = new ByteArrayOutputStream();
+      byte[] buf = new byte[1024];
+      for (int readCount = content.read(buf); readCount > 0; readCount = content.read(buf)) {
+        out.write(buf, 0, readCount);
+      }
+      return Util.toString(out.toByteArray());
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    } finally {
+      Utility.close(content);
+    }
+  }
+
+  public static String toPath(String qualifiedTypeName) {
+    return qualifiedTypeName.replace('.', '/') + ".java";
+  }
+
+  public static String toTypeName(String path) {
+    assert (path.endsWith(".java"));
+    path = path.substring(0, path.lastIndexOf('.'));
+    return path.replace('/', '.');
+  }
+
+  static String[] modifierBitsToNames(int bits) {
+    List<String> strings = new ArrayList<String>();
+
+    // The order is based on the order in which we want them to appear.
+    //
+    if (0 != (bits & MOD_PUBLIC)) {
+      strings.add("public");
+    }
+
+    if (0 != (bits & MOD_PRIVATE)) {
+      strings.add("private");
+    }
+
+    if (0 != (bits & MOD_PROTECTED)) {
+      strings.add("protected");
+    }
+
+    if (0 != (bits & MOD_STATIC)) {
+      strings.add("static");
+    }
+
+    if (0 != (bits & MOD_ABSTRACT)) {
+      strings.add("abstract");
+    }
+
+    if (0 != (bits & MOD_FINAL)) {
+      strings.add("final");
+    }
+
+    if (0 != (bits & MOD_NATIVE)) {
+      strings.add("native");
+    }
+
+    if (0 != (bits & MOD_TRANSIENT)) {
+      strings.add("transient");
+    }
+
+    if (0 != (bits & MOD_VOLATILE)) {
+      strings.add("volatile");
+    }
+
+    return strings.toArray(NO_STRINGS);
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/javac/impl/SourceFileCompilationUnit.java b/dev/core/src/com/google/gwt/dev/javac/impl/SourceFileCompilationUnit.java
new file mode 100644
index 0000000..4ad2541
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/javac/impl/SourceFileCompilationUnit.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2008 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.impl;
+
+import com.google.gwt.dev.javac.CompilationUnit;
+import com.google.gwt.dev.javac.JavaSourceFile;
+
+/**
+ * A compilation unit that was generated.
+ */
+public class SourceFileCompilationUnit extends CompilationUnit {
+
+  private String sourceCode;
+  private JavaSourceFile sourceFile;
+
+  public SourceFileCompilationUnit(JavaSourceFile sourceFile) {
+    this.sourceFile = sourceFile;
+  }
+
+  @Override
+  public String getDisplayLocation() {
+    return sourceFile.getLocation();
+  }
+
+  @Override
+  public String getSource() {
+    if (sourceCode == null) {
+      sourceCode = sourceFile.readSource();
+    }
+    return sourceCode;
+  }
+
+  public JavaSourceFile getSourceFile() {
+    return sourceFile;
+  }
+
+  @Override
+  public String getTypeName() {
+    return sourceFile.getTypeName();
+  }
+
+  @Override
+  public boolean isGenerated() {
+    return false;
+  }
+
+  @Override
+  protected void dumpSource() {
+    sourceCode = null;
+  }
+  
+}
diff --git a/dev/core/src/com/google/gwt/dev/jdt/AbstractCompiler.java b/dev/core/src/com/google/gwt/dev/jdt/AbstractCompiler.java
index b575d62..885ef4b 100644
--- a/dev/core/src/com/google/gwt/dev/jdt/AbstractCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jdt/AbstractCompiler.java
@@ -16,9 +16,12 @@
 package com.google.gwt.dev.jdt;
 
 import com.google.gwt.core.ext.TreeLogger;
-import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.TreeLogger.HelpInfo;
-import com.google.gwt.core.ext.typeinfo.CompilationUnitProvider;
+import com.google.gwt.dev.javac.CompilationState;
+import com.google.gwt.dev.javac.CompilationUnit;
+import com.google.gwt.dev.javac.GWTProblem;
+import com.google.gwt.dev.javac.JdtCompiler.CompilationUnitAdapter;
+import com.google.gwt.dev.javac.impl.Shared;
 import com.google.gwt.dev.util.CharArrayComparator;
 import com.google.gwt.dev.util.Empty;
 import com.google.gwt.dev.util.PerfLogger;
@@ -59,22 +62,6 @@
 public abstract class AbstractCompiler {
 
   /**
-   * A policy that can be set to affect which
-   * {@link org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration
-   * CompilationUnitDeclarations} the compiler processes.
-   */
-  public interface CachePolicy {
-
-    /**
-     * Return true if <code>cud</code> should be processed, otherwise false.
-     * 
-     * @param cud a not <code>null</code> unit
-     * @return true iff <code>cud</code> should be fully processed
-     */
-    boolean shouldProcess(CompilationUnitDeclaration cud);
-  }
-
-  /**
    * Adapted to hook the processing of compilation unit declarations so as to be
    * able to add additional compilation units based on the results of
    * previously-compiled ones. Examples of cases where this is useful include
@@ -105,11 +92,6 @@
 
       long processBeginNanos = System.nanoTime();
 
-      if (!cachePolicy.shouldProcess(cud)) {
-        jdtProcessNanos += System.nanoTime() - processBeginNanos;
-        return;
-      }
-
       // The following block of code is a copy of super.process(cud, index),
       // with the modification that cud.generateCode is conditionally called
       // based on doGenerateBytes
@@ -313,34 +295,11 @@
       TreeLogger logger = threadLogger.branch(TreeLogger.SPAM,
           "Compiler is asking about '" + qname + "'", null);
 
-      if (sourceOracle.isPackage(qname)) {
+      if (isPackage(qname)) {
         logger.log(TreeLogger.SPAM, "Found to be a package", null);
         return null;
       }
 
-      // Try to find the compiled type in the cache.
-      //
-      ByteCode byteCode = doGetByteCodeFromCache(logger, qname);
-      if (byteCode != null) {
-        // Return it as a binary type to JDT.
-        //
-        byte[] classBytes = byteCode.getBytes();
-        char[] loc = byteCode.getLocation().toCharArray();
-        try {
-          logger.log(TreeLogger.SPAM, "Found cached bytes", null);
-          ClassFileReader cfr = new ClassFileReader(classBytes, loc);
-          NameEnvironmentAnswer out = new NameEnvironmentAnswer(cfr, null);
-          nameEnvironmentAnswerForTypeName.put(qname, out);
-          return out;
-        } catch (ClassFormatException e) {
-          // Bad bytecode in the cache. Remove it from the cache.
-          //
-          String msg = "Bad bytecode for '" + qname + "'";
-          compiler.problemReporter.abortDueToInternalError(msg);
-          return null;
-        }
-      }
-
       // Didn't find it in the cache, so let's compile from source.
       // Strip off the inner types, if any
       //
@@ -353,49 +312,40 @@
           return (nameEnvironmentAnswerForTypeName.get(qname));
         }
       }
-      CompilationUnitProvider cup;
-      try {
-        cup = sourceOracle.findCompilationUnit(logger, qname);
-        if (cup != null) {
-          logger.log(TreeLogger.SPAM, "Found type in compilation unit: "
-              + cup.getLocation(), null);
-          ICompilationUnitAdapter unit = new ICompilationUnitAdapter(cup);
-          NameEnvironmentAnswer out = new NameEnvironmentAnswer(unit, null);
-          nameEnvironmentAnswerForTypeName.put(qname, out);
-          return out;
-        } else {
-          ClassLoader classLoader = getClassLoader();
-          URL resourceURL = classLoader.getResource(className.replace('.', '/')
-              + ".class");
-          if (resourceURL != null) {
-            /*
-             * We know that there is a .class file that matches the name that we
-             * are looking for. However, at least on OSX, this lookup is case
-             * insensitive so we need to use Class.forName to effectively verify
-             * the case.
-             */
-            if (isBinaryType(classLoader, className)) {
-              byte[] classBytes = Util.readURLAsBytes(resourceURL);
-              ClassFileReader cfr;
-              try {
-                cfr = new ClassFileReader(classBytes, null);
-                NameEnvironmentAnswer out = new NameEnvironmentAnswer(cfr, null);
-                nameEnvironmentAnswerForTypeName.put(qname, out);
-                return out;
-              } catch (ClassFormatException e) {
-                // Ignored.
-              }
+      CompilationUnit unit = findCompilationUnit(qname);
+      if (unit != null) {
+        logger.log(TreeLogger.SPAM, "Found type in compilation unit: "
+            + unit.getDisplayLocation());
+        ICompilationUnit icu = new CompilationUnitAdapter(unit);
+        NameEnvironmentAnswer out = new NameEnvironmentAnswer(icu, null);
+        nameEnvironmentAnswerForTypeName.put(qname, out);
+        return out;
+      } else {
+        ClassLoader classLoader = getClassLoader();
+        URL resourceURL = classLoader.getResource(className.replace('.', '/')
+            + ".class");
+        if (resourceURL != null) {
+          /*
+           * We know that there is a .class file that matches the name that we
+           * are looking for. However, at least on OSX, this lookup is case
+           * insensitive so we need to use Class.forName to effectively verify
+           * the case.
+           */
+          if (isBinaryType(classLoader, className)) {
+            byte[] classBytes = Util.readURLAsBytes(resourceURL);
+            ClassFileReader cfr;
+            try {
+              cfr = new ClassFileReader(classBytes, null);
+              NameEnvironmentAnswer out = new NameEnvironmentAnswer(cfr, null);
+              nameEnvironmentAnswerForTypeName.put(qname, out);
+              return out;
+            } catch (ClassFormatException e) {
+              // Ignored.
             }
           }
-
-          logger.log(TreeLogger.SPAM, "Not a known type", null);
-          return null;
         }
-      } catch (UnableToCompleteException e) {
-        // It was found, but something went really wrong trying to get it.
-        //
-        String msg = "Error acquiring source for '" + qname + "'";
-        compiler.problemReporter.abortDueToInternalError(msg);
+
+        logger.log(TreeLogger.SPAM, "Not a known type", null);
         return null;
       }
     }
@@ -409,7 +359,7 @@
       String packageName = String.valueOf(pathChars);
       if (knownPackages.contains(packageName)) {
         return true;
-      } else if (sourceOracle.isPackage(packageName)
+      } else if (isPackage(packageName)
           || isPackage(getClassLoader(), packageName)) {
         // Grow our own list to spare calls into the host.
         //
@@ -442,6 +392,10 @@
       String packageAsPath = packageName.replace('.', '/');
       return classLoader.getResource(packageAsPath) != null;
     }
+
+    private boolean isPackage(String packageName) {
+      return packages.contains(packageName);
+    }
   }
 
   private static final Comparator<CompilationUnitDeclaration> CUD_COMPARATOR = new Comparator<CompilationUnitDeclaration>() {
@@ -468,16 +422,10 @@
     }
   };
 
-  private static final CachePolicy DEFAULT_POLICY = new CachePolicy() {
-    public boolean shouldProcess(CompilationUnitDeclaration cud) {
-      return true;
-    }
-  };
+  protected final CompilationState compilationState;
 
   protected final ThreadLocalTreeLoggerProxy threadLogger = new ThreadLocalTreeLoggerProxy();
 
-  private CachePolicy cachePolicy = DEFAULT_POLICY;
-
   private final CompilerImpl compiler;
 
   private final boolean doGenerateBytes;
@@ -486,12 +434,11 @@
 
   private final Map<String, NameEnvironmentAnswer> nameEnvironmentAnswerForTypeName = new HashMap<String, NameEnvironmentAnswer>();
 
-  private final SourceOracle sourceOracle;
+  private final Set<String> packages = new HashSet<String>();
 
-  private final Map<String, ICompilationUnit> unitsByTypeName = new HashMap<String, ICompilationUnit>();
-
-  protected AbstractCompiler(SourceOracle sourceOracle, boolean doGenerateBytes) {
-    this.sourceOracle = sourceOracle;
+  protected AbstractCompiler(CompilationState compilationState,
+      boolean doGenerateBytes) {
+    this.compilationState = compilationState;
     this.doGenerateBytes = doGenerateBytes;
     rememberPackage("");
 
@@ -528,30 +475,14 @@
         probFact);
   }
 
-  public CachePolicy getCachePolicy() {
-    return cachePolicy;
-  }
-
-  public final void invalidateUnitsInFiles(Set<String> typeNames) {
-    // StandardSourceOracle has its own cache that needs to be cleared
-    // out. Short of modifying the interface SourceOracle to have an
-    // invalidateCups, this check is needed.
-    if (sourceOracle instanceof StandardSourceOracle) {
-      StandardSourceOracle sso = (StandardSourceOracle) sourceOracle;
-      sso.invalidateCups(typeNames);
-    }
-    for (String qname : typeNames) {
-      unitsByTypeName.remove(qname);
-      nameEnvironmentAnswerForTypeName.remove(qname);
-    }
-  }
-
-  public void setCachePolicy(CachePolicy policy) {
-    this.cachePolicy = policy;
-  }
-
   protected final CompilationUnitDeclaration[] compile(TreeLogger logger,
       ICompilationUnit[] units) {
+    // Initialize the packages list.
+    for (CompilationUnit unit : compilationState.getCompilationUnits()) {
+      String packageName = Shared.getPackageName(unit.getTypeName());
+      addPackages(packageName);
+    }
+
     // Any additional compilation units that are found to be needed will be
     // pulled in while procssing compilation units. See CompilerImpl.process().
     //
@@ -596,53 +527,18 @@
     return Empty.STRINGS;
   }
 
-  /**
-   * Checks to see if we already have the bytecode definition of the requested
-   * type. By default we compile everything from source, so we never have it
-   * unless a subclass overrides this method.
-   */
-  @SuppressWarnings("unused")
-  // overrider may use unused parameter
-  protected ByteCode doGetByteCodeFromCache(TreeLogger logger,
-      String binaryTypeName) {
-    return null;
-  }
-
-  /**
-   * Finds a compilation unit for the given type. This is often used to
-   * bootstrap compiles since during compiles, the compiler will directly ask
-   * the name environment internally, bypassing this call.
-   */
-  protected ICompilationUnit getCompilationUnitForType(TreeLogger logger,
-      String binaryTypeName) throws UnableToCompleteException {
-
-    // We really look for the topmost type rather than a nested type.
-    //
-    String top = stripNestedTypeNames(binaryTypeName);
-
-    // Check the cache.
-    //
-    ICompilationUnit unit = unitsByTypeName.get(top);
-    if (unit != null) {
-      return unit;
+  protected CompilationUnit findCompilationUnit(String qname) {
+    // Build the initial set of compilation units.
+    Map<String, CompilationUnit> unitMap = compilationState.getCompilationUnitMap();
+    CompilationUnit unit = unitMap.get(qname);
+    while (unit == null) {
+      int pos = qname.lastIndexOf('.');
+      if (pos < 0) {
+        return null;
+      }
+      qname = qname.substring(0, pos);
+      unit = unitMap.get(qname);
     }
-
-    // Not cached, so actually look for it.
-    //
-    CompilationUnitProvider cup = sourceOracle.findCompilationUnit(logger, top);
-    if (cup == null) {
-      // Could not find the starting type.
-      //
-      String s = "Unable to find compilation unit for type '" + top + "'";
-      logger.log(TreeLogger.WARN, s, null);
-      throw new UnableToCompleteException();
-    }
-
-    // Create a cup adapter and cache it.
-    //
-    unit = new ICompilationUnitAdapter(cup);
-    unitsByTypeName.put(top, unit);
-
     return unit;
   }
 
@@ -673,20 +569,19 @@
     return compiler.resolvePossiblyNestedType(typeName);
   }
 
-  SourceOracle getSourceOracle() {
-    return sourceOracle;
-  }
-
-  private String stripNestedTypeNames(String binaryTypeName) {
-    int i = binaryTypeName.lastIndexOf('.');
-    if (i == -1) {
-      i = 0;
+  private void addPackages(String packageName) {
+    if (packages.contains(packageName)) {
+      return;
     }
-    int j = binaryTypeName.indexOf('$', i);
-    if (j != -1) {
-      return binaryTypeName.substring(0, j);
-    } else {
-      return binaryTypeName;
+    while (true) {
+      packages.add(String.valueOf(packageName));
+      int pos = packageName.lastIndexOf('.');
+      if (pos > 0) {
+        packageName = packageName.substring(0, pos);
+      } else {
+        packages.add("");
+        break;
+      }
     }
   }
 }
diff --git a/dev/core/src/com/google/gwt/dev/jdt/AstCompiler.java b/dev/core/src/com/google/gwt/dev/jdt/AstCompiler.java
deleted file mode 100644
index 5ae0c7e..0000000
--- a/dev/core/src/com/google/gwt/dev/jdt/AstCompiler.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright 2008 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.jdt;
-
-import com.google.gwt.core.ext.TreeLogger;
-import com.google.gwt.core.ext.typeinfo.CompilationUnitProvider;
-
-import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
-import org.eclipse.jdt.internal.compiler.env.ICompilationUnit;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * A facade around the JDT compiler to make many repeated compiles as fast as
- * possible, where the result of each compile is a fully-resolved abstract
- * syntax tree that can be walked for detailed analysis.
- */
-public class AstCompiler extends AbstractCompiler {
-
-  /**
-   * Manages the caching of <code>CompilationUnitDeclaration</code>.
-   */
-  private class CompilationUnitDeclarationCache {
-
-    private final Map<String, Long> lastModified = new HashMap<String, Long>();
-
-    private final Map<String, ArrayList<CompilationUnitDeclaration>> map = new HashMap<String, ArrayList<CompilationUnitDeclaration>>();
-
-    public void remove(String newLoc) {
-      map.remove(newLoc);
-    }
-
-    private void add(String location, CompilationUnitDeclaration item) {
-      File file = new File(location);
-      if (file.exists()) {
-        lastModified.put(location, new Long(file.lastModified()));
-      }
-      if (!map.containsKey(location)) {
-        map.put(location, new ArrayList<CompilationUnitDeclaration>());
-      }
-      get(location).add(item);
-    }
-
-    private boolean containsKey(String location) {
-      return map.containsKey(location);
-    }
-
-    private List<CompilationUnitDeclaration> get(Object key) {
-      return map.get(key);
-    }
-
-    private void removeAll(Collection<String> c) {
-      map.keySet().removeAll(c);
-    }
-  }
-
-  private final CompilationUnitDeclarationCache cachedResults = new CompilationUnitDeclarationCache();
-  private final boolean disableChecks;
-
-  public AstCompiler(SourceOracle sourceOracle) {
-    super(sourceOracle, false);
-    this.disableChecks = false;
-  }
-
-  public AstCompiler(SourceOracle sourceOracle, boolean disableChecks) {
-    super(sourceOracle, false);
-    this.disableChecks = disableChecks;
-  }
-
-  public CompilationUnitDeclaration[] getChangedCompilationUnitDeclarations(
-      TreeLogger logger, ICompilationUnit[] units) {
-    List<ICompilationUnit> allUnits = Arrays.asList(units);
-    List<ICompilationUnitAdapter> newUnits = new ArrayList<ICompilationUnitAdapter>();
-
-    // Check for newer units that need to be processed.
-    for (Iterator<ICompilationUnit> iter = allUnits.iterator(); iter.hasNext();) {
-      ICompilationUnitAdapter adapter = (ICompilationUnitAdapter) iter.next();
-      CompilationUnitProvider cup = adapter.getCompilationUnitProvider();
-      String location = cup.getLocation();
-      if (!(cachedResults.containsKey(location))) {
-        newUnits.add(adapter);
-      }
-    }
-    ICompilationUnit[] toBeProcessed = new ICompilationUnit[newUnits.size()];
-    newUnits.toArray(toBeProcessed);
-    CompilationUnitDeclaration[] newCuds = compile(logger, toBeProcessed);
-
-    // Put new cuds into cache.
-    List<CompilationUnitDeclaration> cudResults = new ArrayList<CompilationUnitDeclaration>();
-    for (int i = 0; i < newCuds.length; i++) {
-      CompilationUnitDeclaration newCud = newCuds[i];
-      String newLoc = String.valueOf(newCud.getFileName());
-      cachedResults.remove(newLoc);
-      cachedResults.add(newLoc, newCud);
-      cudResults.add(newCud);
-    }
-
-    return cudResults.toArray(new CompilationUnitDeclaration[] {});
-  }
-
-  public void invalidateChangedFiles(Set<String> changedFiles,
-      Set<String> typeNames) {
-    cachedResults.removeAll(changedFiles);
-    invalidateUnitsInFiles(typeNames);
-  }
-
-  @Override
-  protected void doCompilationUnitDeclarationValidation(
-      CompilationUnitDeclaration cud, TreeLogger logger) {
-    if (!disableChecks) {
-      JSORestrictionsChecker.check(cud);
-      LongFromJSNIChecker.check(cud);
-      BinaryTypeReferenceRestrictionsChecker.check(cud);
-    }
-  }
-}
diff --git a/dev/core/src/com/google/gwt/dev/jdt/ByteCode.java b/dev/core/src/com/google/gwt/dev/jdt/ByteCode.java
deleted file mode 100644
index d44dcf3..0000000
--- a/dev/core/src/com/google/gwt/dev/jdt/ByteCode.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright 2006 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.jdt;
-
-import com.google.gwt.dev.About;
-
-import java.io.Serializable;
-
-/**
- * Represents bytecode for a cached class.
- */
-public class ByteCode implements Serializable {
-
-  private static final String systemString = System.getProperty(
-      "java.class.path", ".");
-
-  private static final String systemStringAsIdentifier = About.GWT_VERSION
-      + "_" + systemString.hashCode() + "_jsniMethods";
-
-  /**
-   * This method returns the current system identifier, used to detect changes
-   * in the environment that would make cached data unusable.
-   * 
-   * @return the current system identifier, which is sensitive to classpath and
-   *         os changes as well as GWT version changes
-   */
-  public static String getCurrentSystemIdentifier() {
-    return systemStringAsIdentifier;
-  }
-
-  private final String binaryTypeName;
-
-  private final byte[] bytes;
-
-  private final String location;
-
-  private final String version;
-
-  private final boolean isTransient;
-
-  /**
-   * Specifies the bytecode for a given type.
-   */
-  public ByteCode(String binaryTypeName, byte[] bytes, String location,
-      boolean isTransient) {
-    this.binaryTypeName = binaryTypeName;
-    this.bytes = bytes;
-    this.location = location;
-    this.version = systemStringAsIdentifier;
-    this.isTransient = isTransient;
-  }
-
-  public String getBinaryTypeName() {
-    return binaryTypeName;
-  }
-
-  public byte[] getBytes() {
-    return bytes;
-  }
-
-  public String getLocation() {
-    return location;
-  }
-
-  public String getSystemIdentifier() {
-    return version;
-  }
-
-  public boolean isTransient() {
-    return isTransient;
-  }
-
-  // We explicitly do not set serialVersionUID, as it is generated
-  // automatically, and is more sensitive to class file changes than if
-  // it were generated manually.
-}
diff --git a/dev/core/src/com/google/gwt/dev/jdt/ByteCodeCompiler.java b/dev/core/src/com/google/gwt/dev/jdt/ByteCodeCompiler.java
deleted file mode 100644
index f1d9c54..0000000
--- a/dev/core/src/com/google/gwt/dev/jdt/ByteCodeCompiler.java
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * Copyright 2007 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.jdt;
-
-import com.google.gwt.core.ext.TreeLogger;
-import com.google.gwt.core.ext.UnableToCompleteException;
-
-import org.eclipse.jdt.core.compiler.CharOperation;
-import org.eclipse.jdt.internal.compiler.ClassFile;
-import org.eclipse.jdt.internal.compiler.CompilationResult;
-import org.eclipse.jdt.internal.compiler.env.ICompilationUnit;
-
-/**
- * A facade around the JDT compiler to manage on-demand Java source to bytecode
- * compilation, caching compiled bytecode where possible.
- */
-public class ByteCodeCompiler extends AbstractCompiler {
-
-  private final CacheManager cacheManager;
-
-  /**
-   * Creates a bytecode compiler for use not in hosted mode. All bytecode will
-   * be thrown away after reload.
-   * 
-   * @param sourceOracle used to find the source
-   */
-  public ByteCodeCompiler(SourceOracle sourceOracle) {
-    super(sourceOracle, true);
-    this.cacheManager = new CacheManager();
-  }
-
-  /**
-   * Creates a byte code compiler given the supplied sourceOracle (to find the
-   * source) and the supplied cacheManager (to keep the bytecode and other
-   * info). If the cacheManager has a cacheDir, it will keep bytecode across
-   * reload, and load them from the cacheDir on startup. Otherwise, each reload
-   * will clear the cache. In hosted mode, the cacheManager used to create this
-   * object should be the same one used to create the typeOracleBuilder.
-   * 
-   * @param sourceOracle used to find the source
-   * @param cacheManager used to keep the cached information
-   */
-  public ByteCodeCompiler(SourceOracle sourceOracle, CacheManager cacheManager) {
-    super(sourceOracle, true);
-    this.cacheManager = cacheManager;
-  }
-
-  /**
-   * Get the bytecode for the specified type.
-   * 
-   * @param binaryTypeName the binary type name to look up or compile
-   */
-  public byte[] getClassBytes(TreeLogger logger, String binaryTypeName)
-      throws UnableToCompleteException {
-
-    // We use a thread logger proxy because we can't wind the logger through
-    // JDT directly.
-    //
-    String msg = "Getting bytecode for '" + binaryTypeName + "'";
-    logger = logger.branch(TreeLogger.SPAM, msg, null);
-
-    TreeLogger oldLogger = threadLogger.push(logger);
-    try {
-
-      // Check the bytecode cache in case we've already compiled it.
-      //
-      ByteCode byteCode = doGetByteCodeFromCache(logger, binaryTypeName);
-      if (byteCode != null) {
-        // We have it already.
-        //
-        return byteCode.getBytes();
-      }
-
-      // Need to compile it. It could be the case that we have tried before and
-      // failed, but on the off chance that it's been fixed since then, we adopt
-      // a policy of always trying to recompile if we don't have it cached.
-      //
-      ICompilationUnit start = getCompilationUnitForType(logger, binaryTypeName);
-      compile(logger, new ICompilationUnit[] {start});
-
-      // Check the cache again. If it's there now, we succeeded.
-      // If it isn't there now, we've already logged the error.
-      //
-      byteCode = doGetByteCodeFromCache(logger, binaryTypeName);
-      if (byteCode != null) {
-        return byteCode.getBytes();
-      } else {
-        throw new UnableToCompleteException();
-      }
-    } finally {
-      threadLogger.pop(oldLogger);
-    }
-  }
-
-  /**
-   * Prevents the compile process from ever trying to compile these types from
-   * source. This is used for special types that would not compile correctly
-   * from source.
-   * 
-   * @param binaryTypeName the binary name of the specified type
-   */
-  public void putClassBytes(TreeLogger logger, String binaryTypeName,
-      byte[] bytes, String location) {
-
-    // We must remember the package name independently in case this is a type
-    // the host doesn't actually know about.
-    //
-    String pkgName = "";
-    int lastDot = binaryTypeName.lastIndexOf('.');
-    if (lastDot != -1) {
-      pkgName = binaryTypeName.substring(0, lastDot);
-    }
-    rememberPackage(pkgName);
-
-    // Cache the bytes.
-    //
-    ByteCode byteCode = new ByteCode(binaryTypeName, bytes, location, true);
-    cacheManager.acceptIntoCache(logger, binaryTypeName, byteCode);
-  }
-
-  /**
-   * This method removes the bytecode which is no longer current, or if the
-   * cacheManager does not have a cacheDir, all the bytecode.
-   * 
-   * @param logger used to describe the results to the user
-   */
-  public void removeStaleByteCode(TreeLogger logger) {
-    cacheManager.removeStaleByteCode(logger, this);
-  }
-
-  @Override
-  protected void doAcceptResult(CompilationResult result) {
-    // Take all compiled class files and put them in the byte cache.
-    //
-    TreeLogger logger = getLogger();
-    ClassFile[] classFiles = result.getClassFiles();
-    for (int i = 0; i < classFiles.length; i++) {
-      ClassFile classFile = classFiles[i];
-      char[][] compoundName = classFile.getCompoundName();
-      char[] classNameChars = CharOperation.concatWith(compoundName, '.');
-      String className = String.valueOf(classNameChars);
-      byte bytes[] = classFile.getBytes();
-      String loc = String.valueOf(result.compilationUnit.getFileName());
-      boolean isTransient = true;
-      if (result.compilationUnit instanceof ICompilationUnitAdapter) {
-        ICompilationUnitAdapter unit = (ICompilationUnitAdapter) result.compilationUnit;
-        isTransient = unit.getCompilationUnitProvider().isTransient();
-      }
-      ByteCode byteCode = new ByteCode(className, bytes, loc, isTransient);
-      if (cacheManager.acceptIntoCache(logger, className, byteCode)) {
-        logger.log(TreeLogger.SPAM, "Successfully compiled and cached '"
-            + className + "'", null);
-      }
-    }
-  }
-
-  /**
-   * Checks the cache for bytecode for the specified binary type name. Silently
-   * removes and pretends it didn't see any bytecode that is out-of-date with
-   * respect to the compilation unit that provides it.
-   */
-  @Override
-  protected ByteCode doGetByteCodeFromCache(TreeLogger logger,
-      String binaryTypeName) {
-    return cacheManager.getByteCode(binaryTypeName);
-  }
-}
diff --git a/dev/core/src/com/google/gwt/dev/jdt/CacheManager.java b/dev/core/src/com/google/gwt/dev/jdt/CacheManager.java
deleted file mode 100644
index c6895db..0000000
--- a/dev/core/src/com/google/gwt/dev/jdt/CacheManager.java
+++ /dev/null
@@ -1,1025 +0,0 @@
-/*
- * Copyright 2008 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.jdt;
-
-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;
-import com.google.gwt.dev.shell.JavaScriptHost;
-import com.google.gwt.dev.shell.JsniMethods;
-import com.google.gwt.dev.shell.ShellGWT;
-import com.google.gwt.dev.shell.ShellJavaScriptHost;
-import com.google.gwt.dev.shell.JsniMethods.JsniMethod;
-import com.google.gwt.dev.util.PerfLogger;
-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.ReferenceBinding;
-import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.util.AbstractMap;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.IdentityHashMap;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeSet;
-
-/**
- * CacheManager manages all the caching used to speed up hosted mode startup and
- * refresh, and manages the invalidations required to ensure that changes are
- * reflected correctly on reload.
- */
-public class CacheManager {
-
-  /**
-   * Maps SourceTypeBindings to their associated types.
-   */
-  static class Mapper {
-    private final Map<ReferenceBinding, JClassType> map = new IdentityHashMap<ReferenceBinding, JClassType>();
-
-    public JClassType get(ReferenceBinding referenceBinding) {
-      JClassType type = map.get(referenceBinding);
-      return type;
-    }
-
-    public void put(ReferenceBinding binding, JClassType type) {
-      boolean firstPut = (null == map.put(binding, type));
-      assert (firstPut);
-    }
-
-    public void reset() {
-      map.clear();
-    }
-  }
-
-  /**
-   * This class is a very simple multi-valued map.
-   */
-  private static class Dependencies {
-    private Map<String, HashSet<String>> map = new HashMap<String, HashSet<String>>();
-
-    /**
-     * This method adds <code>item</code> to the list stored under
-     * <code>key</code>.
-     * 
-     * @param key the key used to access the list
-     * @param item the item to be added to the list
-     */
-    private void add(String dependerFilename, String dependeeFilename) {
-      if (!map.containsKey(dependeeFilename)) {
-        map.put(dependeeFilename, new HashSet<String>());
-      }
-
-      get(dependeeFilename).add(dependerFilename);
-    }
-
-    /**
-     * This method gets the list stored under <code>key</code>.
-     * 
-     * @param key the key used to access the list.
-     * @return the list stored under <code>key</code>
-     */
-    private Set<String> get(String filename) {
-      return map.get(filename);
-    }
-
-    private Set<String> transitiveClosure(final String filename) {
-      String current = filename;
-      TreeSet<String> queue = new TreeSet<String>();
-      Set<String> finished = new HashSet<String>();
-      queue.add(filename);
-      while (true) {
-        finished.add(current);
-        Set<String> children = get(current);
-        if (children != null) {
-          for (Iterator<String> iter = children.iterator(); iter.hasNext();) {
-            String child = iter.next();
-            if (!finished.contains(child)) {
-              queue.add(child);
-            }
-          }
-        }
-        if (queue.size() == 0) {
-          return finished;
-        } else {
-          current = queue.first();
-          queue.remove(current);
-        }
-      }
-    }
-  }
-
-  /**
-   * 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;
-    }
-
-    @Override
-    public void endVisit(FieldDeclaration fieldDeclaration,
-        final MethodScope scope) {
-      extractDependenciesFromTypeArgs(fieldDeclaration.javadoc,
-          scope.referenceContext());
-    }
-
-    @Override
-    public void endVisit(MethodDeclaration methodDeclaration, ClassScope scope) {
-      extractDependenciesFromTypeArgs(methodDeclaration.javadoc,
-          scope.referenceContext());
-    }
-
-    @Override
-    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) {
-      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<String> typeNames = new HashSet<String>();
-
-          /*
-           * 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<String> it = typeNames.iterator();
-          while (it.hasNext()) {
-            String typeName = 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<String> typeNames) {
-      // Remove all whitespace
-      typeArg = 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.
-   */
-  private static class DiskCache extends AbstractMap<String, Object> {
-
-    private class FileEntry implements Map.Entry<String, Object> {
-
-      private File file;
-
-      private FileEntry(File file) {
-        this.file = file;
-      }
-
-      private FileEntry(String className) {
-        this(new File(directory, possiblyAddTmpExtension(className)));
-      }
-
-      private FileEntry(String className, Object o) {
-        this(new File(directory, possiblyAddTmpExtension(className)));
-        setValue(o);
-      }
-
-      public String getKey() {
-        return possiblyRemoveTmpExtension(file.getName());
-      }
-
-      public Object getValue() {
-        if (!file.exists()) {
-          return null;
-        }
-        try {
-          FileInputStream fis = new FileInputStream(file);
-          ObjectInputStream ois = new ObjectInputStream(fis);
-          Object out = ois.readObject();
-          ois.close();
-          fis.close();
-          return out;
-        } catch (IOException e) {
-          return null;
-          // If we can't read the file, we can't get the item from the cache.
-        } catch (ClassNotFoundException e) {
-          return null;
-          // The class does not match because the serialUID is not correct
-          // so we don't want this item anyway.
-        }
-      }
-
-      public void remove() {
-        file.delete();
-      }
-
-      public Object setValue(Object value) {
-        Object o = getValue();
-        FileOutputStream fos;
-        try {
-          fos = new FileOutputStream(file);
-          ObjectOutputStream oos = new ObjectOutputStream(fos);
-          oos.writeObject(value);
-          oos.close();
-          fos.close();
-        } catch (IOException e) {
-          markCacheDirectoryUnusable();
-        }
-        return o;
-      }
-
-      private long lastModified() {
-        return file.lastModified();
-      }
-    }
-
-    private final Map<String, Object> cache = new HashMap<String, Object>();
-
-    // May be set to null after the fact if the cache directory becomes
-    // unusable.
-    private File directory;
-
-    public DiskCache(File dirName) {
-      if (dirName != null) {
-        directory = dirName;
-        possiblyCreateCacheDirectory();
-      } else {
-        directory = null;
-      }
-    }
-
-    @Override
-    public void clear() {
-      cache.clear();
-      if (directory != null) {
-        for (Iterator<String> iter = keySet().iterator(); iter.hasNext();) {
-          iter.remove();
-        }
-      }
-    }
-
-    @Override
-    public Set<Entry<String, Object>> entrySet() {
-      Set<Entry<String, Object>> out = new HashSet<Entry<String, Object>>() {
-        @Override
-        public boolean remove(Object o) {
-          if (o instanceof Entry) {
-            Entry<?, ?> entry = (Entry<?, ?>) o;
-            boolean removed = (DiskCache.this.remove(entry.getKey())) != null;
-            super.remove(o);
-            return removed;
-          }
-          return false;
-        }
-      };
-      out.addAll(cache.entrySet());
-      // No directory means no persistence.
-      if (directory != null) {
-        possiblyCreateCacheDirectory();
-        // Add files not yet loaded into this cache.
-        File[] entries = directory.listFiles();
-        for (int i = 0; i < entries.length; i++) {
-          if (!cache.containsKey(new FileEntry(entries[i]).getKey())) {
-            out.add(new FileEntry(entries[i]));
-          }
-        }
-      }
-      return out;
-    }
-
-    public Object get(String key) {
-      if (cache.containsKey(key)) {
-        return cache.get(key);
-      }
-      Object value = null;
-      if (directory != null) {
-        value = new FileEntry(key).getValue();
-        cache.put(key, value);
-      }
-      return value;
-    }
-
-    @Override
-    public Set<String> keySet() {
-      Set<String> out = new HashSet<String>() {
-        @Override
-        public boolean remove(Object o) {
-          boolean removed = (DiskCache.this.remove(o)) != null;
-          super.remove(o);
-          return removed;
-        }
-      };
-      out.addAll(cache.keySet());
-      // No directory means no persistence.
-      if (directory != null) {
-        possiblyCreateCacheDirectory();
-        // Add files not yet loaded into this cache.
-        File[] entries = directory.listFiles();
-        for (int i = 0; i < entries.length; i++) {
-          out.add(new FileEntry(entries[i].getName()).getKey());
-        }
-      }
-      return out;
-    }
-
-    @Override
-    public Object put(String key, Object value) {
-      return put(key, value, true);
-    }
-
-    @Override
-    public Object remove(Object key) {
-      String fileName = (String) key;
-      Object out = get(fileName);
-      // No directory means no persistence.
-      if (directory != null) {
-        possiblyCreateCacheDirectory();
-        FileEntry e = new FileEntry(fileName);
-        e.remove();
-      }
-      cache.remove(key);
-      return out;
-    }
-
-    private long lastModified(String className) {
-      if (directory == null) {
-        // we have no file on disk to refer to, so should return the same result
-        // as if the file did not exist -- namely 0.
-        return 0;
-      }
-      return new FileEntry(className).lastModified();
-    }
-
-    /**
-     * This method marks the cache directory as being invalid, so we do not try
-     * to use it.
-     */
-    private void markCacheDirectoryUnusable() {
-      System.err.println("The directory " + directory.getAbsolutePath()
-          + " is not usable as a cache directory");
-      directory = null;
-    }
-
-    /**
-     * This is used to ensure that if something wicked happens to the cache
-     * directory while we are running, we do not crash.
-     */
-    private void possiblyCreateCacheDirectory() {
-      directory.mkdirs();
-      if (!(directory.exists() && directory.canWrite())) {
-        markCacheDirectoryUnusable();
-      }
-    }
-
-    private Object put(String key, Object value, boolean persist) {
-      Object out = get(key);
-
-      // We use toString to match the string value in FileEntry.
-      cache.remove(key.toString());
-
-      // Writes the file.
-      if (persist && directory != null) {
-        // This writes the file to the disk and is all that is needed.
-        new FileEntry(key, value);
-      }
-      cache.put(key, value);
-      return out;
-    }
-  }
-
-  /**
-   * The set of all classes whose bytecode needs to exist as bootstrap bytecode
-   * to be taken as given by the bytecode compiler.
-   */
-  public static final Class<?>[] BOOTSTRAP_CLASSES = new Class<?>[] {
-      JavaScriptHost.class, ShellJavaScriptHost.class, ShellGWT.class,
-      JsniMethods.class, JsniMethod.class};
-
-  /**
-   * The set of bootstrap classes, which are marked transient, but are
-   * nevertheless not recompiled each time, as they are bootstrap classes.
-   */
-  private static final Set<String> TRANSIENT_CLASS_NAMES = new HashSet<String>();
-
-  static {
-    for (Class<?> c : BOOTSTRAP_CLASSES) {
-      TRANSIENT_CLASS_NAMES.add(c.getName());
-    }
-  }
-
-  // This method must be outside of DiskCache because of the restriction against
-  // defining static methods in inner classes.
-  private static String possiblyAddTmpExtension(Object className) {
-    String fileName = className.toString();
-    if (fileName.indexOf("-") == -1) {
-      int hashCode = fileName.hashCode();
-      String hashCodeStr = Integer.toHexString(hashCode);
-      while (hashCodeStr.length() < 8) {
-        hashCodeStr = '0' + hashCodeStr;
-      }
-      fileName = fileName + "-" + hashCodeStr + ".tmp";
-    }
-    return fileName;
-  }
-
-  // This method must be outside of DiskCache because of the restriction against
-  // defining static methods in inner classes.
-  private static String possiblyRemoveTmpExtension(Object fileName) {
-    String className = fileName.toString();
-    if (className.indexOf("-") != -1) {
-      className = className.split("-")[0];
-    }
-    return className;
-  }
-
-  private final Set<CompilationUnitProvider> addedCups = new HashSet<CompilationUnitProvider>();
-
-  private final AstCompiler astCompiler;
-
-  private final DiskCache byteCodeCache;
-
-  private final File cacheDir;
-
-  private final Set<String> changedFiles;
-
-  private final Map<String, CompilationUnitDeclaration> cudsByFileName;
-
-  private final Map<String, CompilationUnitProvider> cupsByLocation = new HashMap<String, CompilationUnitProvider>();
-
-  private boolean firstTime = true;
-
-  /**
-   * Set of {@link CompilationUnitProvider} locations for all of the compilation
-   * units generated by {@link com.google.gwt.core.ext.Generator Generator}s.
-   * 
-   * TODO: This seems like it should be a Set of CUPs rather than a set of CUP
-   * locations.
-   */
-  private final Set<String> generatedCupLocations = new HashSet<String>();
-
-  private final Set<String> generatedResources = new HashSet<String>();
-
-  private final Mapper identityMapper = new Mapper();
-
-  private final Set<String> invalidatedTypes = new HashSet<String>();
-
-  private final TypeOracle oracle;
-
-  private final Map<String, Long> timesByLocation = new HashMap<String, Long>();
-
-  private final Map<String, ICompilationUnitAdapter> unitsByCup = new HashMap<String, ICompilationUnitAdapter>();
-
-  /**
-   * Creates a new <code>CacheManager</code>, creating a new
-   * <code>TypeOracle</code>. This constructor does not specify a cache
-   * directory, and therefore is to be used in unit tests and executables that
-   * do not need caching.
-   */
-  public CacheManager() {
-    this(null, null);
-  }
-
-  /**
-   * Creates a new <code>CacheManager</code>, creating a new
-   * <code>TypeOracle</code>. This constructor uses the specified cacheDir,
-   * and does cache information across reloads. If the specified cacheDir is
-   * null, caching across reloads will be disabled.
-   */
-  public CacheManager(String cacheDir, TypeOracle oracle) {
-    this(cacheDir, oracle, false);
-  }
-
-  public CacheManager(String cacheDir, TypeOracle oracle, boolean disableChecks) {
-    if (oracle == null) {
-      this.oracle = new TypeOracle();
-    } else {
-      this.oracle = oracle;
-    }
-    changedFiles = new HashSet<String>();
-    cudsByFileName = new HashMap<String, CompilationUnitDeclaration>();
-    if (cacheDir != null) {
-      this.cacheDir = new File(cacheDir);
-      this.cacheDir.mkdirs();
-      byteCodeCache = new DiskCache(new File(cacheDir, "bytecode"));
-    } else {
-      this.cacheDir = null;
-      byteCodeCache = new DiskCache(null);
-    }
-    SourceOracleOnTypeOracle sooto = new SourceOracleOnTypeOracle(this.oracle);
-    astCompiler = new AstCompiler(sooto, disableChecks);
-
-    astCompiler.setCachePolicy(new AstCompiler.CachePolicy() {
-      public boolean shouldProcess(CompilationUnitDeclaration cud) {
-        ICompilationUnit unit = cud.compilationResult.compilationUnit;
-        ICompilationUnitAdapter adapter = ((ICompilationUnitAdapter) unit);
-        CompilationUnitProvider cup = adapter.getCompilationUnitProvider();
-        return getTypeOracle().findType(cup.getPackageName(),
-            cup.getMainTypeName()) == null;
-      }
-    });
-  }
-
-  /**
-   * Creates a new <code>CacheManager</code>, using the supplied
-   * <code>TypeOracle</code>. This constructor does not specify a cache
-   * directory, and therefore is to be used in unit tests and executables that
-   * do not need caching.
-   */
-  public CacheManager(TypeOracle typeOracle) {
-    this(null, typeOracle);
-  }
-
-  /**
-   * Adds the specified {@link CompilationUnitProvider} to the set of CUPs
-   * generated by {@link com.google.gwt.core.ext.Generator Generator}s.
-   * Generated <code>CompilationUnitProviders</code> are not cached across
-   * reloads.
-   */
-  public void addGeneratedCup(CompilationUnitProvider generatedCup) {
-    assert (generatedCup != null);
-
-    generatedCupLocations.add(generatedCup.getLocation());
-  }
-
-  public void addGeneratedResource(String partialPath) {
-    generatedResources.add(partialPath);
-  }
-
-  /**
-   * This method returns the <code>TypeOracle</code> associated with this
-   * <code>CacheManager</code>.
-   */
-  public TypeOracle getTypeOracle() {
-    return oracle;
-  }
-
-  public boolean hasGeneratedResource(String partialPath) {
-    return generatedResources.contains(partialPath);
-  }
-
-  /**
-   * This removes all state changed since the last time the typeOracle was run.
-   * 
-   * @param typeOracle
-   */
-  public void invalidateOnRefresh(TypeOracle typeOracle) {
-
-    // If a class is changed, the set of classes in the transitive closure
-    // of "refers to" must be marked changed as well.
-    // The initial change set is computed in addCompilationUnit.
-
-    changedFiles.addAll(generatedCupLocations);
-    addDependentsToChangedFiles();
-
-    for (Iterator<String> iter = changedFiles.iterator(); iter.hasNext();) {
-      String location = iter.next();
-      CompilationUnitProvider cup = getCupsByLocation().get(location);
-      unitsByCup.remove(location);
-      Util.invokeInaccessableMethod(TypeOracle.class,
-          "invalidateTypesInCompilationUnit",
-          new Class[] {CompilationUnitProvider.class}, typeOracle,
-          new Object[] {cup});
-    }
-
-    /*
-     * NOTE: It appears that invalidatedTypes is always empty. Therefore the
-     * AstCompiler may not be properly removing types from the changed files.
-     */
-    astCompiler.invalidateChangedFiles(changedFiles, invalidatedTypes);
-  }
-
-  /**
-   * Ensures that all compilation units generated via generators are removed
-   * from the system so that they will be generated again, and thereby take into
-   * account input that may have changed since the last reload.
-   */
-  public void invalidateVolatileFiles() {
-    for (Iterator<CompilationUnitProvider> iter = addedCups.iterator(); iter.hasNext();) {
-      CompilationUnitProvider cup = iter.next();
-      if (isGeneratedCup(cup)) {
-        iter.remove();
-      }
-    }
-    generatedResources.clear();
-  }
-
-  /**
-   * This method adds byte.
-   * 
-   * @param logger
-   * @param binaryTypeName
-   * @param byteCode
-   * @return
-   */
-  boolean acceptIntoCache(TreeLogger logger, String binaryTypeName,
-      ByteCode byteCode) {
-    synchronized (byteCodeCache) {
-      if (getByteCode(binaryTypeName) == null) {
-        byteCodeCache.put(binaryTypeName, byteCode, (!byteCode.isTransient()));
-        logger.log(TreeLogger.SPAM, "Cached bytecode for " + binaryTypeName,
-            null);
-        return true;
-      } else {
-        logger.log(TreeLogger.SPAM, "Bytecode not re-cached for "
-            + binaryTypeName, null);
-        return false;
-      }
-    }
-  }
-
-  /**
-   * Adds this compilation unit if it is not present, or is older. Otherwise
-   * does nothing.
-   * 
-   * @throws UnableToCompleteException thrown if we cannot figure out when this
-   *           cup was modified
-   */
-  void addCompilationUnit(CompilationUnitProvider cup)
-      throws UnableToCompleteException {
-    Long lastModified = new Long(cup.getLastModified());
-    if (isCupUnchanged(cup, lastModified)) {
-      return;
-    }
-    CompilationUnitProvider oldCup = getCup(cup);
-    if (oldCup != null) {
-      addedCups.remove(oldCup);
-      markCupChanged(cup);
-    }
-    timesByLocation.put(cup.getLocation(), lastModified);
-    cupsByLocation.put(cup.getLocation(), cup);
-    addedCups.add(cup);
-  }
-
-  /**
-   * This method modifies the field <code>changedFiles</code> to contain all
-   * of the additional files that are capable of reaching any of the files
-   * currently contained within <code>changedFiles</code>.
-   */
-  void addDependentsToChangedFiles() {
-    final Dependencies dependencies = new Dependencies();
-
-    DependencyVisitor trv = new DependencyVisitor(dependencies);
-
-    // Find references to type in units that aren't any longer valid.
-    //
-    for (CompilationUnitDeclaration cud : cudsByFileName.values()) {
-      cud.traverse(trv, cud.scope);
-    }
-
-    Set<String> toTraverse = new HashSet<String>(changedFiles);
-    for (Iterator<String> iter = toTraverse.iterator(); iter.hasNext();) {
-      String fileName = iter.next();
-      changedFiles.addAll(dependencies.transitiveClosure(fileName));
-    }
-  }
-
-  ICompilationUnit findUnitForCup(CompilationUnitProvider cup) {
-    if (!unitsByCup.containsKey(cup.getLocation())) {
-      unitsByCup.put(cup.getLocation(), new ICompilationUnitAdapter(cup));
-    }
-    return unitsByCup.get(cup.getLocation());
-  }
-
-  Set<CompilationUnitProvider> getAddedCups() {
-    return addedCups;
-  }
-
-  AstCompiler getAstCompiler() {
-    return astCompiler;
-  }
-
-  /**
-   * Gets the bytecode from the cache, rejecting it if an incompatible change
-   * occurred since it was cached.
-   */
-  ByteCode getByteCode(String binaryTypeName) {
-    synchronized (byteCodeCache) {
-      ByteCode byteCode = (ByteCode) byteCodeCache.get(binaryTypeName);
-      // we do not want bytecode created with a different classpath or os or
-      // version of GWT.
-      if ((byteCode != null)
-          && byteCode.getSystemIdentifier() != null
-          && (!(byteCode.getSystemIdentifier().equals(ByteCode.getCurrentSystemIdentifier())))) {
-        byteCodeCache.remove(binaryTypeName);
-        byteCode = null;
-      }
-      if (byteCode != null) {
-        // Found it.
-        //
-        return byteCode;
-      } else {
-        // This type has not been compiled before, or we tried but failed.
-        //
-        return null;
-      }
-    }
-  }
-
-  Set<String> getChangedFiles() {
-    return changedFiles;
-  }
-
-  Map<String, CompilationUnitDeclaration> getCudsByFileName() {
-    return cudsByFileName;
-  }
-
-  CompilationUnitProvider getCup(CompilationUnitProvider cup) {
-    return getCupsByLocation().get(cup.getLocation());
-  }
-
-  Object getCupLastUpdateTime(CompilationUnitProvider cup) {
-    return getTimesByLocation().get(cup.getLocation());
-  }
-
-  Map<String, CompilationUnitProvider> getCupsByLocation() {
-    return cupsByLocation;
-  }
-
-  Mapper getIdentityMapper() {
-    return identityMapper;
-  }
-
-  Map<String, Long> getTimesByLocation() {
-    return timesByLocation;
-  }
-
-  JType getTypeForBinding(ReferenceBinding referenceBinding) {
-    return identityMapper.get(referenceBinding);
-  }
-
-  /**
-   * Was this cup, last modified at time lastModified modified since it was last
-   * processed by the system?
-   */
-  boolean isCupUnchanged(CompilationUnitProvider cup, Long lastModified) {
-    Long oldTime = (Long) getCupLastUpdateTime(cup);
-    if (oldTime != null) {
-      if (oldTime.longValue() >= lastModified.longValue()
-          && (!cup.isTransient())) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  /**
-   * This method is called when a cup is known to have changed. This will ensure
-   * that all the types defined in this cup are invalidated.
-   * 
-   * @param cup the cup modified
-   */
-  void markCupChanged(CompilationUnitProvider cup) {
-    changedFiles.add(String.valueOf(cup.getLocation()));
-  }
-
-  boolean removeFromCache(TreeLogger logger, String binaryTypeName) {
-    synchronized (byteCodeCache) {
-      if (getByteCode(binaryTypeName) == null) {
-        logger.log(TreeLogger.SPAM, "Bytecode for " + binaryTypeName
-            + " was not cached, so not removing", null);
-        return false;
-      } else {
-        byteCodeCache.remove(binaryTypeName);
-        logger.log(TreeLogger.SPAM, "Bytecode not re-cached for "
-            + binaryTypeName, null);
-        return false;
-      }
-    }
-  }
-
-  /**
-   * This method removes all of the bytecode which is out of date from the
-   * bytecode cache. The set of files needing to be changed are going to be the
-   * set already known to be changed plus those that are out of date in the
-   * bytecode cache.
-   */
-  void removeStaleByteCode(TreeLogger logger, AbstractCompiler compiler) {
-    PerfLogger.start("CacheManager.removeStaleByteCode");
-
-    if (cacheDir == null) {
-      byteCodeCache.clear();
-      return;
-    }
-    if (isFirstTime()) {
-      Set<String> classNames = byteCodeCache.keySet();
-      for (Iterator<String> iter = classNames.iterator(); iter.hasNext();) {
-        String className = iter.next();
-        ByteCode byteCode = ((ByteCode) (byteCodeCache.get(className)));
-        if (byteCode == null) {
-          iter.remove();
-          continue;
-        }
-        String qname = byteCode.getBinaryTypeName();
-        if (TRANSIENT_CLASS_NAMES.contains(qname)) {
-          continue;
-        }
-        String location = byteCode.getLocation();
-        if (byteCode.isTransient()) {
-          // GWT transient classes; no need to test.
-          // Either standardGeneratorContext created it
-          // in which case we already know it is invalid
-          // or its something like GWT and it lives.
-          continue;
-        }
-        String fileName = Util.findFileName(location);
-        CompilationUnitDeclaration compilationUnitDeclaration = cudsByFileName.get(location);
-        if (compilationUnitDeclaration == null) {
-          changedFiles.add(location);
-          continue;
-        }
-        long srcLastModified = Long.MAX_VALUE;
-        File srcLocation = new File(fileName);
-        if (srcLocation.exists()) {
-          srcLastModified = srcLocation.lastModified();
-        }
-        long byteCodeLastModified = byteCodeCache.lastModified(className);
-        if (srcLastModified >= byteCodeLastModified) {
-          changedFiles.add(location);
-        }
-      }
-      addDependentsToChangedFiles();
-    }
-    becomeNotFirstTime();
-    invalidateChangedFiles(logger, compiler);
-    PerfLogger.end();
-  }
-
-  void setTypeForBinding(ReferenceBinding binding, JClassType type) {
-    identityMapper.put(binding, type);
-  }
-
-  private void becomeNotFirstTime() {
-    firstTime = false;
-  }
-
-  /**
-   * Actually performs the work of removing the invalidated data from the
-   * system. At this point, changedFiles should be complete. After this method
-   * is called, changedFiles should now be empty, since all invalidation that is
-   * needed to be done.
-   * 
-   * @param logger logs the process
-   * @param compiler the compiler caches data, so must be invalidated
-   */
-  private void invalidateChangedFiles(TreeLogger logger,
-      AbstractCompiler compiler) {
-    Set<String> invalidTypes = new HashSet<String>();
-    if (logger.isLoggable(TreeLogger.TRACE)) {
-      TreeLogger branch = logger.branch(TreeLogger.TRACE,
-          "The following compilation units have changed since "
-              + "the last compilation to bytecode", null);
-      for (Iterator<String> iter = changedFiles.iterator(); iter.hasNext();) {
-        String filename = iter.next();
-        branch.log(TreeLogger.TRACE, filename, null);
-      }
-    }
-    for (String key : byteCodeCache.keySet()) {
-      ByteCode byteCode = ((ByteCode) (byteCodeCache.get(key)));
-      if (byteCode != null) {
-        String location = byteCode.getLocation();
-        if (changedFiles.contains(location)) {
-          String binaryTypeName = byteCode.getBinaryTypeName();
-          invalidTypes.add(binaryTypeName);
-          removeFromCache(logger, binaryTypeName);
-        }
-      }
-    }
-    compiler.invalidateUnitsInFiles(invalidTypes);
-    changedFiles.clear();
-  }
-
-  private boolean isFirstTime() {
-    return firstTime;
-  }
-
-  private boolean isGeneratedCup(CompilationUnitProvider cup) {
-    return generatedCupLocations.contains(cup.getLocation());
-  }
-}
diff --git a/dev/core/src/com/google/gwt/dev/jdt/CompilationUnitProviderWithAlternateSource.java b/dev/core/src/com/google/gwt/dev/jdt/CompilationUnitProviderWithAlternateSource.java
deleted file mode 100644
index adb8005..0000000
--- a/dev/core/src/com/google/gwt/dev/jdt/CompilationUnitProviderWithAlternateSource.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright 2006 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.jdt;
-
-import com.google.gwt.core.ext.UnableToCompleteException;
-import com.google.gwt.core.ext.typeinfo.CompilationUnitProvider;
-
-/**
- * Wraps an existing compilation unit, retaining the metadata of the original
- * unit but providing modified source.
- */
-public class CompilationUnitProviderWithAlternateSource implements
-    CompilationUnitProvider {
-  private final CompilationUnitProvider cup;
-
-  private final char[] source;
-
-  public CompilationUnitProviderWithAlternateSource(
-      CompilationUnitProvider cup, char[] source) {
-    this.cup = cup;
-    this.source = source;
-  }
-
-  public long getLastModified() throws UnableToCompleteException {
-    return cup.getLastModified();
-  }
-
-  public String getLocation() {
-    return cup.getLocation();
-  }
-
-  public String getMainTypeName() {
-    return cup.getMainTypeName();
-  }
-
-  public String getPackageName() {
-    return cup.getPackageName();
-  }
-
-  public char[] getSource() throws UnableToCompleteException {
-    return source;
-  }
-
-  public boolean isTransient() {
-    return cup.isTransient();
-  }
-}
diff --git a/dev/core/src/com/google/gwt/dev/jdt/FindDeferredBindingSitesVisitor.java b/dev/core/src/com/google/gwt/dev/jdt/FindDeferredBindingSitesVisitor.java
index 33e6eca..e9e1eab 100644
--- a/dev/core/src/com/google/gwt/dev/jdt/FindDeferredBindingSitesVisitor.java
+++ b/dev/core/src/com/google/gwt/dev/jdt/FindDeferredBindingSitesVisitor.java
@@ -15,6 +15,8 @@
  */
 package com.google.gwt.dev.jdt;
 
+import com.google.gwt.dev.javac.GWTProblem;
+
 import org.eclipse.jdt.core.compiler.CharOperation;
 import org.eclipse.jdt.internal.compiler.ASTVisitor;
 import org.eclipse.jdt.internal.compiler.ast.ClassLiteralAccess;
diff --git a/dev/core/src/com/google/gwt/dev/jdt/ICompilationUnitAdapter.java b/dev/core/src/com/google/gwt/dev/jdt/ICompilationUnitAdapter.java
deleted file mode 100644
index e483c59..0000000
--- a/dev/core/src/com/google/gwt/dev/jdt/ICompilationUnitAdapter.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright 2008 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.jdt;
-
-import com.google.gwt.core.ext.UnableToCompleteException;
-import com.google.gwt.core.ext.typeinfo.CompilationUnitProvider;
-
-import org.eclipse.jdt.core.compiler.CharOperation;
-import org.eclipse.jdt.internal.compiler.env.ICompilationUnit;
-
-/**
- * Implements <code>ICompilationUnit</code> in terms of a
- * {@link CompilationUnitProvider}.
- */
-public class ICompilationUnitAdapter implements ICompilationUnit {
-
-  private final CompilationUnitProvider cup;
-
-  public ICompilationUnitAdapter(CompilationUnitProvider cup) {
-    assert (cup != null);
-    this.cup = cup;
-  }
-
-  public CompilationUnitProvider getCompilationUnitProvider() {
-    return cup;
-  }
-
-  public char[] getContents() {
-    try {
-      return cup.getSource();
-    } catch (UnableToCompleteException e) {
-      return null;
-    }
-  }
-
-  public char[] getFileName() {
-    return cup.getLocation().toCharArray();
-  }
-
-  /**
-   * This method is supposed to return the simple class name for this
-   * compilation unit. Examples of simple class names would be "String", or
-   * "ArrayList". 
-   * 
-   * <p>Although JDT allows this method to return null in the cases 
-   * where this compilation unit is not a package-info class, JDT never 
-   * constructs a CUP with a null main type, and we should never do so 
-   * either.</p>
-   */
-  public char[] getMainTypeName() {
-    String typeName = cup.getMainTypeName();
-    return typeName.toCharArray();
-  }
-
-  public char[][] getPackageName() {
-    final char[] pkg = cup.getPackageName().toCharArray();
-    final char[][] pkgParts = CharOperation.splitOn('.', pkg);
-    return pkgParts;
-  }
-}
diff --git a/dev/core/src/com/google/gwt/dev/jdt/SourceOracle.java b/dev/core/src/com/google/gwt/dev/jdt/SourceOracle.java
deleted file mode 100644
index 9853ec2..0000000
--- a/dev/core/src/com/google/gwt/dev/jdt/SourceOracle.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright 2006 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.jdt;
-
-import com.google.gwt.core.ext.TreeLogger;
-import com.google.gwt.core.ext.UnableToCompleteException;
-import com.google.gwt.core.ext.typeinfo.CompilationUnitProvider;
-
-/**
- * Abstracts the process of determining which source file contains a given Java
- * type and specifying whether or not a given name is a package.
- */
-public interface SourceOracle {
-
-  /**
-   * Attempts to find a compilation unit for the specified source type name.
-   * 
-   * @return <code>null</code> if a compilation unit for the specified type
-   *         was not found or an error prevented the compilation unit from being
-   *         provided
-   */
-  CompilationUnitProvider findCompilationUnit(TreeLogger logger,
-      String sourceTypeName) throws UnableToCompleteException;
-
-  /**
-   * Determines whether or not a string is the name of a package. Remember that
-   * every part of a package name is also a package. For example, the fact that
-   * <code>java.lang</code> is a package implies that <code>java</code> is
-   * also a package.
-   */
-  boolean isPackage(String possiblePackageName);
-}
diff --git a/dev/core/src/com/google/gwt/dev/jdt/SourceOracleOnTypeOracle.java b/dev/core/src/com/google/gwt/dev/jdt/SourceOracleOnTypeOracle.java
deleted file mode 100644
index 28a7eba..0000000
--- a/dev/core/src/com/google/gwt/dev/jdt/SourceOracleOnTypeOracle.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright 2006 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.jdt;
-
-import com.google.gwt.core.ext.TreeLogger;
-import com.google.gwt.core.ext.typeinfo.CompilationUnitProvider;
-import com.google.gwt.core.ext.typeinfo.JClassType;
-import com.google.gwt.core.ext.typeinfo.TypeOracle;
-
-/**
- * Implements a {@link SourceOracle} in terms of a {@link TypeOracle}.
- */
-public class SourceOracleOnTypeOracle implements SourceOracle {
-
-  private final TypeOracle typeOracle;
-
-  public SourceOracleOnTypeOracle(TypeOracle typeOracle) {
-    this.typeOracle = typeOracle;
-  }
-
-  public CompilationUnitProvider findCompilationUnit(TreeLogger logger,
-      String sourceTypeName) {
-    JClassType type = typeOracle.findType(sourceTypeName);
-    if (type != null) {
-      return type.getCompilationUnit();
-    }
-    return null;
-  }
-
-  public boolean isPackage(String possiblePackageName) {
-    if (typeOracle.findPackage(possiblePackageName) != null) {
-      return true;
-    } else {
-      return false;
-    }
-  }
-}
diff --git a/dev/core/src/com/google/gwt/dev/jdt/StandardSourceOracle.java b/dev/core/src/com/google/gwt/dev/jdt/StandardSourceOracle.java
deleted file mode 100644
index e9c923e..0000000
--- a/dev/core/src/com/google/gwt/dev/jdt/StandardSourceOracle.java
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
- * Copyright 2006 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.jdt;
-
-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.JClassType;
-import com.google.gwt.core.ext.typeinfo.TypeOracle;
-import com.google.gwt.dev.util.Util;
-
-import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
-
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Provides a mutable compilation service host on top of a
- * {@link com.google.gwt.dev.typeinfo.TypeOracle} as well as providing
- * subclasses an opportunity to substitute their own source to implement
- * special-handling, such as rewriting JSNI source.
- */
-public class StandardSourceOracle implements SourceOracle {
-
-  private final Map cupsByTypeName = new HashMap();
-
-  private final Set knownPackages = new HashSet();
-
-  private final TypeOracle typeOracle;
-
-  /**
-   * @param typeOracle answers questions about compilation unit locations
-   * @param genDir for compilation units whose location does not correspond to a
-   *          URL that can be opened, their source will be written to this
-   *          directory to support debugging, or <code>null</code> if the
-   *          source need not be written to disk
-   */
-  public StandardSourceOracle(TypeOracle typeOracle) {
-    this.typeOracle = typeOracle;
-  }
-
-  /**
-   * Attempts to find the compilation unit for the requested type. Often
-   * legitimately returns <code>null</code> because the compilation service
-   * does tests to help determine whether a particular symbol refers to a
-   * package or a type.
-   */
-  public final CompilationUnitProvider findCompilationUnit(TreeLogger logger,
-      String typeName) throws UnableToCompleteException {
-
-    // Check the cache first.
-    //
-    CompilationUnitProvider cup = (CompilationUnitProvider) cupsByTypeName.get(typeName);
-
-    if (cup != null) {
-      // Found in cache.
-      //
-      return cup;
-    }
-
-    // See if the type oracle can find it.
-    //
-    JClassType type = typeOracle.findType(typeName);
-    if (type != null) {
-      // All type oracle types are supposed to know their compilation unit.
-      //
-      cup = type.getCompilationUnit();
-      assert (cup != null);
-    }
-
-    // Give the subclass a chance to replace the source. This used for JSNI in
-    // hosted mode at present but could be used for any source rewriting trick.
-    //
-    if (cup != null) {
-      try {
-        CompilationUnitProvider specialCup = doFilterCompilationUnit(logger,
-            typeName, cup);
-
-        if (specialCup != null) {
-          // Use the cup that the subclass returned instead. Note that even
-          // though this file may not exist on disk, it is special so we don't
-          // want users to have to debug into it unless they specifically ask
-          // to.
-          //
-          cup = specialCup;
-        }
-      } catch (UnableToCompleteException e) {
-        String location = cup.getLocation();
-        char[] source = cup.getSource();
-        Util.maybeDumpSource(logger, location, source, typeName);
-        throw e;
-      }
-    }
-
-    if (cup == null) {
-      // Did not find a cup for the type.
-      // This happens commonly and is not a cause for alarm.
-      //
-      return null;
-    }
-
-    // Remember its package and cache it.
-    //
-    cupsByTypeName.put(typeName, cup);
-    return cup;
-  }
-
-  public TypeOracle getTypeOracle() {
-    return typeOracle;
-  }
-
-  /**
-   * Determines whether or not a particular name is a package name.
-   */
-  public final boolean isPackage(String possiblePackageName) {
-    if (knownPackages.contains(possiblePackageName)) {
-      // The quick route -- we've already answered yes to this question.
-      // OPTIMIZE: cache NOs as well
-      return true;
-    }
-
-    if (typeOracle.findPackage(possiblePackageName) != null) {
-      // Was a package know on the source path.
-      //
-      rememberPackage(possiblePackageName);
-      return true;
-    } else {
-      // Not a package.
-      //
-      return false;
-    }
-  }
-
-  /**
-   * Subclasses can override this method if they use a special mechanism to find
-   * the compilation unit for a type. For example, rewriting source code (as
-   * with JSNI) or preempting the source for a given type (as with
-   * <code>GWT.create</code>).
-   * <p>
-   * Note that subclasses should <em>not</em> call
-   * <code>super.{@link #findCompilationUnit(TreeLogger, String)}</code> in
-   * their implementation.
-   * 
-   * @return <code>null</code> if you want the superclass to use its normal
-   *         mechanism for finding types
-   */
-  protected CompilationUnitProvider doFilterCompilationUnit(TreeLogger logger,
-      String typeName, CompilationUnitProvider existing)
-      throws UnableToCompleteException {
-    return null;
-  }
-
-  /**
-   * Subclasses can override this method if they use additional mechanisms to
-   * find types magically.
-   * <p>
-   * Note that subclasses should <em>not</em> call
-   * <code>super.{@link #findAdditionalTypesUsingMagic(TreeLogger, CompilationUnitDeclaration)}</code>
-   * in their implementation.
-   * 
-   * @return <code>null</code> to indicate that no additional types should be
-   *         added
-   */
-  protected String[] doFindAdditionalTypesUsingMagic(TreeLogger logger,
-      CompilationUnitDeclaration unit) throws UnableToCompleteException {
-    return null;
-  }
-
-  void invalidateCups(Set typeNames) {
-    cupsByTypeName.keySet().removeAll(typeNames);
-  }
-
-  /**
-   * Remember that this package was added. Used for generated packages.
-   */
-  private void rememberPackage(String packageName) {
-    int i = packageName.lastIndexOf('.');
-    if (i != -1) {
-      // Ensure the parent package is also created.
-      //
-      rememberPackage(packageName.substring(0, i));
-    }
-    knownPackages.add(packageName);
-  }
-}
diff --git a/dev/core/src/com/google/gwt/dev/jdt/StaticCompilationUnitProvider.java b/dev/core/src/com/google/gwt/dev/jdt/StaticCompilationUnitProvider.java
deleted file mode 100644
index 1b314a8..0000000
--- a/dev/core/src/com/google/gwt/dev/jdt/StaticCompilationUnitProvider.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright 2006 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.jdt;
-
-import com.google.gwt.core.ext.typeinfo.CompilationUnitProvider;
-
-/**
- * Implements a {@link CompilationUnitProvider} as transient (in-memory) source.
- */
-public class StaticCompilationUnitProvider implements CompilationUnitProvider {
-
-  private final String packageName;
-
-  private final String simpleTypeName;
-
-  private final char[] source;
-
-  /**
-   * @param source if <code>null</code>, override this class and return
-   *          source from {@link #getSource()}
-   */
-  public StaticCompilationUnitProvider(String packageName,
-      String simpleTypeName, char[] source) {
-    this.packageName = packageName;
-    this.simpleTypeName = simpleTypeName;
-    this.source = source;
-  }
-
-  /**
-   * Stubbed to return the same value every time.
-   */
-  public long getLastModified() {
-    return 0;
-  }
-
-  /**
-   * Creates a stable name for this compilation unit.
-   */
-  public final String getLocation() {
-    String prefix = (packageName.length() > 0) ? packageName + "." : "";
-    return "transient source for " + prefix + simpleTypeName;
-  }
-
-  public String getMainTypeName() {
-    return getTypeName();
-  }
-
-  public String getPackageName() {
-    return packageName;
-  }
-
-  public char[] getSource() {
-    return source;
-  }
-
-  public String getTypeName() {
-    return simpleTypeName;
-  }
-
-  public boolean isTransient() {
-    return true;
-  }
-
-  public String toString() {
-    return getLocation();
-  }
-}
diff --git a/dev/core/src/com/google/gwt/dev/jdt/URLCompilationUnitProvider.java b/dev/core/src/com/google/gwt/dev/jdt/URLCompilationUnitProvider.java
deleted file mode 100644
index 0277dc1..0000000
--- a/dev/core/src/com/google/gwt/dev/jdt/URLCompilationUnitProvider.java
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright 2006 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.jdt;
-
-import com.google.gwt.core.ext.UnableToCompleteException;
-import com.google.gwt.core.ext.typeinfo.CompilationUnitProvider;
-import com.google.gwt.dev.util.Util;
-
-import java.io.File;
-import java.io.IOException;
-import java.net.URL;
-import java.net.URLConnection;
-
-/**
- * Implements {@link CompilationUnitProvider} in terms of a URL.
- */
-public class URLCompilationUnitProvider implements CompilationUnitProvider {
-
-  private static File trySimplify(URL url) {
-    String s = url.toExternalForm();
-    File f = null;
-    if (s.startsWith("file:")) {
-      // Strip the file: off, and use the result. If the result
-      // does not start with file, we cannot simplify. Using URI
-      // to do the simplification fails for paths with spaces.
-      // Any number of slashes at the beginning cause no problem for Java, so
-      // if c:/windows exists so will ///////c:/windows.
-      f = new File(s.substring(5));
-      if (!f.exists()) {
-        f = null;
-      }
-    }
-    return f;
-  }
-
-  private final File file;
-
-  private final String location;
-
-  private final String packageName;
-
-  private char[] source;
-
-  private long sourceCurrentTime = Long.MIN_VALUE;
-
-  private final URL url;
-
-  private final String mainTypeName;
-
-  public URLCompilationUnitProvider(URL url, String packageName) {
-    assert (url != null);
-    assert (packageName != null);
-    this.url = url;
-
-    // Files are faster to work with, so use file if available.
-    this.file = trySimplify(url);
-    String simpleTypeName;
-    if (file == null) {
-      this.location = url.toExternalForm();
-      simpleTypeName = new File(url.getPath()).getName();
-    } else {
-      this.location = this.file.getAbsolutePath();
-      simpleTypeName = this.file.getName();
-    }
-
-    int i = simpleTypeName.lastIndexOf(".java");
-    if (i != -1) {
-      simpleTypeName = simpleTypeName.substring(0, i);
-    }
-    mainTypeName = simpleTypeName;
-
-    this.packageName = packageName;
-  }
-
-  public long getLastModified() throws UnableToCompleteException {
-    try {
-      if (file != null) {
-        return file.lastModified();
-      } else {
-        String converted = Util.findFileName(location);
-        if (converted != location) {
-          return new File(converted).lastModified();
-        }
-        URLConnection conn = url.openConnection();
-        return conn.getLastModified();
-      }
-    } catch (IOException e) {
-      throw new UnableToCompleteException();
-    }
-  }
-
-  public String getLocation() {
-    return location;
-  }
-
-  public String getMainTypeName() {
-    return mainTypeName;
-  }
-
-  public String getPackageName() {
-    return packageName;
-  }
-
-  public char[] getSource() throws UnableToCompleteException {
-    long lastModified = getLastModified();
-    if (sourceCurrentTime >= lastModified && source != null) {
-      return source;
-    } else {
-      sourceCurrentTime = lastModified;
-    }
-    if (file == null) {
-      // Pre-read source.
-      source = Util.readURLAsChars(url);
-    } else {
-      source = Util.readFileAsChars(file);
-    }
-    if (source == null) {
-      throw new UnableToCompleteException();
-    }
-    return source;
-  }
-
-  public boolean isTransient() {
-    return false;
-  }
-
-  @Override
-  public String toString() {
-    return location;
-  }
-}
diff --git a/dev/core/src/com/google/gwt/dev/jdt/WebModeCompilerFrontEnd.java b/dev/core/src/com/google/gwt/dev/jdt/WebModeCompilerFrontEnd.java
index d7efced..689ed08 100644
--- a/dev/core/src/com/google/gwt/dev/jdt/WebModeCompilerFrontEnd.java
+++ b/dev/core/src/com/google/gwt/dev/jdt/WebModeCompilerFrontEnd.java
@@ -17,6 +17,9 @@
 
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.dev.javac.CompilationState;
+import com.google.gwt.dev.javac.CompilationUnit;
+import com.google.gwt.dev.javac.JdtCompiler.CompilationUnitAdapter;
 import com.google.gwt.dev.jdt.FindDeferredBindingSitesVisitor.DeferredBindingSite;
 import com.google.gwt.dev.util.Empty;
 import com.google.gwt.dev.util.JsniRef;
@@ -36,13 +39,13 @@
  * Provides a reusable front-end based on the JDT compiler that incorporates
  * GWT-specific concepts such as JSNI and deferred binding.
  */
-public class WebModeCompilerFrontEnd extends AstCompiler {
+public class WebModeCompilerFrontEnd extends AbstractCompiler {
 
   private final RebindPermutationOracle rebindPermOracle;
 
-  public WebModeCompilerFrontEnd(SourceOracle sourceOracle,
+  public WebModeCompilerFrontEnd(CompilationState compilationState,
       RebindPermutationOracle rebindPermOracle) {
-    super(sourceOracle);
+    super(compilationState, false);
     this.rebindPermOracle = rebindPermOracle;
   }
 
@@ -51,17 +54,29 @@
       throws UnableToCompleteException {
 
     // Build the initial set of compilation units.
-    //
-    ICompilationUnit[] units = new ICompilationUnit[seedTypeNames.length];
+    Map<String, CompilationUnit> unitMap = compilationState.getCompilationUnitMap();
+    ICompilationUnit[] icus = new ICompilationUnit[seedTypeNames.length];
     for (int i = 0; i < seedTypeNames.length; i++) {
       String seedTypeName = seedTypeNames[i];
-      units[i] = getCompilationUnitForType(logger, seedTypeName);
+      CompilationUnit unit = unitMap.get(seedTypeName);
+      while (unit == null) {
+        int pos = seedTypeName.lastIndexOf('.');
+        if (pos < 0) {
+          logger.log(TreeLogger.ERROR,
+              "Unable to find compilation unit for type '" + seedTypeNames[i]
+                  + "'");
+          throw new UnableToCompleteException();
+        }
+        seedTypeName = seedTypeName.substring(0, pos);
+        unit = unitMap.get(seedTypeName);
+      }
+      icus[i] = new CompilationUnitAdapter(unit);
     }
 
     // Compile, which will pull in everything else via
     // doFindAdditionalTypesUsingMagic()
     //
-    CompilationUnitDeclaration[] cuds = compile(logger, units);
+    CompilationUnitDeclaration[] cuds = compile(logger, icus);
     return cuds;
   }
 
diff --git a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
index 99546d5..00590f6 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -17,8 +17,6 @@
 
 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.dev.jdt.ICompilationUnitAdapter;
 import com.google.gwt.dev.jdt.RebindOracle;
 import com.google.gwt.dev.jdt.RebindPermutationOracle;
 import com.google.gwt.dev.jdt.WebModeCompilerFrontEnd;
@@ -254,7 +252,6 @@
 
   private final String[] declEntryPoints;
   private final CompilationUnitDeclaration[] goldenCuds;
-  private long lastModified;
   private final JJSOptions options;
   private final Set<IProblem> problemSet = new HashSet<IProblem>();
 
@@ -302,25 +299,6 @@
     // found here will have already been logged by AbstractCompiler.
     //
     checkForErrors(logger, false);
-
-    // Find the newest of all these.
-    //
-    lastModified = 0;
-    CompilationUnitProvider newestCup = null;
-    for (CompilationUnitDeclaration cud : goldenCuds) {
-      ICompilationUnitAdapter icua = (ICompilationUnitAdapter) cud.compilationResult.compilationUnit;
-      CompilationUnitProvider cup = icua.getCompilationUnitProvider();
-      long cupLastModified = cup.getLastModified();
-      if (cupLastModified > lastModified) {
-        newestCup = cup;
-        lastModified = cupLastModified;
-      }
-    }
-    if (newestCup != null) {
-      String loc = newestCup.getLocation();
-      String msg = "Newest compilation unit is '" + loc + "'";
-      logger.log(TreeLogger.DEBUG, msg, null);
-    }
   }
 
   /**
@@ -535,10 +513,6 @@
     }
   }
 
-  public long getLastModifiedTimeOfNewestCompilationUnit() {
-    return lastModified;
-  }
-
   private void checkForErrors(final TreeLogger logger, boolean itemizeErrors)
       throws UnableToCompleteException {
     boolean compilationFailed = false;
diff --git a/dev/core/src/com/google/gwt/dev/resource/Resource.java b/dev/core/src/com/google/gwt/dev/resource/Resource.java
new file mode 100644
index 0000000..b5310df
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/resource/Resource.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2008 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.resource;
+
+import java.io.InputStream;
+import java.net.URL;
+
+/**
+ * Provides information about a file-like resource.
+ */
+public abstract class Resource {
+
+  /**
+   * Overridden to finalize; always returns object identity.
+   */
+  @Override
+  public final boolean equals(Object obj) {
+    return super.equals(obj);
+  }
+
+  /**
+   * Returns the user-relevant location of the resource. No programmatic
+   * assumptions should be made about the return value.
+   */
+  public abstract String getLocation();
+
+  /**
+   * Returns the full abstract path of the resource.
+   */
+  public abstract String getPath();
+
+  /**
+   * Returns a URL for this resource; this URL will only be valid for resources
+   * based off the file system.
+   * 
+   * TODO: get rid of this method?
+   */
+  public abstract URL getURL();
+
+  /**
+   * Overridden to finalize; always returns identity hash code.
+   */
+  @Override
+  public final int hashCode() {
+    return super.hashCode();
+  }
+
+  /**
+   * Returns the contents of the resource. May return <code>null</code> if
+   * this {@link Resource} has been invalidated by its containing
+   * {@link ResourceOracle}. The caller is responsible for closing the stream.
+   */
+  public abstract InputStream openContents();
+
+  /**
+   * Overridden to finalize; always returns {@link #getLocation()}.
+   */
+  @Override
+  public final String toString() {
+    return getLocation();
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/resource/ResourceOracle.java b/dev/core/src/com/google/gwt/dev/resource/ResourceOracle.java
new file mode 100644
index 0000000..de67ad5
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/resource/ResourceOracle.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2008 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.resource;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * An abstraction for finding and retrieving {@link Resource}s by abstract path
+ * name. Intuitively, it works like a jar in that each URL is uniquely located
+ * somewhere in an abstract namespace. The abstract names must be constructed
+ * from a series of zero or more valid Java identifiers followed by the '/'
+ * character and finally ending in a valid filename, for example,
+ * <code>com/google/gwt/blah.txt</code>.
+ * 
+ * <p>
+ * The identity of the returned sets and maps will change exactly when the
+ * underlying module is refreshed.
+ * </p>
+ * 
+ * <p>
+ * Even when the identity of a returned set changes, the identity of any
+ * contained {@link Resource} values is guaranteed to differ from a previous
+ * result exactly when that particular resource becomes invalid.
+ * </p>
+ * 
+ * <p>
+ * A resource could become invalid for various reasons, including:
+ * <ul>
+ * <li>the underlying file was deleted or modified</li>
+ * <li>another file with the same logical name superceded it on the classpath</li>
+ * <li>the underlying module changed to exclude this file or supercede it with
+ * another file</li>
+ * </ul>
+ * </p>
+ * 
+ * <p>
+ * After a refresh, a client can reliably detect changes by checking which of
+ * its cached resource is still contained in the new result of
+ * {@link #getResources()}.
+ * </p>
+ */
+public interface ResourceOracle {
+
+  /**
+   * Returns an unmodifiable set of unique abstract path names with constant
+   * lookup time.
+   */
+  Set<String> getPathNames();
+
+  /**
+   * Returns an unmodifiable map of abstract path name to resource.
+   */
+  Map<String, Resource> getResourceMap();
+
+  /**
+   * Returns an unmodifiable set of unique resources with constant lookup time.
+   */
+  Set<Resource> getResources();
+}
diff --git a/dev/core/src/com/google/gwt/dev/resource/impl/AbstractResource.java b/dev/core/src/com/google/gwt/dev/resource/impl/AbstractResource.java
new file mode 100644
index 0000000..99c9fd9
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/resource/impl/AbstractResource.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2008 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.resource.impl;
+
+import com.google.gwt.dev.resource.Resource;
+
+/**
+ * TODO(bruce): write me.
+ */
+public abstract class AbstractResource extends Resource {
+
+  /**
+   * Accesses the path root under which this resource was found. Only available
+   * within this package.
+   */
+  public abstract ClassPathEntry getClassPathEntry();
+
+  public abstract boolean isStale();
+}
+
diff --git a/dev/core/src/com/google/gwt/dev/resource/impl/ClassPathEntry.java b/dev/core/src/com/google/gwt/dev/resource/impl/ClassPathEntry.java
new file mode 100644
index 0000000..aaa46f9
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/resource/impl/ClassPathEntry.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2008 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.resource.impl;
+
+import com.google.gwt.core.ext.TreeLogger;
+
+import java.util.Set;
+
+/**
+ * A location that acts as a starting point for finding resources
+ * {@link ResourceOracleImpl}.
+ */
+public abstract class ClassPathEntry {
+
+  /**
+   * Finds every resource at abstract path P within this classpath such that P
+   * begins with a prefix X from the path prefix set and P is allowed by the
+   * filter associated with X.
+   * 
+   * @return a set of zero or more resources; note no guarantees are made
+   *         regarding the identities of the returned resource objects, and the
+   *         same object may be returned across multiple calls
+   */
+  public abstract Set<AbstractResource> findApplicableResources(
+      TreeLogger logger, PathPrefixSet pathPrefixSet);
+
+  /**
+   * Gets a URL string that describes this class path entry.
+   */
+  public abstract String getLocation();
+
+  @Override
+  public String toString() {
+    return getClass().getSimpleName() + ": " + getLocation();
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/resource/impl/DirectoryClassPathEntry.java b/dev/core/src/com/google/gwt/dev/resource/impl/DirectoryClassPathEntry.java
new file mode 100644
index 0000000..7d29ca6
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/resource/impl/DirectoryClassPathEntry.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2008 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.resource.impl;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.dev.util.msg.Message1String;
+
+import java.io.File;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * TODO(bruce): write me.
+ */
+public class DirectoryClassPathEntry extends ClassPathEntry {
+
+  private static class Messages {
+    static final Message1String NOT_DESCENDING_INTO_DIR = new Message1String(
+        TreeLogger.SPAM, "Prefix set does not include dir: $0");
+
+    static final Message1String DESCENDING_INTO_DIR = new Message1String(
+        TreeLogger.SPAM, "Descending into dir: $0");
+
+    static final Message1String EXCLUDING_FILE = new Message1String(
+        TreeLogger.DEBUG, "Filter excludes file: $0");
+
+    static final Message1String INCLUDING_FILE = new Message1String(
+        TreeLogger.DEBUG, "Including file: $0");
+  }
+
+  private final File dir;
+
+  public DirectoryClassPathEntry(File dir) {
+    this.dir = dir;
+  }
+
+  @Override
+  public Set<AbstractResource> findApplicableResources(TreeLogger logger,
+      PathPrefixSet pathPrefixSet) {
+    Set<AbstractResource> results = new HashSet<AbstractResource>();
+    descendToFindResources(logger, pathPrefixSet, results, dir, "");
+    return results;
+  }
+
+  @Override
+  public String getLocation() {
+    return dir.getAbsoluteFile().toURI().toString();
+  }
+
+  /**
+   * @param logger logs progress
+   * @param resources the accumulating set of resources found
+   * @param dir the file or directory to consider
+   * @param dirPath the abstract path name associated with 'parent', which
+   *          explicitly does not include the classpath entry in its path
+   */
+  private void descendToFindResources(TreeLogger logger,
+      PathPrefixSet pathPrefixSet, Set<AbstractResource> resources, File dir,
+      String dirPath) {
+    assert (dir.isDirectory());
+
+    // Assert: this directory is included in the path prefix set.
+
+    File[] children = dir.listFiles();
+    for (File child : children) {
+      String childPath = dirPath + child.getName();
+      if (child.isDirectory()) {
+        String childDirPath = childPath + "/";
+        if (pathPrefixSet.includesDirectory(childDirPath)) {
+          Messages.DESCENDING_INTO_DIR.log(logger, child.getAbsolutePath(),
+              null);
+          descendToFindResources(logger, pathPrefixSet, resources, child,
+              childDirPath);
+        } else {
+          Messages.NOT_DESCENDING_INTO_DIR.log(logger, child.getAbsolutePath(),
+              null);
+        }
+      } else {
+        if (pathPrefixSet.includesResource(childPath)) {
+          Messages.INCLUDING_FILE.log(logger, childPath, null);
+          FileResource r = new FileResource(this, childPath, child);
+          resources.add(r);
+        } else {
+          Messages.EXCLUDING_FILE.log(logger, childPath, null);
+        }
+      }
+    }
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/resource/impl/FileResource.java b/dev/core/src/com/google/gwt/dev/resource/impl/FileResource.java
new file mode 100644
index 0000000..74ba79f
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/resource/impl/FileResource.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2008 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.resource.impl;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+/**
+ * Represents a resource contained in directory on a file system.
+ */
+public class FileResource extends AbstractResource {
+
+  private final String abstractPathName;
+  private final DirectoryClassPathEntry classPathEntry;
+  private final File file;
+  private final long modificationSeconds;
+
+  public FileResource(DirectoryClassPathEntry classPathEntry,
+      String abstractPathName, File file) {
+    assert (file.isFile());
+    this.classPathEntry = classPathEntry;
+    this.abstractPathName = abstractPathName;
+    this.file = file;
+    this.modificationSeconds = lastModifiedSeconds(file);
+  }
+
+  @Override
+  public DirectoryClassPathEntry getClassPathEntry() {
+    return classPathEntry;
+  }
+
+  @Override
+  public String getLocation() {
+    return file.getAbsoluteFile().toURI().toString();
+  }
+
+  @Override
+  public String getPath() {
+    return abstractPathName;
+  }
+
+  @Override
+  public URL getURL() {
+    try {
+      return new URL(getLocation());
+    } catch (MalformedURLException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  @Override
+  public boolean isStale() {
+    if (!file.exists()) {
+      // File was deleted. Always stale.
+      return true;
+    }
+
+    long currentModificationSeconds = lastModifiedSeconds(file);
+    /*
+     * We use != instead of > because the point is to reflect what's actually on
+     * the file system, not to worry about freshness per se.
+     */
+    return (currentModificationSeconds != modificationSeconds);
+  }
+
+  @Override
+  public InputStream openContents() {
+    try {
+      return new FileInputStream(file);
+    } catch (FileNotFoundException e) {
+      return null;
+    }
+  }
+
+  private long lastModifiedSeconds(File file) {
+    return file.lastModified() / 1000;
+  }
+
+}
diff --git a/dev/core/src/com/google/gwt/dev/resource/impl/JarFileClassPathEntry.java b/dev/core/src/com/google/gwt/dev/resource/impl/JarFileClassPathEntry.java
new file mode 100644
index 0000000..7e7de4b
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/resource/impl/JarFileClassPathEntry.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2008 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.resource.impl;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.dev.util.msg.Message1String;
+
+import java.io.File;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+/**
+ * A classpath entry that is a jar file.
+ */
+public class JarFileClassPathEntry extends ClassPathEntry {
+
+  /**
+   * Logger messages related to this class.
+   */
+  private static class Messages {
+    static final Message1String BUILDING_INDEX = new Message1String(
+        TreeLogger.TRACE, "Indexing jar file: $0");
+
+    static final Message1String EXCLUDING_RESOURCE = new Message1String(
+        TreeLogger.DEBUG, "Excluding $0");
+
+    static final Message1String FINDING_INCLUDED_RESOURCES = new Message1String(
+        TreeLogger.DEBUG, "Searching for included resources in $0");
+
+    static final Message1String INCLUDING_RESOURCE = new Message1String(
+        TreeLogger.DEBUG, "Including $0");
+
+    static final Message1String READ_JAR_ENTRY = new Message1String(
+        TreeLogger.DEBUG, "$0");
+  }
+
+  private Set<JarFileResource> allJarFileResources;
+  private Set<AbstractResource> cachedAnswers;
+  private final JarFile jarFile;
+  private PathPrefixSet lastPrefixSet;
+
+  public JarFileClassPathEntry(JarFile jarFile) {
+    this.jarFile = jarFile;
+  }
+
+  /**
+   * Indexes the jar file on-demand, and only once over the life of the process.
+   */
+  @Override
+  public Set<AbstractResource> findApplicableResources(TreeLogger logger,
+      PathPrefixSet pathPrefixSet) {
+    // Never re-index.
+    if (allJarFileResources == null) {
+      allJarFileResources = buildIndex(logger);
+    }
+
+    if (cachedAnswers == null || lastPrefixSet != pathPrefixSet
+        || lastPrefixSet.getModCount() != pathPrefixSet.getModCount()) {
+      cachedAnswers = computeApplicableResources(logger, pathPrefixSet);
+    }
+
+    return cachedAnswers;
+  }
+
+  public JarFile getJarFile() {
+    return jarFile;
+  }
+
+  @Override
+  public String getLocation() {
+    return new File(jarFile.getName()).toURI().toString();
+  }
+
+  private Set<JarFileResource> buildIndex(TreeLogger logger) {
+    logger = Messages.BUILDING_INDEX.branch(logger, jarFile.getName(), null);
+
+    HashSet<JarFileResource> results = new HashSet<JarFileResource>();
+    Enumeration<JarEntry> e = jarFile.entries();
+    while (e.hasMoreElements()) {
+      JarEntry jarEntry = e.nextElement();
+      if (jarEntry.isDirectory()) {
+        // Skip directories.
+        continue;
+      }
+      if (jarEntry.getName().startsWith("META-INF/")) {
+        // Skip META-INF since classloaders normally make this invisible.
+        continue;
+      }
+      JarFileResource jarResource = new JarFileResource(this, jarEntry);
+      results.add(jarResource);
+      Messages.READ_JAR_ENTRY.log(logger, jarEntry.getName(), null);
+    }
+    return results;
+  }
+
+  private Set<AbstractResource> computeApplicableResources(TreeLogger logger,
+      PathPrefixSet pathPrefixSet) {
+    logger = Messages.FINDING_INCLUDED_RESOURCES.branch(logger,
+        jarFile.getName(), null);
+
+    Set<AbstractResource> results = new HashSet<AbstractResource>();
+    for (JarFileResource r : allJarFileResources) {
+      String path = r.getPath();
+      if (pathPrefixSet.includesResource(path)) {
+        Messages.INCLUDING_RESOURCE.log(logger, path, null);
+        results.add(r);
+      } else {
+        Messages.EXCLUDING_RESOURCE.log(logger, path, null);
+      }
+    }
+    return results;
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/resource/impl/JarFileResource.java b/dev/core/src/com/google/gwt/dev/resource/impl/JarFileResource.java
new file mode 100644
index 0000000..650d053
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/resource/impl/JarFileResource.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2008 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.resource.impl;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.jar.JarEntry;
+
+/**
+ * Represents a resource contained in a jar file.
+ */
+public class JarFileResource extends AbstractResource {
+
+  private final JarFileClassPathEntry classPathEntry;
+  private final JarEntry jarEntry;
+
+  public JarFileResource(JarFileClassPathEntry classPathEntry, JarEntry jarEntry) {
+    this.classPathEntry = classPathEntry;
+    this.jarEntry = jarEntry;
+  }
+
+  @Override
+  public JarFileClassPathEntry getClassPathEntry() {
+    return classPathEntry;
+  }
+
+  public JarEntry getJarEntry() {
+    return jarEntry;
+  }
+
+  @Override
+  public String getLocation() {
+    return "jar:" + classPathEntry.getLocation() + "!/" + getPath();
+  }
+
+  @Override
+  public String getPath() {
+    return jarEntry.getName();
+  }
+
+  @Override
+  public URL getURL() {
+    try {
+      return new URL(getLocation());
+    } catch (MalformedURLException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  /**
+   * Since we don't dynamically reload jars during a run, jar-based resources
+   * cannot become stale.
+   */
+  @Override
+  public boolean isStale() {
+    return false;
+  }
+
+  @Override
+  public InputStream openContents() {
+    try {
+      return classPathEntry.getJarFile().getInputStream(jarEntry);
+    } catch (IOException e) {
+      // The spec for this method says it can return null.
+      return null;
+    }
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/resource/impl/PathPrefix.java b/dev/core/src/com/google/gwt/dev/resource/impl/PathPrefix.java
new file mode 100644
index 0000000..25537bb
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/resource/impl/PathPrefix.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2008 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.resource.impl;
+
+/**
+ * Represents the abstract path prefix that goes between the
+ * {@link ClassPathEntry} and the rest of resource's abstract path. This concept
+ * allows us to specify subsets of path hierarchies orthogonally from path
+ * roots. For example, a path root might be <code>/home/gwt/src/</code> and an
+ * abstract path prefix might be <code>module/client/</code>. Importantly,
+ * you can apply the same abstract path prefix to multiple path roots and find
+ * more than one set of resources residing in disjoint locations yet occupying
+ * the same logical hierarchy. Sorry this explanation is so abstract; it's how
+ * we model things like the GWT module's client source path, public path, and
+ * super source path.
+ */
+public final class PathPrefix {
+
+  public static final PathPrefix ALL = new PathPrefix("", null);
+
+  private final ResourceFilter filter;
+  private final String prefix;
+  private final boolean shouldReroot;
+
+  /**
+   * Construct a non-rerooting prefix.
+   * 
+   * @param prefix a string prefix that (1) is the empty string or (2) begins
+   *          with something other than a slash and ends with a slash
+   * @param filter the resource filter to use, or <code>null</code> for no
+   *          filter; note that the filter must always return the same answer
+   *          for the same candidate path (doing otherwise will produce
+   *          inconsistent behavior in identifying available resources)
+   */
+  public PathPrefix(String prefix, ResourceFilter filter) {
+    this(prefix, filter, false);
+  }
+
+  /**
+   * Construct a prefix.
+   * 
+   * @param prefix a string prefix that (1) is the empty string or (2) begins
+   *          with something other than a slash and ends with a slash
+   * @param filter the resource filter to use, or <code>null</code> for no
+   *          filter; note that the filter must always return the same answer
+   *          for the same candidate path (doing otherwise will produce
+   *          inconsistent behavior in identifying available resources)
+   * @param shouldReroot if <code>true</code>, any matching {@link Resource}
+   *          for this prefix will be rerooted to not include the initial prefix
+   *          path; if <code>false</code>, the prefix will be included in a
+   *          matching resource's path.
+   * 
+   */
+  public PathPrefix(String prefix, ResourceFilter filter, boolean shouldReroot) {
+    assertValidPrefix(prefix);
+    this.prefix = prefix;
+    this.filter = filter;
+    this.shouldReroot = shouldReroot;
+  }
+
+  /**
+   * Determines whether or not a given path is allowed by this path prefix by
+   * checking both the prefix string and the filter.
+   * 
+   * @param path
+   * @return
+   */
+  public boolean allows(String path) {
+    if (!path.startsWith(prefix)) {
+      return false;
+    }
+    if (filter == null) {
+      return true;
+    }
+    if (shouldReroot) {
+      path = getRerootedPath(path);
+    }
+    return filter.allows(path);
+  }
+
+  /**
+   * Equality is based on prefixes representing the same string. Importantly,
+   * the filter does not affect equality.
+   */
+  @Override
+  public boolean equals(Object obj) {
+    if (obj instanceof PathPrefix) {
+      if (prefix.equals(((PathPrefix) obj).prefix)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * The prefix.
+   * 
+   * @return the result is guaranteed to be non-<code>null</code>, and
+   *         either be the empty string or it will not begin with a slash and
+   *         will end with a slash; these guarantees are very useful when
+   *         concatenating paths that incorporate prefixes
+   */
+  public String getPrefix() {
+    return prefix;
+  }
+
+  public String getRerootedPath(String path) {
+    assert (path.startsWith(prefix));
+    if (shouldReroot) {
+      return path.substring(prefix.length());
+    } else {
+      return path;
+    }
+  }
+
+  @Override
+  public int hashCode() {
+    return prefix.hashCode();
+  }
+
+  public boolean shouldReroot() {
+    return shouldReroot;
+  }
+
+  private void assertValidPrefix(String prefix) {
+    assert (prefix != null);
+    assert ("".equals(prefix) || (!prefix.startsWith("/") && prefix.endsWith("/"))) : "malformed prefix";
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/resource/impl/PathPrefixSet.java b/dev/core/src/com/google/gwt/dev/resource/impl/PathPrefixSet.java
new file mode 100644
index 0000000..08a7832
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/resource/impl/PathPrefixSet.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright 2008 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.resource.impl;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Combines the information conveyed about a set of path prefixes to quickly
+ * answer questions regarding an entire set of path prefixes.
+ */
+public class PathPrefixSet {
+
+  private static class TrieNode {
+    // TODO(bruce): test to see if Map would be faster; I'm on the fence
+    private final List<TrieNode> children = new ArrayList<TrieNode>();
+    private final String part;
+    private PathPrefix prefix;
+
+    public TrieNode(String part) {
+      this.part = part;
+    }
+
+    public TrieNode addChild(String part) {
+      assert (findChild(part) == null);
+      TrieNode newChild = new TrieNode(part);
+      children.add(newChild);
+      return newChild;
+    }
+
+    public TrieNode findChild(String part) {
+      for (TrieNode child : children) {
+        if (child.part.equals(part)) {
+          return child;
+        }
+      }
+      return null;
+    }
+
+    public PathPrefix getPathPrefix() {
+      return prefix;
+    }
+
+    public boolean hasChildren() {
+      return !children.isEmpty();
+    }
+
+    public void setPathPrefix(PathPrefix prefix) {
+      this.prefix = prefix;
+    }
+
+    @Override
+    public String toString() {
+      StringBuilder sb = new StringBuilder();
+      toString(sb, "");
+      return sb.toString();
+    }
+
+    private void toString(StringBuilder sb, String indent) {
+      if (sb.length() > 0) {
+        sb.append('\n');
+      }
+      sb.append(indent);
+      sb.append(' ');
+      sb.append(part);
+      for (TrieNode child : children) {
+        child.toString(sb, indent + "  ");
+      }
+    }
+  }
+
+  private int modCount;
+  private final Map<String, PathPrefix> prefixes = new HashMap<String, PathPrefix>();
+  private final TrieNode rootTrieNode = new TrieNode("/");
+
+  /**
+   * @param prefix the prefix to add
+   * @return <code>true</code> if the prefix was not already in the set;
+   *         otherwise, it replaced an identical one having the same prefix,
+   *         which has the effect of changing which filter is used (last one
+   *         wins)
+   */
+  public boolean add(PathPrefix prefix) {
+    ++modCount;
+    String pathPrefix = prefix.getPrefix();
+    prefixes.put(pathPrefix, prefix);
+
+    /*
+     * An empty prefix means we have no prefix requirement, but we do attached
+     * the prefix to the root so that we can apply the filter.
+     */
+    if ("".equals(pathPrefix)) {
+      rootTrieNode.setPathPrefix(prefix);
+      return false;
+    }
+
+    // TODO(bruce): consider not using split for speed
+    String[] parts = pathPrefix.split("/");
+    TrieNode parentNode = rootTrieNode;
+    boolean didAdd = false;
+    for (String part : parts) {
+      TrieNode childNode = parentNode.findChild(part);
+      if (childNode != null) {
+        // Follow existing branch.
+        parentNode = childNode;
+      } else {
+        // Add a new branch.
+        parentNode = parentNode.addChild(part);
+        didAdd = true;
+      }
+    }
+    assert (parentNode != null);
+    // This may clobber an existing one, but that's okay. Last one wins.
+    parentNode.setPathPrefix(prefix);
+    return didAdd;
+  }
+
+  public int getModCount() {
+    return modCount;
+  }
+
+  /**
+   * Determines whether or not a directory might have resources that could be
+   * included. The primary purpose of this method is to allow
+   * {@link ClassPathEntry} subclasses to avoid descending into directory
+   * hierarchies that could not possibly contain resources that would be
+   * included by {@link #includesResource(String).
+   * 
+   * @param dirPath must be a valid abstract directory name or the empty string
+   * @return
+   */
+  public boolean includesDirectory(String dirPath) {
+    assertValidAbstractDirectoryPathName(dirPath);
+
+    /*
+     * There are five cases:
+     * 
+     * (0) dirPath is the empty string, which is (a) trivially included unless
+     * (b) no prefix paths have been specified at all.
+     * 
+     * (1) The empty string was specified as a prefix, which causes everything
+     * to be included.
+     * 
+     * (2) As we walk the parts of dirPath, we see a path prefix attached to one
+     * of the trie nodes we encounter. This means that there was a specified
+     * prefix that this dirPath falls underneath, so it is included.
+     * 
+     * (3) dirPath is longer than the trie, but we never encounter a path prefix
+     * as we walk the trie. This indicates that this directory doesn't fall into
+     * any of the specified prefixes.
+     * 
+     * (4) dirPath is not longer than the trie and stays on the trie the whole
+     * time, which means it is included (since at least some longer prefix
+     * includes it).
+     */
+
+    // if ("".equals(dirPath)) {
+    // if (rootTrieNode.hasChildren() || rootTrieNode.getPathPrefix() != null) {
+    // // Case (0)(a): trivially true.
+    // return true;
+    // } else {
+    // // Case (0)(b): no directories are included.
+    // return false;
+    // }
+    // }
+    if (rootTrieNode.getPathPrefix() != null) {
+      // Case (1).
+      return true;
+    }
+
+    TrieNode parentNode = rootTrieNode;
+
+    String[] parts = dirPath.split("/");
+    for (String part : parts) {
+      assert (!"".equals(part));
+      TrieNode childNode = parentNode.findChild(part);
+      if (childNode != null) {
+        PathPrefix pathPrefix = childNode.getPathPrefix();
+        if (pathPrefix != null) {
+          // Case (2).
+          return true;
+        }
+
+        // Haven't found a path prefix yet, so keep walking.
+        parentNode = childNode;
+      } else {
+        // Case (3).
+        return false;
+      }
+    }
+
+    // Case (4).
+    return true;
+  }
+
+  /**
+   * Determines whether or not a given resource should be allowed by this path
+   * prefix set and the corresponding filters.
+   * 
+   * @param resourceAbstractPathName
+   * @return <code>true</code> if the resource matches some specified prefix
+   *         and any associated filters don't exclude it
+   */
+  public boolean includesResource(String resourceAbstractPathName) {
+    assertValidAbstractResourcePathName(resourceAbstractPathName);
+
+    TrieNode parentNode = rootTrieNode;
+    PathPrefix matchingPrefix = rootTrieNode.getPathPrefix();
+
+    // TODO(bruce): consider not using split for speed
+    String[] parts = resourceAbstractPathName.split("/");
+
+    // Walk all but the last path part, which is assumed to be a file name.
+    for (int i = 0, n = parts.length - 1; i < n; ++i) {
+      String part = parts[i];
+      assert (!"".equals(part));
+      TrieNode childNode = parentNode.findChild(part);
+      if (childNode != null) {
+        // Follow valid branch.
+        matchingPrefix = childNode.getPathPrefix();
+        parentNode = childNode;
+      } else {
+        // No valid branch to follow.
+        break;
+      }
+    }
+
+    if (matchingPrefix == null) {
+      // Didn't match any specified prefix.
+      return false;
+    }
+
+    // It did match a prefix, but we still need to test.
+    return matchingPrefix.allows(resourceAbstractPathName);
+  }
+
+  public Collection<PathPrefix> values() {
+    return Collections.unmodifiableCollection(prefixes.values());
+  }
+
+  private void assertValidAbstractDirectoryPathName(String name) {
+    assert (name != null);
+    // assert ("".equals(name) || (!name.startsWith("/") &&
+    // name.endsWith("/")));
+    assert (!name.startsWith("/") && name.endsWith("/"));
+  }
+
+  private void assertValidAbstractResourcePathName(String name) {
+    assert (name != null);
+    assert (!"".equals(name));
+    assert (!name.startsWith("/") && !name.endsWith("/"));
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/resource/impl/ResourceFilter.java b/dev/core/src/com/google/gwt/dev/resource/impl/ResourceFilter.java
new file mode 100644
index 0000000..51cbfdc
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/resource/impl/ResourceFilter.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2008 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.resource.impl;
+
+/**
+ * Used to decide whether or not a resource name should be included.
+ */
+public interface ResourceFilter {
+
+  /**
+   * Determines if the specified path is included by the filter.
+   */
+  boolean allows(String path);
+
+}
diff --git a/dev/core/src/com/google/gwt/dev/resource/impl/ResourceOracleImpl.java b/dev/core/src/com/google/gwt/dev/resource/impl/ResourceOracleImpl.java
new file mode 100644
index 0000000..0d429ff
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/resource/impl/ResourceOracleImpl.java
@@ -0,0 +1,366 @@
+/*
+ * Copyright 2008 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.resource.impl;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.dev.resource.Resource;
+import com.google.gwt.dev.resource.ResourceOracle;
+import com.google.gwt.dev.util.msg.Message0;
+import com.google.gwt.dev.util.msg.Message1String;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.jar.JarFile;
+
+/**
+ * The normal implementation of {@link ResourceOracle}.
+ */
+public class ResourceOracleImpl implements ResourceOracle {
+
+  private static class Messages {
+    static final Message1String EXAMINING_PATH_ROOT = new Message1String(
+        TreeLogger.DEBUG, "Searching for resources within $0");
+
+    static final Message1String IGNORING_SHADOWED_RESOURCE = new Message1String(
+        TreeLogger.DEBUG,
+        "Resource '$0' is being shadowed by another resource higher in the classpath having the same name; this one will not be used");
+
+    static final Message1String NEW_RESOURCE_FOUND = new Message1String(
+        TreeLogger.TRACE, "Found new resource: $0");
+
+    static final Message0 NO_RESOURCES_CHANGED = new Message0(TreeLogger.DEBUG,
+        "No resources changed");
+
+    static final Message0 REFRESHING_RESOURCES = new Message0(TreeLogger.TRACE,
+        "Refreshing resources");
+
+    static final Message1String RESOURCE_BECAME_INVALID_BECAUSE_IT_IS_STALE = new Message1String(
+        TreeLogger.SPAM,
+        "Resource '$0' has been modified since it was last loaded and needs to be reloaded");
+
+    static final Message1String RESOURCE_BECAME_INVALID_BECAUSE_IT_MOVED = new Message1String(
+        TreeLogger.DEBUG,
+        "Resource '$0' was found on a different classpath entry and needs to be reloaded");
+  }
+
+  /**
+   * Used by rebasing {@link ResourceOracle ResourceOracles} to map from a full
+   * classpath-based abstract path to an abstract path within a logical package.
+   * 
+   * @see ResourceOracleImpl#shouldRebasePaths()
+   */
+  private static class ResourceWrapper extends AbstractResource {
+    private final String path;
+    private final AbstractResource resource;
+
+    public ResourceWrapper(String path, AbstractResource resource) {
+      this.path = path;
+      this.resource = resource;
+    }
+
+    @Override
+    public ClassPathEntry getClassPathEntry() {
+      return resource.getClassPathEntry();
+    }
+
+    @Override
+    public String getLocation() {
+      return resource.getLocation();
+    }
+
+    @Override
+    public String getPath() {
+      return path;
+    }
+
+    @Override
+    public URL getURL() {
+      return resource.getURL();
+    }
+
+    @Override
+    public boolean isStale() {
+      return resource.isStale();
+    }
+
+    @Override
+    public InputStream openContents() {
+      return resource.openContents();
+    }
+  }
+
+  public static ClassPathEntry createEntryForUrl(TreeLogger logger, URL url)
+      throws URISyntaxException, IOException {
+    String urlString = url.toString();
+    if (url.getProtocol().equals("file")) {
+      URI uri = new URI(urlString);
+      File f = new File(uri);
+      if (f.isDirectory()) {
+        return new DirectoryClassPathEntry(f);
+      } else if (f.isFile() && f.getName().endsWith(".jar")) {
+        return new JarFileClassPathEntry(new JarFile(f));
+      } else {
+        logger.log(TreeLogger.WARN, "Unexpected error reading classpath; " + f
+            + " is neither a directory nor a jar");
+        return null;
+      }
+    } else {
+      logger.log(TreeLogger.WARN, "Unknown URL type for " + urlString, null);
+      return null;
+    }
+  }
+
+  private static void addAllClassPathEntries(TreeLogger logger,
+      ClassLoader classLoader, List<ClassPathEntry> classPath) {
+    for (; classLoader != null; classLoader = classLoader.getParent()) {
+      if (classLoader instanceof URLClassLoader) {
+        URLClassLoader urlClassLoader = (URLClassLoader) classLoader;
+        URL[] urls = urlClassLoader.getURLs();
+        for (URL url : urls) {
+          Throwable caught;
+          try {
+            ClassPathEntry entry = createEntryForUrl(logger, url);
+            if (entry != null) {
+              classPath.add(entry);
+            }
+            continue;
+          } catch (URISyntaxException e) {
+            caught = e;
+          } catch (IOException e) {
+            caught = e;
+          }
+          logger.log(TreeLogger.WARN, "Error processing classpath URL '" + url
+              + "'", caught);
+        }
+      }
+    }
+  }
+
+  private static List<ClassPathEntry> getAllClassPathEntries(TreeLogger logger,
+      ClassLoader classLoader) {
+    ArrayList<ClassPathEntry> classPath = new ArrayList<ClassPathEntry>();
+    addAllClassPathEntries(logger, classLoader, classPath);
+    return classPath;
+  }
+
+  private final List<ClassPathEntry> classPath = new ArrayList<ClassPathEntry>();
+
+  private Set<String> exposedPathNames = Collections.emptySet();
+
+  private Map<String, Resource> exposedResourceMap = Collections.emptyMap();
+
+  private Set<Resource> exposedResources = Collections.emptySet();
+
+  private Map<String, AbstractResource> internalMap = Collections.emptyMap();
+
+  private PathPrefixSet pathPrefixSet = new PathPrefixSet();
+
+  /**
+   * Constructs a {@link ResourceOracleImpl} from a set of
+   * {@link ClassPathEntry ClassPathEntries}. The passed-in list is copied, but
+   * the underlying entries in the list are not. Those entries must be
+   * effectively immutable except for reflecting actual changes to the
+   * underlying resources.
+   */
+  public ResourceOracleImpl(List<ClassPathEntry> classPath) {
+    this.classPath.addAll(classPath);
+  }
+
+  /**
+   * Constructs a {@link ResourceOracleImpl} from the thread's default
+   * {@link ClassLoader}.
+   */
+  public ResourceOracleImpl(TreeLogger logger) {
+    this(logger, Thread.currentThread().getContextClassLoader());
+  }
+
+  /**
+   * Constructs a {@link ResourceOracleImpl} from a {@link ClassLoader}. The
+   * specified {@link ClassLoader} and all of its parents which are instances of
+   * {@link URLClassLoader} will have their class path entries added to this
+   * instances underlying class path.
+   */
+  public ResourceOracleImpl(TreeLogger logger, ClassLoader classLoader) {
+    this(getAllClassPathEntries(logger, classLoader));
+  }
+
+  public Set<String> getPathNames() {
+    return exposedPathNames;
+  }
+
+  public Map<String, Resource> getResourceMap() {
+    return exposedResourceMap;
+  }
+
+  public Set<Resource> getResources() {
+    return exposedResources;
+  }
+
+  /**
+   * Rescans the associated paths to recompute the available resources.
+   * 
+   * @param logger status and error details are written here
+   * @throws UnableToCompleteException
+   */
+  public void refresh(TreeLogger logger) {
+    TreeLogger refreshBranch = Messages.REFRESHING_RESOURCES.branch(logger,
+        null);
+
+    /*
+     * Allocate fresh data structures in anticipation of needing to honor the
+     * "new identity for the collections if anything changes" guarantee.
+     */
+    final Map<String, AbstractResource> newInternalMap = new HashMap<String, AbstractResource>();
+
+    /*
+     * Walk across path roots (i.e. classpath entries) in priority order. This
+     * is a "reverse painter's algorithm", relying on being careful never to add
+     * a resource that has already been added to the new map under construction
+     * to create the effect that resources founder earlier on the classpath take
+     * precedence.
+     */
+    int changeCount = 0;
+    for (ClassPathEntry pathRoot : classPath) {
+      TreeLogger branchForClassPathEntry = Messages.EXAMINING_PATH_ROOT.branch(
+          refreshBranch, pathRoot.getLocation(), null);
+
+      int prevChangeCount = changeCount;
+
+      Set<AbstractResource> newResources = pathRoot.findApplicableResources(
+          branchForClassPathEntry, pathPrefixSet);
+      for (AbstractResource newResource : newResources) {
+        String resourcePath = newResource.getPath();
+
+        // Make sure we don't already have a resource by this name.
+        if (newInternalMap.containsKey(resourcePath)) {
+          Messages.IGNORING_SHADOWED_RESOURCE.log(branchForClassPathEntry,
+              resourcePath, null);
+          continue;
+        }
+
+        AbstractResource oldResource = internalMap.get(resourcePath);
+        if (shouldUseNewResource(branchForClassPathEntry, oldResource,
+            newResource)) {
+          newInternalMap.put(resourcePath, newResource);
+          ++changeCount;
+        } else if (oldResource != null) {
+          // Nothing changed, so carry the identity of the old one forward.
+          newInternalMap.put(resourcePath, oldResource);
+        }
+      }
+
+      if (changeCount == prevChangeCount) {
+        Messages.NO_RESOURCES_CHANGED.log(branchForClassPathEntry, null);
+      }
+    }
+
+    if (changeCount == 0) {
+      /*
+       * Nothing was added or modified, but we still have to be sure we didn't
+       * lose any resources.
+       */
+      if (newInternalMap.size() == internalMap.size()) {
+        /*
+         * Exit without changing the current exposed collections to maintain the
+         * identity requirements described in the spec for ResourceOracle.
+         */
+        return;
+      }
+    }
+
+    internalMap = newInternalMap;
+    Map<String, Resource> externalMap = rerootResourcePaths(newInternalMap);
+
+    // Create a constant-time set for resources.
+    Set<Resource> newResources = new HashSet<Resource>(externalMap.values());
+    assert (newResources.size() == externalMap.size());
+
+    // Update the gettable fields with the new (unmodifiable) data structures.
+    exposedResources = Collections.unmodifiableSet(newResources);
+    exposedResourceMap = Collections.unmodifiableMap(externalMap);
+    exposedPathNames = Collections.unmodifiableSet(externalMap.keySet());
+  }
+
+  public void setPathPrefixes(PathPrefixSet pathPrefixSet) {
+    this.pathPrefixSet = pathPrefixSet;
+  }
+
+  private Map<String, Resource> rerootResourcePaths(
+      Map<String, AbstractResource> newInternalMap) {
+    Map<String, Resource> externalMap;
+    // Create an external map with rebased path names.
+    externalMap = new HashMap<String, Resource>();
+    for (AbstractResource resource : newInternalMap.values()) {
+      String path = resource.getPath();
+      if (externalMap.get(path) instanceof ResourceWrapper) {
+        // A rerooted resource blocks any other resource at this path.
+        continue;
+      }
+      for (PathPrefix pathPrefix : pathPrefixSet.values()) {
+        if (pathPrefix.allows(path)) {
+          assert (path.startsWith(pathPrefix.getPrefix()));
+          if (pathPrefix.shouldReroot()) {
+            path = pathPrefix.getRerootedPath(path);
+            AbstractResource wrapper = new ResourceWrapper(path, resource);
+            externalMap.put(path, wrapper);
+          } else {
+            externalMap.put(path, resource);
+          }
+          break;
+        }
+      }
+      assert (externalMap.containsKey(path));
+    }
+    return externalMap;
+  }
+
+  private boolean shouldUseNewResource(TreeLogger logger,
+      AbstractResource oldResource, AbstractResource newResource) {
+    String resourcePath = newResource.getPath();
+    if (oldResource != null) {
+      // Test 1: Is the resource found in a different location than before?
+      if (oldResource.getClassPathEntry() == newResource.getClassPathEntry()) {
+        // Test 2: Has the resource changed since we last found it?
+        if (!oldResource.isStale()) {
+          // The resource has not changed.
+          return false;
+        } else {
+          Messages.RESOURCE_BECAME_INVALID_BECAUSE_IT_IS_STALE.log(logger,
+              resourcePath, null);
+        }
+      } else {
+        Messages.RESOURCE_BECAME_INVALID_BECAUSE_IT_MOVED.log(logger,
+            resourcePath, null);
+      }
+    } else {
+      Messages.NEW_RESOURCE_FOUND.log(logger, resourcePath, null);
+    }
+
+    return true;
+  }
+}
diff --git a/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java b/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java
index 2359e09..9d083a6 100644
--- a/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java
+++ b/dev/core/src/com/google/gwt/dev/shell/CompilingClassLoader.java
@@ -15,17 +15,19 @@
  */
 package com.google.gwt.dev.shell;
 
+import com.google.gwt.core.client.GWTBridge;
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.typeinfo.JClassType;
 import com.google.gwt.core.ext.typeinfo.JMethod;
 import com.google.gwt.core.ext.typeinfo.JParameter;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
-import com.google.gwt.dev.jdt.ByteCodeCompiler;
-import com.google.gwt.dev.jdt.CacheManager;
-import com.google.gwt.dev.shell.JsniMethods.JsniMethod;
+import com.google.gwt.dev.javac.CompilationState;
+import com.google.gwt.dev.javac.CompiledClass;
+import com.google.gwt.dev.javac.JsniMethod;
 import com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter;
 import com.google.gwt.dev.shell.rewrite.HostedModeClassRewriter.InstanceMethodOracle;
+import com.google.gwt.dev.util.Jsni;
 import com.google.gwt.dev.util.JsniRef;
 import com.google.gwt.util.tools.Utility;
 
@@ -45,7 +47,6 @@
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -342,12 +343,17 @@
    * space (thus, they bridge across the spaces).
    */
   private static final Class<?>[] BRIDGE_CLASSES = new Class<?>[] {
-      ShellJavaScriptHost.class, JsniMethods.class, JsniMethod.class};
+      ShellJavaScriptHost.class, GWTBridge.class};
 
   private static final boolean CLASS_DUMP = false;
 
   private static final String CLASS_DUMP_PATH = "rewritten-classes";
 
+  /**
+   * Caches the byte code for {@link JavaScriptHost}.
+   */
+  private static byte[] javaScriptHostBytes;
+
   static {
     for (Class<?> c : BRIDGE_CLASSES) {
       BRIDGE_CLASS_NAMES.put(c.getName(), c);
@@ -381,31 +387,65 @@
     }
   }
 
+  /**
+   * Magic: {@link JavaScriptHost} was never compiled because it's a part of the
+   * hosted mode infrastructure. However, unlike {@link #BRIDGE_CLASSES},
+   * {@code JavaScriptHost} needs a separate copy per inside the ClassLoader for
+   * each module.
+   */
+  private static void ensureJavaScriptHostBytes(TreeLogger logger)
+      throws UnableToCompleteException {
+
+    if (javaScriptHostBytes != null) {
+      return;
+    }
+
+    String className = JavaScriptHost.class.getName();
+    try {
+      String path = className.replace('.', '/') + ".class";
+      ClassLoader cl = Thread.currentThread().getContextClassLoader();
+      URL url = cl.getResource(path);
+      if (url != null) {
+        javaScriptHostBytes = getClassBytesFromStream(url.openStream());
+      } else {
+        logger.log(TreeLogger.ERROR,
+            "Could not find required bootstrap class '" + className
+                + "' in the classpath", null);
+        throw new UnableToCompleteException();
+      }
+    } catch (IOException e) {
+      logger.log(TreeLogger.ERROR,
+          "Error reading class bytes for " + className, e);
+      throw new UnableToCompleteException();
+    }
+  }
+
+  private static byte[] getClassBytesFromStream(InputStream is)
+      throws IOException {
+    try {
+      byte classBytes[] = new byte[is.available()];
+      int read = 0;
+      while (read < classBytes.length) {
+        read += is.read(classBytes, read, classBytes.length - read);
+      }
+      return classBytes;
+    } finally {
+      Utility.close(is);
+    }
+  }
+
   private final HostedModeClassRewriter classRewriter;
 
-  private final ByteCodeCompiler compiler;
+  private CompilationState compilationState;
 
   private final DispatchClassInfoOracle dispClassInfoOracle = new DispatchClassInfoOracle();
 
-  private Class<?> javaScriptHostClass;
+  private Class<?> gwtClass, javaScriptHostClass;
 
   private final TreeLogger logger;
 
-  /**
-   * Stores a list of classes needing JSNI injection. This list will be cleared
-   * when the {@link #stackDepth} is <code>0</code>.
-   */
-  private final List<Class<?>> pendingJsniInjectionClasses = new ArrayList<Class<?>>();
-
   private ShellJavaScriptHost shellJavaScriptHost;
 
-  /**
-   * Used to guard against {@link ClassCircularityError}. Attempting to read
-   * class annotations for the purpose of JSNI injection while defining a class
-   * can lead to circularities; we must wait until we're at the "top of stack".
-   */
-  private int stackDepth = 0;
-
   private final TypeOracle typeOracle;
 
   @SuppressWarnings("unchecked")
@@ -416,48 +456,19 @@
   private final Map<Integer, Object> weakJsoCache = new ReferenceMap(
       AbstractReferenceMap.HARD, AbstractReferenceMap.WEAK);
 
-  public CompilingClassLoader(TreeLogger logger, ByteCodeCompiler compiler,
-      TypeOracle typeOracle, ShellJavaScriptHost javaScriptHost)
+  public CompilingClassLoader(TreeLogger logger,
+      CompilationState compilationState, ShellJavaScriptHost javaScriptHost)
       throws UnableToCompleteException {
     super(null);
     this.logger = logger;
-    this.compiler = compiler;
-    this.typeOracle = typeOracle;
+    this.compilationState = compilationState;
     this.shellJavaScriptHost = javaScriptHost;
+    this.typeOracle = compilationState.getTypeOracle();
 
     // Assertions are always on in hosted mode.
     setDefaultAssertionStatus(true);
 
-    // SPECIAL MAGIC: Prevents the compile process from ever trying to compile
-    // these guys from source, which is what we want, since they are special and
-    // neither of them would compile correctly from source.
-    // 
-    // JavaScriptHost is special because its type cannot be known to the user.
-    // It is referenced only from generated code and GWT.create.
-    //
-    for (Class<?> clazz : CacheManager.BOOTSTRAP_CLASSES) {
-      String className = clazz.getName();
-      try {
-        String path = clazz.getName().replace('.', '/').concat(".class");
-        ClassLoader cl = Thread.currentThread().getContextClassLoader();
-        URL url = cl.getResource(path);
-        if (url != null) {
-          byte classBytes[] = getClassBytesFromStream(url.openStream());
-          String loc = url.toExternalForm();
-          compiler.putClassBytes(logger, className, classBytes, loc);
-        } else {
-          logger.log(TreeLogger.ERROR,
-              "Could not find required bootstrap class '" + className
-                  + "' in the classpath", null);
-          throw new UnableToCompleteException();
-        }
-      } catch (IOException e) {
-        logger.log(TreeLogger.ERROR, "Error reading class bytes for "
-            + className, e);
-        throw new UnableToCompleteException();
-      }
-    }
-    compiler.removeStaleByteCode(logger);
+    ensureJavaScriptHostBytes(logger);
 
     // Create a class rewriter based on all the subtypes of the JSO class.
     JClassType jsoType = typeOracle.findType(JsValueGlue.JSO_CLASS);
@@ -574,82 +585,22 @@
     }
 
     // Get the bytes, compiling if necessary.
-    byte[] classBytes;
-    try {
-      ++stackDepth;
-      if (classRewriter != null && classRewriter.isJsoIntf(className)) {
-        // Generate a synthetic JSO interface class.
-        classBytes = classRewriter.writeJsoIntf(className);
-      } else {
-        // A JSO impl class needs the class bytes for the original class.
-        String lookupClassName = className;
-        if (classRewriter != null && classRewriter.isJsoImpl(className)) {
-          lookupClassName = className.substring(0, className.length() - 1);
-        }
-        classBytes = compiler.getClassBytes(logger, lookupClassName);
-        if (classRewriter != null) {
-          byte[] newBytes = classRewriter.rewrite(className, classBytes);
-          if (CLASS_DUMP) {
-            if (!Arrays.equals(classBytes, newBytes)) {
-              classDump(className, classBytes);
-            }
-          }
-          classBytes = newBytes;
-        }
-      }
-      Class<?> newClass = defineClass(className, classBytes, 0,
-          classBytes.length);
-
-      if (className.equals(JavaScriptHost.class.getName())) {
-        javaScriptHostClass = newClass;
-        updateJavaScriptHost();
-      }
-
-      return newClass;
-    } catch (UnableToCompleteException e) {
+    byte[] classBytes = findClassBytes(className);
+    if (classBytes == null) {
       throw new ClassNotFoundException(className);
-    } finally {
-      --stackDepth;
-    }
-  }
-
-  /**
-   * Overridden to process JSNI annotations.
-   */
-  @Override
-  protected synchronized Class<?> loadClass(String name, boolean resolve)
-      throws ClassNotFoundException {
-    Class<?> newClass = super.loadClass(name, resolve);
-
-    // Only real, non-local classes can have JSNI method annotations.
-    if (!newClass.isInterface() && !newClass.isLocalClass()) {
-      pendingJsniInjectionClasses.add(newClass);
     }
 
-    if (stackDepth == 0 && !pendingJsniInjectionClasses.isEmpty()) {
-      // Save a copy because this can re-enter.
-      Class<?>[] toCheck = pendingJsniInjectionClasses.toArray(new Class<?>[pendingJsniInjectionClasses.size()]);
-      pendingJsniInjectionClasses.clear();
-      for (Class<?> checkClass : toCheck) {
-        JsniMethods jsniMethods = checkClass.getAnnotation(JsniMethods.class);
-        if (jsniMethods != null) {
-          for (JsniMethod jsniMethod : jsniMethods.value()) {
-            String[] bodyParts = jsniMethod.body();
-            int size = 0;
-            for (String bodyPart : bodyParts) {
-              size += bodyPart.length();
-            }
-            StringBuilder body = new StringBuilder(size);
-            for (String bodyPart : bodyParts) {
-              body.append(bodyPart);
-            }
-            shellJavaScriptHost.createNative(jsniMethod.file(),
-                jsniMethod.line(), jsniMethod.name(), jsniMethod.paramNames(),
-                body.toString());
-          }
-        }
-      }
+    Class<?> newClass = defineClass(className, classBytes, 0, classBytes.length);
+    if (className.equals(JavaScriptHost.class.getName())) {
+      javaScriptHostClass = newClass;
+      updateJavaScriptHost();
     }
+
+    if (className.equals("com.google.gwt.core.client.GWT")) {
+      gwtClass = newClass;
+      updateGwtClass();
+    }
+
     return newClass;
   }
 
@@ -662,22 +613,59 @@
     dispClassInfoOracle.clear();
   }
 
+  private byte[] findClassBytes(String className) {
+    if (JavaScriptHost.class.getName().equals(className)) {
+      // No need to rewrite.
+      return javaScriptHostBytes;
+    }
+
+    if (classRewriter != null && classRewriter.isJsoIntf(className)) {
+      // Generate a synthetic JSO interface class.
+      return classRewriter.writeJsoIntf(className);
+    }
+
+    // A JSO impl class needs the class bytes for the original class.
+    String lookupClassName = className.replace('.', '/');
+    if (classRewriter != null && classRewriter.isJsoImpl(className)) {
+      lookupClassName = lookupClassName.substring(0,
+          lookupClassName.length() - 1);
+    }
+
+    CompiledClass compiledClass = compilationState.getClassFileMap().get(
+        lookupClassName);
+    if (compiledClass != null) {
+      injectJsniFor(compiledClass);
+
+      byte[] classBytes = compiledClass.getBytes();
+      if (classRewriter != null) {
+        byte[] newBytes = classRewriter.rewrite(className, classBytes);
+        if (CLASS_DUMP) {
+          if (!Arrays.equals(classBytes, newBytes)) {
+            classDump(className, newBytes);
+          }
+        }
+        classBytes = newBytes;
+      }
+      return classBytes;
+    }
+    return null;
+  }
+
   private String getBinaryName(JClassType type) {
     String name = type.getPackage().getName() + '.';
     name += type.getName().replace('.', '$');
     return name;
   }
 
-  private byte[] getClassBytesFromStream(InputStream is) throws IOException {
-    try {
-      byte classBytes[] = new byte[is.available()];
-      int read = 0;
-      while (read < classBytes.length) {
-        read += is.read(classBytes, read, classBytes.length - read);
+  private void injectJsniFor(CompiledClass compiledClass) {
+    for (JsniMethod jsniMethod : compiledClass.getJsniMethods()) {
+      String body = Jsni.getJavaScriptForHostedMode(logger, jsniMethod);
+      if (body == null) {
+        // The error has been logged; just ignore it for now.
+        continue;
       }
-      return classBytes;
-    } finally {
-      Utility.close(is);
+      shellJavaScriptHost.createNative(jsniMethod.location(),
+          jsniMethod.line(), jsniMethod.name(), jsniMethod.paramNames(), body);
     }
   }
 
@@ -695,8 +683,47 @@
 
   /**
    * Tricky one, this. Reaches over into this modules's JavaScriptHost class and
-   * sets its static 'host' field to be the specified ModuleSpace instance
-   * (which will either be this ModuleSpace or null).
+   * sets its static 'host' field to our module space.
+   * 
+   * @param moduleSpace the ModuleSpace instance to store using
+   *          JavaScriptHost.setHost().
+   * @see JavaScriptHost
+   */
+  private void updateGwtClass() {
+    if (gwtClass == null) {
+      return;
+    }
+    Throwable caught;
+    try {
+      GWTBridgeImpl bridge;
+      if (shellJavaScriptHost == null) {
+        bridge = null;
+      } else {
+        bridge = new GWTBridgeImpl(shellJavaScriptHost);
+      }
+      final Class<?>[] paramTypes = new Class[] {GWTBridge.class};
+      Method setBridgeMethod = gwtClass.getDeclaredMethod("setBridge",
+          paramTypes);
+      setBridgeMethod.setAccessible(true);
+      setBridgeMethod.invoke(gwtClass, new Object[] {bridge});
+      return;
+    } catch (SecurityException e) {
+      caught = e;
+    } catch (NoSuchMethodException e) {
+      caught = e;
+    } catch (IllegalArgumentException e) {
+      caught = e;
+    } catch (IllegalAccessException e) {
+      caught = e;
+    } catch (InvocationTargetException e) {
+      caught = e.getTargetException();
+    }
+    throw new RuntimeException("Error initializing GWT bridge", caught);
+  }
+
+  /**
+   * Tricky one, this. Reaches over into this modules's JavaScriptHost class and
+   * sets its static 'host' field to our module space.
    * 
    * @param moduleSpace the ModuleSpace instance to store using
    *          JavaScriptHost.setHost().
diff --git a/dev/core/src/com/google/gwt/dev/shell/GWTBridgeImpl.java b/dev/core/src/com/google/gwt/dev/shell/GWTBridgeImpl.java
new file mode 100644
index 0000000..973145c
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/shell/GWTBridgeImpl.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2008 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.shell;
+
+import com.google.gwt.core.client.GWTBridge;
+import com.google.gwt.dev.About;
+
+/**
+ * This class is the hosted-mode peer for {@link com.google.gwt.core.client.GWT}.
+ */
+public class GWTBridgeImpl extends GWTBridge {
+
+  private final ShellJavaScriptHost host;
+
+  public GWTBridgeImpl(ShellJavaScriptHost host) {
+    this.host = host;
+  }
+
+  /**
+   * Resolves a deferred binding request and create the requested object.
+   */
+  public <T> T create(Class<?> requestedClass) {
+    String className = requestedClass.getName();
+    try {
+      return host.<T> rebindAndCreate(className);
+    } catch (Throwable e) {
+      String msg = "Deferred binding failed for '" + className
+          + "' (did you forget to inherit a required module?)";
+      throw new RuntimeException(msg, e);
+    }
+  };
+
+  public String getVersion() {
+    return About.GWT_VERSION_NUM;
+  }
+
+  /**
+   * Logs in dev shell.
+   */
+  public void log(String message, Throwable e) {
+    host.log(message, e);
+  }
+
+}
diff --git a/dev/core/src/com/google/gwt/dev/shell/GWTShellServlet.java b/dev/core/src/com/google/gwt/dev/shell/GWTShellServlet.java
index 90f3a5a..bdbcd1b 100644
--- a/dev/core/src/com/google/gwt/dev/shell/GWTShellServlet.java
+++ b/dev/core/src/com/google/gwt/dev/shell/GWTShellServlet.java
@@ -25,6 +25,7 @@
 import com.google.gwt.dev.cfg.ModuleDef;
 import com.google.gwt.dev.cfg.ModuleDefLoader;
 import com.google.gwt.dev.jjs.JJSOptions;
+import com.google.gwt.dev.resource.Resource;
 import com.google.gwt.dev.util.HttpHeaders;
 import com.google.gwt.dev.util.Util;
 import com.google.gwt.dev.util.log.ServletContextTreeLogger;
@@ -410,12 +411,15 @@
       return;
     }
 
-    URL foundResource;
+    URL foundResource = null;
     try {
       // Look for the requested file on the public path.
       //
       ModuleDef moduleDef = getModuleDef(logger, moduleName);
-      foundResource = moduleDef.findPublicFile(partialPath);
+      Resource publicResource = moduleDef.findPublicFile(partialPath);
+      if (publicResource != null) {
+        foundResource = publicResource.getURL();
+      }
 
       if (foundResource == null) {
         // Look for generated files
diff --git a/dev/core/src/com/google/gwt/dev/shell/HostedModeServletContextProxy.java b/dev/core/src/com/google/gwt/dev/shell/HostedModeServletContextProxy.java
index adf1ced..5270bf1 100644
--- a/dev/core/src/com/google/gwt/dev/shell/HostedModeServletContextProxy.java
+++ b/dev/core/src/com/google/gwt/dev/shell/HostedModeServletContextProxy.java
@@ -17,6 +17,7 @@
 
 import com.google.gwt.dev.GWTShell;
 import com.google.gwt.dev.cfg.ModuleDef;
+import com.google.gwt.dev.resource.Resource;
 
 import java.io.File;
 import java.io.IOException;
@@ -172,15 +173,17 @@
     String partialPath = path.substring(moduleContext.length());
 
     // Try to get the resource from the application's public path
-    URL url = moduleDef.findPublicFile(partialPath);
-    if (url == null) {
-      // Otherwise try the path but rooted in the shell's output directory
-      File shellDir = new File(outDir, GWTShell.GWT_SHELL_PATH + File.separator
-          + moduleDef.getName());
-      File requestedFile = new File(shellDir, partialPath);
-      if (requestedFile.exists()) {
-        url = requestedFile.toURI().toURL();
-      }
+    Resource publicResource = moduleDef.findPublicFile(partialPath);
+    if (publicResource != null) {
+      return publicResource.getURL();
+    }
+
+    // Otherwise try the path but rooted in the shell's output directory
+    File shellDir = new File(outDir, GWTShell.GWT_SHELL_PATH + File.separator
+        + moduleDef.getName());
+    File requestedFile = new File(shellDir, partialPath);
+    if (requestedFile.exists()) {
+      return requestedFile.toURI().toURL();
     }
 
     /*
@@ -188,19 +191,16 @@
      * directory for the file. We'll default to using the output directory of
      * the first linker defined in the <set-linker> tab.
      */
-    if (url == null) {
-      File requestedFile = new File(new File(outDir, moduleDef.getName()),
-          partialPath);
-      if (requestedFile.exists()) {
-        try {
-          url = requestedFile.toURI().toURL();
-        } catch (MalformedURLException e) {
-          // ignore since it was speculative anyway
-        }
+    requestedFile = new File(new File(outDir, moduleDef.getName()), partialPath);
+    if (requestedFile.exists()) {
+      try {
+        return requestedFile.toURI().toURL();
+      } catch (MalformedURLException e) {
+        // ignore since it was speculative anyway
       }
     }
 
-    return url;
+    return null;
   }
 
   /**
diff --git a/dev/core/src/com/google/gwt/dev/shell/HostedModeSourceOracle.java b/dev/core/src/com/google/gwt/dev/shell/HostedModeSourceOracle.java
deleted file mode 100644
index 64547c5..0000000
--- a/dev/core/src/com/google/gwt/dev/shell/HostedModeSourceOracle.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright 2007 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.shell;
-
-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.TypeOracle;
-import com.google.gwt.dev.jdt.StandardSourceOracle;
-import com.google.gwt.dev.jdt.StaticCompilationUnitProvider;
-import com.google.gwt.util.tools.Utility;
-
-import java.io.File;
-import java.io.IOException;
-
-/**
- * Does a little extra magic to handle hosted mode JSNI and
- * <code>GWT.create()</code>.
- */
-public class HostedModeSourceOracle extends StandardSourceOracle {
-
-  private final JsniInjector injector;
-  private final File jsniSaveDirectory;
-
-  public HostedModeSourceOracle(TypeOracle typeOracle, File jsniSaveDirectory) {
-    super(typeOracle);
-    this.injector = new JsniInjector(typeOracle);
-    this.jsniSaveDirectory = jsniSaveDirectory;
-  }
-
-  @Override
-  protected CompilationUnitProvider doFilterCompilationUnit(TreeLogger logger,
-      String typeName, CompilationUnitProvider existing)
-      throws UnableToCompleteException {
-
-    /*
-     * MAGIC: The implementation of GWT can be very different between hosted
-     * mode and web mode. The compiler has special knowledge of GWT for web
-     * mode. The source for hosted mode is in GWT.java-hosted.
-     */
-    if (typeName.equals("com.google.gwt.core.client.GWT")) {
-      try {
-        String source = Utility.getFileFromClassPath("com/google/gwt/core/client/GWT.java-hosted");
-        return new StaticCompilationUnitProvider("com.google.gwt.core.client",
-            "GWT", source.toCharArray());
-      } catch (IOException e) {
-        logger.log(
-            TreeLogger.ERROR,
-            "Unable to load 'com/google/gwt/core/client/GWT.java-hosted' from class path; is your installation corrupt?",
-            e);
-        throw new UnableToCompleteException();
-      }
-    }
-
-    // Otherwise, it's a regular translatable type, but we want to make sure
-    // its JSNI stuff, if any, gets handled.
-    //
-    CompilationUnitProvider jsnified = injector.inject(logger, existing,
-        jsniSaveDirectory);
-    return jsnified;
-  }
-}
diff --git a/dev/core/src/com/google/gwt/dev/shell/JavaScriptHost.java b/dev/core/src/com/google/gwt/dev/shell/JavaScriptHost.java
index b0cd9f8..17a1b5c 100644
--- a/dev/core/src/com/google/gwt/dev/shell/JavaScriptHost.java
+++ b/dev/core/src/com/google/gwt/dev/shell/JavaScriptHost.java
@@ -108,27 +108,6 @@
   }
 
   /**
-   * Logs in dev shell.
-   */
-  public static void log(String message, Throwable e) {
-    sHost.log(message, e);
-  }
-
-  /**
-   * Resolves a deferred binding request and create the requested object.
-   */
-  public static <T> T rebindAndCreate(Class<?> requestedClass) {
-    String className = requestedClass.getName();
-    try {
-      return sHost.<T> rebindAndCreate(className);
-    } catch (Throwable e) {
-      String msg = "Deferred binding failed for '" + className
-          + "' (did you forget to inherit a required module?)";
-      throw new RuntimeException(msg, e);
-    }
-  }
-
-  /**
    * This method is called via reflection from the {@link CompilingClassLoader},
    * providing the hosted mode application with all of the methods it needs to
    * interface with the browser and the server (for deferred binding).
diff --git a/dev/core/src/com/google/gwt/dev/shell/JsniInjector.java b/dev/core/src/com/google/gwt/dev/shell/JsniInjector.java
deleted file mode 100644
index 34d2917..0000000
--- a/dev/core/src/com/google/gwt/dev/shell/JsniInjector.java
+++ /dev/null
@@ -1,304 +0,0 @@
-/*
- * Copyright 2008 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.shell;
-
-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.JClassType;
-import com.google.gwt.core.ext.typeinfo.JMethod;
-import com.google.gwt.core.ext.typeinfo.JParameter;
-import com.google.gwt.core.ext.typeinfo.TypeOracle;
-import com.google.gwt.dev.jdt.CompilationUnitProviderWithAlternateSource;
-import com.google.gwt.dev.js.ast.JsBlock;
-import com.google.gwt.dev.shell.JsniMethods.JsniMethod;
-import com.google.gwt.dev.util.Jsni;
-import com.google.gwt.dev.util.StringCopier;
-import com.google.gwt.dev.util.Util;
-
-import java.io.File;
-import java.lang.annotation.Annotation;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.IdentityHashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Adapts compilation units containing JSNI-accessible code by rewriting the
- * source.
- */
-public class JsniInjector {
-
-  /**
-   * A chunk of replacement text and where to put it.
-   */
-  private static class Replacement implements Comparable<Replacement> {
-    public final int end;
-
-    public final int start;
-
-    public final char[] text;
-
-    public Replacement(int start, int end, char[] text) {
-      this.start = start;
-      this.end = end;
-      this.text = text;
-    }
-
-    public int compareTo(Replacement other) {
-      if (start < other.start) {
-        assert (end <= other.start) : "Overlapping changes not supported";
-        return -1;
-      } else if (start > other.start) {
-        assert (start >= other.end) : "Overlapping changes not supported";
-        return 1;
-      } else {
-        return 0;
-      }
-    }
-  }
-
-  private static final int BLOCK_SIZE = 1024;
-
-  private static final String JSNIMETHOD_NAME = JsniMethod.class.getName().replace(
-      '$', '.');
-
-  private static final String JSNIMETHODS_NAME = JsniMethods.class.getName();
-
-  private final Map<JClassType, List<JsniMethod>> jsniMethodMap = new IdentityHashMap<JClassType, List<JsniMethod>>();
-  private final TypeOracle oracle;
-
-  public JsniInjector(TypeOracle oracle) {
-    this.oracle = oracle;
-  }
-
-  public CompilationUnitProvider inject(TreeLogger logger,
-      CompilationUnitProvider cup, File jsniSaveDirectory)
-      throws UnableToCompleteException {
-
-    logger = logger.branch(TreeLogger.SPAM,
-        "Checking for JavaScript native methods", null);
-
-    // Analyze the source and build a list of changes.
-    char[] source = cup.getSource();
-    List<Replacement> changes = new ArrayList<Replacement>();
-    rewriteCompilationUnit(logger, source, changes, cup, false);
-
-    // Sort and apply the changes.
-    int n = changes.size();
-    if (n > 0) {
-      Replacement[] repls = changes.toArray(new Replacement[n]);
-      Arrays.sort(repls);
-      StringCopier copier = new StringCopier(source);
-      for (int i = 0; i < n; ++i) {
-        Replacement repl = repls[i];
-        copier.commit(repl.text, repl.start, repl.end);
-      }
-
-      char[] results = copier.finish();
-
-      if (jsniSaveDirectory != null) {
-        String originalPath = cup.getLocation().replace(File.separatorChar, '/');
-        String suffix = cup.getPackageName().replace('.', '/');
-        int pos = originalPath.indexOf(suffix);
-        if (pos >= 0) {
-          String filePath = originalPath.substring(pos);
-          File out = new File(jsniSaveDirectory, filePath);
-          Util.writeCharsAsFile(logger, out, results);
-        }
-      }
-
-      return new CompilationUnitProviderWithAlternateSource(cup, results);
-    } else {
-      // No changes were made, so we return the original.
-      logger.log(TreeLogger.SPAM, "No JavaScript native methods were found",
-          null);
-      return cup;
-    }
-  }
-
-  private void collectJsniMethods(TreeLogger logger, char[] source,
-      JClassType type) throws UnableToCompleteException {
-
-    // Locate the nearest non-local type; don't try to annotate local types.
-    JClassType targetType = type;
-    while (targetType.isLocalType()) {
-      targetType = targetType.getEnclosingType();
-    }
-    List<JsniMethod> jsniMethods = jsniMethodMap.get(targetType);
-    String loc = type.getCompilationUnit().getLocation();
-
-    for (JMethod method : type.getMethods()) {
-      if (!method.isNative()) {
-        continue;
-      }
-      Jsni.Interval interval = Jsni.findJsniSource(method);
-      if (interval == null) {
-        String msg = "No JavaScript body found for native method '" + method
-            + "' in type '" + type + "'";
-        logger.log(TreeLogger.ERROR, msg, null);
-        throw new UnableToCompleteException();
-      }
-      // Parse it.
-      String js = String.valueOf(source, interval.start, interval.end
-          - interval.start);
-      int startLine = Jsni.countNewlines(source, 0, interval.start) + 1;
-      JsBlock body = Jsni.parseAsFunctionBody(logger, js, loc, startLine);
-
-      // Add JsniMethod annotations to the target type.
-      if (jsniMethods == null) {
-        jsniMethods = new ArrayList<JsniMethod>();
-        jsniMethodMap.put(targetType, jsniMethods);
-      }
-      jsniMethods.add(createJsniMethod(method, body, loc, source));
-    }
-  }
-
-  private JsniMethod createJsniMethod(JMethod method, JsBlock jsniBody,
-      final String file, char[] source) {
-
-    final int line = Jsni.countNewlines(source, 0, method.getBodyStart()) + 1;
-
-    final String name = Jsni.getJsniSignature(method);
-
-    JParameter[] params = method.getParameters();
-    final String[] paramNames = new String[params.length];
-    for (int i = 0; i < params.length; ++i) {
-      paramNames[i] = params[i].getName();
-    }
-
-    /*
-     * Surround the original JS body statements with a try/catch so that we can
-     * map JavaScript exceptions back into Java. Note that the method body
-     * itself will print curly braces, so we don't need them around the
-     * try/catch.
-     */
-    String jsTry = "try ";
-    String jsCatch = " catch (e) {\n  __static[\"@" + Jsni.JAVASCRIPTHOST_NAME
-        + "::exceptionCaught(Ljava/lang/Object;)\"](e == null ? null : e);\n"
-        + "}\n";
-    String body = jsTry + Jsni.generateJavaScriptForHostedMode(jsniBody)
-        + jsCatch;
-
-    /*
-     * Break up the body into 1k strings; this ensures we don't blow up any
-     * class file limits.
-     */
-    int length = body.length();
-    final String[] bodyParts = new String[(length + BLOCK_SIZE - 1)
-        / BLOCK_SIZE];
-    for (int i = 0; i < bodyParts.length; ++i) {
-      int startIndex = i * BLOCK_SIZE;
-      int endIndex = Math.min(startIndex + BLOCK_SIZE, length);
-      bodyParts[i] = body.substring(startIndex, endIndex);
-    }
-
-    return new JsniMethod() {
-
-      public Class<? extends Annotation> annotationType() {
-        return JsniMethod.class;
-      }
-
-      public String[] body() {
-        return bodyParts;
-      }
-
-      public String file() {
-        return file;
-      }
-
-      public int line() {
-        return line;
-      }
-
-      public String name() {
-        return name;
-      }
-
-      public String[] paramNames() {
-        return paramNames;
-      }
-
-      @Override
-      public String toString() {
-        StringBuffer sb = new StringBuffer();
-        sb.append("@" + JSNIMETHOD_NAME + "(file=\"");
-        sb.append(Jsni.escapedJavaScriptForStringLiteral(file));
-        sb.append("\",line=");
-        sb.append(line);
-        sb.append(",name=\"@");
-        sb.append(name);
-        sb.append("\",paramNames={");
-        for (String paramName : paramNames) {
-          sb.append('\"');
-          sb.append(paramName);
-          sb.append('\"');
-          sb.append(',');
-        }
-        sb.append("},body={");
-        for (String bodyPart : bodyParts) {
-          sb.append('"');
-          sb.append(Jsni.escapedJavaScriptForStringLiteral(bodyPart));
-          sb.append('"');
-          sb.append(',');
-        }
-        sb.append("})");
-        return sb.toString();
-      }
-    };
-  }
-
-  /**
-   * Generate annotation metadata for all the JSNI methods in a list.
-   */
-  private char[] genJsniMethodsAnnotation(List<JsniMethod> jsniMethods,
-      boolean pretty) {
-    StringBuffer sb = new StringBuffer();
-    String nl = pretty ? "\n " : "";
-    sb.append("@" + JSNIMETHODS_NAME + "({");
-    for (JsniMethod jsniMethod : jsniMethods) {
-      sb.append(jsniMethod.toString());
-      sb.append(',');
-      sb.append(nl);
-    }
-    sb.append("})");
-    return sb.toString().toCharArray();
-  }
-
-  private void rewriteCompilationUnit(TreeLogger logger, char[] source,
-      List<Replacement> changes, CompilationUnitProvider cup, boolean pretty)
-      throws UnableToCompleteException {
-
-    // Collect all JSNI methods in the compilation unit.
-    JClassType[] types = oracle.getTypesInCompilationUnit(cup);
-    for (JClassType type : types) {
-      if (!type.getQualifiedSourceName().startsWith("java.")) {
-        collectJsniMethods(logger, source, type);
-      }
-    }
-
-    // Annotate the appropriate types with JsniMethod annotations.
-    for (JClassType type : types) {
-      List<JsniMethod> jsniMethods = jsniMethodMap.get(type);
-      if (jsniMethods != null && jsniMethods.size() > 0) {
-        char[] annotation = genJsniMethodsAnnotation(jsniMethods, pretty);
-        int declStart = type.getDeclStart();
-        changes.add(new Replacement(declStart, declStart, annotation));
-      }
-    }
-  }
-}
diff --git a/dev/core/src/com/google/gwt/dev/shell/JsniMethods.java b/dev/core/src/com/google/gwt/dev/shell/JsniMethods.java
deleted file mode 100644
index 2631851..0000000
--- a/dev/core/src/com/google/gwt/dev/shell/JsniMethods.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright 2008 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.shell;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Encodes all JSNI methods into a compiled hosted mode class file.
- */
-@Retention(RetentionPolicy.RUNTIME)
-@Target(ElementType.TYPE)
-public @interface JsniMethods {
-
-  /**
-   * Encodes a JSNI method into a compiled hosted mode class file.
-   */
-  @Target(value = {})
-  public @interface JsniMethod {
-    /**
-     * Source file of the method.
-     */
-    String file();
-
-    /**
-     * Starting line number of the method.
-     */
-    int line();
-
-    /**
-     * The mangled method name (a jsni signature).
-     */
-    String name();
-
-    /**
-     * The parameter names.
-     */
-    String[] paramNames();
-
-    /**
-     * The script body. The reason this is an array rather than a single string
-     * is that 64k is the max size of a single string in a class file, and some
-     * methods (such as TypeSerializer method maps) will exceed this.
-     */
-    String[] body();
-  }
-
-  /**
-   * The set of all methods.
-   */
-  JsniMethod[] value();
-}
diff --git a/dev/core/src/com/google/gwt/dev/shell/ShellGWT.java b/dev/core/src/com/google/gwt/dev/shell/ShellGWT.java
deleted file mode 100644
index a38cea1..0000000
--- a/dev/core/src/com/google/gwt/dev/shell/ShellGWT.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright 2007 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.shell;
-
-import com.google.gwt.dev.About;
-
-/**
- * This class is the hosted-mode peer for {@link com.google.gwt.core.client.GWT}.
- */
-public class ShellGWT {
-
-  public static <T> T create(Class<?> classLiteral) {
-    return JavaScriptHost.<T>rebindAndCreate(classLiteral);
-  }
-
-  public static String getTypeName(Object o) {
-    return o != null ? o.getClass().getName() : null;
-  }
-
-  public static String getVersion() {
-    return About.GWT_VERSION_NUM;
-  };
-
-  public static void log(String message, Throwable e) {
-    JavaScriptHost.log(message, e);
-  }
-}
diff --git a/dev/core/src/com/google/gwt/dev/shell/ShellModuleSpaceHost.java b/dev/core/src/com/google/gwt/dev/shell/ShellModuleSpaceHost.java
index 2175316..39cb165 100644
--- a/dev/core/src/com/google/gwt/dev/shell/ShellModuleSpaceHost.java
+++ b/dev/core/src/com/google/gwt/dev/shell/ShellModuleSpaceHost.java
@@ -17,18 +17,13 @@
 
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.linker.ArtifactSet;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
 import com.google.gwt.dev.cfg.ModuleDef;
 import com.google.gwt.dev.cfg.Rules;
-import com.google.gwt.dev.jdt.ByteCodeCompiler;
 import com.google.gwt.dev.jdt.RebindOracle;
-import com.google.gwt.dev.jdt.SourceOracle;
-
-import org.apache.commons.collections.map.AbstractReferenceMap;
-import org.apache.commons.collections.map.ReferenceIdentityMap;
 
 import java.io.File;
-import java.util.Map;
 
 /**
  * Provides an environment for a {@link com.google.gwt.dev.shell.ModuleSpace}
@@ -36,10 +31,6 @@
  */
 public class ShellModuleSpaceHost implements ModuleSpaceHost {
 
-  @SuppressWarnings("unchecked")
-  private static Map<ModuleDef, ByteCodeCompiler> byteCodeCompilersByModule = new ReferenceIdentityMap(
-      AbstractReferenceMap.WEAK, AbstractReferenceMap.HARD, true);
-
   protected final File genDir;
 
   protected final TypeOracle typeOracle;
@@ -52,8 +43,6 @@
 
   private RebindOracle rebindOracle;
 
-  private final boolean saveJsni;
-
   private final File shellDir;
 
   private ModuleSpace space;
@@ -63,12 +52,11 @@
    * @param saveJsni
    */
   public ShellModuleSpaceHost(TreeLogger logger, TypeOracle typeOracle,
-      ModuleDef module, File genDir, File shellDir, boolean saveJsni) {
+      ModuleDef module, File genDir, File shellDir) {
     this.logger = logger;
     this.typeOracle = typeOracle;
     this.module = module;
     this.genDir = genDir;
-    this.saveJsni = saveJsni;
 
     // Combine the user's output dir with the module name to get the
     // module-specific output dir.
@@ -93,16 +81,6 @@
       throws UnableToCompleteException {
     this.space = readySpace;
 
-    // Create a host for the hosted mode compiler.
-    // We add compilation units to it as deferred binding generators write them.
-    //
-    SourceOracle srcOracle = new HostedModeSourceOracle(typeOracle, saveJsni
-        ? genDir : null);
-
-    // Create or find the compiler to be used by the compiling class loader.
-    //
-    ByteCodeCompiler compiler = getOrCreateByteCodeCompiler(srcOracle);
-
     // Establish an environment for JavaScript property providers to run.
     //
     ModuleSpacePropertyOracle propOracle = new ModuleSpacePropertyOracle(
@@ -112,8 +90,8 @@
     // It has to wait until now because we need to inject javascript.
     //
     Rules rules = module.getRules();
-    rebindOracle = new StandardRebindOracle(typeOracle, propOracle, module,
-        rules, genDir, shellDir, module.getCacheManager(), null);
+    rebindOracle = new StandardRebindOracle(module.getCompilationState(),
+        propOracle, module, rules, genDir, shellDir, new ArtifactSet());
 
     // Create a completely isolated class loader which owns all classes
     // associated with a particular module. This effectively builds a
@@ -126,8 +104,8 @@
     // accidentally 'escaping' its domain and loading classes from the system
     // class loader (the one that loaded the shell itself).
     //
-    classLoader = new CompilingClassLoader(logger, compiler, typeOracle,
-        readySpace);
+    classLoader = new CompilingClassLoader(logger,
+        module.getCompilationState(), readySpace);
   }
 
   public String rebind(TreeLogger rebindLogger, String sourceTypeName)
@@ -136,18 +114,6 @@
     return rebindOracle.rebind(rebindLogger, sourceTypeName);
   }
 
-  ByteCodeCompiler getOrCreateByteCodeCompiler(SourceOracle srcOracle) {
-    ByteCodeCompiler compiler;
-    synchronized (byteCodeCompilersByModule) {
-      compiler = byteCodeCompilersByModule.get(module);
-      if (compiler == null) {
-        compiler = new ByteCodeCompiler(srcOracle, module.getCacheManager());
-        byteCodeCompilersByModule.put(module, compiler);
-      }
-    }
-    return compiler;
-  }
-
   private void checkForModuleSpace() {
     if (space == null) {
       throw new IllegalStateException("Module initialization error");
diff --git a/dev/core/src/com/google/gwt/dev/shell/StandardGeneratorContext.java b/dev/core/src/com/google/gwt/dev/shell/StandardGeneratorContext.java
index f8fb4cd..1a3dfcc 100644
--- a/dev/core/src/com/google/gwt/dev/shell/StandardGeneratorContext.java
+++ b/dev/core/src/com/google/gwt/dev/shell/StandardGeneratorContext.java
@@ -24,24 +24,21 @@
 import com.google.gwt.core.ext.linker.ArtifactSet;
 import com.google.gwt.core.ext.linker.GeneratedResource;
 import com.google.gwt.core.ext.linker.impl.StandardGeneratedResource;
-import com.google.gwt.core.ext.typeinfo.CompilationUnitProvider;
 import com.google.gwt.core.ext.typeinfo.JClassType;
 import com.google.gwt.core.ext.typeinfo.NotFoundException;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
 import com.google.gwt.dev.cfg.PublicOracle;
-import com.google.gwt.dev.jdt.CacheManager;
-import com.google.gwt.dev.jdt.StaticCompilationUnitProvider;
-import com.google.gwt.dev.jdt.TypeOracleBuilder;
-import com.google.gwt.dev.jdt.URLCompilationUnitProvider;
+import com.google.gwt.dev.javac.CompilationState;
+import com.google.gwt.dev.javac.CompilationUnit;
+import com.google.gwt.dev.javac.impl.Shared;
 import com.google.gwt.dev.util.Util;
 
 import java.io.ByteArrayOutputStream;
-import java.io.CharArrayWriter;
 import java.io.File;
 import java.io.OutputStream;
 import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.net.MalformedURLException;
-import java.net.URL;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
@@ -49,80 +46,91 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.SortedSet;
 
 /**
- * An abstract implementation of a generator context in terms of a
- * {@link com.google.gwt.dev.jdt.MutableCompilationServiceHost}, a
- * {@link com.google.gwt.dev.jdt.PropertyOracle}, and a
- * {@link com.google.gwt.core.server.typeinfo.TypeOracle}. The generator
- * interacts with the mutable source oracle by increasing the available
- * compilation units as they are generated.
+ * Manages generators and generated units during a single compilation.
  */
 public class StandardGeneratorContext implements GeneratorContext {
 
   /**
-   * This compilation unit provider acts as a normal compilation unit provider
-   * as well as a buffer into which generators can write their source. A
-   * controller should ensure that source isn't requested until the generator
-   * has finished writing it.
+   * This compilation unit acts as a normal compilation unit as well as a buffer
+   * into which generators can write their source. A controller should ensure
+   * that source isn't requested until the generator has finished writing it.
    */
-  private static class GeneratedCompilationUnitProvider extends
-      StaticCompilationUnitProvider {
+  private static class GeneratedUnitWithFile extends CompilationUnit {
 
-    public CharArrayWriter caw;
+    private File file;
 
-    public PrintWriter pw;
+    private PrintWriter pw;
 
-    public char[] source;
+    private String source;
 
-    public GeneratedCompilationUnitProvider(String packageName,
-        String simpleTypeName) {
-      super(packageName, simpleTypeName, null);
-      caw = new CharArrayWriter();
-      pw = new PrintWriter(caw, true);
+    private StringWriter sw;
+
+    private final String typeName;
+
+    public GeneratedUnitWithFile(String typeName) {
+      this.typeName = typeName;
+      sw = new StringWriter();
+      pw = new PrintWriter(sw, true);
     }
 
     /**
      * Finalizes the source and adds this compilation unit to the host.
      */
     public void commit() {
-      source = caw.toCharArray();
-      pw.close();
+      source = sw.toString();
       pw = null;
-      caw.close();
-      caw = null;
+      sw = null;
     }
 
     @Override
-    public char[] getSource() {
-      if (source == null) {
+    public String getDisplayLocation() {
+      if (file == null) {
+        return "transient source for " + typeName;
+      } else {
+        return file.getAbsoluteFile().toURI().toString();
+      }
+    }
+
+    @Override
+    public String getSource() {
+      if (source == null && file == null) {
         throw new IllegalStateException("source not committed");
       }
+      if (source == null) {
+        source = Util.readFileAsString(file);
+      }
+      assert (source != null);
       return source;
     }
-  }
 
-  /**
-   * {@link CompilationUnitProvider} used to represent generated source code
-   * which is stored on disk. This class is only used if the -gen flag is
-   * specified.
-   */
-  private static final class GeneratedCUP extends URLCompilationUnitProvider {
-    private GeneratedCUP(URL url, String name) {
-      super(url, name);
+    @Override
+    public String getTypeName() {
+      return typeName;
     }
 
     @Override
-    public long getLastModified() throws UnableToCompleteException {
-      // Make it seem really old so it won't cause recompiles.
-      //
-      return 0L;
-    }
-
-    @Override
-    public boolean isTransient() {
+    public boolean isGenerated() {
       return true;
     }
+
+    public boolean isOnDisk() {
+      return file != null;
+    }
+
+    public void setFile(File file) {
+      assert (file.exists() && file.canRead());
+      this.file = file;
+    }
+
+    @Override
+    protected void dumpSource() {
+      if (file != null) {
+        source = null;
+      }
+    }
   }
 
   /**
@@ -161,9 +169,9 @@
 
   private final ArtifactSet artifactSet;
 
-  private final CacheManager cacheManager;
+  private final Set<GeneratedUnitWithFile> committedGeneratedCups = new HashSet<GeneratedUnitWithFile>();
 
-  private final Set<GeneratedCompilationUnitProvider> committedGeneratedCups = new HashSet<GeneratedCompilationUnitProvider>();
+  private final CompilationState compilationState;
 
   private Class<? extends Generator> currentGenerator;
 
@@ -179,23 +187,20 @@
 
   private final PublicOracle publicOracle;
 
-  private final TypeOracle typeOracle;
-
-  private final Map<PrintWriter, GeneratedCompilationUnitProvider> uncommittedGeneratedCupsByPrintWriter = new IdentityHashMap<PrintWriter, GeneratedCompilationUnitProvider>();
+  private final Map<PrintWriter, GeneratedUnitWithFile> uncommittedGeneratedCupsByPrintWriter = new IdentityHashMap<PrintWriter, GeneratedUnitWithFile>();
 
   /**
    * Normally, the compiler host would be aware of the same types that are
    * available in the supplied type oracle although it isn't strictly required.
    */
-  public StandardGeneratorContext(TypeOracle typeOracle,
+  public StandardGeneratorContext(CompilationState compilationState,
       PropertyOracle propOracle, PublicOracle publicOracle, File genDir,
-      File outDir, CacheManager cacheManager, ArtifactSet artifactSet) {
-    this.typeOracle = typeOracle;
+      File outDir, ArtifactSet artifactSet) {
+    this.compilationState = compilationState;
     this.propOracle = propOracle;
     this.publicOracle = publicOracle;
     this.genDir = genDir;
     this.outDir = outDir;
-    this.cacheManager = cacheManager;
     this.artifactSet = artifactSet;
   }
 
@@ -203,7 +208,7 @@
    * Commits a pending generated type.
    */
   public final void commit(TreeLogger logger, PrintWriter pw) {
-    GeneratedCompilationUnitProvider gcup = uncommittedGeneratedCupsByPrintWriter.get(pw);
+    GeneratedUnitWithFile gcup = uncommittedGeneratedCupsByPrintWriter.get(pw);
     if (gcup != null) {
       gcup.commit();
       uncommittedGeneratedCupsByPrintWriter.remove(pw);
@@ -221,9 +226,7 @@
   public void commitArtifact(TreeLogger logger, Artifact<?> artifact)
       throws UnableToCompleteException {
     // The artifactSet will be null in hosted mode, since we never run Linkers
-    if (artifactSet != null) {
-      artifactSet.replace(artifact);
-    }
+    artifactSet.replace(artifact);
   }
 
   public GeneratedResource commitResource(TreeLogger logger, OutputStream os)
@@ -234,7 +237,6 @@
     if (pendingResource != null) {
       // Actually write the bytes to disk.
       pendingResource.commit(logger);
-      cacheManager.addGeneratedResource(pendingResource.getPartialPath());
 
       // Add the GeneratedResource to the ArtifactSet
       GeneratedResource toReturn;
@@ -293,26 +295,22 @@
               "Generated source files...", null);
         }
 
-        assert (cacheManager.getTypeOracle() == typeOracle);
-        TypeOracleBuilder builder = new TypeOracleBuilder(cacheManager);
-        for (Iterator<GeneratedCompilationUnitProvider> iter = committedGeneratedCups.iterator(); iter.hasNext();) {
-          GeneratedCompilationUnitProvider gcup = iter.next();
-          String typeName = gcup.getTypeName();
-          String genTypeName = gcup.getPackageName() + "." + typeName;
-          genTypeNames.add(genTypeName);
-          CompilationUnitProvider cup = writeSource(logger, gcup, typeName);
-          builder.addCompilationUnit(cup);
-          cacheManager.addGeneratedCup(cup);
+        for (GeneratedUnitWithFile gcup : committedGeneratedCups) {
+          String qualifiedTypeName = gcup.getTypeName();
+          genTypeNames.add(qualifiedTypeName);
+          maybeWriteSource(gcup, qualifiedTypeName);
+          compilationState.addGeneratedCompilationUnit(gcup);
 
           if (subBranch != null) {
-            subBranch.log(TreeLogger.DEBUG, cup.getLocation(), null);
+            subBranch.log(TreeLogger.DEBUG, gcup.getDisplayLocation(), null);
           }
         }
 
-        builder.build(branch);
+        compilationState.compile(logger);
       }
 
       // Return the generated types.
+      TypeOracle typeOracle = getTypeOracle();
       JClassType[] genTypes = new JClassType[genTypeNames.size()];
       int next = 0;
       for (Iterator<String> iter = genTypeNames.iterator(); iter.hasNext();) {
@@ -333,10 +331,8 @@
         String msg = "For the following type(s), generated source was never committed (did you forget to call commit()?)";
         logger = logger.branch(TreeLogger.WARN, msg, null);
 
-        for (Iterator<GeneratedCompilationUnitProvider> iter = uncommittedGeneratedCupsByPrintWriter.values().iterator(); iter.hasNext();) {
-          StaticCompilationUnitProvider cup = iter.next();
-          String typeName = cup.getPackageName() + "." + cup.getTypeName();
-          logger.log(TreeLogger.WARN, typeName, null);
+        for (GeneratedUnitWithFile unit : uncommittedGeneratedCupsByPrintWriter.values()) {
+          logger.log(TreeLogger.WARN, unit.getTypeName(), null);
         }
       }
 
@@ -355,7 +351,7 @@
   }
 
   public final TypeOracle getTypeOracle() {
-    return typeOracle;
+    return compilationState.getTypeOracle();
   }
 
   public void setCurrentGenerator(Class<? extends Generator> currentGenerator) {
@@ -367,7 +363,8 @@
     String typeName = packageName + "." + simpleTypeName;
 
     // Is type already known to the host?
-    JClassType existingType = typeOracle.findType(packageName, simpleTypeName);
+    JClassType existingType = getTypeOracle().findType(packageName,
+        simpleTypeName);
     if (existingType != null) {
       logger.log(TreeLogger.DEBUG, "Type '" + typeName
           + "' already exists and will not be re-created ", null);
@@ -385,8 +382,13 @@
 
     // The type isn't there, so we can let the caller create it. Remember that
     // it is pending so another attempt to create the same type will fail.
-    GeneratedCompilationUnitProvider gcup = new GeneratedCompilationUnitProvider(
-        packageName, simpleTypeName);
+    String qualifiedSourceName;
+    if (packageName.length() == 0) {
+      qualifiedSourceName = simpleTypeName;
+    } else {
+      qualifiedSourceName = packageName + '.' + simpleTypeName;
+    }
+    GeneratedUnitWithFile gcup = new GeneratedUnitWithFile(qualifiedSourceName);
     uncommittedGeneratedCupsByPrintWriter.put(gcup.pw, gcup);
     generatedTypeNames.add(typeName);
 
@@ -433,8 +435,11 @@
     }
 
     // See if the file is already committed.
-    if (cacheManager.hasGeneratedResource(partialPath)) {
-      return null;
+    SortedSet<GeneratedResource> resources = artifactSet.find(GeneratedResource.class);
+    for (GeneratedResource resource : resources) {
+      if (partialPath.equals(resource.getPartialPath())) {
+        return null;
+      }
     }
 
     // See if the file is pending.
@@ -483,43 +488,25 @@
    * Writes the source of the specified compilation unit to disk if a gen
    * directory is specified.
    * 
-   * @param cup the compilation unit whose contents might need to be written
-   * @param simpleTypeName the fully-qualified type name
-   * @return a wrapper for the existing cup with a proper location
+   * @param unit the compilation unit whose contents might need to be written
+   * @param qualifiedTypeName the fully-qualified type name
    */
-  private CompilationUnitProvider writeSource(TreeLogger logger,
-      CompilationUnitProvider cup, String simpleTypeName)
-      throws UnableToCompleteException {
+  private void maybeWriteSource(GeneratedUnitWithFile unit,
+      String qualifiedTypeName) {
 
-    if (genDir == null) {
+    if (unit.isOnDisk() || genDir == null) {
       // No place to write it.
-      return cup;
-    }
-
-    if (Util.isCompilationUnitOnDisk(cup.getLocation())) {
-      // Already on disk.
-      return cup;
+      return;
     }
 
     // Let's do write it.
-    String typeName = cup.getPackageName() + "." + simpleTypeName;
-    String relativePath = typeName.replace('.', '/') + ".java";
-    File srcFile = new File(genDir, relativePath);
-    Util.writeCharsAsFile(logger, srcFile, cup.getSource());
-
-    // Update the location of the cup
-    Throwable caught = null;
-    try {
-      URL fileURL = srcFile.toURI().toURL();
-      URLCompilationUnitProvider fileBaseCup = new GeneratedCUP(fileURL,
-          cup.getPackageName());
-      return fileBaseCup;
-    } catch (MalformedURLException e) {
-      caught = e;
+    String packageName = Shared.getPackageName(qualifiedTypeName);
+    String shortName = Shared.getShortName(qualifiedTypeName);
+    File dir = new File(genDir, packageName.replace('.', File.separatorChar));
+    dir.mkdirs();
+    File srcFile = new File(dir, shortName + ".java");
+    if (Util.writeStringAsFile(srcFile, unit.getSource())) {
+      unit.setFile(srcFile);
     }
-    logger.log(TreeLogger.ERROR,
-        "Internal error: cannot build URL from synthesized file name '"
-            + srcFile.getAbsolutePath() + "'", caught);
-    throw new UnableToCompleteException();
   }
 }
diff --git a/dev/core/src/com/google/gwt/dev/shell/StandardRebindOracle.java b/dev/core/src/com/google/gwt/dev/shell/StandardRebindOracle.java
index 554a351..c338a91 100644
--- a/dev/core/src/com/google/gwt/dev/shell/StandardRebindOracle.java
+++ b/dev/core/src/com/google/gwt/dev/shell/StandardRebindOracle.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2007 Google Inc.
+ * Copyright 2008 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
@@ -20,11 +20,10 @@
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.linker.ArtifactSet;
 import com.google.gwt.core.ext.typeinfo.JClassType;
-import com.google.gwt.core.ext.typeinfo.TypeOracle;
 import com.google.gwt.dev.cfg.PublicOracle;
 import com.google.gwt.dev.cfg.Rule;
 import com.google.gwt.dev.cfg.Rules;
-import com.google.gwt.dev.jdt.CacheManager;
+import com.google.gwt.dev.javac.CompilationState;
 import com.google.gwt.dev.jdt.RebindOracle;
 import com.google.gwt.dev.util.Util;
 
@@ -51,10 +50,9 @@
 
     private final List<String> usedTypeNames = new ArrayList<String>();
 
-    public Rebinder(TypeOracle typeOracle, PropertyOracle propOracle,
-        PublicOracle publicOracle) {
-      genCtx = new StandardGeneratorContext(typeOracle, propOracle,
-          publicOracle, genDir, outDir, cacheManager, artifactSet);
+    public Rebinder() {
+      genCtx = new StandardGeneratorContext(compilationState, propOracle,
+          publicOracle, genDir, outDir, artifactSet);
     }
 
     public String rebind(TreeLogger logger, String typeName)
@@ -135,7 +133,7 @@
 
   private final ArtifactSet artifactSet;
 
-  private final CacheManager cacheManager;
+  private final CompilationState compilationState;
 
   private final File genDir;
 
@@ -147,22 +145,15 @@
 
   private final Rules rules;
 
-  private final TypeOracle typeOracle;
-
-  public StandardRebindOracle(TypeOracle typeOracle, PropertyOracle propOracle,
-      PublicOracle publicOracle, Rules rules, File genDir, File moduleOutDir,
-      CacheManager cacheManager, ArtifactSet artifactSet) {
-    this.typeOracle = typeOracle;
+  public StandardRebindOracle(CompilationState compilationState,
+      PropertyOracle propOracle, PublicOracle publicOracle, Rules rules,
+      File genDir, File moduleOutDir, ArtifactSet artifactSet) {
+    this.compilationState = compilationState;
     this.propOracle = propOracle;
     this.publicOracle = publicOracle;
     this.rules = rules;
     this.genDir = genDir;
     this.outDir = moduleOutDir;
-    if (cacheManager != null) {
-      this.cacheManager = cacheManager;
-    } else {
-      this.cacheManager = new CacheManager(typeOracle);
-    }
     this.artifactSet = artifactSet;
   }
 
@@ -171,7 +162,7 @@
 
     logger = Messages.TRACE_TOPLEVEL_REBIND.branch(logger, typeName, null);
 
-    Rebinder rebinder = new Rebinder(typeOracle, propOracle, publicOracle);
+    Rebinder rebinder = new Rebinder();
     String result = rebinder.rebind(logger, typeName);
 
     Messages.TRACE_TOPLEVEL_REBIND_RESULT.log(logger, result, null);
diff --git a/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteJsniMethods.java b/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteJsniMethods.java
index 54a536f..a00ce8d 100644
--- a/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteJsniMethods.java
+++ b/dev/core/src/com/google/gwt/dev/shell/rewrite/RewriteJsniMethods.java
@@ -367,7 +367,6 @@
         + descriptor;
     String argsDescriptor = descriptor.substring(argsIndexBegin,
         argsIndexEnd + 1);
-    String sourceName = classDesc.replace('/', '.').replace('$', '.');
-    return "@" + sourceName + "::" + name + argsDescriptor;
+    return "@" + classDesc.replace('/', '.') + "::" + name + argsDescriptor;
   }
 }
diff --git a/dev/core/src/com/google/gwt/dev/shell/tomcat/EmbeddedTomcatServer.java b/dev/core/src/com/google/gwt/dev/shell/tomcat/EmbeddedTomcatServer.java
index ae78db3..723a431 100644
--- a/dev/core/src/com/google/gwt/dev/shell/tomcat/EmbeddedTomcatServer.java
+++ b/dev/core/src/com/google/gwt/dev/shell/tomcat/EmbeddedTomcatServer.java
@@ -16,8 +16,11 @@
 package com.google.gwt.dev.shell.tomcat;
 
 import com.google.gwt.core.ext.TreeLogger;
-import com.google.gwt.dev.util.FileOracle;
-import com.google.gwt.dev.util.FileOracleFactory;
+import com.google.gwt.dev.resource.Resource;
+import com.google.gwt.dev.resource.impl.ClassPathEntry;
+import com.google.gwt.dev.resource.impl.PathPrefix;
+import com.google.gwt.dev.resource.impl.PathPrefixSet;
+import com.google.gwt.dev.resource.impl.ResourceOracleImpl;
 import com.google.gwt.util.tools.Utility;
 
 import org.apache.catalina.Connector;
@@ -39,7 +42,11 @@
 import java.lang.reflect.Field;
 import java.net.InetAddress;
 import java.net.ServerSocket;
+import java.net.URISyntaxException;
 import java.net.URL;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Map.Entry;
 
 /**
  * Wraps an instance of the Tomcat web server used in hosted mode.
@@ -249,14 +256,14 @@
   /*
    * Assumes that the leaf is a file (not a directory).
    */
-  private void copyFileNoOverwrite(TreeLogger logger, FileOracle fileOracle,
-      String srcResName, File catBase) {
+  private void copyFileNoOverwrite(TreeLogger logger, String srcResName,
+      Resource resource, File catBase) {
 
     File dest = new File(catBase, srcResName);
     InputStream is = null;
     FileOutputStream os = null;
     try {
-      URL srcRes = fileOracle.find(srcResName);
+      URL srcRes = resource.getURL();
       if (srcRes == null) {
         logger.log(TreeLogger.TRACE, "Cannot find: " + srcResName, null);
         return;
@@ -321,28 +328,61 @@
         "Property 'catalina.base' not specified; checking for a standard catalina base image instead",
         null);
 
-    // Recursively copies out files and directories under
-    // com.google.gwt.dev.etc.tomcat.
-    //
-    FileOracleFactory fof = new FileOracleFactory();
-    final String tomcatEtcDir = "com/google/gwt/dev/etc/tomcat/";
-    fof.addRootPackage(tomcatEtcDir, null);
-    FileOracle fo = fof.create(logger);
-    if (fo.isEmpty()) {
-      logger.log(TreeLogger.WARN, "Could not find " + tomcatEtcDir, null);
-      return null;
+    // Recursively copies out files and directories
+    String tomcatEtcDir = "com/google/gwt/dev/etc/tomcat/";
+    Map<String, Resource> resourceMap = null;
+    Throwable caught = null;
+    try {
+      resourceMap = getResourcesFor(logger, tomcatEtcDir);
+    } catch (URISyntaxException e) {
+      caught = e;
+    } catch (IOException e) {
+      caught = e;
     }
 
     File catBase = new File(workDir, "tomcat");
-    String[] allChildren = fo.getAllFiles();
-    for (int i = 0; i < allChildren.length; i++) {
-      String src = allChildren[i];
-      copyFileNoOverwrite(logger, fo, src, catBase);
+    if (resourceMap == null || resourceMap.isEmpty()) {
+      logger.log(TreeLogger.WARN, "Could not find " + tomcatEtcDir, caught);
+    } else {
+      for (Entry<String, Resource> entry : resourceMap.entrySet()) {
+        copyFileNoOverwrite(logger, entry.getKey(), entry.getValue(), catBase);
+      }
     }
 
     return catBase.getAbsolutePath();
   }
 
+  /**
+   * Hacky, but fast.
+   */
+  private Map<String, Resource> getResourcesFor(TreeLogger logger,
+      String tomcatEtcDir) throws URISyntaxException, IOException {
+    ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+    URL url = contextClassLoader.getResource(tomcatEtcDir);
+    if (url == null) {
+      return null;
+    }
+    String prefix = "";
+    String urlString = url.toString();
+    if (urlString.startsWith("jar:")) {
+      assert urlString.contains(".jar!/" + tomcatEtcDir);
+      urlString = urlString.substring(4, urlString.indexOf('!'));
+      url = new URL(urlString);
+      prefix = tomcatEtcDir;
+    }
+    ClassPathEntry entry = ResourceOracleImpl.createEntryForUrl(logger, url);
+    assert (entry != null);
+    ResourceOracleImpl resourceOracle = new ResourceOracleImpl(
+        Collections.singletonList(entry));
+    PathPrefixSet pathPrefixSet = new PathPrefixSet();
+    PathPrefix pathPrefix = new PathPrefix(prefix, null, true);
+    pathPrefixSet.add(pathPrefix);
+    resourceOracle.setPathPrefixes(pathPrefixSet);
+    resourceOracle.refresh(logger);
+    Map<String, Resource> resourceMap = resourceOracle.getResourceMap();
+    return resourceMap;
+  }
+
   private void publishAttributeToWebApp(TreeLogger logger,
       StandardContext webapp, String attrName, Object attrValue) {
     logger.log(TreeLogger.TRACE, "Adding attribute  '" + attrName
diff --git a/dev/core/src/com/google/gwt/dev/util/FileOracle.java b/dev/core/src/com/google/gwt/dev/util/FileOracle.java
deleted file mode 100644
index e44a207..0000000
--- a/dev/core/src/com/google/gwt/dev/util/FileOracle.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright 2006 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.util;
-
-import java.net.URL;
-
-/**
- * An abstraction for finding and retrieving a set of URLs by logical name.
- * Intuitively, it works like a jar in that each URL is uniquely located
- * somewhere in an abstract namespace. The abstract names must be constructed
- * from a series of zero or more valid Java identifiers followed the '/'
- * character and finally ending in a valid filename, for example,
- * "com/google/gwt/blah.txt". Each contained abstract path corresponds to a
- * physical URL.
- */
-public abstract class FileOracle {
-
-  /**
-   * Finds a URL by abstract path.
-   * 
-   * @param abstractPath the abstract path of the URL to find.
-   * @return the physical URL of the contained URL, or <code>null</code> the
-   *         abstract path does not refer to a contained URL.
-   */
-  public abstract URL find(String abstractPath);
-
-  /**
-   * Gets the abstract path for every URL indexed by this FileOracle. Elements
-   * of the result set can be passed into {@link #find(String)} to retrieve the
-   * physical URL.
-   * 
-   * @return the abstract path of every URL indexed by this FileOracle
-   */
-  public abstract String[] getAllFiles();
-
-  /**
-   * Tests if this FileOracle has URLs.
-   * 
-   * @return <tt>true</tt> if this list has no elements; <tt>false</tt>
-   *         otherwise.
-   */
-  public abstract boolean isEmpty();
-
-}
diff --git a/dev/core/src/com/google/gwt/dev/util/FileOracleFactory.java b/dev/core/src/com/google/gwt/dev/util/FileOracleFactory.java
deleted file mode 100644
index 70d80e1..0000000
--- a/dev/core/src/com/google/gwt/dev/util/FileOracleFactory.java
+++ /dev/null
@@ -1,488 +0,0 @@
-/*
- * Copyright 2006 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.util;
-
-import com.google.gwt.core.ext.TreeLogger;
-
-import java.io.File;
-import java.io.IOException;
-import java.net.MalformedURLException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URL;
-import java.net.URLClassLoader;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.jar.JarEntry;
-import java.util.jar.JarFile;
-
-/**
- * Creates a FileOracle based on a set of logical packages combined with either
- * a URLClassLoader. For each specified package, the ClassLoader is searched for
- * instances of that package as a directory. The results of this operation are
- * merged together into a single list of URLs whose order is determined by the
- * order of URLs in the ClassLoader. The relative order of different logical
- * packages originating from the same URL in the ClassLoader is undefined.
- * 
- * Once the sorted list of URLs is resolved, each URL is recursively searched to
- * index all of its files (optionally, that pass the given FileOracleFilter).
- * The results of this indexing are used to create the output FileOracle. Once
- * the FileOracle is created, its index is fixed and no longer depends on the
- * underlying URLClassLoader or file system. However, URLs returned from the
- * FileOracle may become invalid if the contents of the file system change.
- * 
- * Presently, only URLs beginning with <code>file:</code> and
- * <code>jar:file:</code> can be inspected to index children. Any other types
- * of URLs will generate a warning. The set of children indexed by
- * <code>jar:file:</code> type URLs is fixed at creation time, but the set of
- * children from <code>file:</code> type URLs will dynamically query the
- * underlying file system.
- */
-public class FileOracleFactory {
-
-  /**
-   * Used to decide whether or not a resource name should be included in an
-   * enumeration.
-   */
-  public interface FileFilter {
-    boolean accept(String name);
-  }
-
-  /**
-   * Implementation of a FileOracle as an ordered (based on class path) list of
-   * abstract names (relative to some root), each mapped to a concrete URL.
-   */
-  private static final class FileOracleImpl extends FileOracle {
-
-    private final String[] logicalNames;
-
-    private final Map logicalToPhysical;
-
-    /**
-     * Creates a new FileOracle.
-     * 
-     * @param logicalNames An ordered list of abstract path name strings.
-     * @param logicalToPhysical A map of every item in logicalNames onto a URL.
-     */
-    public FileOracleImpl(List logicalNames, Map logicalToPhysical) {
-      this.logicalNames = (String[]) logicalNames.toArray(new String[logicalNames.size()]);
-      this.logicalToPhysical = new HashMap(logicalToPhysical);
-    }
-
-    /*
-     * (non-Javadoc)
-     * 
-     * @see com.google.gwt.dev.util.FileOracle#find(java.lang.String)
-     */
-    public URL find(String partialPath) {
-      return (URL) logicalToPhysical.get(partialPath);
-    }
-
-    /*
-     * (non-Javadoc)
-     * 
-     * @see com.google.gwt.dev.util.FileOracle#getAllFiles()
-     */
-    public String[] getAllFiles() {
-      return logicalNames;
-    }
-
-    /*
-     * (non-Javadoc)
-     * 
-     * @see com.google.gwt.dev.util.FileOracle#isEmpty()
-     */
-    public boolean isEmpty() {
-      return logicalNames.length == 0;
-    }
-  }
-
-  /**
-   * Given a set of logical packages, finds every occurrence of each of those
-   * packages within cl, and then sorts them relative to each other based on
-   * classPathUrlList.
-   * 
-   * @param logger Logs the process.
-   * @param cl Provides the underlying class path.
-   * @param packageSet The input set of logical packages to search for and sort.
-   * @param classPathUrlList The order in which to sort the results.
-   * @param sortedUrls An output list to which urls are appended.
-   * @param sortedPackages An output list to which logical packages are appended
-   *          exactly corresponding to appends made to sortedUrls.
-   * @param recordPackages If false, only empty strings are appended to
-   *          sortedPackages.
-   */
-  private static void addPackagesInSortedOrder(TreeLogger logger,
-      URLClassLoader cl, Map packageMap, List classPathUrlList,
-      List sortedUrls, List sortedPackages, List sortedFilters,
-      boolean recordPackages) {
-
-    // Exhaustively find every package on the classpath in an unsorted fashion
-    //
-    List unsortedUrls = new ArrayList();
-    List unsortedPackages = new ArrayList();
-    List unsortedFilters = new ArrayList();
-    for (Iterator itPkg = packageMap.keySet().iterator(); itPkg.hasNext();) {
-      String curPkg = (String) itPkg.next();
-      FileFilter curFilter = (FileFilter) packageMap.get(curPkg);
-      try {
-        Enumeration found = cl.findResources(curPkg);
-        if (!recordPackages) {
-          curPkg = "";
-        }
-        while (found.hasMoreElements()) {
-          URL match = (URL) found.nextElement();
-          unsortedUrls.add(match);
-          unsortedPackages.add(curPkg);
-          unsortedFilters.add(curFilter);
-        }
-      } catch (IOException e) {
-        logger.log(TreeLogger.WARN, "Unexpected error searching classpath for "
-            + curPkg, e);
-      }
-    }
-
-    /*
-     * Now sort the collected list by the proper class path order. This is an
-     * O(N*M) operation, but it should be okay for what we're doing
-     */
-
-    // pre-convert the List of URL to String[] to speed up the inner loop below
-    int c = unsortedUrls.size();
-    String[] unsortedUrlStrings = new String[c];
-    for (int i = 0; i < c; ++i) {
-      unsortedUrlStrings[i] = unsortedUrls.get(i).toString();
-      // strip the jar prefix for text matching purposes
-      if (unsortedUrlStrings[i].startsWith("jar:")) {
-        unsortedUrlStrings[i] = unsortedUrlStrings[i].substring(4);
-      }
-    }
-
-    // now sort the URLs based on classPathUrlList
-    for (Iterator itCp = classPathUrlList.iterator(); itCp.hasNext();) {
-      URL curCpUrl = (URL) itCp.next();
-      String curUrlString = curCpUrl.toExternalForm();
-      // find all URLs that match this particular entry
-      for (int i = 0; i < c; ++i) {
-        if (unsortedUrlStrings[i].startsWith(curUrlString)) {
-          sortedUrls.add(unsortedUrls.get(i));
-          sortedPackages.add(unsortedPackages.get(i));
-          sortedFilters.add(unsortedFilters.get(i));
-        }
-      }
-    }
-  }
-
-  /**
-   * Index all the children of a particular folder (recursively).
-   * 
-   * @param logger Logs the process.
-   * @param filter If non-null, filters out which files get indexed.
-   * @param stripBaseLen The number of characters to strip from the beginning of
-   *          every child's file path when computing the logical name.
-   * @param curDir The directory to index.
-   * @param logicalNames An output List of Children found under this URL.
-   * @param logicalToPhysical An output Map of Children found under this URL
-   *          mapped to their concrete URLs.
-   */
-  private static void indexFolder(TreeLogger logger, FileFilter filter,
-      int stripBaseLen, File curDir, List logicalNames, Map logicalToPhysical) {
-    File[] files = curDir.listFiles();
-    for (int i = 0; i < files.length; i++) {
-      File f = files[i];
-      if (f.exists()) {
-        if (f.isDirectory()) {
-          indexFolder(logger, filter, stripBaseLen, f, logicalNames,
-              logicalToPhysical);
-        } else if (f.isFile()) {
-          try {
-            String logicalName = f.getAbsolutePath().substring(stripBaseLen);
-            logicalName = logicalName.replace(File.separatorChar, '/');
-            if (logicalToPhysical.containsKey(logicalName)) {
-              // this logical name is shadowed
-              logger.log(TreeLogger.DEBUG, "Ignoring already-resolved "
-                  + logicalName, null);
-              continue;
-            }
-            if (filter != null && !filter.accept(logicalName)) {
-              // filtered out
-              logger.log(TreeLogger.SPAM, "Filtered out " + logicalName, null);
-              continue;
-            }
-            URL physicalUrl = f.toURL();
-            logicalToPhysical.put(logicalName, physicalUrl);
-            logicalNames.add(logicalName);
-            logger.log(TreeLogger.TRACE, "Found " + logicalName, null);
-          } catch (IOException e) {
-            logger.log(TreeLogger.WARN, "Unexpected error resolving " + f, e);
-          }
-        }
-      }
-    }
-  }
-
-  /**
-   * Index all the children in a particular folder of a jar.
-   * 
-   * @param logger Logs the process.
-   * @param filter If non-null, filters out which files get indexed.
-   * @param jarUrl The URL of the containing jar file.
-   * @param jarFile The jarFile to index.
-   * @param basePath The sub tree within the jarFile to index.
-   * @param pkgBase If non-empty, causes the logical names of children to be
-   *          shorter (rooting them higher in the tree).
-   * @param logicalNames An output List of Children found under this URL.
-   * @param logicalToPhysical An output Map of Children found under this URL
-   *          mapped to their concrete URLs.
-   */
-  private static void indexJar(TreeLogger logger, FileFilter filter,
-      String jarUrl, JarFile jarFile, String basePath, String pkgBase,
-      List logicalNames, Map logicalToPhysical) {
-    int prefixCharsToStrip = basePath.length() - pkgBase.length();
-    for (Enumeration enumJar = jarFile.entries(); enumJar.hasMoreElements();) {
-      JarEntry jarEntry = (JarEntry) enumJar.nextElement();
-      String jarEntryName = jarEntry.getName();
-      if (jarEntryName.startsWith(basePath) && !jarEntry.isDirectory()) {
-        String logicalName = jarEntryName.substring(prefixCharsToStrip);
-        String physicalUrlString = jarUrl + "!/" + jarEntryName;
-        if (logicalToPhysical.containsKey(logicalName)) {
-          // this logical name is shadowed
-          logger.log(TreeLogger.DEBUG, "Ignoring already-resolved "
-              + logicalName, null);
-          continue;
-        }
-        if (filter != null && !filter.accept(logicalName)) {
-          // filtered out
-          logger.log(TreeLogger.SPAM, "Filtered out " + logicalName, null);
-          continue;
-        }
-        try {
-          URL physicalUrl = new URL(physicalUrlString);
-          logicalToPhysical.put(logicalName, physicalUrl);
-          logicalNames.add(logicalName);
-          logger.log(TreeLogger.TRACE, "Found " + logicalName, null);
-        } catch (MalformedURLException e) {
-          logger.log(TreeLogger.WARN, "Unexpected error resolving "
-              + physicalUrlString, e);
-        }
-      }
-    }
-  }
-
-  /**
-   * Finds all children of the specified URL and indexes them.
-   * 
-   * @param logger Logs the process.
-   * @param filter If non-null, filters out which files get indexed.
-   * @param url The URL to index, must be <code>file:</code> or
-   *          <code>jar:file:</code>
-   * @param pkgBase A prefix to exclude when indexing children.
-   * @param logicalNames An output List of Children found under this URL.
-   * @param logicalToPhysical An output Map of Children found under this URL
-   *          mapped to their concrete URLs.
-   * @throws URISyntaxException if an unexpected error occurs.
-   * @throws IOException if an unexpected error occurs.
-   */
-  private static void indexURL(TreeLogger logger, FileFilter filter, URL url,
-      String pkgBase, List logicalNames, Map logicalToPhysical)
-      throws URISyntaxException, IOException {
-
-    String urlString = url.toString();
-    if (url.getProtocol().equals("file")) {
-      URI uri = new URI(urlString);
-      File f = new File(uri);
-      if (f.isDirectory()) {
-        int prefixCharsToStrip = f.getAbsolutePath().length() + 1
-            - pkgBase.length();
-        indexFolder(logger, filter, prefixCharsToStrip, f, logicalNames,
-            logicalToPhysical);
-      } else {
-        // We can't handle files here, only directories. If this is a jar
-        // reference, the url must come in as a "jar:file:<stuff>!/[stuff/]".
-        // Fall through.
-        logger.log(TreeLogger.WARN, "Unexpected error, " + f
-            + " is neither a file nor a jar", null);
-      }
-    } else if (url.getProtocol().equals("jar")) {
-      String path = url.getPath();
-      int pos = path.indexOf('!');
-      if (pos >= 0) {
-        String jarPath = path.substring(0, pos);
-        String dirPath = path.substring(pos + 2);
-        URL jarURL = new URL(jarPath);
-        if (jarURL.getProtocol().equals("file")) {
-          URI jarURI = new URI(jarURL.toString());
-          File f = new File(jarURI);
-          JarFile jarFile = new JarFile(f);
-          // From each child, strip off the leading classpath portion when
-          // determining the logical name (sans the pkgBase name we want!)
-          //
-          indexJar(logger, filter, "jar" + ":" + jarPath, jarFile, dirPath, pkgBase,
-              logicalNames, logicalToPhysical);
-        } else {
-          logger.log(TreeLogger.WARN, "Unexpected error, jar at " + jarURL
-              + " must be a file: type URL", null);
-        }
-      } else {
-        throw new URISyntaxException(path, "Cannot locate '!' separator");
-      }
-    } else {
-      logger.log(TreeLogger.WARN, "Unknown URL type for " + urlString, null);
-    }
-  }
-
-  /**
-   * The underlying classloader.
-   */
-  private final URLClassLoader classLoader;
-
-  /**
-   * A map of packages indexed from the root of the class path onto their
-   * corresponding FileFilters.
-   */
-  private final Map packages = new HashMap();
-
-  /**
-   * A map of packages that become their own roots (that is their children are
-   * indexed relative to them) onto their corresponding FileFilters.
-   */
-  private final Map rootPackages = new HashMap();
-
-  /**
-   * Creates a FileOracleFactory with the default URLClassLoader.
-   */
-  public FileOracleFactory() {
-    this((URLClassLoader) FileOracleFactory.class.getClassLoader());
-  }
-
-  /**
-   * Creates a FileOracleFactory.
-   * 
-   * @param classLoader The underlying class path to use.
-   */
-  public FileOracleFactory(URLClassLoader classLoader) {
-    this.classLoader = classLoader;
-  }
-
-  /**
-   * Adds a logical package to the product FileOracle. All instances of this
-   * package that can be found in the underlying URLClassLoader will have their
-   * their children indexed, relative to the class path entry on which they are
-   * found.
-   * 
-   * @param packageAsPath For example, "com/google/gwt/core/client".
-   */
-  public void addPackage(String packageAsPath, FileFilter filter) {
-    packageAsPath = ensureTrailingBackslash(packageAsPath);
-    packages.put(packageAsPath, filter);
-  }
-
-  /**
-   * Adds a logical root package to the product FileOracle. All instances of
-   * this package that can be found in the underlying URLClassLoader will have
-   * their their children indexed, relative to their location within
-   * packageAsPath. All root packages trump all non-root packages when
-   * determining the final precedence order.
-   * 
-   * @param packageAsPath For example, "com/google/gwt/core/client".
-   */
-  public void addRootPackage(String packageAsPath, FileFilter filter) {
-    packageAsPath = ensureTrailingBackslash(packageAsPath);
-    rootPackages.put(packageAsPath, filter);
-  }
-
-  /**
-   * Creates the product FileOracle based on the logical packages previously
-   * added.
-   * 
-   * @param logger Logs the process.
-   * @return a new FileOracle.
-   */
-  public FileOracle create(TreeLogger logger) {
-
-    // get the full expanded URL class path for sorting purposes
-    //
-    List classPathUrls = new ArrayList();
-    for (ClassLoader curCL = classLoader; curCL != null; curCL = curCL.getParent()) {
-      if (curCL instanceof URLClassLoader) {
-        URLClassLoader curURLCL = (URLClassLoader) curCL;
-        URL[] curURLs = curURLCL.getURLs();
-        classPathUrls.addAll(Arrays.asList(curURLs));
-      }
-    }
-
-    /*
-     * Collect a sorted list of URLs corresponding to all of the logical
-     * packages mapped onto the
-     */
-
-    // The list of
-    List urls = new ArrayList();
-    List pkgNames = new ArrayList();
-    List filters = new ArrayList();
-
-    // don't record package names for root packages, they are rebased
-    addPackagesInSortedOrder(logger, classLoader, rootPackages, classPathUrls,
-        urls, pkgNames, filters, false);
-    // record package names for non-root packages
-    addPackagesInSortedOrder(logger, classLoader, packages, classPathUrls,
-        urls, pkgNames, filters, true);
-
-    // We have a complete sorted list of mapped URLs with package prefixes
-
-    // Setup data collectors
-    List logicalNames = new ArrayList();
-    Map logicalToPhysical = new HashMap();
-
-    for (int i = 0, c = urls.size(); i < c; ++i) {
-      try {
-        URL url = (URL) urls.get(i);
-        String pkgName = (String) pkgNames.get(i);
-        FileFilter filter = (FileFilter) filters.get(i);
-        TreeLogger branch = logger.branch(TreeLogger.TRACE, url.toString(),
-            null);
-        indexURL(branch, filter, url, pkgName, logicalNames, logicalToPhysical);
-      } catch (URISyntaxException e) {
-        logger.log(TreeLogger.WARN,
-            "Unexpected error searching " + urls.get(i), e);
-      } catch (IOException e) {
-        logger.log(TreeLogger.WARN,
-            "Unexpected error searching " + urls.get(i), e);
-      }
-    }
-
-    return new FileOracleImpl(logicalNames, logicalToPhysical);
-  }
-
-  /**
-   * Helper method to regularize packages.
-   * 
-   * @param packageAsPath For exmaple, "com/google/gwt/core/client"
-   * @return For example, "com/google/gwt/core/client/"
-   */
-  private String ensureTrailingBackslash(String packageAsPath) {
-    if (packageAsPath.endsWith("/")) {
-      return packageAsPath;
-    } else {
-      return packageAsPath + "/";
-    }
-  }
-}
diff --git a/dev/core/src/com/google/gwt/dev/util/Jsni.java b/dev/core/src/com/google/gwt/dev/util/Jsni.java
index b316b0b..801a192 100644
--- a/dev/core/src/com/google/gwt/dev/util/Jsni.java
+++ b/dev/core/src/com/google/gwt/dev/util/Jsni.java
@@ -16,50 +16,21 @@
 package com.google.gwt.dev.util;
 
 import com.google.gwt.core.ext.TreeLogger;
-import com.google.gwt.core.ext.UnableToCompleteException;
-import com.google.gwt.core.ext.typeinfo.JMethod;
-import com.google.gwt.core.ext.typeinfo.JParameter;
-import com.google.gwt.core.ext.typeinfo.JType;
-import com.google.gwt.dev.js.JsParser;
-import com.google.gwt.dev.js.JsParserException;
+import com.google.gwt.dev.javac.JsniMethod;
 import com.google.gwt.dev.js.JsSourceGenerationVisitor;
-import com.google.gwt.dev.js.JsParserException.SourceDetail;
-import com.google.gwt.dev.js.ast.JsBlock;
 import com.google.gwt.dev.js.ast.JsContext;
-import com.google.gwt.dev.js.ast.JsExprStmt;
 import com.google.gwt.dev.js.ast.JsExpression;
 import com.google.gwt.dev.js.ast.JsFunction;
 import com.google.gwt.dev.js.ast.JsNameRef;
 import com.google.gwt.dev.js.ast.JsNode;
-import com.google.gwt.dev.js.ast.JsProgram;
-import com.google.gwt.dev.js.ast.JsStatement;
 import com.google.gwt.dev.shell.JavaScriptHost;
 
-import java.io.IOException;
-import java.io.StringReader;
-import java.util.List;
-
 /**
  * Helper methods working with JSNI.
  */
 public class Jsni {
 
   /**
-   * Represents a logical interval of text.
-   */
-  public static class Interval {
-
-    public final int end;
-
-    public final int start;
-
-    public Interval(int start, int end) {
-      this.start = start;
-      this.end = end;
-    }
-  }
-
-  /**
    * Generate source code, fixing up any JSNI references for hosted mode.
    * 
    * <p/><table>
@@ -127,205 +98,36 @@
   public static final String JSNI_BLOCK_START = "/*-{";
 
   /**
-   * Generates the code to wrap a set of parameters as an object array. In Java
-   * 1.5 we can take advantage of autoboxing to not have to wrap primitives.
+   * Gets the body of a JSNI method, with Java refs escaped for hosted mode
+   * injection.
    */
-  public static String buildArgList(JMethod method) {
-    StringBuilder sb = new StringBuilder();
-    sb.append("new Object[]{");
-
-    JParameter[] params = method.getParameters();
-    for (int i = 0; i < params.length; ++i) {
-      sb.append(params[i].getName());
-      sb.append(", ");
-    }
-
-    sb.append("}");
-    String args = sb.toString();
-    return args;
-  }
-
-  /**
-   * Generates the code to pass the exact types associated with each argument of
-   * this method.
-   */
-  public static String buildTypeList(JMethod method) {
-    StringBuilder sb = new StringBuilder();
-    sb.append("new Class[]{");
-
-    JParameter[] params = method.getParameters();
-    for (int i = 0; i < params.length; ++i) {
-      JType type = params[i].getType();
-      String typeName = type.getErasedType().getQualifiedSourceName();
-      sb.append(typeName);
-      sb.append(".class, ");
-    }
-
-    sb.append("}");
-    String classes = sb.toString();
-    return classes;
-  }
-
-  public static int countNewlines(char[] buf, int start, int end) {
-    int total = 0;
-    while (start < end) {
-      switch (buf[start]) {
-        case '\r':
-          ++total;
-          // if the next character is a line feed, eat it too
-          if (start + 1 < end && buf[start + 1] == '\n') {
-            ++start;
-          }
-          break;
-        case '\n':
-          ++total;
-          break;
-      }
-      ++start;
-    }
-    return total;
-  }
-
-  public static int countNewlines(String src, int start, int end) {
-    return countNewlines(src.toCharArray(), start, end);
-  }
-
-  /**
-   * Replaces double-quotes, backslashes, and newlines in native JS code with
-   * their appropriate escaped form (so they can be encoded in a java string).
-   */
-  public static String escapedJavaScriptForStringLiteral(String js) {
-    StringBuilder sb = new StringBuilder(js);
-    for (int i = 0; i < sb.length(); ++i) {
-      char c = sb.charAt(i);
-      switch (c) {
-        case '\"':
-        case '\\':
-          sb.insert(i, '\\');
-          ++i;
-          break;
-        case '\r':
-          sb.setCharAt(i, 'r');
-          sb.insert(i, '\\');
-          ++i;
-          break;
-        case '\n':
-          sb.setCharAt(i, 'n');
-          sb.insert(i, '\\');
-          ++i;
-          break;
-      }
-    }
-    return sb.toString();
-  }
-
-  public static Interval findJsniSource(JMethod method)
-      throws UnableToCompleteException {
-    assert (method.isNative());
-    int bodyStart = method.getBodyStart();
-    int bodyEnd = method.getBodyEnd();
-    int bodyLen = bodyEnd - bodyStart + 1;
-    char[] source = method.getEnclosingType().getCompilationUnit().getSource();
-    String js = String.valueOf(source, bodyStart, bodyLen);
-
-    int jsniStart = js.indexOf(JSNI_BLOCK_START);
-    if (jsniStart == -1) {
+  public static String getJavaScriptForHostedMode(TreeLogger logger,
+      JsniMethod jsniMethod) {
+    /*
+     * Surround the original JS body statements with a try/catch so that we can
+     * map JavaScript exceptions back into Java. Note that the method body
+     * itself will print curly braces, so we don't need them around the
+     * try/catch.
+     */
+    String jsTry = "try ";
+    String jsCatch = " catch (e) {\n  __static[\"@" + Jsni.JAVASCRIPTHOST_NAME
+        + "::exceptionCaught(Ljava/lang/Object;)\"](e);\n" + "}\n";
+    JsFunction func = jsniMethod.function(logger);
+    if (func == null) {
       return null;
     }
-
-    int jsniEnd = js.indexOf(JSNI_BLOCK_END, jsniStart);
-    if (jsniEnd == -1) {
-      // Suspicious, but maybe this is just a weird comment, so let it slide.
-      //
-      return null;
-    }
-
-    int srcStart = bodyStart + jsniStart + JSNI_BLOCK_START.length();
-    int srcEnd = bodyStart + jsniEnd;
-    return new Interval(srcStart, srcEnd);
+    return jsTry + generateJavaScriptForHostedMode(func.getBody()) + jsCatch;
   }
 
   /**
    * Returns a string representing the source output of the JsNode, where all
    * JSNI idents have been replaced with legal JavaScript for hosted mode.
    */
-  public static String generateJavaScriptForHostedMode(JsNode<?> node) {
+  private static String generateJavaScriptForHostedMode(JsNode<?> node) {
     DefaultTextOutput out = new DefaultTextOutput(false);
     JsSourceGenWithJsniIdentFixup vi = new JsSourceGenWithJsniIdentFixup(out);
     vi.accept(node);
     return out.toString();
   }
 
-  /**
-   * Gets a unique name for this method and its signature (this is used to
-   * determine whether one method overrides another).
-   */
-  public static String getJsniSignature(JMethod method) {
-    return method.getEnclosingType().getQualifiedSourceName() + "::"
-        + getMemberSignature(method);
-  }
-
-  /**
-   * Gets a unique name for this method and its signature (this is used to
-   * determine whether one method overrides another).
-   */
-  public static String getMemberSignature(JMethod method) {
-    String name = method.getName();
-    StringBuilder sb = new StringBuilder();
-    sb.append(name);
-    sb.append("(");
-    JParameter[] params = method.getParameters();
-    for (int i = 0; i < params.length; ++i) {
-      JParameter param = params[i];
-      String typeSig = param.getType().getJNISignature();
-      sb.append(typeSig);
-    }
-    sb.append(")");
-    String result = sb.toString();
-    return result;
-  }
-
-  /**
-   * In other words, it can have <code>return</code> statements.
-   */
-  public static JsBlock parseAsFunctionBody(TreeLogger logger, String js,
-      String location, int startLine) throws UnableToCompleteException {
-    // Wrap it in fake function and parse it.
-    js = "function(){ " + js + " }";
-
-    JsParser jsParser = new JsParser();
-    JsProgram jsPgm = new JsProgram();
-    StringReader r = new StringReader(js);
-
-    try {
-      List<JsStatement> stmts = jsParser.parse(jsPgm.getScope(), r, startLine);
-
-      // Rip the body out of the parsed function and attach the JavaScript
-      // AST to the method.
-      //
-      JsFunction fn = (JsFunction) ((JsExprStmt) stmts.get(0)).getExpression();
-      return fn.getBody();
-    } catch (IOException e) {
-      logger.log(TreeLogger.ERROR, "Error reading JavaScript source", e);
-      throw new UnableToCompleteException();
-    } catch (JsParserException e) {
-      SourceDetail dtl = e.getSourceDetail();
-      if (dtl != null) {
-        StringBuilder sb = new StringBuilder();
-        sb.append(location);
-        sb.append("(");
-        sb.append(dtl.getLine());
-        sb.append(", ");
-        sb.append(dtl.getLineOffset());
-        sb.append("): ");
-        sb.append(e.getMessage());
-        logger.log(TreeLogger.ERROR, sb.toString(), e);
-        throw new UnableToCompleteException();
-      } else {
-        logger.log(TreeLogger.ERROR, "Error parsing JSNI source", e);
-        throw new UnableToCompleteException();
-      }
-    }
-  }
-
 }
diff --git a/dev/core/src/com/google/gwt/dev/util/Util.java b/dev/core/src/com/google/gwt/dev/util/Util.java
index 16edda6..237a3c3 100644
--- a/dev/core/src/com/google/gwt/dev/util/Util.java
+++ b/dev/core/src/com/google/gwt/dev/util/Util.java
@@ -30,6 +30,7 @@
 
 import java.io.BufferedReader;
 import java.io.BufferedWriter;
+import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileFilter;
 import java.io.FileInputStream;
@@ -173,23 +174,16 @@
   }
 
   /**
-   * Copies an input stream out to a file. Closes the input steam and output
-   * stream.
+   * Copies an input stream out to an output stream. Closes the input steam and
+   * output stream.
    */
   public static void copy(TreeLogger logger, InputStream is, OutputStream os)
       throws UnableToCompleteException {
     try {
-      byte[] buf = new byte[8 * 1024];
-      int i = 0;
-      while ((i = is.read(buf)) != -1) {
-        os.write(buf, 0, i);
-      }
+      copy(is, os);
     } catch (IOException e) {
       logger.log(TreeLogger.ERROR, "Error during copy", e);
       throw new UnableToCompleteException();
-    } finally {
-      Utility.close(is);
-      Utility.close(os);
     }
   }
 
@@ -540,16 +534,14 @@
    * Give the developer a chance to see the in-memory source that failed.
    */
   public static void maybeDumpSource(TreeLogger logger, String location,
-      char[] source, String typeName) {
+      String source, String typeName) {
 
     if (isCompilationUnitOnDisk(location)) {
       // Don't write another copy.
       return;
     }
 
-    TreeLogger branch = logger.branch(TreeLogger.ERROR,
-        "Compilation problem due to '" + location + "'", null);
-    if (!branch.isLoggable(TreeLogger.INFO)) {
+    if (!logger.isLoggable(TreeLogger.INFO)) {
       // Don't bother dumping source if they can't see the related message.
       return;
     }
@@ -558,16 +550,14 @@
     Throwable caught = null;
     try {
       tmpSrc = File.createTempFile(typeName, ".java");
-      writeCharsAsFile(logger, tmpSrc, source);
+      writeStringAsFile(tmpSrc, source);
       String dumpPath = tmpSrc.getAbsolutePath();
-      branch.log(TreeLogger.INFO, "See snapshot: " + dumpPath, null);
+      logger.log(TreeLogger.INFO, "See snapshot: " + dumpPath, null);
       return;
     } catch (IOException e) {
       caught = e;
-    } catch (UnableToCompleteException e) {
-      caught = e;
     }
-    branch.log(TreeLogger.INFO, "Unable to dump source to disk", caught);
+    logger.log(TreeLogger.INFO, "Unable to dump source to disk", caught);
   }
 
   public static byte[] readFileAsBytes(File file) {
@@ -623,6 +613,22 @@
   }
 
   /**
+   * Reads an entire input stream as String. Closes the input stream.
+   */
+  public static String readStreamAsString(InputStream in) {
+    try {
+      ByteArrayOutputStream out = new ByteArrayOutputStream(1024);
+      copy(in, out);
+      return out.toString(DEFAULT_ENCODING);
+    } catch (UnsupportedEncodingException e) {
+      throw new RuntimeException(
+          "The JVM does not support the compiler's default encoding.", e);
+    } catch (IOException e) {
+      return null;
+    }
+  }
+
+  /**
    * @return null if the file could not be read
    */
   public static byte[] readURLAsBytes(URL url) {
@@ -830,6 +836,14 @@
   }
 
   /**
+   * Returns a String representing the character content of the bytes; the bytes
+   * must be encoded using the compiler's default encoding.
+   */
+  public static String toString(byte[] bytes) {
+    return toString(bytes, DEFAULT_ENCODING);
+  }
+
+  /**
    * Creates a string array from the contents of a collection.
    */
   public static String[] toStringArray(Collection<String> coll) {
@@ -1018,6 +1032,19 @@
     return true;
   }
 
+  private static void copy(InputStream is, OutputStream os) throws IOException {
+    try {
+      byte[] buf = new byte[8 * 1024];
+      int i;
+      while ((i = is.read(buf)) != -1) {
+        os.write(buf, 0, i);
+      }
+    } finally {
+      Utility.close(is);
+      Utility.close(os);
+    }
+  }
+
   /**
    * Reads the specified number of bytes from the {@link InputStream}.
    * 
diff --git a/dev/core/src/com/google/gwt/util/tools/Utility.java b/dev/core/src/com/google/gwt/util/tools/Utility.java
index cf07319..dcc60b6 100644
--- a/dev/core/src/com/google/gwt/util/tools/Utility.java
+++ b/dev/core/src/com/google/gwt/util/tools/Utility.java
@@ -201,7 +201,7 @@
    */
   public static String getFileFromClassPath(String partialPath)
       throws IOException {
-    InputStream in = Utility.class.getClassLoader().getResourceAsStream(
+    InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream(
         partialPath);
     try {
       if (in == null) {
@@ -279,7 +279,8 @@
       if (override == null) {
         String partialPath = Utility.class.getName().replace('.', '/').concat(
             ".class");
-        URL url = Utility.class.getClassLoader().getResource(partialPath);
+        URL url = Thread.currentThread().getContextClassLoader().getResource(
+            partialPath);
         if (url != null && "jar".equals(url.getProtocol())) {
           String path = url.toString();
           String jarPath = path.substring(path.indexOf("file:"),
diff --git a/dev/core/super/com/google/gwt/core/client/GWTBridge.java b/dev/core/super/com/google/gwt/core/client/GWTBridge.java
new file mode 100644
index 0000000..005bece
--- /dev/null
+++ b/dev/core/super/com/google/gwt/core/client/GWTBridge.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2008 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.core.client;
+
+/**
+ * When running in hosted mode, acts as a bridge from {@link GWT} into the
+ * hosted mode environment.
+ */
+public abstract class GWTBridge {
+
+  public abstract <T> T create(Class<?> classLiteral);
+
+  public abstract String getVersion();
+
+  public abstract void log(String message, Throwable e);
+}
diff --git a/dev/core/test/com/google/gwt/core/ext/typeinfo/JArrayTypeTest.java b/dev/core/test/com/google/gwt/core/ext/typeinfo/JArrayTypeTest.java
index 90b5b94..6966b56 100644
--- a/dev/core/test/com/google/gwt/core/ext/typeinfo/JArrayTypeTest.java
+++ b/dev/core/test/com/google/gwt/core/ext/typeinfo/JArrayTypeTest.java
@@ -17,7 +17,6 @@
 
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
-import com.google.gwt.core.ext.typeinfo.test.CA;
 import com.google.gwt.core.ext.typeinfo.test.CB;
 import com.google.gwt.core.ext.typeinfo.test.MyCustomList;
 import com.google.gwt.core.ext.typeinfo.test.MyList;
diff --git a/dev/core/test/com/google/gwt/core/ext/typeinfo/JClassTypeTest.java b/dev/core/test/com/google/gwt/core/ext/typeinfo/JClassTypeTest.java
index 462da59..7d385fc 100644
--- a/dev/core/test/com/google/gwt/core/ext/typeinfo/JClassTypeTest.java
+++ b/dev/core/test/com/google/gwt/core/ext/typeinfo/JClassTypeTest.java
@@ -17,19 +17,10 @@
 
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
-import com.google.gwt.dev.jdt.StaticCompilationUnitProvider;
-import com.google.gwt.dev.jdt.TypeOracleBuilder;
-import com.google.gwt.dev.jdt.URLCompilationUnitProvider;
 import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
 
 import junit.framework.TestCase;
 
-import java.io.File;
-import java.net.MalformedURLException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URL;
-
 /**
  * Tests related to JClassType. See individual test methods to details.
  */
@@ -43,7 +34,6 @@
   }
 
   public void testGetOverridableMethods() throws TypeOracleException {
-    TreeLogger logger = TreeLogger.NULL;
     TypeOracle typeOracle = moduleContext.getOracle();
     // TypeOracle typeOracle = buildOracleFromTestPackage(logger);
 
@@ -223,34 +213,6 @@
     }
   }
 
-  private void addCompilationUnitsInPath(TypeOracleBuilder builder,
-      File sourcePathEntry, String pkgName) throws UnableToCompleteException,
-      MalformedURLException {
-    File pkgPath = new File(sourcePathEntry, pkgName.replace('.', '/'));
-    File[] files = pkgPath.listFiles();
-    if (files == null) {
-      // No files found.
-      return;
-    }
-
-    for (int i = 0; i < files.length; i++) {
-      File file = files[i];
-      if (file.isFile()) {
-        // If it's a source file, slurp it in.
-        if (file.getName().endsWith(".java")) {
-          URL location = file.toURL();
-          CompilationUnitProvider cup = new URLCompilationUnitProvider(
-              location, pkgName);
-          builder.addCompilationUnit(cup);
-        }
-      } else {
-        // Recurse into subpackages.
-        addCompilationUnitsInPath(builder, sourcePathEntry, pkgName
-            + file.getName());
-      }
-    }
-  }
-
   private void assertMethodNotOverridable(TypeOracle typeOracle,
       String expectedTypeName, String searchTypeName, String methodName,
       String[] paramTypeNames) throws TypeOracleException {
diff --git a/dev/core/test/com/google/gwt/core/ext/typeinfo/JDelegatingClassTypeTestBase.java b/dev/core/test/com/google/gwt/core/ext/typeinfo/JDelegatingClassTypeTestBase.java
index d00e716..2c3e989 100644
--- a/dev/core/test/com/google/gwt/core/ext/typeinfo/JDelegatingClassTypeTestBase.java
+++ b/dev/core/test/com/google/gwt/core/ext/typeinfo/JDelegatingClassTypeTestBase.java
@@ -398,17 +398,6 @@
 
   /**
    * Test method for
-   * {@link com.google.gwt.core.ext.typeinfo.JDelegatingClassType#getCompilationUnit()}.
-   */
-  public void testGetCompilationUnit() throws NotFoundException {
-    JDelegatingClassType testType = getTestType();
-    JClassType baseType = testType.getBaseType();
-
-    assertEquals(testType.getCompilationUnit(), baseType.getCompilationUnit());
-  }
-
-  /**
-   * Test method for
    * {@link com.google.gwt.core.ext.typeinfo.JDelegatingClassType#getConstructor(com.google.gwt.core.ext.typeinfo.JType[])}.
    */
   public void testGetConstructor() throws NotFoundException {
diff --git a/dev/core/test/com/google/gwt/core/ext/typeinfo/JEnumTypeTest.java b/dev/core/test/com/google/gwt/core/ext/typeinfo/JEnumTypeTest.java
index 7ec4612..f10d243 100644
--- a/dev/core/test/com/google/gwt/core/ext/typeinfo/JEnumTypeTest.java
+++ b/dev/core/test/com/google/gwt/core/ext/typeinfo/JEnumTypeTest.java
@@ -18,8 +18,7 @@
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.typeinfo.test.MyEnum;
-import com.google.gwt.dev.cfg.ModuleDef;
-import com.google.gwt.dev.cfg.ModuleDefLoader;
+import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
 
 import junit.framework.TestCase;
 
@@ -27,18 +26,18 @@
  * Tests for {@link JEnumType}.
  */
 public class JEnumTypeTest extends TestCase {
-  private final TreeLogger logger = TreeLogger.NULL;
 
-  private ModuleDef moduleDef;
+  private final boolean logToConsole = false;
 
-  private final TypeOracle typeOracle;
+  private final ModuleContext moduleContext = new ModuleContext(logToConsole
+      ? new PrintWriterTreeLogger() : TreeLogger.NULL,
+      "com.google.gwt.core.ext.typeinfo.TypeOracleTest");
 
-  public JEnumTypeTest() throws UnableToCompleteException, NotFoundException {
-    moduleDef = ModuleDefLoader.loadFromClassPath(logger,
-        "com.google.gwt.core.ext.typeinfo.TypeOracleTest");
-    typeOracle = moduleDef.getTypeOracle(logger);
+  public JEnumTypeTest() throws UnableToCompleteException {
   }
 
+  private final TypeOracle typeOracle = moduleContext.getOracle();
+
   /**
    * Test method for
    * {@link com.google.gwt.core.ext.typeinfo.JEnumType#getEnumConstants()}.
diff --git a/dev/core/test/com/google/gwt/core/ext/typeinfo/ModuleContext.java b/dev/core/test/com/google/gwt/core/ext/typeinfo/ModuleContext.java
index acb8b2f..51ac681 100644
--- a/dev/core/test/com/google/gwt/core/ext/typeinfo/ModuleContext.java
+++ b/dev/core/test/com/google/gwt/core/ext/typeinfo/ModuleContext.java
@@ -21,7 +21,7 @@
 import com.google.gwt.dev.cfg.ModuleDefLoader;
 
 /**
- * Helper for loading modules from the classpath. 
+ * Helper for loading modules from the classpath.
  */
 class ModuleContext {
   private final TypeOracle oracle;
@@ -32,12 +32,8 @@
     moduleDef = ModuleDefLoader.loadFromClassPath(logger, moduleName);
     oracle = moduleDef.getTypeOracle(logger);
   }
-  
+
   public TypeOracle getOracle() {
     return oracle;
   }
-  
-  public ModuleDef getModule() {
-    return moduleDef;
-  }
 }
\ No newline at end of file
diff --git a/dev/core/test/com/google/gwt/core/ext/typeinfo/TypeOracleAnnotationSupportTest.java b/dev/core/test/com/google/gwt/core/ext/typeinfo/TypeOracleAnnotationSupportTest.java
index f9b6f16..a6ddb67 100644
--- a/dev/core/test/com/google/gwt/core/ext/typeinfo/TypeOracleAnnotationSupportTest.java
+++ b/dev/core/test/com/google/gwt/core/ext/typeinfo/TypeOracleAnnotationSupportTest.java
@@ -24,8 +24,7 @@
 import com.google.gwt.core.ext.typeinfo.test.PrimitivesAnnotatedClass;
 import com.google.gwt.core.ext.typeinfo.test.SourceRetentionAnnotation;
 import com.google.gwt.core.ext.typeinfo.test.TestAnnotation;
-import com.google.gwt.dev.cfg.ModuleDef;
-import com.google.gwt.dev.cfg.ModuleDefLoader;
+import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
 
 import junit.framework.TestCase;
 
@@ -89,15 +88,14 @@
         testAnnotation.emptyArray().length);
   }
 
-  private final TreeLogger logger = TreeLogger.NULL;
-  private ModuleDef moduleDef;
+  private final boolean logToConsole = false;
+  private final ModuleContext moduleContext = new ModuleContext(logToConsole
+      ? new PrintWriterTreeLogger() : TreeLogger.NULL,
+      "com.google.gwt.core.ext.typeinfo.TypeOracleTest");
 
-  private final TypeOracle typeOracle;
+  private final TypeOracle typeOracle = moduleContext.getOracle();
 
   public TypeOracleAnnotationSupportTest() throws UnableToCompleteException {
-    moduleDef = ModuleDefLoader.loadFromClassPath(logger,
-        "com.google.gwt.core.ext.typeinfo.TypeOracleTest");
-    typeOracle = moduleDef.getTypeOracle(logger);
   }
 
   /**
diff --git a/dev/core/test/com/google/gwt/dev/jdt/BinaryTypeReferenceRestrictionsCheckerTest.java b/dev/core/test/com/google/gwt/dev/javac/BinaryTypeReferenceRestrictionsCheckerTest.java
similarity index 96%
rename from dev/core/test/com/google/gwt/dev/jdt/BinaryTypeReferenceRestrictionsCheckerTest.java
rename to dev/core/test/com/google/gwt/dev/javac/BinaryTypeReferenceRestrictionsCheckerTest.java
index 5d60966..16b7e90 100644
--- a/dev/core/test/com/google/gwt/dev/jdt/BinaryTypeReferenceRestrictionsCheckerTest.java
+++ b/dev/core/test/com/google/gwt/dev/javac/BinaryTypeReferenceRestrictionsCheckerTest.java
@@ -13,9 +13,9 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.dev.jdt;
+package com.google.gwt.dev.javac;
 
-import com.google.gwt.dev.jdt.BinaryTypeReferenceRestrictionsChecker.BinaryTypeReferenceSite;
+import com.google.gwt.dev.javac.BinaryTypeReferenceRestrictionsChecker.BinaryTypeReferenceSite;
 
 import junit.framework.TestCase;
 
@@ -159,7 +159,7 @@
    * annotation and in a local variable declaration. It then checks that the we
    * find all of these locations except for the one used in an annotation.
    */
-  public void testFindInvalidBinaryTypeReferenceSites() {
+  public void testFindAllBinaryTypeReferenceSites() {
     CompilationResult compilationResult = new CompilationResult(
         "TestCompilationUnit.java".toCharArray(), 0, 0, 0);
     CompilationUnitDeclaration cud = new CompilationUnitDeclaration(null,
@@ -201,7 +201,7 @@
         typeDeclaration.superclass, methodDeclaration.returnType,
         localDeclaration.type};
 
-    List<BinaryTypeReferenceSite> binaryTypeReferenceSites = BinaryTypeReferenceRestrictionsChecker.findInvalidBinaryTypeReferenceSites(cud);
+    List<BinaryTypeReferenceSite> binaryTypeReferenceSites = BinaryTypeReferenceRestrictionsChecker.findAllBinaryTypeReferenceSites(cud);
     assertEquals(expectedExpressions.length, binaryTypeReferenceSites.size());
     for (int i = 0; i < binaryTypeReferenceSites.size(); ++i) {
       BinaryTypeReferenceSite binaryTypeReferenceSite = binaryTypeReferenceSites.get(i);
diff --git a/dev/core/test/com/google/gwt/dev/javac/CompilationStateTest.java b/dev/core/test/com/google/gwt/dev/javac/CompilationStateTest.java
new file mode 100644
index 0000000..23cdb49
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/javac/CompilationStateTest.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright 2008 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.core.ext.UnableToCompleteException;
+import com.google.gwt.dev.javac.CompilationUnit.State;
+import com.google.gwt.dev.javac.impl.MockJavaSourceFile;
+import com.google.gwt.dev.javac.impl.SourceFileCompilationUnit;
+import com.google.gwt.dev.util.log.AbstractTreeLogger;
+import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
+
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.Map.Entry;
+
+/**
+ * Tests {@link CompilationState}.
+ */
+public class CompilationStateTest extends TestCase {
+
+  private MockJavaSourceOracle oracle = new MockJavaSourceOracle(
+      JavaSourceCodeBase.getStandardResources());
+
+  private CompilationState state = new CompilationState(oracle);
+
+  public void testAddGeneratedCompilationUnit() {
+    validateCompilationState();
+
+    // Add a unit and ensure it shows up.
+    addGeneratedUnit(JavaSourceCodeBase.FOO);
+    validateCompilationState(JavaSourceCodeBase.FOO.getTypeName());
+
+    // Ensure it disappears after a refresh.
+    state.refresh();
+    validateCompilationState();
+  }
+
+  static void assertUnitsChecked(Collection<CompilationUnit> units) {
+    for (CompilationUnit unit : units) {
+      assertSame(State.CHECKED, unit.getState());
+      assertNull(unit.getErrors());
+      assertTrue(unit.getCompiledClasses().size() > 0);
+    }
+  }
+
+  public void testCompile() throws UnableToCompleteException {
+    validateUncompiled();
+    state.compile(createTreeLogger());
+    assertUnitsChecked(state.getCompilationUnits());
+  }
+
+  public void testCompileError() throws UnableToCompleteException {
+    oracle.add(JavaSourceCodeBase.BAR);
+    state.refresh();
+    validateUncompiled();
+    state.compile(createTreeLogger());
+
+    CompilationUnit badUnit = state.getCompilationUnitMap().get(
+        JavaSourceCodeBase.BAR.getTypeName());
+    assertSame(State.ERROR, badUnit.getState());
+
+    Set<CompilationUnit> goodUnits = new HashSet<CompilationUnit>(
+        state.getCompilationUnits());
+    goodUnits.remove(badUnit);
+    assertUnitsChecked(goodUnits);
+  }
+
+  public void testCompileWithGeneratedUnits() throws UnableToCompleteException {
+    validateUncompiled();
+    state.compile(createTreeLogger());
+    assertUnitsChecked(state.getCompilationUnits());
+    addGeneratedUnit(JavaSourceCodeBase.FOO);
+    state.compile(createTreeLogger());
+    assertUnitsChecked(state.getCompilationUnits());
+  }
+
+  public void testCompileWithGeneratedUnitsError()
+      throws UnableToCompleteException {
+    validateUncompiled();
+    state.compile(createTreeLogger());
+    assertUnitsChecked(state.getCompilationUnits());
+    addGeneratedUnit(JavaSourceCodeBase.BAR);
+    state.compile(createTreeLogger());
+
+    CompilationUnit badUnit = state.getCompilationUnitMap().get(
+        JavaSourceCodeBase.BAR.getTypeName());
+    assertSame(State.ERROR, badUnit.getState());
+
+    Set<CompilationUnit> goodUnits = new HashSet<CompilationUnit>(
+        state.getCompilationUnits());
+    goodUnits.remove(badUnit);
+    assertUnitsChecked(goodUnits);
+  }
+
+  public void testSourceOracleAdd() {
+    validateCompilationState();
+
+    int size = state.getCompilationUnits().size();
+    oracle.add(JavaSourceCodeBase.FOO);
+    state.refresh();
+    assertEquals(size + 1, state.getCompilationUnits().size());
+    validateCompilationState();
+  }
+
+  public void testSourceOracleBasic() {
+    validateCompilationState();
+  }
+
+  public void testSourceOracleEmpty() {
+    oracle = new MockJavaSourceOracle();
+    state = new CompilationState(oracle);
+    validateCompilationState();
+  }
+
+  public void testSourceOracleRemove() {
+    validateCompilationState();
+
+    int size = state.getCompilationUnits().size();
+    oracle.remove(JavaSourceCodeBase.OBJECT.getTypeName());
+    state.refresh();
+    assertEquals(size - 1, state.getCompilationUnits().size());
+    validateCompilationState();
+  }
+
+  public void testSourceOracleReplace() {
+    validateCompilationState();
+
+    int size = state.getCompilationUnits().size();
+    oracle.replace(new MockJavaSourceFile(JavaSourceCodeBase.OBJECT));
+    state.refresh();
+    assertEquals(size, state.getCompilationUnits().size());
+    validateCompilationState();
+  }
+
+  public void testSourceOracleReplaceWithSame() {
+    validateCompilationState();
+
+    int size = state.getCompilationUnits().size();
+    oracle.replace(JavaSourceCodeBase.OBJECT);
+    state.refresh();
+    assertEquals(size, state.getCompilationUnits().size());
+    validateCompilationState();
+  }
+
+  private void addGeneratedUnit(JavaSourceFile sourceFile) {
+    state.addGeneratedCompilationUnit(new SourceFileCompilationUnit(sourceFile) {
+      @Override
+      public boolean isGenerated() {
+        return true;
+      }
+    });
+  }
+
+  private void validateCompilationState(String... generatedTypeNames) {
+    // Save off the reflected collections.
+    Map<String, CompilationUnit> unitMap = state.getCompilationUnitMap();
+    Set<CompilationUnit> units = state.getCompilationUnits();
+
+    // Validate that the collections are consistent with each other.
+    assertEquals(new HashSet<CompilationUnit>(unitMap.values()), units);
+
+    // Save off a mutable copy of the source map and generated types to compare.
+    Map<String, JavaSourceFile> sourceMap = new HashMap<String, JavaSourceFile>(
+        oracle.getSourceMap());
+    Set<String> generatedTypes = new HashSet<String>(
+        Arrays.asList(generatedTypeNames));
+    assertEquals(sourceMap.size() + generatedTypes.size(), units.size());
+    for (Entry<String, CompilationUnit> entry : unitMap.entrySet()) {
+      // Validate source file internally consistent.
+      String className = entry.getKey();
+      CompilationUnit unit = entry.getValue();
+      assertEquals(className, unit.getTypeName());
+
+      // Find the matching resource (and remove it).
+      if (unit.isGenerated()) {
+        assertTrue(generatedTypes.contains(className));
+        assertNotNull(generatedTypes.remove(className));
+      } else {
+        assertTrue(sourceMap.containsKey(className));
+        // TODO: Validate the source file matches the resource.
+        assertNotNull(sourceMap.remove(className));
+      }
+    }
+    // The mutable sets should be empty now.
+    assertEquals(0, sourceMap.size());
+    assertEquals(0, generatedTypes.size());
+  }
+
+  private void validateUncompiled() {
+    for (CompilationUnit unit : state.getCompilationUnits()) {
+      assertNull(unit.getJdtCud());
+    }
+  }
+
+  /**
+   * Tweak this if you want to see the log output.
+   */
+  private TreeLogger createTreeLogger() {
+    boolean reallyLog = false;
+    if (reallyLog) {
+      AbstractTreeLogger logger = new PrintWriterTreeLogger();
+      logger.setMaxDetail(TreeLogger.ALL);
+      return logger;
+    } else {
+      return TreeLogger.NULL;
+    }
+  }
+}
diff --git a/dev/core/test/com/google/gwt/dev/jdt/GWTProblemTest.java b/dev/core/test/com/google/gwt/dev/javac/GWTProblemTest.java
similarity index 97%
rename from dev/core/test/com/google/gwt/dev/jdt/GWTProblemTest.java
rename to dev/core/test/com/google/gwt/dev/javac/GWTProblemTest.java
index 301bcba..865a490 100644
--- a/dev/core/test/com/google/gwt/dev/jdt/GWTProblemTest.java
+++ b/dev/core/test/com/google/gwt/dev/javac/GWTProblemTest.java
@@ -13,7 +13,7 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.dev.jdt;
+package com.google.gwt.dev.javac;
 
 import com.google.gwt.core.ext.TreeLogger.HelpInfo;
 
diff --git a/dev/core/test/com/google/gwt/dev/jdt/JSORestrictionsTest.java b/dev/core/test/com/google/gwt/dev/javac/JSORestrictionsTest.java
similarity index 90%
rename from dev/core/test/com/google/gwt/dev/jdt/JSORestrictionsTest.java
rename to dev/core/test/com/google/gwt/dev/javac/JSORestrictionsTest.java
index ac2d451..ce0305d 100644
--- a/dev/core/test/com/google/gwt/dev/jdt/JSORestrictionsTest.java
+++ b/dev/core/test/com/google/gwt/dev/javac/JSORestrictionsTest.java
@@ -13,7 +13,7 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.dev.jdt;
+package com.google.gwt.dev.javac;
 
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
@@ -42,7 +42,7 @@
     buggyCode.append("  int myStsate = 3;\n");
     buggyCode.append("}\n");
 
-    shouldGenerateError(buggyCode, "Line 4:  "
+    shouldGenerateError(buggyCode, "Line 4: "
         + JSORestrictionsChecker.ERR_INSTANCE_FIELD);
   }
 
@@ -53,7 +53,7 @@
     buggyCode.append("  protected Buggy(int howBuggy) { }\n");
     buggyCode.append("}\n");
 
-    shouldGenerateError(buggyCode, "Line 3:  "
+    shouldGenerateError(buggyCode, "Line 3: "
         + JSORestrictionsChecker.ERR_CONSTRUCTOR_WITH_PARAMETERS);
   }
 
@@ -67,7 +67,7 @@
     buggyCode.append("  MyJSO makeOne() { return new MyJSO(); }\n");
     buggyCode.append("}\n");
 
-    shouldGenerateError(buggyCode, "Line 6:  "
+    shouldGenerateError(buggyCode, "Line 6: "
         + JSORestrictionsChecker.ERR_NEW_JSO);
   }
 
@@ -78,7 +78,7 @@
     buggyCode.append("}\n");
 
     // The public constructor is implicit.
-    shouldGenerateError(buggyCode, "Line 2:  "
+    shouldGenerateError(buggyCode, "Line 2: "
         + JSORestrictionsChecker.ERR_NONPROTECTED_CONSTRUCTOR);
   }
 
@@ -95,7 +95,7 @@
     buggyCode.append("  }\n");
     buggyCode.append("}\n");
 
-    shouldGenerateError(buggyCode, "Line 6:  "
+    shouldGenerateError(buggyCode, "Line 6: "
         + JSORestrictionsChecker.errInterfaceWithMethods("Buggy.Squeaks"));
   }
 
@@ -106,7 +106,7 @@
     buggyCode.append("  protected Buggy() { while(true) { } }\n");
     buggyCode.append("}\n");
 
-    shouldGenerateError(buggyCode, "Line 3:  "
+    shouldGenerateError(buggyCode, "Line 3: "
         + JSORestrictionsChecker.ERR_NONEMPTY_CONSTRUCTOR);
   }
 
@@ -118,7 +118,7 @@
     buggyCode.append("  protected Buggy() { }\n");
     buggyCode.append("}\n");
 
-    shouldGenerateError(buggyCode, "Line 3:  "
+    shouldGenerateError(buggyCode, "Line 3: "
         + JSORestrictionsChecker.ERR_INSTANCE_METHOD_NONFINAL);
   }
 
@@ -129,7 +129,7 @@
     buggyCode.append("  Buggy() { }\n");
     buggyCode.append("}\n");
 
-    shouldGenerateError(buggyCode, "Line 3:  "
+    shouldGenerateError(buggyCode, "Line 3: "
         + JSORestrictionsChecker.ERR_NONPROTECTED_CONSTRUCTOR);
   }
 
@@ -142,7 +142,7 @@
     buggyCode.append("  }\n");
     buggyCode.append("}\n");
 
-    shouldGenerateError(buggyCode, "Line 3:  "
+    shouldGenerateError(buggyCode, "Line 3: "
         + JSORestrictionsChecker.ERR_IS_NONSTATIC_NESTED);
   }
 
@@ -154,7 +154,7 @@
     buggyCode.append("  public final Object clone() { return this; }\n");
     buggyCode.append("}\n");
 
-    shouldGenerateError(buggyCode, "Line 4:  "
+    shouldGenerateError(buggyCode, "Line 4: "
         + JSORestrictionsChecker.ERR_OVERRIDDEN_METHOD);
   }
 
@@ -179,13 +179,13 @@
     UnitTestTreeLogger.Builder builder = new UnitTestTreeLogger.Builder();
     builder.setLowestLogLevel(TreeLogger.ERROR);
     if (expectedError != null) {
-      builder.expectError("Errors in \'transient source for Buggy\'", null);
+      builder.expectError("Errors in \'/mock/Buggy\'", null);
       builder.expectError(expectedError, null);
-      builder.expectError(
-          "Compilation problem due to \'transient source for Buggy\'", null);
     }
     UnitTestTreeLogger logger = builder.createLogger();
-    TypeOracleTestingUtils.buildTypeOracleForCode("Buggy", buggyCode, logger);
+    CompilationUnit buggyCup = new MockCompilationUnit("Buggy",
+        buggyCode.toString());
+    TypeOracleTestingUtils.buildStandardTypeOracleWith(logger, buggyCup);
     logger.assertCorrectLogEntries();
   }
 
diff --git a/dev/core/test/com/google/gwt/dev/javac/JavaCompilationSuite.java b/dev/core/test/com/google/gwt/dev/javac/JavaCompilationSuite.java
new file mode 100644
index 0000000..1505a36
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/javac/JavaCompilationSuite.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2008 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.dev.javac.impl.JavaSourceOracleImplTest;
+import com.google.gwt.dev.javac.impl.JdtBehaviorTest;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+/**
+ * Tests script and resource injection.
+ */
+public class JavaCompilationSuite {
+  public static Test suite() {
+    TestSuite suite = new TestSuite(JavaCompilationSuite.class.getName());
+
+    suite.addTestSuite(BinaryTypeReferenceRestrictionsCheckerTest.class);
+    suite.addTestSuite(CompilationStateTest.class);
+    suite.addTestSuite(GWTProblemTest.class);
+    suite.addTestSuite(JdtBehaviorTest.class);
+    suite.addTestSuite(JdtCompilerTest.class);
+    suite.addTestSuite(JSORestrictionsTest.class);
+    suite.addTestSuite(JavaSourceOracleImplTest.class);
+    suite.addTestSuite(LongFromJSNITest.class);
+    suite.addTestSuite(TypeOracleMediatorTest.class);
+
+    return suite;
+  }
+}
diff --git a/dev/core/test/com/google/gwt/dev/javac/JavaSourceCodeBase.java b/dev/core/test/com/google/gwt/dev/javac/JavaSourceCodeBase.java
new file mode 100644
index 0000000..d3679d9
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/javac/JavaSourceCodeBase.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2008 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.dev.javac.impl.JavaResourceBase;
+import com.google.gwt.dev.javac.impl.MockJavaSourceFile;
+
+/**
+ * Contains standard Java source files for testing.
+ */
+public class JavaSourceCodeBase {
+
+  public static final MockJavaSourceFile ANNOTATION = new MockJavaSourceFile(
+      JavaResourceBase.ANNOTATION);
+  public static final MockJavaSourceFile BAR = new MockJavaSourceFile(
+      JavaResourceBase.BAR);
+  public static final MockJavaSourceFile CLASS = new MockJavaSourceFile(
+      JavaResourceBase.CLASS);
+  public static final MockJavaSourceFile FOO = new MockJavaSourceFile(
+      JavaResourceBase.FOO);
+  public static final MockJavaSourceFile JAVASCRIPTOBJECT = new MockJavaSourceFile(
+      JavaResourceBase.JAVASCRIPTOBJECT);
+  public static final MockJavaSourceFile MAP = new MockJavaSourceFile(
+      JavaResourceBase.MAP);
+  public static final MockJavaSourceFile OBJECT = new MockJavaSourceFile(
+      JavaResourceBase.OBJECT);
+  public static final MockJavaSourceFile SERIALIZABLE = new MockJavaSourceFile(
+      JavaResourceBase.SERIALIZABLE);
+  public static final MockJavaSourceFile STRING = new MockJavaSourceFile(
+      JavaResourceBase.STRING);
+  public static final MockJavaSourceFile SUPPRESS_WARNINGS = new MockJavaSourceFile(
+      JavaResourceBase.SUPPRESS_WARNINGS);
+
+  public static MockJavaSourceFile[] getStandardResources() {
+    return new MockJavaSourceFile[] {
+        ANNOTATION, CLASS, JAVASCRIPTOBJECT, MAP, OBJECT, SERIALIZABLE, STRING,
+        SUPPRESS_WARNINGS};
+  }
+}
diff --git a/dev/core/test/com/google/gwt/dev/javac/JdtCompilerTest.java b/dev/core/test/com/google/gwt/dev/javac/JdtCompilerTest.java
new file mode 100644
index 0000000..539abe7
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/javac/JdtCompilerTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2008 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.dev.javac.impl.SourceFileCompilationUnit;
+
+import junit.framework.TestCase;
+
+import org.eclipse.jdt.internal.compiler.CompilationResult;
+import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Test class for {@link JdtCompiler}.
+ */
+public class JdtCompilerTest extends TestCase {
+
+  static void assertUnitsCompiled(Collection<CompilationUnit> units) {
+    for (CompilationUnit unit : units) {
+      CompilationUnitDeclaration cud = unit.getJdtCud();
+      assertFalse(cud.hasErrors());
+      CompilationResult result = cud.compilationResult();
+      assertFalse(result.hasProblems());
+      assertTrue(result.getClassFiles().length > 0);
+    }
+  }
+
+  static void assertUnitHasErrors(CompilationUnit unit, int numErrors) {
+    CompilationUnitDeclaration cud = unit.getJdtCud();
+    CompilationResult result = cud.compilationResult();
+    assertTrue(result.hasErrors());
+    assertEquals(numErrors, result.getErrors().length);
+    assertTrue(result.getClassFiles().length > 0);
+  }
+
+  public void testCompile() {
+    List<CompilationUnit> units = new ArrayList<CompilationUnit>();
+    addAll(units, JavaSourceCodeBase.getStandardResources());
+    addAll(units, JavaSourceCodeBase.FOO, JavaSourceCodeBase.BAR);
+    JdtCompiler.compile(units);
+    assertUnitsCompiled(units);
+  }
+
+  public void testCompileError() {
+    List<CompilationUnit> units = new ArrayList<CompilationUnit>();
+    addAll(units, JavaSourceCodeBase.getStandardResources());
+    addAll(units, JavaSourceCodeBase.BAR);
+    JdtCompiler.compile(units);
+    assertUnitsCompiled(units.subList(0, units.size() - 1));
+    assertUnitHasErrors(units.get(units.size() - 1), 1);
+  }
+
+  public void testCompileIncremental() {
+    List<CompilationUnit> units = new ArrayList<CompilationUnit>();
+    addAll(units, JavaSourceCodeBase.getStandardResources());
+    JdtCompiler.compile(units);
+    assertUnitsCompiled(units);
+    addAll(units, JavaSourceCodeBase.FOO, JavaSourceCodeBase.BAR);
+    JdtCompiler.compile(units);
+    assertUnitsCompiled(units);
+  }
+
+  private void addAll(Collection<CompilationUnit> units,
+      JavaSourceFile... sourceFiles) {
+    for (JavaSourceFile sourceFile : sourceFiles) {
+      units.add(new SourceFileCompilationUnit(sourceFile));
+    }
+  }
+}
diff --git a/dev/core/test/com/google/gwt/dev/jdt/LongFromJSNITest.java b/dev/core/test/com/google/gwt/dev/javac/LongFromJSNITest.java
similarity index 86%
rename from dev/core/test/com/google/gwt/dev/jdt/LongFromJSNITest.java
rename to dev/core/test/com/google/gwt/dev/javac/LongFromJSNITest.java
index 4bb445c..0269e7b 100644
--- a/dev/core/test/com/google/gwt/dev/jdt/LongFromJSNITest.java
+++ b/dev/core/test/com/google/gwt/dev/javac/LongFromJSNITest.java
@@ -13,32 +13,23 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.dev.jdt;
+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.TypeOracle;
+import com.google.gwt.dev.javac.CompilationUnit;
 import com.google.gwt.dev.util.UnitTestTreeLogger;
 
 import junit.framework.TestCase;
 
+import java.util.HashSet;
+import java.util.Set;
+
 /**
  * Test access to longs from JSNI.
  */
 public class LongFromJSNITest extends TestCase {
-  public void testBogusRef() throws UnableToCompleteException {
-    StringBuffer code = new StringBuffer();
-    code.append("class Buggy {                                  \n");
-    code.append("volatile long x = -1;                          \n");
-    code.append("native void jsniMeth() /*-{                    \n");
-    code.append("  // @\\bogus refs should just be skipped      \n");
-    code.append("  $wnd.alert(\"x is: \"+this.@Buggy::x); }-*/; \n");
-    code.append("}                                              \n");
-
-    shouldGenerateError(code, 3,
-        "Referencing field 'Buggy.x': type 'long' is not safe to access in JSNI code");
-  }
-
   public void testCyclicReferences() throws UnableToCompleteException {
     {
       StringBuffer buggy = new StringBuffer();
@@ -204,19 +195,6 @@
         "Referencing method 'Buggy.m': return type 'long' is not safe to access in JSNI code");
   }
 
-  public void testRefInString() throws UnableToCompleteException {
-    {
-      StringBuffer code = new StringBuffer();
-      code.append("import com.google.gwt.core.client.UnsafeNativeLong;");
-      code.append("class Buggy {\n");
-      code.append("  void print(long x) { }\n");
-      code.append("  native void jsniMeth() /*-{ 'this.@Buggy::print(J)(0)'; }-*/;\n");
-      code.append("}\n");
-
-      shouldGenerateNoError(code);
-    }
-  }
-
   public void testUnsafeAnnotation() throws UnableToCompleteException {
     {
       StringBuffer code = new StringBuffer();
@@ -231,6 +209,19 @@
     }
   }
 
+  public void testRefInString() throws UnableToCompleteException {
+    {
+      StringBuffer code = new StringBuffer();
+      code.append("import com.google.gwt.core.client.UnsafeNativeLong;");
+      code.append("class Buggy {\n");
+      code.append("  void print(long x) { }\n");
+      code.append("  native void jsniMeth() /*-{ 'this.@Buggy::print(J)(0)'; }-*/;\n");
+      code.append("}\n");
+
+      shouldGenerateNoError(code);
+    }
+  }
+
   public void testViolator() throws UnableToCompleteException {
     {
       StringBuffer okay = new StringBuffer();
@@ -273,30 +264,26 @@
     }
   }
 
-  private void addLongCheckingCups(TypeOracleBuilder builder)
-      throws UnableToCompleteException {
-    {
-      StringBuilder code = new StringBuilder();
-      code.append("package com.google.gwt.core.client;\n");
-      code.append("public @interface UnsafeNativeLong {\n");
-      code.append("}\n");
-
-      TypeOracleTestingUtils.addCup(builder,
-          "com.google.gwt.core.client.UnsafeNativeLong", code);
-    }
+  private void addLongCheckingCups(Set<CompilationUnit> units) {
+    StringBuilder code = new StringBuilder();
+    code.append("package com.google.gwt.core.client;\n");
+    code.append("public @interface UnsafeNativeLong {\n");
+    code.append("}\n");
+    units.add(new MockCompilationUnit(
+        "com.google.gwt.core.client.UnsafeNativeLong", code.toString()));
   }
 
   private TypeOracle buildOracle(CharSequence buggyCode,
       CharSequence extraCode, UnitTestTreeLogger logger)
       throws UnableToCompleteException {
-    TypeOracleBuilder builder = new TypeOracleBuilder();
-    TypeOracleTestingUtils.addStandardCups(builder);
-    addLongCheckingCups(builder);
-    TypeOracleTestingUtils.addCup(builder, "Buggy", buggyCode);
+    Set<CompilationUnit> units = new HashSet<CompilationUnit>();
+    addLongCheckingCups(units);
+    units.add(new MockCompilationUnit("Buggy", buggyCode.toString()));
     if (extraCode != null) {
-      TypeOracleTestingUtils.addCup(builder, "Extra", extraCode);
+      units.add(new MockCompilationUnit("Extra", extraCode.toString()));
     }
-    return builder.build(logger);
+    return TypeOracleTestingUtils.buildStandardTypeOracleWith(logger,
+        units.toArray(new CompilationUnit[units.size()]));
   }
 
   private void shouldGenerateError(CharSequence buggyCode,
@@ -305,11 +292,9 @@
     UnitTestTreeLogger.Builder b = new UnitTestTreeLogger.Builder();
     b.setLowestLogLevel(TreeLogger.ERROR);
     if (message != null) {
-      b.expect(TreeLogger.ERROR, "Errors in 'transient source for Buggy'", null);
-      final String fullMessage = "Line " + line + ":  " + message;
+      b.expect(TreeLogger.ERROR, "Errors in '/mock/Buggy'", null);
+      final String fullMessage = "Line " + line + ": " + message;
       b.expect(TreeLogger.ERROR, fullMessage, null);
-      b.expect(TreeLogger.ERROR,
-          "Compilation problem due to 'transient source for Buggy'", null);
     }
     UnitTestTreeLogger logger = b.createLogger();
     TypeOracle oracle = buildOracle(buggyCode, extraCode, logger);
diff --git a/dev/core/test/com/google/gwt/dev/javac/MockCompilationUnit.java b/dev/core/test/com/google/gwt/dev/javac/MockCompilationUnit.java
new file mode 100644
index 0000000..9f07db9
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/javac/MockCompilationUnit.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2008 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;
+
+public class MockCompilationUnit extends CompilationUnit {
+
+  private final String typeName;
+  private final String source;
+
+  public MockCompilationUnit(String typeName) {
+    this.typeName = typeName;
+    this.source = null;
+  }
+
+  public MockCompilationUnit(String typeName, String source) {
+    this.typeName = typeName;
+    this.source = source;
+  }
+
+  public String getDisplayLocation() {
+    return "/mock/" + getTypeName();
+  }
+
+  @Override
+  public String getSource() {
+    assert source != null;
+    return source;
+  }
+
+  public String getTypeName() {
+    return typeName;
+  }
+
+  public boolean isGenerated() {
+    return true;
+  }
+}
\ No newline at end of file
diff --git a/dev/core/test/com/google/gwt/dev/javac/MockJavaSourceOracle.java b/dev/core/test/com/google/gwt/dev/javac/MockJavaSourceOracle.java
new file mode 100644
index 0000000..bb77e8c
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/javac/MockJavaSourceOracle.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2008 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.dev.javac.JavaSourceFile;
+import com.google.gwt.dev.javac.JavaSourceOracle;
+
+import junit.framework.Assert;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A simple {@link ResourceOracle} for testing.
+ */
+public class MockJavaSourceOracle implements JavaSourceOracle {
+
+  private Map<String, JavaSourceFile> exportedMap = Collections.emptyMap();
+  private Set<JavaSourceFile> exportedValues = Collections.emptySet();
+
+  public MockJavaSourceOracle(JavaSourceFile... sourceFiles) {
+    add(sourceFiles);
+  }
+
+  public Set<String> getClassNames() {
+    return exportedMap.keySet();
+  }
+
+  public Set<JavaSourceFile> getSourceFiles() {
+    return exportedValues;
+  }
+
+  public Map<String, JavaSourceFile> getSourceMap() {
+    return exportedMap;
+  }
+
+  void add(JavaSourceFile... sourceFiles) {
+    Map<String, JavaSourceFile> newMap = new HashMap<String, JavaSourceFile>(
+        exportedMap);
+    for (JavaSourceFile sourceFile : sourceFiles) {
+      String className = sourceFile.getTypeName();
+      Assert.assertFalse(newMap.containsKey(className));
+      newMap.put(className, sourceFile);
+    }
+    export(newMap);
+  }
+
+  void remove(String... classNames) {
+    Map<String, JavaSourceFile> newMap = new HashMap<String, JavaSourceFile>(
+        exportedMap);
+    for (String className : classNames) {
+      JavaSourceFile oldValue = newMap.remove(className);
+      Assert.assertNotNull(oldValue);
+    }
+    export(newMap);
+  }
+
+  void replace(JavaSourceFile... sourceFiles) {
+    Map<String, JavaSourceFile> newMap = new HashMap<String, JavaSourceFile>(
+        exportedMap);
+    for (JavaSourceFile sourceFile : sourceFiles) {
+      String className = sourceFile.getTypeName();
+      Assert.assertTrue(newMap.containsKey(className));
+      newMap.put(className, sourceFile);
+    }
+    export(newMap);
+  }
+
+  private void export(Map<String, JavaSourceFile> newMap) {
+    exportedMap = Collections.unmodifiableMap(newMap);
+    // Make a new hash set for constant lookup.
+    exportedValues = Collections.unmodifiableSet(new HashSet<JavaSourceFile>(
+        exportedMap.values()));
+  }
+
+}
\ No newline at end of file
diff --git a/dev/core/test/com/google/gwt/dev/jdt/TypeOracleBuilderTest.java b/dev/core/test/com/google/gwt/dev/javac/TypeOracleMediatorTest.java
similarity index 62%
rename from dev/core/test/com/google/gwt/dev/jdt/TypeOracleBuilderTest.java
rename to dev/core/test/com/google/gwt/dev/javac/TypeOracleMediatorTest.java
index 02599d1..7218d07 100644
--- a/dev/core/test/com/google/gwt/dev/jdt/TypeOracleBuilderTest.java
+++ b/dev/core/test/com/google/gwt/dev/javac/TypeOracleMediatorTest.java
@@ -13,11 +13,10 @@
  * License for the specific language governing permissions and limitations under
  * the License.
  */
-package com.google.gwt.dev.jdt;
+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.CompilationUnitProvider;
 import com.google.gwt.core.ext.typeinfo.JArrayType;
 import com.google.gwt.core.ext.typeinfo.JClassType;
 import com.google.gwt.core.ext.typeinfo.JField;
@@ -27,114 +26,34 @@
 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 java.io.IOException;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
+import java.util.Set;
 
-public class TypeOracleBuilderTest extends TestCase {
-  private static abstract class TestCup implements CompilationUnitProvider {
-    private final String packageName;
+public class TypeOracleMediatorTest extends TestCase {
 
-    private final String[] typeNames;
-
-    /**
-     * Creates a new {@code TestCup} with several types. The first type in
-     * {@code typeNames} is considered to be the main type.
-     * 
-     * @param packageName the package for the types in this {@code TestCup}
-     * @param typeNames the types for this {@code TestCup}. Must have at least
-     *          one type. The first type is considered to be the main type for
-     *          this {@code TestCup}.
-     */
-    public TestCup(String packageName, String... typeNames) {
-      this.packageName = packageName;
-      this.typeNames = typeNames;
-      assert typeNames != null && typeNames.length > 0;
-      for (int i = 0; i < typeNames.length; i++) {
-        String typeName = typeNames[i];
-        register(typeName, this);
+  private abstract class CheckedMockCompilationUnit extends MockCompilationUnit {
+    public CheckedMockCompilationUnit(String packageName,
+        String shortMainTypeName, String... shortTypeNames) {
+      super(Shared.makeTypeName(packageName, shortMainTypeName));
+      register(getTypeName(), this);
+      for (String shortTypeName : shortTypeNames) {
+        register(Shared.makeTypeName(packageName, shortTypeName), this);
       }
     }
 
+    @Override
+    public abstract String getSource();
+
     public abstract void check(JClassType type) throws NotFoundException;
-
-    public long getLastModified() throws UnableToCompleteException {
-      return 0;
-    }
-
-    public String getLocation() {
-      return "transient source for " + this.packageName + "."
-          + this.typeNames[0];
-    }
-
-    public String getMainTypeName() {
-      return typeNames[0];
-    }
-
-    public String getPackageName() {
-      return packageName;
-    }
-
-    public String[] getTypeNames() {
-      return typeNames;
-    }
-
-    public boolean isTransient() {
-      return true;
-    }
-  }
-
-  private static Map<String, TestCup> publicTypeNameToTestCupMap = new HashMap<String, TestCup>();
-
-  /**
-   * Creates a non-transient {@link CompilationUnitProvider} and
-   * adds it the {@link TypeOracleBuilder}.
-   * 
-   * @throws UnableToCompleteException
-   */
-  private static void addNonTransientCompilationUnitProvider(
-      TypeOracleBuilder builder, String qualifiedTypeName, CharSequence code)
-      throws UnableToCompleteException {
-
-    final CompilationUnitProvider cup = TypeOracleTestingUtils.createCup(
-        qualifiedTypeName, code);
-
-    CompilationUnitProvider nonTransientCup = new CompilationUnitProvider() {
-      public long getLastModified() throws UnableToCompleteException {
-        return cup.getLastModified();
-      }
-
-      public String getLocation() {
-        /*
-         * Fake out TypeOracleBuilder so it does not look for a file at this
-         * location.
-         */
-        return "http://" + cup.getLocation();
-      }
-
-      public String getMainTypeName() {
-        return cup.getMainTypeName();
-      }
-
-      public String getPackageName() {
-        return cup.getPackageName();
-      }
-
-      public char[] getSource() throws UnableToCompleteException {
-        return cup.getSource();
-      }
-
-      public boolean isTransient() {
-        return false;
-      }
-    };
-
-    builder.addCompilationUnit(nonTransientCup);
   }
 
   private static void assertEqualArraysUnordered(Object[] expected,
@@ -152,34 +71,52 @@
     }
   }
 
-  private static void check(JClassType classInfo) throws NotFoundException {
-    final String qName = classInfo.getQualifiedSourceName();
-    TestCup cup = publicTypeNameToTestCupMap.get(qName);
-    assertNotNull(cup); // should've been declared during TestCup ctor
-    cup.check(classInfo);
+  private static void assertIsAssignable(JClassType from, JClassType to) {
+    assertTrue("'" + from + "' should be assignable to '" + to + "'",
+        from.isAssignableTo(to));
+    assertTrue("'" + to + "' should be assignable from '" + from + "'",
+        to.isAssignableFrom(from));
   }
 
-  private static void register(String simpleTypeName, TestCup cup) {
-    String qName = cup.getPackageName() + "." + simpleTypeName;
-    publicTypeNameToTestCupMap.put(qName, cup);
+  private static void assertIsNotAssignable(JClassType from, JClassType to) {
+    assertFalse(from.isAssignableTo(to));
+    assertFalse(to.isAssignableFrom(from));
   }
 
-  protected TestCup CU_AfterAssimilate = new TestCup("test.assim",
-      "AfterAssimilate") {
+  private static void recordAssignability(
+      Map<JClassType, Set<JClassType>> assignabilityMap, JClassType from,
+      JClassType to) {
+    Set<JClassType> set = assignabilityMap.get(from);
+    if (set == null) {
+      set = new HashSet<JClassType>();
+      assignabilityMap.put(from, set);
+    }
+    set.add(to);
+  }
+
+  /**
+   * Public so that this will be initialized before the CUs.
+   */
+  public final Map<String, CheckedMockCompilationUnit> publicTypeNameToTestCupMap = new HashMap<String, CheckedMockCompilationUnit>();
+
+  protected CheckedMockCompilationUnit CU_AfterAssimilate = new CheckedMockCompilationUnit(
+      "test.assim", "AfterAssimilate") {
     public void check(JClassType type) {
-      // Don't need to check the type itself.
+      assertEquals("test.assim.BeforeAssimilate",
+          type.getSuperclass().getQualifiedSourceName());
     }
 
-    public char[] getSource() {
+    public String getSource() {
       StringBuffer sb = new StringBuffer();
       sb.append("package test.assim;\n");
       sb.append("class AfterAssimilate extends BeforeAssimilate { }");
-      return sb.toString().toCharArray();
+      return sb.toString();
     }
   };
 
-  protected TestCup CU_Assignable = new TestCup("test.sub", "Derived",
-      "BaseInterface", "DerivedInterface", "Derived.Nested") {
+  protected CheckedMockCompilationUnit CU_Assignable = new CheckedMockCompilationUnit(
+      "test.sub", "Derived", "BaseInterface", "DerivedInterface",
+      "Derived.Nested") {
     public void check(JClassType type) {
       if ("Derived".equals(type.getSimpleSourceName()))
         checkDerived(type);
@@ -187,7 +124,7 @@
         checkNested(type);
     }
 
-    public char[] getSource() {
+    public String getSource() {
       StringBuffer sb = new StringBuffer();
       sb.append("package test.sub;\n");
       sb.append("import test.Outer;");
@@ -196,7 +133,7 @@
       sb.append("public class Derived extends Outer.Inner {\n");
       sb.append("   public static class Nested extends Outer.Inner implements DerivedInterface { }\n");
       sb.append("}\n");
-      return sb.toString().toCharArray();
+      return sb.toString();
     }
 
     private void checkDerived(JClassType type) {
@@ -209,22 +146,23 @@
     }
   };
 
-  protected TestCup CU_BeforeAssimilate = new TestCup("test.assim",
-      "BeforeAssimilate") {
+  protected CheckedMockCompilationUnit CU_BeforeAssimilate = new CheckedMockCompilationUnit(
+      "test.assim", "BeforeAssimilate") {
     public void check(JClassType type) {
-      // Don't need to check the type itself.
+      assertEquals("test.assim.BeforeAssimilate", type.getQualifiedSourceName());
     }
 
-    public char[] getSource() {
+    public String getSource() {
       StringBuffer sb = new StringBuffer();
       sb.append("package test.assim;\n");
       sb.append("class BeforeAssimilate { }");
-      return sb.toString().toCharArray();
+      return sb.toString();
     }
   };
 
-  protected TestCup CU_BindToTypeScope = new TestCup("test", "BindToTypeScope",
-      "BindToTypeScope.Object", "BindToTypeScope.DerivedObject") {
+  protected CheckedMockCompilationUnit CU_BindToTypeScope = new CheckedMockCompilationUnit(
+      "test", "BindToTypeScope", "BindToTypeScope.Object",
+      "BindToTypeScope.DerivedObject") {
 
     public void check(JClassType type) throws NotFoundException {
       if ("BindToTypeScope".equals(type.getSimpleSourceName()))
@@ -249,14 +187,14 @@
       assertEquals("test.BindToTypeScope.Object", type.getQualifiedSourceName());
     }
 
-    public char[] getSource() {
+    public String getSource() {
       StringBuffer sb = new StringBuffer();
       sb.append("package test;\n");
       sb.append("public class BindToTypeScope {\n");
       sb.append("   public static class Object { }\n");
       sb.append("   public static class DerivedObject extends Object { }\n");
       sb.append("}\n");
-      return sb.toString().toCharArray();
+      return sb.toString();
     }
 
     private void checkDerivedObject(JClassType type) throws NotFoundException {
@@ -273,23 +211,25 @@
     }
   };
 
-  protected TestCup CU_DeclaresInnerGenericType = new TestCup(
-      "parameterized.type.build.dependency", "Class1") {
+  protected CheckedMockCompilationUnit CU_DeclaresInnerGenericType = new CheckedMockCompilationUnit(
+      "parameterized.type.build.dependency", "Class1", "Class1.Inner") {
     @Override
     public void check(JClassType type) throws NotFoundException {
+      assertNotNull(type.isGenericType());
     }
 
-    public char[] getSource() throws UnableToCompleteException {
+    public String getSource() {
       StringBuilder sb = new StringBuilder();
       sb.append("package parameterized.type.build.dependency;\n");
       sb.append("public class Class1<T> {\n");
       sb.append("  public interface Inner<T> {}\n");
       sb.append("}\n");
-      return sb.toString().toCharArray();
+      return sb.toString();
     }
   };
 
-  protected TestCup CU_DefaultClass = new TestCup("test", "DefaultClass") {
+  protected CheckedMockCompilationUnit CU_DefaultClass = new CheckedMockCompilationUnit(
+      "test", "DefaultClass") {
     public void check(JClassType type) {
       assertEquals("DefaultClass", type.getSimpleSourceName());
       assertEquals("test.DefaultClass", type.getQualifiedSourceName());
@@ -301,45 +241,47 @@
       assertEquals(0, type.getFields().length);
     }
 
-    public char[] getSource() {
+    public String getSource() {
       StringBuffer sb = new StringBuffer();
       sb.append("package test;\n");
       sb.append("public class DefaultClass extends Object { }\n");
-      return sb.toString().toCharArray();
+      return sb.toString();
     }
   };
 
-  protected TestCup CU_ExtendsGenericList = new TestCup("test.refresh",
-      "ExtendsGenericList") {
+  protected CheckedMockCompilationUnit CU_ExtendsGenericList = new CheckedMockCompilationUnit(
+      "test.refresh", "ExtendsGenericList") {
 
     @Override
     public void check(JClassType type) throws NotFoundException {
+      assertNotNull(type.getSuperclass().isParameterized());
     }
 
-    public char[] getSource() {
+    public String getSource() {
       StringBuilder sb = new StringBuilder();
       sb.append("package test.refresh;\n");
       sb.append("class ExtendsGenericList extends GenericList<Object> {}");
-      return sb.toString().toCharArray();
+      return sb.toString();
     }
   };
 
-  protected TestCup CU_ExtendsParameterizedType = new TestCup(
+  protected CheckedMockCompilationUnit CU_ExtendsParameterizedType = new CheckedMockCompilationUnit(
       "parameterized.type.build.dependency", "Class2") {
     @Override
     public void check(JClassType type) throws NotFoundException {
+      assertNotNull(type.getSuperclass().isParameterized());
     }
 
-    public char[] getSource() throws UnableToCompleteException {
+    public String getSource() {
       StringBuilder sb = new StringBuilder();
       sb.append("package parameterized.type.build.dependency;\n");
       sb.append("public class Class2 extends Class1<Object> {}\n");
-      return sb.toString().toCharArray();
+      return sb.toString();
     }
   };
 
-  protected TestCup CU_FieldsAndTypes = new TestCup("test", "Fields",
-      "SomeType") {
+  protected CheckedMockCompilationUnit CU_FieldsAndTypes = new CheckedMockCompilationUnit(
+      "test", "Fields", "SomeType") {
     public void check(JClassType type) throws NotFoundException {
       if ("Fields".equals(type.getSimpleSourceName())) {
         assertEquals("test.Fields", type.getQualifiedSourceName());
@@ -423,12 +365,13 @@
         assertEquals("int[][]", fieldType.getQualifiedSourceName());
 
       } else {
-        // No need to check SomeType since there's already a DefaultClass
+        // No need to check SomeType since
+        // there's already a DefaultClass
         // test.
       }
     }
 
-    public char[] getSource() {
+    public String getSource() {
       StringBuffer sb = new StringBuffer();
       sb.append("package test;\n");
       sb.append("class SomeType { }");
@@ -446,56 +389,98 @@
       sb.append("   private SomeType[] someTypeArray;\n");
       sb.append("   private int[][] intArrayArray;\n");
       sb.append("}\n");
-      return sb.toString().toCharArray();
+      return sb.toString();
     }
   };
 
-  protected TestCup CU_GenericList = new TestCup("test.refresh", "GenericList") {
+  protected CheckedMockCompilationUnit CU_GenericList = new CheckedMockCompilationUnit(
+      "test.refresh", "GenericList") {
     @Override
     public void check(JClassType type) throws NotFoundException {
+      assertNotNull(type.isGenericType());
     }
 
-    public char[] getSource() throws UnableToCompleteException {
+    public String getSource() {
       StringBuilder sb = new StringBuilder();
       sb.append("package test.refresh;\n");
       sb.append("class GenericList<T> {\n");
       sb.append("  public static final int CONSTANT = 0;\n");
       sb.append("}");
-      return sb.toString().toCharArray();
+      return sb.toString();
     }
   };
 
-  protected TestCup CU_HasSyntaxErrors = new TestCup("test", "HasSyntaxErrors",
-      "NoSyntaxErrors") {
+  protected CheckedMockCompilationUnit CU_HasSyntaxErrors = new CheckedMockCompilationUnit(
+      "test", "HasSyntaxErrors", "NoSyntaxErrors") {
     public void check(JClassType classInfo) {
       fail("This class should have been removed");
     }
 
-    public char[] getSource() {
+    public String getSource() {
       StringBuffer sb = new StringBuffer();
       sb.append("package test;\n");
       sb.append("class NoSyntaxErrors { }\n");
       sb.append("public class HasSyntaxErrors { a syntax error }\n");
-      return sb.toString().toCharArray();
+      return sb.toString();
     }
   };
 
-  protected TestCup CU_HasUnresolvedSymbols = new TestCup("test", "Invalid",
-      "Valid") {
+  protected CheckedMockCompilationUnit CU_HasUnresolvedSymbols = new CheckedMockCompilationUnit(
+      "test", "Invalid", "Valid") {
     public void check(JClassType classInfo) {
       fail("Both classes should have been removed");
     }
 
-    public char[] getSource() {
+    public String getSource() {
       StringBuffer sb = new StringBuffer();
       sb.append("package test;\n");
       sb.append("public class Invalid extends NoSuchClass { }\n");
       sb.append("class Valid extends Object { }\n");
-      return sb.toString().toCharArray();
+      return sb.toString();
     }
   };
 
-  protected TestCup CU_MetaData = new TestCup("test", "MetaData") {
+  protected CheckedMockCompilationUnit CU_LocalClass = new CheckedMockCompilationUnit(
+      "test", "Enclosing", "Enclosing.1") {
+
+    public void check(JClassType type) {
+      final String name = type.getSimpleSourceName();
+      if ("Enclosing".equals(name))
+        checkEnclosing(type);
+      else
+        checkLocal(type);
+    }
+
+    public void checkEnclosing(JClassType type) {
+      assertEquals("Enclosing", type.getSimpleSourceName());
+      assertEquals("test.Enclosing", type.getQualifiedSourceName());
+      JClassType[] nested = type.getNestedTypes();
+      assertEquals(1, nested.length);
+      JClassType inner = nested[0];
+      assertEquals("test.Enclosing.1", inner.getQualifiedSourceName());
+    }
+
+    public void checkLocal(JClassType type) {
+      assertEquals("1", type.getSimpleSourceName());
+      assertEquals("test.Enclosing.1", type.getQualifiedSourceName());
+      assertEquals("test.Enclosing",
+          type.getEnclosingType().getQualifiedSourceName());
+    }
+
+    public String getSource() {
+      StringBuffer sb = new StringBuffer();
+      sb.append("package test;\n");
+      sb.append("public class Enclosing {\n");
+      sb.append("   public static Object getLocal() {");
+      sb.append("     return new Object() { };\n");
+      sb.append("   }\n");
+      sb.append("}\n");
+      return sb.toString();
+    }
+  };
+
+  protected CheckedMockCompilationUnit CU_MetaData = new CheckedMockCompilationUnit(
+      "test", "MetaData") {
 
     public void check(JClassType type) throws NotFoundException {
       {
@@ -553,7 +538,7 @@
       }
     }
 
-    public char[] getSource() {
+    public String getSource() {
       StringBuffer sb = new StringBuffer();
       sb.append("package test;\n");
       sb.append("/**\n");
@@ -574,11 +559,12 @@
       sb.append("   private Object bar = null;\n");
       sb.append("   private Object noMd = null;\n");
       sb.append("}\n");
-      return sb.toString().toCharArray();
+      return sb.toString();
     }
   };
 
-  protected TestCup CU_MethodsAndParams = new TestCup("test", "Methods") {
+  protected CheckedMockCompilationUnit CU_MethodsAndParams = new CheckedMockCompilationUnit(
+      "test", "Methods") {
 
     public void check(JClassType type) throws NotFoundException {
       TypeOracle tio = type.getOracle();
@@ -634,7 +620,7 @@
       assertEquals(0, thrownTypes.length);
     }
 
-    public char[] getSource() {
+    public String getSource() {
       StringBuffer sb = new StringBuffer();
       sb.append("package test;\n");
       sb.append("public class Methods {\n");
@@ -645,25 +631,27 @@
       sb.append("   public void overloaded(int x, Object y) throws Throwable { return; }\n");
       sb.append("   public Object overloaded(int x, char y) { return null; }\n");
       sb.append("}\n");
-      return sb.toString().toCharArray();
+      return sb.toString();
     }
   };
 
-  protected TestCup CU_Object = new TestCup("java.lang", "Object") {
+  protected CheckedMockCompilationUnit CU_Object = new CheckedMockCompilationUnit(
+      "java.lang", "Object") {
     public void check(JClassType type) {
       assertEquals("Object", type.getSimpleSourceName());
       assertEquals("java.lang.Object", type.getQualifiedSourceName());
     }
 
-    public char[] getSource() {
+    public String getSource() {
       StringBuffer sb = new StringBuffer();
       sb.append("package java.lang;");
       sb.append("public class Object { }");
-      return sb.toString().toCharArray();
+      return sb.toString();
     }
   };
 
-  protected TestCup CU_OuterInner = new TestCup("test", "Outer", "Outer.Inner") {
+  protected CheckedMockCompilationUnit CU_OuterInner = new CheckedMockCompilationUnit(
+      "test", "Outer", "Outer.Inner") {
 
     public void check(JClassType type) {
       final String name = type.getSimpleSourceName();
@@ -689,75 +677,87 @@
       assertEquals("test.Outer.Inner", inner.getQualifiedSourceName());
     }
 
-    public char[] getSource() {
+    public String getSource() {
       StringBuffer sb = new StringBuffer();
       sb.append("package test;\n");
       sb.append("public class Outer {\n");
       sb.append("   public static class Inner { }\n");
       sb.append("}\n");
-      return sb.toString().toCharArray();
+      return sb.toString();
     }
   };
 
-  protected TestCup CU_ReferencesGenericListConstant = new TestCup(
+  protected CheckedMockCompilationUnit CU_ReferencesGenericListConstant = new CheckedMockCompilationUnit(
       "test.refresh", "ReferencesGenericListConstant") {
     @Override
     public void check(JClassType type) throws NotFoundException {
+      assertEquals("test.refresh.ReferencesGenericListConstant",
+          type.getQualifiedSourceName());
     }
 
-    public char[] getSource() throws UnableToCompleteException {
+    public String getSource() {
       StringBuilder sb = new StringBuilder();
       sb.append("package test.refresh;\n");
       sb.append("class ReferencesGenericListConstant {\n");
       sb.append("  public static final int MY_CONSTANT = GenericList.CONSTANT;\n");
       sb.append("}");
-      return sb.toString().toCharArray();
+      return sb.toString();
     }
   };
 
-  protected TestCup CU_ReferencesParameterizedTypeBeforeItsGenericFormHasBeenProcessed = new TestCup(
+  protected CheckedMockCompilationUnit CU_ReferencesParameterizedTypeBeforeItsGenericFormHasBeenProcessed = new CheckedMockCompilationUnit(
       "parameterized.type.build.dependency", "Class0") {
     @Override
     public void check(JClassType type) throws NotFoundException {
+      JClassType[] intfs = type.getImplementedInterfaces();
+      assertEquals(1, intfs.length);
+      assertNotNull(intfs[0].isParameterized());
     }
 
-    public char[] getSource() throws UnableToCompleteException {
+    public String getSource() {
       StringBuilder sb = new StringBuilder();
       sb.append("package parameterized.type.build.dependency;\n");
       sb.append("public class Class0 implements Class2.Inner<Object> {\n");
       sb.append("}\n");
-      return sb.toString().toCharArray();
+      return sb.toString();
     }
   };
 
-  protected TestCup CU_RefsInfectedCompilationUnit = new TestCup("test",
-      "RefsInfectedCompilationUnit") {
+  protected CheckedMockCompilationUnit CU_RefsInfectedCompilationUnit = new CheckedMockCompilationUnit(
+      "test", "RefsInfectedCompilationUnit") {
     public void check(JClassType classInfo) {
       fail("This class should should have been removed because it refers to a class in another compilation unit that had problems");
     }
 
-    public char[] getSource() {
+    public String getSource() {
       StringBuffer sb = new StringBuffer();
       sb.append("package test;\n");
       sb.append("public class RefsInfectedCompilationUnit extends Valid { }\n");
-      return sb.toString().toCharArray();
+      return sb.toString();
     }
   };
 
-  protected TestCup CU_Throwable = new TestCup("java.lang", "Throwable") {
+  protected CheckedMockCompilationUnit CU_Throwable = new CheckedMockCompilationUnit(
+      "java.lang", "Throwable") {
     public void check(JClassType type) {
       assertEquals("Throwable", type.getSimpleSourceName());
       assertEquals("java.lang.Throwable", type.getQualifiedSourceName());
     }
 
-    public char[] getSource() {
+    public String getSource() {
       StringBuffer sb = new StringBuffer();
       sb.append("package java.lang;");
       sb.append("public class Throwable { }");
-      return sb.toString().toCharArray();
+      return sb.toString();
     }
   };
 
+  private final TypeOracleMediator mediator = new TypeOracleMediator();
+
+  private final TypeOracle typeOracle = mediator.getTypeOracle();
+
+  private final Set<CompilationUnit> units = new HashSet<CompilationUnit>();
+
   public void checkTypes(JClassType[] types) throws NotFoundException {
     for (int i = 0; i < types.length; i++) {
       JClassType type = types[i];
@@ -768,100 +768,123 @@
     }
   }
 
-  public void testAssimilation() throws UnableToCompleteException {
-    TypeOracle typeOracle0 = new TypeOracle();
-    TreeLogger logger = createTreeLogger();
+  public void testAssignable() throws UnableToCompleteException,
+      TypeOracleException {
+    units.add(CU_Object);
+    units.add(CU_Assignable);
+    units.add(CU_OuterInner);
+    compileAndRefresh();
+    JClassType[] allTypes = typeOracle.getTypes();
+    assertEquals(7, allTypes.length);
 
-    // Build onto an empty type oracle.
-    //
-    TypeOracleBuilder builder1 = new TypeOracleBuilder(typeOracle0);
-    builder1.addCompilationUnit(CU_Object);
-    builder1.addCompilationUnit(CU_BeforeAssimilate);
-    TypeOracle typeOracle1 = builder1.build(logger);
-    assertSame(typeOracle0, typeOracle1);
-    assertEquals(2, typeOracle1.getTypes().length);
-    JClassType before = typeOracle1.findType("test.assim.BeforeAssimilate");
+    Map<JClassType, Set<JClassType>> assignabilityMap = new HashMap<JClassType, Set<JClassType>>();
+
+    JClassType inner = typeOracle.findType("test.Outer.Inner");
+    JClassType baseIntf = typeOracle.findType("test.sub.BaseInterface");
+    JClassType derivedIntf = typeOracle.findType("test.sub.DerivedInterface");
+    recordAssignability(assignabilityMap, derivedIntf, baseIntf);
+    JClassType derived = typeOracle.findType("test.sub.Derived");
+    recordAssignability(assignabilityMap, derived, inner);
+    JClassType nested = typeOracle.findType("test.sub.Derived.Nested");
+    recordAssignability(assignabilityMap, nested, inner);
+    recordAssignability(assignabilityMap, nested, derivedIntf);
+    recordAssignability(assignabilityMap, nested, baseIntf);
+
+    for (JClassType fromType : allTypes) {
+      for (JClassType toType : allTypes) {
+        if (fromType == toType || toType == typeOracle.getJavaLangObject()) {
+          assertIsAssignable(fromType, toType);
+        } else {
+          Set<JClassType> set = assignabilityMap.get(fromType);
+          if (set != null && set.contains(toType)) {
+            assertIsAssignable(fromType, toType);
+          } else {
+            assertIsNotAssignable(fromType, toType);
+          }
+        }
+      }
+    }
+  }
+
+  public void testAssimilation() throws UnableToCompleteException,
+      TypeOracleException {
+    units.add(CU_Object);
+    units.add(CU_BeforeAssimilate);
+    compileAndRefresh();
+    assertEquals(2, typeOracle.getTypes().length);
+    JClassType before = typeOracle.findType("test.assim.BeforeAssimilate");
 
     // Build onto an existing type oracle.
-    //
-    TypeOracleBuilder builder2 = new TypeOracleBuilder(typeOracle1);
-    builder2.addCompilationUnit(CU_AfterAssimilate);
-    TypeOracle typeOracle2 = builder2.build(logger);
-    assertSame(typeOracle1, typeOracle2);
-    assertEquals(3, typeOracle2.getTypes().length);
+    units.add(CU_AfterAssimilate);
+    compileAndRefresh();
+    assertEquals(3, typeOracle.getTypes().length);
 
     // Make sure identities remained intact across the assimilation.
-    //
-    JClassType after = typeOracle2.findType("test.assim.AfterAssimilate");
-
+    JClassType after = typeOracle.findType("test.assim.AfterAssimilate");
     assertSame(before, after.getSuperclass());
   }
 
   public void testBindToTypeScope() throws TypeOracleException,
       UnableToCompleteException {
-    TypeOracleBuilder tiob = createTypeInfoOracleBuilder();
-    tiob.addCompilationUnit(CU_Object);
-    tiob.addCompilationUnit(CU_BindToTypeScope);
-    TypeOracle tio = tiob.build(createTreeLogger());
-    JClassType[] types = tio.getTypes();
+    units.add(CU_Object);
+    units.add(CU_BindToTypeScope);
+    compileAndRefresh();
+    JClassType[] types = typeOracle.getTypes();
     assertEquals(4, types.length);
-    checkTypes(types);
   }
 
   public void testDefaultClass() throws TypeOracleException,
       UnableToCompleteException {
-    TypeOracleBuilder tiob = createTypeInfoOracleBuilder();
-    tiob.addCompilationUnit(CU_Object);
-    tiob.addCompilationUnit(CU_DefaultClass);
-    TypeOracle tio = tiob.build(createTreeLogger());
-    JClassType[] types = tio.getTypes();
+    units.add(CU_Object);
+    units.add(CU_DefaultClass);
+    compileAndRefresh();
+    JClassType[] types = typeOracle.getTypes();
     assertEquals(2, types.length);
-    checkTypes(types);
   }
 
   public void testFieldsAndTypes() throws TypeOracleException,
       UnableToCompleteException {
-    TypeOracleBuilder tiob = createTypeInfoOracleBuilder();
-    tiob.addCompilationUnit(CU_Object);
-    tiob.addCompilationUnit(CU_FieldsAndTypes);
-    TypeOracle tio = tiob.build(createTreeLogger());
-    JClassType[] types = tio.getTypes();
+    units.add(CU_Object);
+    units.add(CU_FieldsAndTypes);
+    compileAndRefresh();
+    JClassType[] types = typeOracle.getTypes();
     assertEquals(3, types.length);
-    checkTypes(types);
+  }
+
+  public void testLocal() throws TypeOracleException, UnableToCompleteException {
+    units.add(CU_Object);
+    units.add(CU_LocalClass);
+    compileAndRefresh();
+    JClassType[] types = typeOracle.getTypes();
+    assertEquals(3, types.length);
   }
 
   public void testMetaData() throws TypeOracleException,
       UnableToCompleteException {
-    TypeOracleBuilder tiob = createTypeInfoOracleBuilder();
-    tiob.addCompilationUnit(CU_Object);
-    tiob.addCompilationUnit(CU_MetaData);
-    TypeOracle tio = tiob.build(createTreeLogger());
-    JClassType[] types = tio.getTypes();
+    units.add(CU_Object);
+    units.add(CU_MetaData);
+    compileAndRefresh();
+    JClassType[] types = typeOracle.getTypes();
     assertEquals(2, types.length);
-    checkTypes(types);
   }
 
   public void testMethodsAndParams() throws TypeOracleException,
       UnableToCompleteException {
-    TypeOracleBuilder tiob = createTypeInfoOracleBuilder();
-    tiob.addCompilationUnit(CU_Object);
-    tiob.addCompilationUnit(CU_Throwable);
-    tiob.addCompilationUnit(CU_MethodsAndParams);
-    TypeOracle tio = tiob.build(createTreeLogger());
-    JClassType[] types = tio.getTypes();
+    units.add(CU_Object);
+    units.add(CU_Throwable);
+    units.add(CU_MethodsAndParams);
+    compileAndRefresh();
+    JClassType[] types = typeOracle.getTypes();
     assertEquals(3, types.length);
-    checkTypes(types);
   }
 
   public void testOuterInner() throws TypeOracleException,
       UnableToCompleteException {
-    TypeOracleBuilder tiob = createTypeInfoOracleBuilder();
-    tiob.addCompilationUnit(CU_Object);
-    tiob.addCompilationUnit(CU_OuterInner);
-    TypeOracle tio = tiob.build(createTreeLogger());
-    JClassType[] types = tio.getTypes();
+    units.add(CU_Object);
+    units.add(CU_OuterInner);
+    compileAndRefresh();
+    JClassType[] types = typeOracle.getTypes();
     assertEquals(3, types.length);
-    checkTypes(types);
   }
 
   /**
@@ -873,16 +896,14 @@
    * CU_DeclaresInnerGenericType.
    */
   public void testParameterizedTypeBuildDependencies()
-      throws UnableToCompleteException {
-    TypeOracleBuilder tiob = createTypeInfoOracleBuilder();
+      throws UnableToCompleteException, TypeOracleException {
+    units.add(CU_ReferencesParameterizedTypeBeforeItsGenericFormHasBeenProcessed);
+    units.add(CU_ExtendsParameterizedType);
+    units.add(CU_DeclaresInnerGenericType);
+    units.add(CU_Object);
 
-    tiob.addCompilationUnit(CU_ReferencesParameterizedTypeBeforeItsGenericFormHasBeenProcessed);
-    tiob.addCompilationUnit(CU_ExtendsParameterizedType);
-    tiob.addCompilationUnit(CU_DeclaresInnerGenericType);
-    tiob.addCompilationUnit(CU_Object);
-
-    TypeOracle tio = tiob.build(createTreeLogger());
-    assertNull(tio.findType("test.parameterizedtype.build.dependencies.Class2"));
+    compileAndRefresh();
+    assertNull(typeOracle.findType("test.parameterizedtype.build.dependencies.Class2"));
   }
 
   /**
@@ -893,44 +914,42 @@
    * @throws NotFoundException
    * @throws IOException
    */
-  public void testRefresh() throws UnableToCompleteException, NotFoundException {
-    TypeOracleBuilder tiob = createTypeInfoOracleBuilder();
+  public void testRefresh() throws UnableToCompleteException,
+      TypeOracleException {
+    units.add(CU_Object);
+    units.add(CU_ExtendsGenericList);
+    units.add(CU_GenericList);
+    units.add(CU_ReferencesGenericListConstant);
 
-    tiob.addCompilationUnit(CU_Object);
-    tiob.addCompilationUnit(CU_ExtendsGenericList);
-    tiob.addCompilationUnit(CU_GenericList);
-    tiob.addCompilationUnit(CU_ReferencesGenericListConstant);
-
-    TreeLogger logger = createTreeLogger();
-    TypeOracle to = tiob.build(logger);
+    compileAndRefresh();
 
     // Get the types produced by the TypeOracle
-    JClassType extendsGenericListType = to.getType("test.refresh.ExtendsGenericList");
-    JClassType genericListType = to.getType("test.refresh.GenericList");
-    JClassType objectType = to.getJavaLangObject();
-    JClassType referencesGenericListConstantType = to.getType("test.refresh.ReferencesGenericListConstant");
+    JClassType extendsGenericListType = typeOracle.getType("test.refresh.ExtendsGenericList");
+    JClassType genericListType = typeOracle.getType("test.refresh.GenericList");
+    JClassType objectType = typeOracle.getJavaLangObject();
+    JClassType referencesGenericListConstantType = typeOracle.getType("test.refresh.ReferencesGenericListConstant");
 
     /*
-     * Add the CU_GenericList again and simulate a refresh. This should cause
+     * Invalidate CU_GenericList and simulate a refresh. This should cause
      * anything that depends on GenericList to be rebuilt by the type oracle.
      */
-    tiob.addCompilationUnit(CU_GenericList);
-    refreshTypeOracle(tiob, to);
-    to = tiob.build(logger);
+    CU_GenericList.setState(State.FRESH);
+    compileAndRefresh();
 
     assertNotSame(genericListType.getQualifiedSourceName() + "; ",
-        to.getType("test.refresh.GenericList"), genericListType);
+        typeOracle.getType("test.refresh.GenericList"), genericListType);
     assertNotSame(extendsGenericListType.getQualifiedSourceName() + "; ",
-        to.getType("test.refresh.ExtendsGenericList"), extendsGenericListType);
+        typeOracle.getType("test.refresh.ExtendsGenericList"),
+        extendsGenericListType);
     assertSame(objectType.getQualifiedSourceName() + "; ",
-        to.getJavaLangObject(), objectType);
+        typeOracle.getJavaLangObject(), objectType);
 
     /*
      * Make sure that referencing a constant field will cause a type to be
      * rebuilt if the constant changes.
      */
     assertNotSame(referencesGenericListConstantType.getQualifiedSourceName(),
-        to.getType("test.refresh.ReferencesGenericListConstant"),
+        typeOracle.getType("test.refresh.ReferencesGenericListConstant"),
         referencesGenericListConstantType);
   }
 
@@ -946,21 +965,19 @@
    * @throws UnableToCompleteException
    * @throws IOException
    */
-  public void testRefreshWithErrors() throws UnableToCompleteException {
-    TypeOracleBuilder tiob = createTypeInfoOracleBuilder();
-
+  public void testRefreshWithErrors() throws UnableToCompleteException,
+      TypeOracleException {
     // Add Object
     StringBuffer sb = new StringBuffer();
     sb.append("package java.lang;");
     sb.append("public class Object { }");
-    addNonTransientCompilationUnitProvider(tiob, "java.lang.Object", sb);
+    addCompilationUnit("java.lang.Object", sb);
 
     // Add UnmodifiedClass that will never change.
     sb = new StringBuffer();
     sb.append("package test.refresh.with.errors;");
     sb.append("public class UnmodifiedClass { }");
-    addNonTransientCompilationUnitProvider(tiob,
-        "test.refresh.with.errors.UnmodifiedClass", sb);
+    addCompilationUnit("test.refresh.with.errors.UnmodifiedClass", sb);
 
     // Add GoodClass that references a class that will go bad.
     sb = new StringBuffer();
@@ -968,95 +985,113 @@
     sb.append("public class GoodClass {\n");
     sb.append("  ClassThatWillGoBad ctwgb;\n");
     sb.append("}\n");
-    addNonTransientCompilationUnitProvider(tiob,
-        "test.refresh.with.errors.GoodClass", sb);
+    addCompilationUnit("test.refresh.with.errors.GoodClass", sb);
 
     // Add ClassThatWillGoBad that goes bad on the next refresh.
-    StaticCompilationUnitProvider cupThatWillGoBad = new StaticCompilationUnitProvider(
-        "test.refresh.with.errors", "ClassThatWillGoBad", null) {
-      boolean goBad = false;
+    MockCompilationUnit unitThatWillGoBad = new MockCompilationUnit(
+        "test.refresh.with.errors.ClassThatWillGoBad") {
+      private String source = "package test.refresh.with.errors;\n"
+          + "public class ClassThatWillGoBad { }\n";
 
       @Override
-      public char[] getSource() {
-        StringBuffer sb = new StringBuffer();
+      public String getSource() {
+        return source;
+      }
 
-        if (!goBad) {
-          sb.append("package test.refresh.with.errors;\n");
-          sb.append("public class ClassThatWillGoBad {\n");
-          sb.append("}\n");
-          goBad = true;
-        } else {
-          sb.append("This will cause a syntax error.");
-        }
-        return sb.toString().toCharArray();
+      @Override
+      void setState(State newState) {
+        super.setState(newState);
+        source = "This will cause a syntax error.";
       }
     };
-    tiob.addCompilationUnit(cupThatWillGoBad);
+    units.add(unitThatWillGoBad);
 
-    TreeLogger logger = createTreeLogger();
-    TypeOracle to = tiob.build(logger);
-    assertNotNull(to.findType("test.refresh.with.errors.UnmodifiedClass"));
-    assertNotNull(to.findType("test.refresh.with.errors.GoodClass"));
-    assertNotNull(to.findType("test.refresh.with.errors.ClassThatWillGoBad"));
+    compileAndRefresh();
 
-    // Add AnotherGoodClass that references a class that was not recompiled.
+    assertNotNull(typeOracle.findType("test.refresh.with.errors.UnmodifiedClass"));
+    assertNotNull(typeOracle.findType("test.refresh.with.errors.GoodClass"));
+    assertNotNull(typeOracle.findType("test.refresh.with.errors.ClassThatWillGoBad"));
+
+    // Add AnotherGoodClass that references a
+    // class that was not recompiled.
     sb = new StringBuffer();
     sb.append("package test.refresh.with.errors;\n");
     sb.append("public class AnotherGoodClass {\n");
     sb.append("  UnmodifiedClass uc; // This will cause the runaway pruning.\n");
     sb.append("}\n");
-    addNonTransientCompilationUnitProvider(tiob,
-        "test.refresh.with.errors.AnotherGoodClass", sb);
+    addCompilationUnit("test.refresh.with.errors.AnotherGoodClass", sb);
 
-    // Add BadClass that has errors and originally forced issue 2238.
+    // Add BadClass that has errors and originally
+    // forced issue 2238.
     sb = new StringBuffer();
     sb.append("package test.refresh.with.errors;\n");
     sb.append("public class BadClass {\n");
     sb.append("  This will trigger a syntax error.\n");
     sb.append("}\n");
-    addNonTransientCompilationUnitProvider(tiob,
-        "test.refresh.with.errors.BadClass", sb);
+    addCompilationUnit("test.refresh.with.errors.BadClass", sb);
 
     // Now this cup should cause errors.
-    tiob.addCompilationUnit(cupThatWillGoBad);
+    unitThatWillGoBad.setState(State.FRESH);
 
-    refreshTypeOracle(tiob, to);
-    to = tiob.build(logger);
+    compileAndRefresh();
 
-    assertNotNull(to.findType("test.refresh.with.errors.UnmodifiedClass"));
-    assertNotNull(to.findType("test.refresh.with.errors.AnotherGoodClass"));
-    assertNull(to.findType("test.refresh.with.errors.BadClass"));
-    assertNull(to.findType("test.refresh.with.errors.ClassThatWillGoBad"));
-    assertNull(to.findType("test.refresh.with.errors.GoodClass"));
+    assertNotNull(typeOracle.findType("test.refresh.with.errors.UnmodifiedClass"));
+    assertNotNull(typeOracle.findType("test.refresh.with.errors.AnotherGoodClass"));
+    assertNull(typeOracle.findType("test.refresh.with.errors.BadClass"));
+    assertNull(typeOracle.findType("test.refresh.with.errors.ClassThatWillGoBad"));
+    assertNull(typeOracle.findType("test.refresh.with.errors.GoodClass"));
   }
 
   public void testSyntaxErrors() throws TypeOracleException,
       UnableToCompleteException {
-    TypeOracleBuilder tiob = createTypeInfoOracleBuilder();
-    tiob.addCompilationUnit(CU_Object);
-    tiob.addCompilationUnit(CU_HasSyntaxErrors);
-    TypeOracle tio = tiob.build(createTreeLogger());
-    JClassType[] types = tio.getTypes();
+    units.add(CU_Object);
+    units.add(CU_HasSyntaxErrors);
+    compileAndRefresh();
+    JClassType[] types = typeOracle.getTypes();
     // Only java.lang.Object should remain.
     //
     assertEquals(1, types.length);
     assertEquals("java.lang.Object", types[0].getQualifiedSourceName());
-    checkTypes(types);
   }
 
   public void testUnresolvedSymbls() throws TypeOracleException,
       UnableToCompleteException {
-    TypeOracleBuilder tiob = createTypeInfoOracleBuilder();
-    tiob.addCompilationUnit(CU_Object);
-    tiob.addCompilationUnit(CU_HasUnresolvedSymbols);
-    tiob.addCompilationUnit(CU_RefsInfectedCompilationUnit);
-    TypeOracle tio = tiob.build(createTreeLogger());
-    JClassType[] types = tio.getTypes();
+    units.add(CU_Object);
+    units.add(CU_HasUnresolvedSymbols);
+    units.add(CU_RefsInfectedCompilationUnit);
+    compileAndRefresh();
+    JClassType[] types = typeOracle.getTypes();
     // Only java.lang.Object should remain.
     //
     assertEquals(1, types.length);
     assertEquals("java.lang.Object", types[0].getQualifiedSourceName());
-    checkTypes(types);
+  }
+
+  /**
+   * Creates a {@link CompilationUnit} and adds it the set of units.
+   * 
+   * @throws UnableToCompleteException
+   */
+  private void addCompilationUnit(String qualifiedTypeName, CharSequence source) {
+    units.add(new MockCompilationUnit(qualifiedTypeName, source.toString()));
+  }
+
+  private void check(JClassType classInfo) throws NotFoundException {
+    final String qName = classInfo.getQualifiedSourceName();
+    CheckedMockCompilationUnit cup = publicTypeNameToTestCupMap.get(qName);
+    if (cup != null) {
+      cup.check(classInfo);
+    }
+  }
+
+  private void compileAndRefresh() throws UnableToCompleteException,
+      TypeOracleException {
+    TreeLogger logger = createTreeLogger();
+    CompilationUnitInvalidator.invalidateUnitsWithInvalidRefs(logger, units);
+    JdtCompiler.compile(units);
+    CompilationUnitInvalidator.invalidateUnitsWithErrors(logger, units);
+    mediator.refresh(logger, units);
+    checkTypes(typeOracle.getTypes());
   }
 
   /**
@@ -1073,12 +1108,7 @@
     }
   }
 
-  private TypeOracleBuilder createTypeInfoOracleBuilder() {
-    return new TypeOracleBuilder();
-  }
-
-  private void refreshTypeOracle(TypeOracleBuilder tiob, TypeOracle to) {
-    CacheManager cacheManager = tiob.getCacheManager();
-    cacheManager.invalidateOnRefresh(to);
+  private void register(String qualifiedTypeName, CheckedMockCompilationUnit cup) {
+    publicTypeNameToTestCupMap.put(qualifiedTypeName, cup);
   }
 }
diff --git a/dev/core/test/com/google/gwt/dev/javac/TypeOracleTestingUtils.java b/dev/core/test/com/google/gwt/dev/javac/TypeOracleTestingUtils.java
new file mode 100644
index 0000000..4a9a2e5
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/javac/TypeOracleTestingUtils.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2008 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.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.dev.javac.impl.SourceFileCompilationUnit;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Utilities for tests that build a type oracle and watch for errors.
+ * 
+ */
+public class TypeOracleTestingUtils {
+
+  public static TypeOracle buildStandardTypeOracleWith(TreeLogger logger,
+      CompilationUnit... extraUnits) throws UnableToCompleteException {
+    Set<CompilationUnit> extraUnitSet = new HashSet<CompilationUnit>();
+    Collections.addAll(extraUnitSet, extraUnits);
+    return buildStandardTypeOracleWith(logger, extraUnitSet);
+  }
+
+  public static TypeOracle buildStandardTypeOracleWith(TreeLogger logger,
+      Set<CompilationUnit> extraUnits) throws UnableToCompleteException {
+    Set<CompilationUnit> unitSet = new HashSet<CompilationUnit>();
+    addStandardCups(unitSet);
+    for (CompilationUnit extraUnit : extraUnits) {
+      unitSet.add(extraUnit);
+    }
+    return buildTypeOracle(logger, unitSet);
+  }
+
+  /**
+   * Add compilation units for basic classes like Object and String.
+   */
+  private static void addStandardCups(Set<CompilationUnit> units) {
+    for (JavaSourceFile resource : JavaSourceCodeBase.getStandardResources()) {
+      units.add(new SourceFileCompilationUnit(resource));
+    }
+  }
+
+  private static TypeOracle buildTypeOracle(TreeLogger logger,
+      Set<CompilationUnit> units) throws UnableToCompleteException {
+    JdtCompiler.compile(units);
+    Map<String, CompiledClass> classMap = new HashMap<String, CompiledClass>();
+    for (CompilationUnit unit : units) {
+      for (CompiledClass compiledClass : unit.getCompiledClasses()) {
+        classMap.put(compiledClass.getBinaryName(), compiledClass);
+      }
+    }
+    CompilationUnitInvalidator.validateCompilationUnits(units, classMap);
+    CompilationUnitInvalidator.invalidateUnitsWithErrors(logger, units);
+    TypeOracleMediator mediator = new TypeOracleMediator();
+    mediator.refresh(logger, units);
+    return mediator.getTypeOracle();
+  }
+}
diff --git a/dev/core/test/com/google/gwt/dev/javac/impl/JavaResourceBase.java b/dev/core/test/com/google/gwt/dev/javac/impl/JavaResourceBase.java
new file mode 100644
index 0000000..43f6657
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/javac/impl/JavaResourceBase.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2008 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.impl;
+
+/**
+ * Contains standard Java source files for testing.
+ */
+public class JavaResourceBase {
+
+  public static final MockResource ANNOTATION = new MockJavaResource(
+      "java.lang.annotation.Annotation") {
+    @Override
+    protected CharSequence getContent() {
+      StringBuffer code = new StringBuffer();
+      code.append("package java.lang.annotation;\n");
+      code.append("public interface Annotation {\n");
+      code.append("}\n");
+      return code;
+    }
+  };
+  public static final MockJavaResource BAR = new MockJavaResource("test.Bar") {
+    @Override
+    protected CharSequence getContent() {
+      StringBuffer code = new StringBuffer();
+      code.append("package test;\n");
+      code.append("public class Bar extends Foo {\n");
+      code.append("  public String value() { return \"Bar\"; }\n");
+      code.append("}\n");
+      return code;
+    }
+  };
+  public static final MockResource CLASS = new MockJavaResource(
+      "java.lang.Class") {
+    @Override
+    protected CharSequence getContent() {
+      StringBuffer code = new StringBuffer();
+      code.append("package java.lang;\n");
+      code.append("public class Class<T> {\n");
+      code.append("}\n");
+      return code;
+    }
+  };
+  public static final MockJavaResource FOO = new MockJavaResource("test.Foo") {
+    @Override
+    protected CharSequence getContent() {
+      StringBuffer code = new StringBuffer();
+      code.append("package test;\n");
+      code.append("public class Foo {\n");
+      code.append("  public String value() { return \"Foo\"; }\n");
+      code.append("}\n");
+      return code;
+    }
+  };
+  public static final MockResource JAVASCRIPTOBJECT = new MockJavaResource(
+      "com.google.gwt.core.client.JavaScriptObject") {
+    @Override
+    protected CharSequence getContent() {
+      StringBuffer code = new StringBuffer();
+      code.append("package com.google.gwt.core.client;\n");
+      code.append("public class JavaScriptObject {\n");
+      code.append("  protected JavaScriptObject() { }\n");
+      code.append("}\n");
+      return code;
+    }
+  };
+  public static final MockResource MAP = new MockJavaResource("java.util.Map") {
+    @Override
+    protected CharSequence getContent() {
+      StringBuffer code = new StringBuffer();
+      code.append("package java.util;\n");
+      code.append("public interface Map<K,V> { }\n");
+      return code;
+    }
+  };
+  public static final MockResource OBJECT = new MockJavaResource(
+      "java.lang.Object") {
+    @Override
+    protected CharSequence getContent() {
+      StringBuffer code = new StringBuffer();
+      code.append("package java.lang;\n");
+      code.append("public class Object {\n");
+      code.append("  public String toString() { return \"Object\"; }\n");
+      code.append("  public Object clone() { return this; } ");
+      code.append("}\n");
+      return code;
+    }
+  };
+  public static final MockResource SERIALIZABLE = new MockJavaResource(
+      "java.lang.Serializable") {
+    @Override
+    protected CharSequence getContent() {
+      StringBuffer code = new StringBuffer();
+      code.append("package java.lang;\n");
+      code.append("public interface Serializable { }\n");
+      return code;
+    }
+  };
+  public static final MockResource STRING = new MockJavaResource(
+      "java.lang.String") {
+    @Override
+    protected CharSequence getContent() {
+      StringBuffer code = new StringBuffer();
+      code.append("package java.lang;\n");
+      code.append("public final class String {\n");
+      code.append("  public int length() { return 0; }\n");
+      code.append("}\n");
+      return code;
+    }
+  };
+  public static final MockResource SUPPRESS_WARNINGS = new MockJavaResource(
+      "java.lang.SuppressWarnings") {
+    @Override
+    protected CharSequence getContent() {
+      StringBuffer code = new StringBuffer();
+      code.append("package java.lang;\n");
+      code.append("public @interface SuppressWarnings {\n");
+      code.append("  String[] value();\n");
+      code.append("}\n");
+      return code;
+    }
+  };
+
+  public static MockResource[] getStandardResources() {
+    return new MockResource[] {
+        ANNOTATION, CLASS, JAVASCRIPTOBJECT, MAP, OBJECT, SERIALIZABLE, STRING,
+        SUPPRESS_WARNINGS};
+  }
+}
diff --git a/dev/core/test/com/google/gwt/dev/javac/impl/JavaSourceOracleImplTest.java b/dev/core/test/com/google/gwt/dev/javac/impl/JavaSourceOracleImplTest.java
new file mode 100644
index 0000000..b063893
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/javac/impl/JavaSourceOracleImplTest.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2008 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.impl;
+
+import com.google.gwt.dev.javac.JavaSourceFile;
+import com.google.gwt.dev.resource.Resource;
+
+import junit.framework.TestCase;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.Map.Entry;
+
+/**
+ * Tests {@link JavaSourceOracleImpl} using a mock {@link ResourceOracle}.
+ */
+public class JavaSourceOracleImplTest extends TestCase {
+
+  private MockResourceOracle resourceOracle = new MockResourceOracle(
+      JavaResourceBase.getStandardResources());
+
+  private JavaSourceOracleImpl sourceOracle = new JavaSourceOracleImpl(
+      resourceOracle);
+
+  public void testAdd() {
+    validateSourceOracle();
+
+    Map<String, JavaSourceFile> originalSourceMap = sourceOracle.getSourceMap();
+    resourceOracle.add(JavaResourceBase.FOO);
+    Map<String, JavaSourceFile> newSourceMap = sourceOracle.getSourceMap();
+    assertNotSame(originalSourceMap, newSourceMap);
+    assertEquals(originalSourceMap.size() + 1, newSourceMap.size());
+    validateSourceOracle();
+  }
+
+  public void testBasic() {
+    validateSourceOracle();
+  }
+
+  public void testEmpty() {
+    resourceOracle = new MockResourceOracle();
+    sourceOracle = new JavaSourceOracleImpl(resourceOracle);
+    validateSourceOracle();
+  }
+
+  public void testRemove() {
+    validateSourceOracle();
+
+    Map<String, JavaSourceFile> originalSourceMap = sourceOracle.getSourceMap();
+    resourceOracle.remove(JavaResourceBase.OBJECT.getPath());
+    Map<String, JavaSourceFile> newSourceMap = sourceOracle.getSourceMap();
+    assertNotSame(originalSourceMap, newSourceMap);
+    assertEquals(originalSourceMap.size() - 1, newSourceMap.size());
+    validateSourceOracle();
+  }
+
+  public void testReplace() {
+    validateSourceOracle();
+
+    Map<String, JavaSourceFile> originalSourceMap = sourceOracle.getSourceMap();
+    resourceOracle.replace(new MockResource(JavaResourceBase.OBJECT.getPath()) {
+      @Override
+      protected CharSequence getContent() {
+        return JavaResourceBase.OBJECT.getContent();
+      }
+    });
+    Map<String, JavaSourceFile> newSourceMap = sourceOracle.getSourceMap();
+    assertNotSame(originalSourceMap, newSourceMap);
+    assertEquals(originalSourceMap.size(), newSourceMap.size());
+    assertFalse(originalSourceMap.equals(newSourceMap));
+    validateSourceOracle();
+  }
+
+  public void testReplaceWithSame() {
+    validateSourceOracle();
+
+    Map<String, JavaSourceFile> originalSourceMap = sourceOracle.getSourceMap();
+    resourceOracle.replace(JavaResourceBase.OBJECT);
+    Map<String, JavaSourceFile> newSourceMap = sourceOracle.getSourceMap();
+    assertNotSame(originalSourceMap, newSourceMap);
+    assertEquals(originalSourceMap.size(), newSourceMap.size());
+    assertEquals(originalSourceMap, newSourceMap);
+    validateSourceOracle();
+  }
+
+  /**
+   * Validate that the source oracle accurately reflects the resource oracle.
+   */
+  private void validateSourceOracle() {
+    // Save off the reflected collections.
+    Map<String, JavaSourceFile> sourceMap = sourceOracle.getSourceMap();
+    Set<String> classNames = sourceOracle.getClassNames();
+    Set<JavaSourceFile> sourceFiles = sourceOracle.getSourceFiles();
+
+    // Validate that the collections are consistent with each other.
+    assertEquals(sourceMap.keySet(), classNames);
+    assertEquals(new HashSet<JavaSourceFile>(sourceMap.values()), sourceFiles);
+
+    // Save off a mutable copy of the resource map to compare with.
+    Map<String, Resource> resourceMap = new HashMap<String, Resource>(
+        resourceOracle.getResourceMap());
+    assertEquals(resourceMap.size(), sourceMap.size());
+    for (Entry<String, JavaSourceFile> entry : sourceMap.entrySet()) {
+      // Validate source file internally consistent.
+      String className = entry.getKey();
+      JavaSourceFile sourceFile = entry.getValue();
+      assertEquals(className, sourceFile.getTypeName());
+      assertEquals(Shared.getPackageName(className),
+          sourceFile.getPackageName());
+      assertEquals(Shared.getShortName(className), sourceFile.getShortName());
+
+      // Find the matching resource (and remove it from the resource map!)
+      String expectedPath = Shared.toPath(className);
+      assertTrue(resourceMap.containsKey(expectedPath));
+
+      // Validate the source file matches the resource.
+      Resource resource = resourceMap.remove(expectedPath);
+      assertNotNull(resource);
+      assertEquals(Shared.readContent(resource.openContents()),
+          sourceFile.readSource());
+    }
+    // The resource map should be empty now.
+    assertEquals(0, resourceMap.size());
+
+    // Validate collection identity hasn't changed.
+    assertSame(sourceMap, sourceOracle.getSourceMap());
+    assertSame(sourceFiles, sourceOracle.getSourceFiles());
+    assertSame(classNames, sourceOracle.getClassNames());
+  }
+}
diff --git a/dev/core/test/com/google/gwt/dev/javac/impl/JdtBehaviorTest.java b/dev/core/test/com/google/gwt/dev/javac/impl/JdtBehaviorTest.java
new file mode 100644
index 0000000..fb4115a
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/javac/impl/JdtBehaviorTest.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright 2008 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.impl;
+
+import junit.framework.TestCase;
+
+import org.eclipse.jdt.core.compiler.CategorizedProblem;
+import org.eclipse.jdt.core.compiler.CharOperation;
+import org.eclipse.jdt.internal.compiler.ClassFile;
+import org.eclipse.jdt.internal.compiler.CompilationResult;
+import org.eclipse.jdt.internal.compiler.Compiler;
+import org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies;
+import org.eclipse.jdt.internal.compiler.ICompilerRequestor;
+import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
+import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException;
+import org.eclipse.jdt.internal.compiler.env.ICompilationUnit;
+import org.eclipse.jdt.internal.compiler.env.INameEnvironment;
+import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer;
+import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
+import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Validates certain JDT behaviors that the compilation process may depend on
+ * for correctness. One useful aspect would be that if we upgrade JDT in the
+ * future, this test could help validate our assumptions.
+ */
+public class JdtBehaviorTest extends TestCase {
+
+  /**
+   * Hook-point if we need to modify the compiler behavior.
+   */
+  private class CompilerImpl extends Compiler {
+
+    public CompilerImpl(INameEnvironment environment,
+        ICompilerRequestor requestor) {
+      super(environment, DefaultErrorHandlingPolicies.proceedWithAllProblems(),
+          getCompilerOptions(), requestor, new DefaultProblemFactory(
+              Locale.getDefault()));
+    }
+  }
+
+  /**
+   * Hook point to accept results.
+   */
+  private class ICompilerRequestorImpl implements ICompilerRequestor {
+    public void acceptResult(CompilationResult result) {
+      if (result.hasErrors()) {
+        StringBuilder sb = new StringBuilder();
+        for (CategorizedProblem problem : result.getErrors()) {
+          sb.append(problem.toString());
+          sb.append('\n');
+        }
+        fail(sb.toString());
+      }
+      for (ClassFile classFile : result.getClassFiles()) {
+        char[][] classNameArray = classFile.getCompoundName();
+        char[][] packageArray = CharOperation.subarray(classNameArray, 0,
+            classNameArray.length - 1);
+        char[] packageName = CharOperation.concatWith(packageArray, '.');
+        char[] className = CharOperation.concatWith(classNameArray, '.');
+        addPackages(String.valueOf(packageName));
+        classFiles.put(String.valueOf(className), classFile);
+      }
+    }
+
+    private void addPackages(String packageName) {
+      while (true) {
+        packages.add(String.valueOf(packageName));
+        int pos = packageName.lastIndexOf('.');
+        if (pos > 0) {
+          packageName = packageName.substring(0, pos);
+        } else {
+          packages.add("");
+          break;
+        }
+      }
+    }
+  }
+
+  /**
+   * How JDT receives files from the environment.
+   */
+  private class INameEnvironmentImpl implements INameEnvironment {
+    public void cleanup() {
+      // intentionally blank
+    }
+
+    public NameEnvironmentAnswer findType(char[] type, char[][] pkg) {
+      return findType(CharOperation.arrayConcat(pkg, type));
+    }
+
+    public NameEnvironmentAnswer findType(char[][] compoundTypeName) {
+      final char[] typeChars = CharOperation.concatWith(compoundTypeName, '.');
+      String typeName = String.valueOf(typeChars);
+      // System.out.println("findType: " + typeName);
+      ClassFile classFile = classFiles.get(typeName);
+      if (classFile != null) {
+        try {
+          byte[] bytes = classFile.getBytes();
+          char[] loc = classFile.fileName();
+          ClassFileReader cfr = new ClassFileReader(bytes, loc);
+          return new NameEnvironmentAnswer(cfr, null);
+        } catch (ClassFormatException e) {
+          throw new RuntimeException("Unexpectedly unable to parse class file",
+              e);
+        }
+      }
+      return null;
+    }
+
+    public boolean isPackage(char[][] parentPkg, char[] pkg) {
+      final char[] pathChars = CharOperation.concatWith(parentPkg, pkg, '.');
+      String packageName = String.valueOf(pathChars);
+      // System.out.println("isPackage: " + packageName);
+      return packages.contains(packageName);
+    }
+  }
+
+  private class ResourceAdapter implements ICompilationUnit {
+
+    private final MockJavaSourceFile sourceFile;
+
+    public ResourceAdapter(MockResource resource) {
+      sourceFile = new MockJavaSourceFile(resource);
+    }
+
+    public char[] getContents() {
+      return sourceFile.readSource().toCharArray();
+    }
+
+    public char[] getFileName() {
+      return sourceFile.getLocation().toCharArray();
+    }
+
+    public char[] getMainTypeName() {
+      return sourceFile.getShortName().toCharArray();
+    }
+
+    public char[][] getPackageName() {
+      return CharOperation.splitOn('.',
+          sourceFile.getPackageName().toCharArray());
+    }
+
+    @Override
+    public String toString() {
+      return sourceFile.toString();
+    }
+  }
+
+  private static CompilerOptions getCompilerOptions() {
+    Map<String, String> settings = new HashMap<String, String>();
+    settings.put(CompilerOptions.OPTION_LineNumberAttribute,
+        CompilerOptions.GENERATE);
+    settings.put(CompilerOptions.OPTION_SourceFileAttribute,
+        CompilerOptions.GENERATE);
+    /*
+     * Tricks like "boolean stopHere = true;" depend on this setting to work in
+     * hosted mode. In web mode, our compiler should optimize them out once we
+     * do real data flow.
+     */
+    settings.put(CompilerOptions.OPTION_PreserveUnusedLocal,
+        CompilerOptions.PRESERVE);
+    settings.put(CompilerOptions.OPTION_ReportDeprecation,
+        CompilerOptions.IGNORE);
+    settings.put(CompilerOptions.OPTION_LocalVariableAttribute,
+        CompilerOptions.GENERATE);
+    settings.put(CompilerOptions.OPTION_Compliance, CompilerOptions.VERSION_1_5);
+    settings.put(CompilerOptions.OPTION_Source, CompilerOptions.VERSION_1_5);
+    settings.put(CompilerOptions.OPTION_TargetPlatform,
+        CompilerOptions.VERSION_1_5);
+
+    // This is needed by TypeOracleBuilder to parse metadata.
+    settings.put(CompilerOptions.OPTION_DocCommentSupport,
+        CompilerOptions.ENABLED);
+    return new CompilerOptions(settings);
+  }
+
+  protected Map<String, ClassFile> classFiles = new HashMap<String, ClassFile>();
+
+  /**
+   * New object for each test case.
+   */
+  protected INameEnvironmentImpl environment = new INameEnvironmentImpl();
+
+  protected Set<String> packages = new HashSet<String>();
+
+  /**
+   * New object for each test case.
+   */
+  protected ICompilerRequestorImpl requestor = new ICompilerRequestorImpl();
+
+  /**
+   * New object for each test case.
+   */
+  CompilerImpl compiler = new CompilerImpl(environment, requestor);
+
+  public void testIncrementalBuild() {
+    List<MockResource> resources = new ArrayList<MockResource>();
+    Collections.addAll(resources, JavaResourceBase.getStandardResources());
+    resources.add(JavaResourceBase.FOO);
+    doCompile(resources);
+
+    // Now incremental build the bar cup.
+    doCompile(Arrays.asList(JavaResourceBase.BAR));
+  }
+
+  public void testSingleBuild() {
+    List<MockResource> resources = new ArrayList<MockResource>();
+    Collections.addAll(resources, JavaResourceBase.getStandardResources());
+    resources.add(JavaResourceBase.FOO);
+    resources.add(JavaResourceBase.BAR);
+    doCompile(resources);
+  }
+
+  private void doCompile(List<? extends MockResource> resources) {
+    ICompilationUnit[] icus = new ICompilationUnit[resources.size()];
+    for (int i = 0; i < icus.length; ++i) {
+      icus[i] = new ResourceAdapter(resources.get(i));
+    }
+    compiler.compile(icus);
+  }
+}
diff --git a/dev/core/test/com/google/gwt/dev/javac/impl/MockJavaResource.java b/dev/core/test/com/google/gwt/dev/javac/impl/MockJavaResource.java
new file mode 100644
index 0000000..2f3de7a
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/javac/impl/MockJavaResource.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2008 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.impl;
+
+public abstract class MockJavaResource extends MockResource {
+
+  public MockJavaResource(String qualifiedTypeName) {
+    super(Shared.toPath(qualifiedTypeName));
+  }
+}
diff --git a/dev/core/test/com/google/gwt/dev/javac/impl/MockJavaSourceFile.java b/dev/core/test/com/google/gwt/dev/javac/impl/MockJavaSourceFile.java
new file mode 100644
index 0000000..1225e50
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/javac/impl/MockJavaSourceFile.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2008 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.impl;
+
+import com.google.gwt.dev.javac.JavaSourceFile;
+
+public class MockJavaSourceFile extends JavaSourceFile {
+
+  private final String location;
+  private final String qualifiedTypeName;
+  private final String source;
+
+  public MockJavaSourceFile(JavaSourceFile sourceFile) {
+    this(sourceFile.getTypeName(), sourceFile.readSource(),
+        sourceFile.getLocation());
+  }
+
+  public MockJavaSourceFile(MockResource resource) {
+    this(Shared.toTypeName(resource.getPath()),
+        resource.getContent().toString(), resource.getLocation());
+  }
+
+  public MockJavaSourceFile(String qualifiedTypeName, String source) {
+    this(qualifiedTypeName, source, "/mock/" + Shared.toPath(qualifiedTypeName));
+  }
+
+  public MockJavaSourceFile(String qualifiedTypeName, String source,
+      String location) {
+    this.qualifiedTypeName = qualifiedTypeName;
+    this.source = source;
+    this.location = location;
+  }
+
+  @Override
+  public String getLocation() {
+    return location;
+  }
+
+  @Override
+  public String getPackageName() {
+    return Shared.getPackageName(qualifiedTypeName);
+  }
+
+  @Override
+  public String getShortName() {
+    return Shared.getShortName(qualifiedTypeName);
+  }
+
+  @Override
+  public String getTypeName() {
+    return qualifiedTypeName;
+  }
+
+  @Override
+  public String readSource() {
+    return source;
+  }
+}
diff --git a/dev/core/test/com/google/gwt/dev/javac/impl/MockResource.java b/dev/core/test/com/google/gwt/dev/javac/impl/MockResource.java
new file mode 100644
index 0000000..a0fbc94
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/javac/impl/MockResource.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2008 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.impl;
+
+import com.google.gwt.dev.resource.Resource;
+import com.google.gwt.dev.util.Util;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.net.URL;
+
+/**
+ * An in-memory {@link Resource}.
+ */
+public abstract class MockResource extends Resource {
+  private final String path;
+
+  public MockResource(String path) {
+    this.path = path;
+  }
+
+  @Override
+  public String getLocation() {
+    return "/mock/" + path;
+  }
+
+  @Override
+  public String getPath() {
+    return path;
+  }
+
+  @Override
+  public URL getURL() {
+    return null;
+  }
+
+  @Override
+  public InputStream openContents() {
+    return new ByteArrayInputStream(Util.getBytes(getContent().toString()));
+  }
+
+  protected abstract CharSequence getContent();
+}
diff --git a/dev/core/test/com/google/gwt/dev/javac/impl/MockResourceOracle.java b/dev/core/test/com/google/gwt/dev/javac/impl/MockResourceOracle.java
new file mode 100644
index 0000000..c224b9c
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/javac/impl/MockResourceOracle.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2008 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.impl;
+
+import com.google.gwt.dev.resource.Resource;
+import com.google.gwt.dev.resource.ResourceOracle;
+
+import junit.framework.Assert;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A simple {@link ResourceOracle} for testing.
+ */
+public class MockResourceOracle implements ResourceOracle {
+
+  private Map<String, Resource> exportedMap = Collections.emptyMap();
+  private Set<Resource> exportedValues = Collections.emptySet();
+
+  public MockResourceOracle(Resource... resources) {
+    add(resources);
+  }
+
+  public Set<String> getPathNames() {
+    return exportedMap.keySet();
+  }
+
+  public Map<String, Resource> getResourceMap() {
+    return exportedMap;
+  }
+
+  public Set<Resource> getResources() {
+    return exportedValues;
+  }
+
+  void add(Resource... resources) {
+    Map<String, Resource> newMap = new HashMap<String, Resource>(exportedMap);
+    for (Resource resource : resources) {
+      String path = resource.getPath();
+      Assert.assertFalse(newMap.containsKey(path));
+      newMap.put(path, resource);
+    }
+    export(newMap);
+  }
+
+  void remove(String... paths) {
+    Map<String, Resource> newMap = new HashMap<String, Resource>(exportedMap);
+    for (String path : paths) {
+      Resource oldValue = newMap.remove(path);
+      Assert.assertNotNull(oldValue);
+    }
+    export(newMap);
+  }
+
+  void replace(Resource... resources) {
+    Map<String, Resource> newMap = new HashMap<String, Resource>(exportedMap);
+    for (Resource resource : resources) {
+      String path = resource.getPath();
+      Assert.assertTrue(newMap.containsKey(path));
+      newMap.put(path, resource);
+    }
+    export(newMap);
+  }
+
+  private void export(Map<String, Resource> newMap) {
+    exportedMap = Collections.unmodifiableMap(newMap);
+    // Make a new hash set for constant lookup.
+    exportedValues = Collections.unmodifiableSet(new HashSet<Resource>(
+        exportedMap.values()));
+  }
+
+}
\ No newline at end of file
diff --git a/dev/core/test/com/google/gwt/dev/jdt/ByteCodeCompilerTest.java b/dev/core/test/com/google/gwt/dev/jdt/ByteCodeCompilerTest.java
deleted file mode 100644
index 0f24786..0000000
--- a/dev/core/test/com/google/gwt/dev/jdt/ByteCodeCompilerTest.java
+++ /dev/null
@@ -1,404 +0,0 @@
-/*
- * Copyright 2008 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.jdt;
-
-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.dev.jdt.ByteCodeCompiler;
-import com.google.gwt.dev.jdt.RebindOracle;
-import com.google.gwt.dev.jdt.SourceOracle;
-import com.google.gwt.dev.jdt.URLCompilationUnitProvider;
-import com.google.gwt.dev.util.FileOracle;
-import com.google.gwt.dev.util.FileOracleFactory;
-import com.google.gwt.dev.util.FileOracleFactory.FileFilter;
-import com.google.gwt.dev.util.log.Loggers;
-
-import junit.framework.TestCase;
-
-import java.net.URL;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-public class ByteCodeCompilerTest extends TestCase {
-
-  private static class TestByteCodeCompilerHost implements SourceOracle,
-      RebindOracle {
-
-    private abstract class TestCup implements CompilationUnitProvider {
-
-      /**
-       * Creates a new {@code TestCup} with several types. The first type in 
-       * {@code typeNames} is considered to be the main type.
-       *
-       * @param packageName the package for the types in this {@code TestCup}
-       * @param typeNames the types for this {@code TestCup}. Must have
-       * at least one type. The first type is considered to be the main type
-       * for this {@code TestCup}. 
-       */
-      public TestCup(String packageName, String... typeNames) {
-        this.packageName = packageName;
-        registerPackage(packageName);
-        for (int i = 0; i < typeNames.length; i++) {
-          if (packageName.length() > 0) {
-            registerType(packageName + "." + typeNames[i], this);
-          } else {
-            // In the default package.
-            //
-            registerType(typeNames[i], this);
-          }
-        }
-        firstTypeName = typeNames[0];
-      }
-
-      public long getLastModified() throws UnableToCompleteException {
-        return 0;
-      }
-
-      public String getLocation() {
-        return "transient source for " + packageName + "." + firstTypeName;
-      }
-
-      public String getMainTypeName() {
-        return firstTypeName;
-      }
-
-      public String getPackageName() {
-        return packageName;
-      }
-
-      public abstract char[] getSource();
-
-      public boolean isTransient() {
-        return true;
-      }
-
-      private final String firstTypeName;
-      private final String packageName;
-    }
-
-    public TestByteCodeCompilerHost() {
-      registerPackage(""); // the default package
-    }
-
-    public final CompilationUnitProvider findCompilationUnit(TreeLogger logger,
-        String typeName) {
-      return cupsByTypeName.get(typeName);
-    }
-
-    public final boolean isPackage(String possiblePackageName) {
-      return pkgNames.contains(possiblePackageName);
-    }
-
-    // Override for specific test cases.
-    //
-    public String rebind(TreeLogger logger, String typeName)
-        throws UnableToCompleteException {
-      return typeName;
-    }
-
-    public final void registerPackage(String packageName) {
-      String[] packageParts = packageName.split("\\.");
-      String toRegister = null;
-      for (int i = 0; i < packageParts.length; i++) {
-        String part = packageParts[i];
-        if (toRegister != null) {
-          toRegister += "." + part;
-        } else {
-          toRegister = part;
-        }
-        pkgNames.add(toRegister);
-      }
-    }
-
-    public final void registerType(String typeName, TestCup cup) {
-      cupsByTypeName.put(typeName, cup);
-    }
-
-    {
-      pkgNames = new HashSet<String>();
-      cupsByTypeName = new HashMap<String, CompilationUnitProvider>();
-    }
-
-    final CompilationUnitProvider CU_AB = new TestCup("test", "A", "A.B") {
-      public char[] getSource() {
-        StringBuffer sb = new StringBuffer();
-        sb.append("package test;\n");
-        sb.append("public class A {\n");
-        sb.append("  public static class B extends A { }\n");
-        sb.append("}\n");
-        return sb.toString().toCharArray();
-      }
-    };
-
-    final CompilationUnitProvider CU_C = new TestCup("test", "C", "C.Message") {
-      public char[] getSource() {
-        StringBuffer sb = new StringBuffer();
-        sb.append("package test;\n");
-        sb.append("import com.google.gwt.core.client.GWT;\n");
-        sb.append("public class C {\n");
-        sb.append("  public static String getMessage() {\n");
-        sb.append("    return ((Message)GWT.create(Message.class)).f();\n");
-        sb.append("  }\n");
-        sb.append("  public static class Message {\n");
-        sb.append("    public String f() {\n");
-        sb.append("      return \"C.Message\";\n");
-        sb.append("    }\n");
-        sb.append("  }\n");
-        sb.append("}\n");
-        return sb.toString().toCharArray();
-      }
-    };
-
-    final CompilationUnitProvider CU_CLASS = new TestCup("java.lang", "Class") {
-
-      public char[] getSource() {
-        StringBuffer sb = new StringBuffer();
-        sb.append("package java.lang;\n");
-        sb.append("public class Class { }\n");
-        return sb.toString().toCharArray();
-      }
-    };
-
-    /**
-     * This one is different because D is not public and it lives in the default
-     * package.
-     */
-    final CompilationUnitProvider CU_DE = new TestCup("", "D", "D.E") {
-      public char[] getSource() {
-        StringBuffer sb = new StringBuffer();
-        sb.append("class D extends test.C.Message {\n");
-        sb.append("  public static class E extends D {\n");
-        sb.append("    public String getMessage() {\n");
-        sb.append("      return \"D.E.Message\";\n");
-        sb.append("    }\n");
-        sb.append("  }\n");
-        sb.append("}\n");
-        return sb.toString().toCharArray();
-      }
-    };
-
-    final CompilationUnitProvider CU_GWT = new TestCup(
-        "com.google.gwt.core.client", "GWT") {
-
-      public char[] getSource() {
-        StringBuffer sb = new StringBuffer();
-        sb.append("package com.google.gwt.core.client;\n");
-        sb.append("public final class GWT {\n");
-        sb.append("  public static Object create(Class classLiteral) { return null; }\n");
-        sb.append("}\n");
-        return sb.toString().toCharArray();
-      }
-    };
-
-    final CompilationUnitProvider CU_MAIN = new TestCup("test", "Main") {
-
-      public char[] getSource() {
-        StringBuffer sb = new StringBuffer();
-        sb.append("package test;\n");
-        sb.append("import com.google.gwt.core.client.GWT;\n");
-        sb.append("public class Main {\n");
-        sb.append("  public static void main(String[] args) {\n");
-        sb.append("    A a = (A)GWT.create(A.class);\n");
-        sb.append("  }\n");
-        sb.append("}\n");
-        return sb.toString().toCharArray();
-      }
-    };
-
-    final TestCup CU_OBJECT = new TestCup("java.lang", "Object") {
-      public char[] getSource() {
-        StringBuffer sb = new StringBuffer();
-        sb.append("package java.lang;\n");
-        sb.append("public class Object { }\n");
-        return sb.toString().toCharArray();
-      }
-    };
-
-    final CompilationUnitProvider CU_STRING = new TestCup("java.lang", "String") {
-
-      public char[] getSource() {
-        StringBuffer sb = new StringBuffer();
-        sb.append("package java.lang;\n");
-        sb.append("public class String { }\n");
-        return sb.toString().toCharArray();
-      }
-    };
-
-    private final Map<String, CompilationUnitProvider> cupsByTypeName;
-    private final Set<String> pkgNames;
-  }
-
-  private static void scanAndCompile(TreeLogger logger)
-      throws UnableToCompleteException {
-    FileOracleFactory fof = new FileOracleFactory();
-    fof.addPackage("", new FileFilter() {
-      public boolean accept(String string) {
-        return string.endsWith(".java");
-      }
-    });
-    final FileOracle fo = fof.create(logger);
-
-    final SourceOracle host = new SourceOracle() {
-
-      public CompilationUnitProvider findCompilationUnit(TreeLogger logger,
-          String typeName) {
-        // ONLY LOOK FOR TOP-LEVEL TYPES.
-        //
-        CompilationUnitProvider cup = cups.get(typeName);
-        if (cup == null) {
-          String path = typeName.replace('.', '/') + ".java";
-          URL url = fo.find(path);
-          if (url != null) {
-            String pkgName = "";
-            int len = findLengthOfPackagePart(typeName);
-            if (len > 0) {
-              pkgName = typeName.substring(0, len);
-            }
-            return new URLCompilationUnitProvider(url, pkgName);
-          } else {
-            return null;
-          }
-        }
-        return cup;
-      }
-
-      public boolean isPackage(String possiblePackageName) {
-        String path = possiblePackageName.replace('.', '/') + "/";
-        URL url = fo.find(path);
-        if (url != null) {
-          return true;
-        } else {
-          return false;
-        }
-      }
-
-      private int findLengthOfPackagePart(String typeName) {
-        int maxDotIndex = 0;
-        int i = typeName.indexOf('.');
-        while (i != -1) {
-          if (!isPackage(typeName.substring(0, i))) {
-            break;
-          } else {
-            maxDotIndex = i;
-            i = typeName.indexOf('.', i + 1);
-          }
-        }
-        return maxDotIndex;
-      }
-
-      private Map<String, CompilationUnitProvider> cups = new HashMap<String, CompilationUnitProvider>();
-    };
-
-    ByteCodeCompiler cs = new ByteCodeCompiler(host);
-    String[] allJava = fo.getAllFiles();
-
-    for (int i = 0; i < 3; ++i) {
-      long before = System.currentTimeMillis();
-
-      for (int j = 0; j < allJava.length; j++) {
-        String typeName = allJava[j].substring(0, allJava[j].length() - 5).replace(
-            '/', '.');
-        cs.getClassBytes(logger, typeName);
-      }
-
-      long after = System.currentTimeMillis();
-      System.out.println("Iter " + i + " took " + (after - before) + " ms");
-    }
-  }
-
-  // This one is standalone.
-  //
-  public void testJavaLangObject() throws Exception {
-    ByteCodeCompiler cs = new ByteCodeCompiler(testHost);
-    assertNotNull(cs.getClassBytes(logger, "java.lang.Object"));
-  }
-
-  // This one requires java.lang.Object.
-  //
-  public void testJavaLangString() throws Exception {
-    ByteCodeCompiler cs = new ByteCodeCompiler(testHost);
-    assertNotNull(cs.getClassBytes(logger, "java.lang.Object"));
-    assertNotNull(cs.getClassBytes(logger, "java.lang.String"));
-  }
-
-  // Try deferred binding that takes three compile iterations.
-  // - In Main, we rebind from A to C
-  // - In C, we rebind from C to D.E
-  //
-  public void testRebindCreateTransitive() throws Exception {
-    ByteCodeCompiler cs = new ByteCodeCompiler(new TestByteCodeCompilerHost() {
-      public String rebind(TreeLogger logger, String typeName)
-          throws com.google.gwt.core.ext.UnableToCompleteException {
-        if ("test.C.Message".equals(typeName)) {
-          return "D.E";
-        } else {
-          return typeName;
-        }
-      }
-    });
-
-    assertNotNull(cs.getClassBytes(logger, "test.C"));
-    assertNotNull(cs.getClassBytes(logger, "test.C$Message"));
-    assertNotNull(cs.getClassBytes(logger, "D"));
-    assertNotNull(cs.getClassBytes(logger, "D$E"));
-
-    assertNotNull(cs.getClassBytes(logger, "java.lang.Object"));
-    assertNotNull(cs.getClassBytes(logger, "java.lang.String"));
-    assertNotNull(cs.getClassBytes(logger, "java.lang.Class"));
-    assertNotNull(cs.getClassBytes(logger, "com.google.gwt.core.client.GWT"));
-  }
-
-  // Try deferred binding that works.
-  //
-  public void testRebindCreateWithSuccess() throws Exception {
-    ByteCodeCompiler cs = new ByteCodeCompiler(new TestByteCodeCompilerHost() {
-      public String rebind(TreeLogger logger, String typeName)
-          throws com.google.gwt.core.ext.UnableToCompleteException {
-        if ("test.A".equals(typeName)) {
-          return "test.A.B";
-        } else {
-          return typeName;
-        }
-      }
-    });
-
-    assertNotNull(cs.getClassBytes(logger, "java.lang.Object"));
-    assertNotNull(cs.getClassBytes(logger, "java.lang.String"));
-    assertNotNull(cs.getClassBytes(logger, "java.lang.Class"));
-    assertNotNull(cs.getClassBytes(logger, "com.google.gwt.core.client.GWT"));
-
-    assertNotNull(cs.getClassBytes(logger, "test.Main"));
-    assertNotNull(cs.getClassBytes(logger, "test.A"));
-    assertNotNull(cs.getClassBytes(logger, "test.A$B"));
-
-    // Check again for the same class to make sure it's cached.
-    // (Although you have to run this test with "-Dgwt.useGuiLogger" defined
-    // to see what it does.)
-    //
-    assertNotNull(cs.getClassBytes(logger, "java.lang.Object"));
-  }
-
-  protected void setUp() throws Exception {
-    logger = Loggers.createOptionalGuiTreeLogger();
-    testHost = new TestByteCodeCompilerHost();
-  }
-
-  private TreeLogger logger = TreeLogger.NULL;
-  private TestByteCodeCompilerHost testHost = null;
-}
diff --git a/dev/core/test/com/google/gwt/dev/jdt/TypeOracleTestingUtils.java b/dev/core/test/com/google/gwt/dev/jdt/TypeOracleTestingUtils.java
deleted file mode 100644
index 1b68679..0000000
--- a/dev/core/test/com/google/gwt/dev/jdt/TypeOracleTestingUtils.java
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Copyright 2008 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.jdt;
-
-import com.google.gwt.core.ext.TreeLogger;
-import com.google.gwt.core.ext.UnableToCompleteException;
-import com.google.gwt.core.ext.typeinfo.CompilationUnitProvider;
-
-/**
- * Utilities for tests that build a type oracle and watch for errors.
- * 
- */
-public class TypeOracleTestingUtils {
-  public static void addCup(TypeOracleBuilder builder, String typeName,
-      CharSequence code) throws UnableToCompleteException {
-    CompilationUnitProvider cup = createCup(typeName, code);
-    builder.addCompilationUnit(cup);
-  }
-
-  public static CompilationUnitProvider createCup(String typeName,
-      CharSequence code) {
-    String packageName;
-    String className;
-    int pos = typeName.lastIndexOf('.');
-    if (pos >= 0) {
-      packageName = typeName.substring(0, pos);
-      className = typeName.substring(pos + 1);
-    } else {
-      packageName = "";
-      className = typeName;
-    }
-    StaticCompilationUnitProvider cup = new StaticCompilationUnitProvider(
-        packageName, className, code.toString().toCharArray());
-    return cup;
-  }
-
-  /**
-   * Add compilation units for basic classes like Object and String.
-   */
-  public static void addStandardCups(TypeOracleBuilder builder)
-      throws UnableToCompleteException {
-    {
-      StringBuffer code = new StringBuffer();
-      code.append("package java.lang;\n");
-      code.append("public class Object {\n");
-      code.append("  public String toString() { return \"Object\"; }\n");
-      code.append("  public Object clone() { return this; } ");
-      code.append("}\n");
-      addCup(builder, "java.lang.Object", code);
-    }
-    {
-      StringBuffer code = new StringBuffer();
-      code.append("package java.lang;\n");
-      code.append("public class Class<T> {\n");
-      code.append("}\n");
-      addCup(builder, "java.lang.Class", code);
-    }
-    {
-      StringBuffer code = new StringBuffer();
-      code.append("package java.lang;\n");
-      code.append("public final class String {\n");
-      code.append("  public int length() { return 0; }\n");
-      code.append("}\n");
-      addCup(builder, "java.lang.String", code);
-    }
-    {
-      StringBuffer code = new StringBuffer();
-      code.append("package java.lang;\n");
-      code.append("public interface Serializable { }\n");
-      addCup(builder, "java.lang.Serializable", code);
-    }
-    {
-      StringBuffer code = new StringBuffer();
-      code.append("package java.util;\n");
-      code.append("public interface Map<K,V> { }\n");
-      addCup(builder, "java.util.Map", code);
-    }
-    {
-      StringBuffer code = new StringBuffer();
-      code.append("package java.lang;\n");
-      code.append("public @interface SuppressWarnings {\n");
-      code.append("  String[] value();\n");
-      code.append("}\n");
-      addCup(builder, "java.lang.SuppressWarnings", code);
-    }
-    {
-      StringBuffer code = new StringBuffer();
-      code.append("package java.lang.annotation;\n");
-      code.append("public interface Annotation {\n");
-      code.append("}\n");
-      addCup(builder, "java.lang.annotation.Annotation", code);
-    }
-    {
-      StringBuffer code = new StringBuffer();
-      code.append("package com.google.gwt.core.client;\n");
-      code.append("public class JavaScriptObject {\n");
-      code.append("  protected JavaScriptObject() { }\n");
-      code.append("}\n");
-      addCup(builder, "com.google.gwt.core.client.JavaScriptObject", code);
-    }
-  }
-
-  public static void buildTypeOracleForCode(String typeName, CharSequence code,
-      TreeLogger testLogger) throws UnableToCompleteException {
-    TypeOracleBuilder builder = new TypeOracleBuilder();
-    addStandardCups(builder);
-    addCup(builder, typeName, code);
-    builder.build(testLogger);
-  }
-}
diff --git a/dev/core/test/com/google/gwt/dev/resource/impl/AbstractResourceOrientedTestBase.java b/dev/core/test/com/google/gwt/dev/resource/impl/AbstractResourceOrientedTestBase.java
new file mode 100644
index 0000000..6d5e3ee
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/resource/impl/AbstractResourceOrientedTestBase.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright 2008 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.resource.impl;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.dev.resource.Resource;
+import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.jar.JarFile;
+
+/**
+ * Shared abstract class for tests that rely on well-known test data.
+ * 
+ * These tests rely on the external existence of the following files under
+ * <code>test/com/google/gwt/dev/javac/impl/testdata/</code>
+ * 
+ * <pre>
+ * cpe1/com/google/gwt/user/User.gwt.xml
+ * cpe1/com/google/gwt/user/client/Command.java
+ * cpe1/com/google/gwt/user/client/Timer.java
+ * cpe1/com/google/gwt/user/client/ui/Widget.java
+ * cpe1/org/example/bar/client/BarClient1.txt
+ * cpe1/org/example/bar/client/BarClient2.txt
+ * cpe1/org/example/bar/client/etc/BarEtc.txt
+ * cpe1/org/example/foo/client/FooClient.java
+ * cpe1/org/example/foo/server/FooServer.java
+ * 
+ * cpe1/com/google/gwt/i18n/I18N.gwt.xml
+ * cpe2/com/google/gwt/i18n/client/Messages.java
+ * cpe2/com/google/gwt/i18n/rebind/LocalizableGenerator.java
+ * cpe2/org/example/bar/client/BarClient2.txt
+ * cpe2/org/example/bar/client/BarClient3.txt
+ * </pre>
+ * 
+ * The same files should be present in jar files named cpe1.jar and cpe2.jar;
+ * note that the contents of each will not have the <code>cpe1/</code> and
+ * <code>cpe2/</code> prefixes (respectively) on their contained files.
+ */
+public abstract class AbstractResourceOrientedTestBase extends TestCase {
+
+  private static class ExcludeSvnClassPathEntry extends ClassPathEntry {
+    private final ClassPathEntry cpe;
+
+    public ExcludeSvnClassPathEntry(ClassPathEntry cpe) {
+      this.cpe = cpe;
+    }
+
+    @Override
+    public Set<AbstractResource> findApplicableResources(TreeLogger logger,
+        PathPrefixSet pathPrefixSet) {
+      Set<AbstractResource> results = new HashSet<AbstractResource>();
+      Set<AbstractResource> rs = cpe.findApplicableResources(logger,
+          pathPrefixSet);
+      for (AbstractResource r : rs) {
+        if (r.getPath().indexOf(".svn/") < 0) {
+          results.add(r);
+        }
+      }
+      return results;
+    }
+
+    @Override
+    public String getLocation() {
+      return cpe.getLocation();
+    }
+
+    @Override
+    public String toString() {
+      return cpe.toString();
+    }
+  }
+
+  private static class MOCK_CPE1 extends MockClassPathEntry {
+    public MOCK_CPE1() {
+      super("/cpe1/");
+      addResource("com/google/gwt/user/User.gwt.xml");
+      addResource("com/google/gwt/user/client/Command.java");
+      addResource("com/google/gwt/user/client/Timer.java");
+      addResource("com/google/gwt/user/client/ui/Widget.java");
+      addResource("org/example/bar/client/BarClient1.txt");
+      addResource("org/example/bar/client/BarClient2.txt");
+      addResource("org/example/bar/client/etc/BarEtc.txt");
+      addResource("org/example/foo/client/FooClient.java");
+      addResource("org/example/foo/server/FooServer.java");
+    }
+  }
+
+  private static class MOCK_CPE2 extends MockClassPathEntry {
+    public MOCK_CPE2() {
+      super("C:\\cpe2");
+      addResource("com/google/gwt/i18n/I18N.gwt.xml");
+      addResource("com/google/gwt/i18n/client/Messages.java");
+      addResource("com/google/gwt/i18n/rebind/LocalizableGenerator.java");
+      addResource("org/example/bar/client/BarClient2.txt");
+      addResource("org/example/bar/client/BarClient3.txt");
+    }
+  }
+
+  // Set LOG_TO_CONSOLE to true to see a play-by-play.
+  private static final boolean LOG_TO_CONSOLE = false;
+
+  public static TreeLogger createTestTreeLogger() {
+    if (LOG_TO_CONSOLE) {
+      PrintWriterTreeLogger treeLogger = new PrintWriterTreeLogger();
+      treeLogger.setMaxDetail(TreeLogger.ALL);
+      treeLogger.log(TreeLogger.INFO, "=== logger start ===");
+      return treeLogger;
+    } else {
+      return TreeLogger.NULL;
+    }
+  }
+
+  protected void assertPathIncluded(Set<AbstractResource> resources, String path) {
+    assertNotNull(findResourceWithPath(resources, path));
+  }
+
+  protected void assertPathNotIncluded(Set<AbstractResource> resources,
+      String path) {
+    assertNull(findResourceWithPath(resources, path));
+  }
+
+  protected File findJarDirectory(String name) throws URISyntaxException {
+    ClassLoader classLoader = getClass().getClassLoader();
+    URL url = classLoader.getResource(name);
+    assertNotNull("Expecting on the classpath: " + name);
+    File file = new File(url.toURI());
+    assertTrue("Cannot read as file: " + url.toExternalForm(), file.canRead());
+    return file;
+  }
+
+  protected File findJarFile(String name) throws URISyntaxException {
+    ClassLoader classLoader = getClass().getClassLoader();
+    URL url = classLoader.getResource(name);
+    assertNotNull(
+        "Expecting on the classpath: "
+            + name
+            + "; did you forget to put the source root containing this very source file to the classpath?",
+        url);
+    File file = new File(url.toURI());
+    assertTrue("Cannot read as file: " + url.toExternalForm(), file.canRead());
+    return file;
+  }
+
+  protected Resource findResourceWithPath(Set<AbstractResource> resources,
+      String path) {
+    for (Resource r : resources) {
+      if (r.getPath().equals(path)) {
+        return r;
+      }
+    }
+    return null;
+  }
+
+  protected ClassPathEntry getClassPathEntry1AsDirectory()
+      throws URISyntaxException {
+    File dir = findJarDirectory("com/google/gwt/dev/resource/impl/testdata/cpe1");
+    return new ExcludeSvnClassPathEntry(new DirectoryClassPathEntry(dir));
+  }
+
+  protected ClassPathEntry getClassPathEntry1AsJar() throws IOException,
+      URISyntaxException {
+    File file = findJarFile("com/google/gwt/dev/resource/impl/testdata/cpe1.jar");
+    return new ExcludeSvnClassPathEntry(new JarFileClassPathEntry(new JarFile(
+        file)));
+  }
+
+  protected ClassPathEntry getClassPathEntry1AsMock() {
+    return new ExcludeSvnClassPathEntry(new MOCK_CPE1());
+  }
+
+  protected ClassPathEntry getClassPathEntry2AsDirectory()
+      throws URISyntaxException {
+    File dir = findJarDirectory("com/google/gwt/dev/resource/impl/testdata/cpe2");
+    return new ExcludeSvnClassPathEntry(new DirectoryClassPathEntry(dir));
+  }
+
+  protected ClassPathEntry getClassPathEntry2AsJar() throws URISyntaxException,
+      IOException {
+    File file = findJarFile("com/google/gwt/dev/resource/impl/testdata/cpe2.jar");
+    return new ExcludeSvnClassPathEntry(new JarFileClassPathEntry(new JarFile(
+        file)));
+  }
+
+  protected ClassPathEntry getClassPathEntry2AsMock() {
+    return new ExcludeSvnClassPathEntry(new MOCK_CPE2());
+  }
+}
diff --git a/dev/core/test/com/google/gwt/dev/resource/impl/ClassPathEntryTest.java b/dev/core/test/com/google/gwt/dev/resource/impl/ClassPathEntryTest.java
new file mode 100644
index 0000000..3454176
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/resource/impl/ClassPathEntryTest.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright 2008 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.resource.impl;
+
+import com.google.gwt.core.ext.TreeLogger;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.util.Set;
+
+public class ClassPathEntryTest extends AbstractResourceOrientedTestBase {
+
+  public void testAllCpe1FilesFound() throws URISyntaxException, IOException {
+    testAllCpe1FilesFound(getClassPathEntry1AsJar());
+    testAllCpe1FilesFound(getClassPathEntry1AsDirectory());
+  }
+
+  public void testAllCpe2FilesFound() throws URISyntaxException, IOException {
+    testAllCpe2FilesFound(getClassPathEntry2AsJar());
+    testAllCpe2FilesFound(getClassPathEntry2AsDirectory());
+  }
+
+  public void testPathPrefixSetChanges() throws IOException, URISyntaxException {
+    ClassPathEntry cpe1jar = getClassPathEntry1AsJar();
+    ClassPathEntry cpe1dir = getClassPathEntry1AsDirectory();
+    ClassPathEntry cpe2jar = getClassPathEntry2AsJar();
+    ClassPathEntry cpe2dir = getClassPathEntry2AsDirectory();
+
+    testPathPrefixSetChanges(cpe1jar, cpe2jar);
+    testPathPrefixSetChanges(cpe1dir, cpe2dir);
+    testPathPrefixSetChanges(cpe1jar, cpe2dir);
+    testPathPrefixSetChanges(cpe1dir, cpe2jar);
+  }
+
+  public void testUseOfPrefixesWithFiltering() throws IOException,
+      URISyntaxException {
+    ClassPathEntry cpe1jar = getClassPathEntry1AsJar();
+    ClassPathEntry cpe1dir = getClassPathEntry1AsDirectory();
+    ClassPathEntry cpe2jar = getClassPathEntry2AsJar();
+    ClassPathEntry cpe2dir = getClassPathEntry2AsDirectory();
+
+    testUseOfPrefixesWithFiltering(cpe1jar, cpe2jar);
+    testUseOfPrefixesWithFiltering(cpe1dir, cpe2dir);
+    testUseOfPrefixesWithFiltering(cpe1jar, cpe2dir);
+    testUseOfPrefixesWithFiltering(cpe1dir, cpe2jar);
+  }
+
+  public void testUseOfPrefixesWithoutFiltering() throws URISyntaxException,
+      IOException {
+    ClassPathEntry cpe1jar = getClassPathEntry1AsJar();
+    ClassPathEntry cpe1dir = getClassPathEntry1AsDirectory();
+    ClassPathEntry cpe2jar = getClassPathEntry2AsJar();
+    ClassPathEntry cpe2dir = getClassPathEntry2AsDirectory();
+
+    testUseOfPrefixesWithoutFiltering(cpe1jar, cpe2jar);
+    testUseOfPrefixesWithoutFiltering(cpe1dir, cpe2dir);
+    testUseOfPrefixesWithoutFiltering(cpe1jar, cpe2dir);
+    testUseOfPrefixesWithoutFiltering(cpe1dir, cpe2jar);
+  }
+
+  public void testUseOfPrefixesWithoutFiltering(ClassPathEntry cpe1,
+      ClassPathEntry cpe2) {
+    TreeLogger logger = createTestTreeLogger();
+
+    PathPrefixSet pps = new PathPrefixSet();
+    pps.add(new PathPrefix("com/google/gwt/user/client/", null));
+    pps.add(new PathPrefix("com/google/gwt/i18n/client/", null));
+
+    {
+      // Examine cpe1.
+      Set<AbstractResource> r = cpe1.findApplicableResources(logger, pps);
+
+      assertEquals(3, r.size());
+      assertPathIncluded(r, "com/google/gwt/user/client/Command.java");
+      assertPathIncluded(r, "com/google/gwt/user/client/Timer.java");
+      assertPathIncluded(r, "com/google/gwt/user/client/ui/Widget.java");
+    }
+
+    {
+      // Examine cpe2.
+      Set<AbstractResource> r = cpe2.findApplicableResources(logger, pps);
+
+      assertEquals(1, r.size());
+      assertPathIncluded(r, "com/google/gwt/i18n/client/Messages.java");
+    }
+  }
+
+  // NOTE: if this test fails, ensure that the source root containing this very
+  // source file is *FIRST* on the classpath
+  private void testAllCpe1FilesFound(ClassPathEntry cpe1) {
+    TreeLogger logger = createTestTreeLogger();
+
+    PathPrefixSet pps = new PathPrefixSet();
+    pps.add(new PathPrefix("", null));
+
+    Set<AbstractResource> r = cpe1.findApplicableResources(logger, pps);
+
+    assertEquals(9, r.size());
+    assertPathIncluded(r, "com/google/gwt/user/User.gwt.xml");
+    assertPathIncluded(r, "com/google/gwt/user/client/Command.java");
+    assertPathIncluded(r, "com/google/gwt/user/client/Timer.java");
+    assertPathIncluded(r, "com/google/gwt/user/client/ui/Widget.java");
+    assertPathIncluded(r, "org/example/bar/client/BarClient1.txt");
+    assertPathIncluded(r, "org/example/bar/client/BarClient2.txt");
+    assertPathIncluded(r, "org/example/bar/client/etc/BarEtc.txt");
+    assertPathIncluded(r, "org/example/foo/client/FooClient.java");
+    assertPathIncluded(r, "org/example/foo/server/FooServer.java");
+  }
+
+  // NOTE: if this test fails, ensure that the source root containing this very
+  // source file is on the classpath
+  private void testAllCpe2FilesFound(ClassPathEntry cpe2) {
+    TreeLogger logger = createTestTreeLogger();
+
+    PathPrefixSet pps = new PathPrefixSet();
+    pps.add(new PathPrefix("", null));
+    Set<AbstractResource> r = cpe2.findApplicableResources(logger, pps);
+
+    assertEquals(5, r.size());
+    assertPathIncluded(r, "com/google/gwt/i18n/I18N.gwt.xml");
+    assertPathIncluded(r, "com/google/gwt/i18n/client/Messages.java");
+    assertPathIncluded(r,
+        "com/google/gwt/i18n/rebind/LocalizableGenerator.java");
+    assertPathIncluded(r, "org/example/bar/client/BarClient2.txt");
+    assertPathIncluded(r, "org/example/bar/client/BarClient3.txt");
+  }
+
+  private void testPathPrefixSetChanges(ClassPathEntry cpe1, ClassPathEntry cpe2) {
+    TreeLogger logger = createTestTreeLogger();
+
+    {
+      // Filter is not set yet.
+      PathPrefixSet pps = new PathPrefixSet();
+      pps.add(new PathPrefix("com/google/gwt/user/", null));
+      pps.add(new PathPrefix("com/google/gwt/i18n/", null));
+
+      // Examine cpe1 in the absence of the filter.
+      Set<AbstractResource> r1 = cpe1.findApplicableResources(logger, pps);
+
+      assertEquals(4, r1.size());
+      assertPathIncluded(r1, "com/google/gwt/user/User.gwt.xml");
+      assertPathIncluded(r1, "com/google/gwt/user/client/Command.java");
+      assertPathIncluded(r1, "com/google/gwt/user/client/Timer.java");
+      assertPathIncluded(r1, "com/google/gwt/user/client/ui/Widget.java");
+
+      // Examine cpe2 in the absence of the filter.
+      Set<AbstractResource> r2 = cpe2.findApplicableResources(logger, pps);
+
+      assertEquals(3, r2.size());
+      assertPathIncluded(r2, "com/google/gwt/i18n/I18N.gwt.xml");
+      assertPathIncluded(r2, "com/google/gwt/i18n/client/Messages.java");
+      assertPathIncluded(r2,
+          "com/google/gwt/i18n/rebind/LocalizableGenerator.java");
+    }
+
+    {
+      // Create a pps with a filter.
+      ResourceFilter excludeXmlFiles = new ResourceFilter() {
+        public boolean allows(String path) {
+          return !path.endsWith(".xml");
+        }
+      };
+
+      PathPrefixSet pps = new PathPrefixSet();
+      pps.add(new PathPrefix("com/google/gwt/user/", excludeXmlFiles));
+      pps.add(new PathPrefix("com/google/gwt/i18n/", excludeXmlFiles));
+
+      // Examine cpe1 in the presence of the filter.
+      Set<AbstractResource> r1 = cpe1.findApplicableResources(logger, pps);
+
+      assertEquals(3, r1.size());
+      assertPathNotIncluded(r1, "com/google/gwt/user/User.gwt.xml");
+      assertPathIncluded(r1, "com/google/gwt/user/client/Command.java");
+      assertPathIncluded(r1, "com/google/gwt/user/client/Timer.java");
+      assertPathIncluded(r1, "com/google/gwt/user/client/ui/Widget.java");
+
+      // Examine cpe2 in the presence of the filter.
+      Set<AbstractResource> r2 = cpe2.findApplicableResources(logger, pps);
+
+      assertEquals(2, r2.size());
+      assertPathNotIncluded(r1, "com/google/gwt/user/User.gwt.xml");
+      assertPathIncluded(r2, "com/google/gwt/i18n/client/Messages.java");
+      assertPathIncluded(r2,
+          "com/google/gwt/i18n/rebind/LocalizableGenerator.java");
+    }
+
+    {
+      /*
+       * Change the prefix path set to the zero-lenth prefix (which allows
+       * everything), but specify a filter that disallows everything.
+       */
+      PathPrefixSet pps = new PathPrefixSet();
+      pps.add(new PathPrefix("", new ResourceFilter() {
+        public boolean allows(String path) {
+          // Exclude everything.
+          return false;
+        }
+      }));
+
+      // Examine cpe1 in the presence of the filter.
+      Set<AbstractResource> r1 = cpe1.findApplicableResources(logger, pps);
+
+      assertEquals(0, r1.size());
+
+      // Examine cpe2 in the presence of the filter.
+      Set<AbstractResource> r2 = cpe2.findApplicableResources(logger, pps);
+
+      assertEquals(0, r2.size());
+    }
+  }
+
+  private void testUseOfPrefixesWithFiltering(ClassPathEntry cpe1,
+      ClassPathEntry cpe2) {
+    TreeLogger logger = createTestTreeLogger();
+
+    PathPrefixSet pps = new PathPrefixSet();
+    ResourceFilter excludeXmlFiles = new ResourceFilter() {
+      public boolean allows(String path) {
+        return !path.endsWith(".xml");
+      }
+    };
+    // The prefix is intentionally starting at the module-level, not 'client'.
+    pps.add(new PathPrefix("com/google/gwt/user/", excludeXmlFiles));
+    pps.add(new PathPrefix("com/google/gwt/i18n/", excludeXmlFiles));
+
+    {
+      // Examine cpe1.
+      Set<AbstractResource> r = cpe1.findApplicableResources(logger, pps);
+
+      assertEquals(3, r.size());
+      // User.gwt.xml would be included but for the filter.
+      assertPathIncluded(r, "com/google/gwt/user/client/Command.java");
+      assertPathIncluded(r, "com/google/gwt/user/client/Timer.java");
+      assertPathIncluded(r, "com/google/gwt/user/client/ui/Widget.java");
+    }
+
+    {
+      // Examine cpe2.
+      Set<AbstractResource> r = cpe2.findApplicableResources(logger, pps);
+
+      assertEquals(2, r.size());
+      // I18N.gwt.xml would be included but for the filter.
+      assertPathIncluded(r, "com/google/gwt/i18n/client/Messages.java");
+      assertPathIncluded(r,
+          "com/google/gwt/i18n/rebind/LocalizableGenerator.java");
+    }
+  }
+
+}
diff --git a/dev/core/test/com/google/gwt/dev/resource/impl/FileResourceTest.java b/dev/core/test/com/google/gwt/dev/resource/impl/FileResourceTest.java
new file mode 100644
index 0000000..7a38934
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/resource/impl/FileResourceTest.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2008 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.resource.impl;
+
+import com.google.gwt.dev.util.Util;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.io.IOException;
+
+public class FileResourceTest extends TestCase {
+
+  public void testDeletion() {
+    File f = null;
+    try {
+      f = File.createTempFile("com.google.gwt.dev.javac.impl.FileResourceTest",
+          ".tmp");
+      f.deleteOnExit();
+      Util.writeStringAsFile(f, "contents 1");
+    } catch (IOException e) {
+      fail("Failed to create test file");
+    }
+
+    File dir = f.getParentFile();
+    DirectoryClassPathEntry cpe = new DirectoryClassPathEntry(dir);
+    FileResource r = new FileResource(cpe, f.getName(), f);
+    assertEquals(f.getAbsoluteFile().toURI().toString(), r.getLocation());
+
+    /*
+     * In this case, there's no subdirectory, so the path should match the
+     * simple filename.
+     */
+    assertEquals(f.getName(), r.getPath());
+
+    // Shouldn't be stale yet.
+    assertFalse(r.isStale());
+
+    /*
+     * Touch the file at more than one second to ensure there's a noticeable
+     * difference on every platform.
+     */
+    f.setLastModified(f.lastModified() + 1500);
+
+    // Should be stale now.
+    assertTrue(r.isStale());
+  }
+
+  public void testModification() {
+    File f = null;
+    try {
+      f = File.createTempFile("com.google.gwt.dev.javac.impl.FileResourceTest",
+          ".tmp");
+      f.deleteOnExit();
+      Util.writeStringAsFile(f, "contents 1");
+    } catch (IOException e) {
+      fail("Failed to create test file");
+    }
+
+    File dir = f.getParentFile();
+    DirectoryClassPathEntry cpe = new DirectoryClassPathEntry(dir);
+    FileResource r = new FileResource(cpe, f.getName(), f);
+    assertEquals(f.getAbsoluteFile().toURI().toString(), r.getLocation());
+
+    /*
+     * In this case, there's no subdirectory, so the path should match the
+     * simple filename.
+     */
+    assertEquals(f.getName(), r.getPath());
+
+    // Shouldn't be stale yet.
+    assertFalse(r.isStale());
+
+    // Delete the file.
+    f.delete();
+
+    // Should be stale now.
+    assertTrue(r.isStale());
+
+    // Get can't contents anymore, either.
+    assertNull(r.openContents());
+  }
+}
diff --git a/dev/core/test/com/google/gwt/dev/resource/impl/MockAbstractResource.java b/dev/core/test/com/google/gwt/dev/resource/impl/MockAbstractResource.java
new file mode 100644
index 0000000..25de30f
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/resource/impl/MockAbstractResource.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2008 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.resource.impl;
+
+import junit.framework.Assert;
+
+import java.io.InputStream;
+import java.net.URL;
+
+public final class MockAbstractResource extends AbstractResource {
+  private boolean isStale;
+  private final MockClassPathEntry mockClassPathEntry;
+  private final String path;
+
+  public MockAbstractResource(MockClassPathEntry mockClassPathEntry, String path) {
+    this.mockClassPathEntry = mockClassPathEntry;
+    this.path = path;
+  }
+
+  @Override
+  public ClassPathEntry getClassPathEntry() {
+    return this.mockClassPathEntry;
+  }
+
+  @Override
+  public String getLocation() {
+    return this.mockClassPathEntry.pathRoot + "/" + path;
+  }
+
+  @Override
+  public String getPath() {
+    return path;
+  }
+
+  @Override
+  public URL getURL() {
+    return null;
+  }
+
+  @Override
+  public boolean isStale() {
+    return isStale;
+  }
+
+  @Override
+  public InputStream openContents() {
+    Assert.fail("Not implemented");
+    return null;
+  }
+
+  public void setStale(boolean isStale) {
+    this.isStale = isStale;
+  }
+}
\ No newline at end of file
diff --git a/dev/core/test/com/google/gwt/dev/resource/impl/MockClassPathEntry.java b/dev/core/test/com/google/gwt/dev/resource/impl/MockClassPathEntry.java
new file mode 100644
index 0000000..913b071
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/resource/impl/MockClassPathEntry.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2008 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.resource.impl;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.dev.resource.Resource;
+
+import junit.framework.Assert;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+public class MockClassPathEntry extends ClassPathEntry {
+
+  final String pathRoot;
+  private final Map<String, MockAbstractResource> resourceMap = new HashMap<String, MockAbstractResource>();
+
+  /**
+   * By default, MockClassPathEntry has an all-inclusive path prefix. Tests may
+   * change it by calling {@link #setPathPrefixes(PathPrefixSet)}.
+   */
+  public MockClassPathEntry(String pathRoot) {
+    this.pathRoot = pathRoot;
+  }
+
+  public void addResource(String resourcePath) {
+    Resource old = resourceMap.get(resourcePath);
+    Assert.assertNull(
+        "resource already exists; use updateResource() to replace", old);
+    resourceMap.put(resourcePath, createMockResource(resourcePath));
+  }
+
+  @Override
+  public Set<AbstractResource> findApplicableResources(TreeLogger logger,
+      PathPrefixSet pathPrefixes) {
+    // Only include resources that have the prefix and pass its filter.
+    HashSet<AbstractResource> results = new HashSet<AbstractResource>();
+    for (Map.Entry<String, MockAbstractResource> entry : resourceMap.entrySet()) {
+      String path = entry.getKey();
+      if (pathPrefixes.includesResource(path)) {
+        results.add(entry.getValue());
+      }
+    }
+
+    return results;
+  }
+
+  @Override
+  public String getLocation() {
+    return pathRoot;
+  }
+
+  public void removeResource(String resourcePath) {
+    Resource old = resourceMap.get(resourcePath);
+    Assert.assertNotNull(
+        "resource does not already exists; use addResource() to add it first",
+        old);
+    resourceMap.remove(resourcePath);
+  }
+
+  public void updateResource(String resourcePath) {
+    MockAbstractResource old = resourceMap.get(resourcePath);
+    Assert.assertNotNull(
+        "resource does not already exists; use addResource() if you were trying to add",
+        old);
+    old.setStale(true);
+    resourceMap.put(resourcePath, createMockResource(resourcePath));
+  }
+
+  private MockAbstractResource createMockResource(final String path) {
+    return new MockAbstractResource(this, path);
+  }
+
+}
diff --git a/dev/core/test/com/google/gwt/dev/resource/impl/PathPrefixSetTest.java b/dev/core/test/com/google/gwt/dev/resource/impl/PathPrefixSetTest.java
new file mode 100644
index 0000000..e9d581c
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/resource/impl/PathPrefixSetTest.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2008 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.resource.impl;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests the trie and filtering behavior of path prefix set.
+ */
+public class PathPrefixSetTest extends TestCase {
+
+  public void testEmptyPrefixSet() {
+    PathPrefixSet pps = new PathPrefixSet();
+    assertFalse(pps.includesResource("com/google/gwt/user/client/Command.java"));
+  }
+
+  public void testNonOverlappingPrefixes() {
+    {
+      /*
+       * Test with null filters to ensure nothing gets filtered out.
+       */
+      PathPrefixSet pps = new PathPrefixSet();
+      pps.add(new PathPrefix("com/google/gwt/user/client/", null));
+      pps.add(new PathPrefix("com/google/gwt/i18n/client/", null));
+      pps.add(new PathPrefix("com/google/gwt/dom/client/", null));
+
+      assertTrue(pps.includesDirectory("com/"));
+      assertTrue(pps.includesDirectory("com/google/"));
+      assertTrue(pps.includesDirectory("com/google/gwt/"));
+      assertTrue(pps.includesDirectory("com/google/gwt/user/"));
+      assertTrue(pps.includesDirectory("com/google/gwt/user/client/"));
+      assertTrue(pps.includesDirectory("com/google/gwt/user/client/ui/"));
+
+      assertFalse(pps.includesDirectory("org/"));
+      assertFalse(pps.includesDirectory("org/example/"));
+      assertFalse(pps.includesDirectory("com/google/gwt/user/server/"));
+      assertFalse(pps.includesDirectory("com/google/gwt/xml/client/"));
+
+      assertTrue(pps.includesResource("com/google/gwt/user/client/Command.java"));
+      assertTrue(pps.includesResource("com/google/gwt/user/client/Timer.java"));
+      assertTrue(pps.includesResource("com/google/gwt/i18n/client/Messages.java"));
+      assertTrue(pps.includesResource("com/google/gwt/dom/client/DivElement.java"));
+
+      assertFalse(pps.includesResource("com/google/gwt/user/rebind/rpc/ServiceInterfaceProxyGenerator.java"));
+      assertFalse(pps.includesResource("com/google/gwt/sample/hello/client/Hello.java"));
+      assertFalse(pps.includesResource("com/google/gwt/user/public/clear.cache.gif"));
+    }
+
+    {
+      /*
+       * Test with a real filter to ensure it does have an effect.
+       */
+      PathPrefixSet pps = new PathPrefixSet();
+      ResourceFilter allowsGifs = new ResourceFilter() {
+        public boolean allows(String path) {
+          return path.toLowerCase().endsWith(".gif");
+        }
+      };
+
+      pps.add(new PathPrefix("com/google/gwt/user/public/", allowsGifs));
+      pps.add(new PathPrefix("com/google/gwt/sample/mail/public/", allowsGifs));
+
+      // Correct prefix, and filter should allow .
+      assertTrue(pps.includesResource("com/google/gwt/user/public/clear.cache.gif"));
+      assertTrue(pps.includesResource("com/google/gwt/sample/mail/public/inboxIcon.gif"));
+
+      // Correct prefix, but filter should exclude.
+      assertFalse(pps.includesResource("com/google/gwt/user/public/README.txt"));
+      assertFalse(pps.includesResource("com/google/gwt/sample/mail/public/README.txt"));
+
+      // Wrong prefix, and filter would have excluded.
+      assertFalse(pps.includesResource("com/google/gwt/user/client/Command.java"));
+      assertFalse(pps.includesResource("com/google/gwt/user/rebind/rpc/ServiceInterfaceProxyGenerator.java"));
+
+      // Wrong prefix, but filter would have allowed it.
+      assertFalse(pps.includesResource("com/google/gwt/i18n/public/flags.gif"));
+    }
+  }
+
+  public void testOverlappingPrefixes() {
+    {
+      /*
+       * Without a filter.
+       */
+      PathPrefixSet pps = new PathPrefixSet();
+      pps.add(new PathPrefix("", null));
+      pps.add(new PathPrefix("a/", null));
+      pps.add(new PathPrefix("a/b/", null));
+      pps.add(new PathPrefix("a/b/c/", null));
+
+      assertTrue(pps.includesResource("W.java"));
+      assertTrue(pps.includesResource("a/X.java"));
+      assertTrue(pps.includesResource("a/b/Y.java"));
+      assertTrue(pps.includesResource("a/b/c/Z.java"));
+      assertTrue(pps.includesResource("a/b/c/d/V.java"));
+    }
+
+    {
+      /*
+       * Ensure the right filter applies.
+       */
+      PathPrefixSet pps = new PathPrefixSet();
+      pps.add(new PathPrefix("", null));
+      pps.add(new PathPrefix("a/", null));
+      pps.add(new PathPrefix("a/b/", new ResourceFilter() {
+        public boolean allows(String path) {
+          // Disallow anything ending with "FILTERMEOUT".
+          return !path.endsWith("FILTERMEOUT");
+        }
+      }));
+      pps.add(new PathPrefix("a/b/c/", null));
+
+      assertTrue(pps.includesResource("W.java"));
+      assertTrue(pps.includesResource("a/X.java"));
+      assertTrue(pps.includesResource("a/b/Y.java"));
+      // This should be gone, since it is found in b.
+      assertFalse(pps.includesResource("a/b/FILTERMEOUT"));
+      /*
+       * This should not be gone, because it is using c's (null) filter instead
+       * of b's. The logic here is that the prefix including c is more specific
+       * and seemed to want c's resources to be included.
+       */
+      assertTrue(pps.includesResource("a/b/c/DONT_FILTERMEOUT"));
+      assertTrue(pps.includesResource("a/b/c/Z.java"));
+      assertTrue(pps.includesResource("a/b/c/d/V.java"));
+    }
+  }
+
+  /**
+   * In essense, this tests support for the default package in Java.
+   */
+  public void testZeroLengthPrefix() {
+    {
+      /*
+       * Without a filter.
+       */
+      PathPrefixSet pps = new PathPrefixSet();
+      pps.add(new PathPrefix("", null));
+
+      assertTrue(pps.includesResource("W.java"));
+      assertTrue(pps.includesResource("a/X.java"));
+      assertTrue(pps.includesResource("a/b/Y.java"));
+      assertTrue(pps.includesResource("a/b/c/Z.java"));
+    }
+
+    {
+      /*
+       * With a filter.
+       */
+      PathPrefixSet pps = new PathPrefixSet();
+      pps.add(new PathPrefix("", new ResourceFilter() {
+        public boolean allows(String path) {
+          return path.endsWith("Y.java");
+        }
+      }));
+
+      assertFalse(pps.includesResource("W.java"));
+      assertFalse(pps.includesResource("a/X.java"));
+      assertTrue(pps.includesResource("a/b/Y.java"));
+      assertFalse(pps.includesResource("a/b/c/Z.java"));
+
+    }
+  }
+}
diff --git a/dev/core/test/com/google/gwt/dev/resource/impl/ResourceOracleImplRealClasspathTest.java b/dev/core/test/com/google/gwt/dev/resource/impl/ResourceOracleImplRealClasspathTest.java
new file mode 100644
index 0000000..2d228bc
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/resource/impl/ResourceOracleImplRealClasspathTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2008 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.resource.impl;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.dev.resource.Resource;
+
+import java.util.Map;
+
+/**
+ * Tests {@link ResourceOracleImpl} using the real class path.
+ * 
+ */
+public class ResourceOracleImplRealClasspathTest extends
+    AbstractResourceOrientedTestBase {
+
+  private static final PathPrefix JUNIT_PREFIX = new PathPrefix(
+      "junit/framework/", new ResourceFilter() {
+        public boolean allows(String path) {
+          return path.endsWith("TestCase.class");
+        }
+      });
+  private static final PathPrefix JUNIT_PREFIX_DUP = new PathPrefix(
+      "junit/framework/", new ResourceFilter() {
+        public boolean allows(String path) {
+          return path.endsWith("TestCase.class");
+        }
+      });
+
+  private static final PathPrefix THIS_CLASS_PREFIX = new PathPrefix(
+      "com/google/gwt/dev/resource/impl/", new ResourceFilter() {
+        public boolean allows(String path) {
+          return path.endsWith("ResourceOracleImplRealClasspathTest.class");
+        }
+      });
+
+  private static final PathPrefix THIS_CLASS_PREFIX_PLUS = new PathPrefix(
+      "com/google/gwt/dev/resource/impl/", new ResourceFilter() {
+        public boolean allows(String path) {
+          return path.endsWith("ResourceOracleImpl.class")
+              || path.endsWith("ResourceOracleImplRealClasspathTest.class");
+        }
+      });
+
+  private final TreeLogger logger = createTestTreeLogger();
+  private final ResourceOracleImpl resourceOracle = new ResourceOracleImpl(
+      logger);
+
+  public void testBasic() {
+    PathPrefixSet pathPrefixSet = new PathPrefixSet();
+    pathPrefixSet.add(JUNIT_PREFIX);
+    pathPrefixSet.add(THIS_CLASS_PREFIX);
+    resourceOracle.setPathPrefixes(pathPrefixSet);
+    resourceOracle.refresh(logger);
+    Map<String, Resource> resourceMap = resourceOracle.getResourceMap();
+    assertEquals(2, resourceMap.size());
+  }
+
+  public void testRefresh() {
+    PathPrefixSet pathPrefixSet = new PathPrefixSet();
+    pathPrefixSet.add(JUNIT_PREFIX);
+    pathPrefixSet.add(THIS_CLASS_PREFIX);
+    resourceOracle.setPathPrefixes(pathPrefixSet);
+    resourceOracle.refresh(logger);
+    Map<String, Resource> resourceMap = resourceOracle.getResourceMap();
+    assertEquals(2, resourceMap.size());
+
+    // Plain refresh should have no effect.
+    resourceOracle.refresh(logger);
+    assertSame(resourceMap, resourceOracle.getResourceMap());
+
+    // Setting same path entries should have no effect.
+    resourceOracle.setPathPrefixes(pathPrefixSet);
+    resourceOracle.refresh(logger);
+    assertSame(resourceMap, resourceOracle.getResourceMap());
+
+    // Setting identical path entries should have no effect.
+    pathPrefixSet = new PathPrefixSet();
+    pathPrefixSet.add(JUNIT_PREFIX);
+    pathPrefixSet.add(THIS_CLASS_PREFIX);
+    resourceOracle.setPathPrefixes(pathPrefixSet);
+    resourceOracle.refresh(logger);
+    assertSame(resourceMap, resourceOracle.getResourceMap());
+
+    // Setting identical result should have no effect.
+    pathPrefixSet.add(JUNIT_PREFIX_DUP);
+    resourceOracle.refresh(logger);
+    assertSame(resourceMap, resourceOracle.getResourceMap());
+
+    // Actually change the working set.
+    pathPrefixSet.add(THIS_CLASS_PREFIX_PLUS);
+    resourceOracle.refresh(logger);
+    Map<String, Resource> newResourceMap = resourceOracle.getResourceMap();
+    assertNotSame(resourceMap, newResourceMap);
+    assertEquals(3, newResourceMap.size());
+  }
+}
diff --git a/dev/core/test/com/google/gwt/dev/resource/impl/ResourceOracleImplTest.java b/dev/core/test/com/google/gwt/dev/resource/impl/ResourceOracleImplTest.java
new file mode 100644
index 0000000..c6fe7e0
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/resource/impl/ResourceOracleImplTest.java
@@ -0,0 +1,491 @@
+/*
+ * Copyright 2008 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.resource.impl;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.dev.resource.Resource;
+import com.google.gwt.util.tools.Utility;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Tests {@link ResourceOracleImpl}.
+ * 
+ * <pre>
+ * Important states to test:
+ * - No class path entries
+ * - A single class path entries
+ *   - init/add/update/remove
+ * - Multiple class path entries
+ *   - init/add/update/remove
+ *   - same as previous with shadowing
+ *   - same as previous with superceding
+ * </pre>
+ */
+public class ResourceOracleImplTest extends AbstractResourceOrientedTestBase {
+
+  // Starts empty but can change during tests.
+  private static class MOCK_CPE0 extends MockClassPathEntry {
+    public MOCK_CPE0() {
+      super("/cpe0/");
+    }
+  }
+
+  private static class MOCK_CPE3 extends MockClassPathEntry {
+    public MOCK_CPE3() {
+      super("/cpe3/");
+    }
+  }
+
+  private static class ResourceOracleSnapshot {
+    private final Set<Resource> resources;
+    private final Map<String, Resource> resourceMap;
+    private final Set<String> pathNames;
+
+    public ResourceOracleSnapshot(ResourceOracleImpl oracle) {
+      resources = oracle.getResources();
+      resourceMap = oracle.getResourceMap();
+      pathNames = oracle.getPathNames();
+    }
+
+    public void assertCollectionsConsistent(int expectedSize) {
+      assertEquals(expectedSize, resources.size());
+      assertEquals(resources.size(), resourceMap.size());
+      assertEquals(resources.size(), pathNames.size());
+
+      // Ensure every resource is in the map correctly.
+      for (Resource r : resources) {
+        assertSame(r, resourceMap.get(r.getPath()));
+      }
+
+      // Ensure that every resource path is in the set.
+      for (Resource r : resources) {
+        assertTrue(pathNames.contains(r.getPath()));
+      }
+    }
+
+    public void assertNotSameCollections(ResourceOracleSnapshot other) {
+      assertNotSame(resourceMap, other.resourceMap);
+      assertNotSame(resources, other.resources);
+      assertNotSame(pathNames, other.pathNames);
+    }
+
+    public void assertPathIncluded(String path) {
+      assertNotNull(findResourceWithPath(path));
+    }
+
+    /**
+     * Asserts that a resource having the specified path is present and that it
+     * was contributed by the specified classpath entry.
+     */
+    public void assertPathIncluded(String expectedPath,
+        ClassPathEntry expectedCpe) {
+      AbstractResource r = findResourceWithPath(expectedPath);
+      assertNotNull(r);
+      ClassPathEntry actualCpe = r.getClassPathEntry();
+      assertEquals(expectedCpe.getLocation(), actualCpe.getLocation());
+    }
+
+    public void assertPathNotIncluded(String path) {
+      assertNull(findResourceWithPath(path));
+    }
+
+    public void assertSameCollections(ResourceOracleSnapshot other) {
+      assertSame(resourceMap, other.resourceMap);
+      assertSame(resources, other.resources);
+      assertSame(pathNames, other.pathNames);
+    }
+
+    public AbstractResource findResourceWithPath(String path) {
+      for (Resource r : resources) {
+        if (r.getPath().equals(path)) {
+          return (AbstractResource) r;
+        }
+      }
+      return null;
+    }
+  }
+
+  public void testNoClassPathEntries() {
+    TreeLogger logger = createTestTreeLogger();
+    ResourceOracleImpl oracle = createResourceOracle(new MOCK_CPE0());
+    ResourceOracleSnapshot s = refreshAndSnapshot(logger, oracle);
+    s.assertCollectionsConsistent(0);
+  }
+
+  /**
+   * Tests the actual reading of resources.
+   * 
+   * @throws URISyntaxException
+   * @throws IOException
+   * @throws UnableToCompleteException
+   */
+  public void testReadingResource() throws IOException, URISyntaxException {
+    ClassPathEntry cpe1jar = getClassPathEntry1AsJar();
+    ClassPathEntry cpe1dir = getClassPathEntry1AsDirectory();
+
+    ClassPathEntry cpe2jar = getClassPathEntry2AsJar();
+    ClassPathEntry cpe2dir = getClassPathEntry2AsDirectory();
+
+    testReadingResource(cpe1jar, cpe2jar);
+    testReadingResource(cpe1dir, cpe2jar);
+
+    testReadingResource(cpe1jar, cpe2dir);
+    testReadingResource(cpe1dir, cpe2dir);
+  }
+
+  public void testResourceAddition() throws IOException, URISyntaxException {
+    ClassPathEntry cpe1mock = getClassPathEntry1AsMock();
+    ClassPathEntry cpe1jar = getClassPathEntry1AsJar();
+    ClassPathEntry cpe1dir = getClassPathEntry1AsDirectory();
+
+    ClassPathEntry cpe2mock = getClassPathEntry2AsMock();
+    ClassPathEntry cpe2jar = getClassPathEntry2AsJar();
+    ClassPathEntry cpe2dir = getClassPathEntry2AsDirectory();
+
+    testResourceAddition(cpe1jar, cpe2jar);
+    testResourceAddition(cpe1dir, cpe2jar);
+    testResourceAddition(cpe1mock, cpe2jar);
+
+    testResourceAddition(cpe1jar, cpe2dir);
+    testResourceAddition(cpe1dir, cpe2dir);
+    testResourceAddition(cpe1mock, cpe2dir);
+
+    testResourceAddition(cpe1jar, cpe2mock);
+    testResourceAddition(cpe1dir, cpe2mock);
+    testResourceAddition(cpe1mock, cpe2mock);
+  }
+
+  public void testResourceDeletion() throws IOException, URISyntaxException {
+    ClassPathEntry cpe1mock = getClassPathEntry1AsMock();
+    ClassPathEntry cpe1jar = getClassPathEntry1AsJar();
+    ClassPathEntry cpe1dir = getClassPathEntry1AsDirectory();
+
+    ClassPathEntry cpe2mock = getClassPathEntry2AsMock();
+    ClassPathEntry cpe2jar = getClassPathEntry2AsJar();
+    ClassPathEntry cpe2dir = getClassPathEntry2AsDirectory();
+
+    testResourceDeletion(cpe1jar, cpe2jar);
+    testResourceDeletion(cpe1dir, cpe2jar);
+    testResourceDeletion(cpe1mock, cpe2jar);
+
+    testResourceDeletion(cpe1jar, cpe2dir);
+    testResourceDeletion(cpe1dir, cpe2dir);
+    testResourceDeletion(cpe1mock, cpe2dir);
+
+    testResourceDeletion(cpe1jar, cpe2mock);
+    testResourceDeletion(cpe1dir, cpe2mock);
+    testResourceDeletion(cpe1mock, cpe2mock);
+  }
+
+  public void testResourceModification() throws IOException, URISyntaxException {
+    ClassPathEntry cpe1mock = getClassPathEntry1AsMock();
+    ClassPathEntry cpe1jar = getClassPathEntry1AsJar();
+    ClassPathEntry cpe1dir = getClassPathEntry1AsDirectory();
+
+    ClassPathEntry cpe2mock = getClassPathEntry2AsMock();
+    ClassPathEntry cpe2jar = getClassPathEntry2AsJar();
+    ClassPathEntry cpe2dir = getClassPathEntry2AsDirectory();
+
+    testResourceModification(cpe1jar, cpe2jar);
+    testResourceModification(cpe1dir, cpe2jar);
+    testResourceModification(cpe1mock, cpe2jar);
+
+    testResourceModification(cpe1jar, cpe2dir);
+    testResourceModification(cpe1dir, cpe2dir);
+    testResourceModification(cpe1mock, cpe2dir);
+
+    testResourceModification(cpe1jar, cpe2mock);
+    testResourceModification(cpe1dir, cpe2mock);
+    testResourceModification(cpe1mock, cpe2mock);
+
+    /*
+     * TODO(bruce): figure out a good way to test real resource modifications of
+     * jar files and directories
+     */
+  }
+
+  /**
+   * Creates an array of class path entries, setting up each one with a
+   * well-known set of client prefixes.
+   * 
+   * @param entries
+   * @return
+   */
+  private ResourceOracleImpl createResourceOracle(ClassPathEntry... entries) {
+    PathPrefixSet pps = new PathPrefixSet();
+    pps.add(new PathPrefix("com/google/gwt/user/client/", null));
+    pps.add(new PathPrefix("org/example/bar/client/", null));
+    pps.add(new PathPrefix("org/example/foo/client/", null));
+    pps.add(new PathPrefix("com/google/gwt/i18n/client/", null));
+
+    List<ClassPathEntry> classPath = new ArrayList<ClassPathEntry>();
+    for (ClassPathEntry entry : entries) {
+      classPath.add(entry);
+    }
+    ResourceOracleImpl oracle = new ResourceOracleImpl(classPath);
+    oracle.setPathPrefixes(pps);
+    return oracle;
+  }
+
+  private ResourceOracleSnapshot refreshAndSnapshot(TreeLogger logger,
+      ResourceOracleImpl oracle) {
+    oracle.refresh(logger);
+    return new ResourceOracleSnapshot(oracle);
+  }
+
+  private void testReadingResource(ClassPathEntry cpe1, ClassPathEntry cpe2)
+      throws IOException {
+    TreeLogger logger = createTestTreeLogger();
+
+    ResourceOracleImpl oracle = createResourceOracle(cpe1, cpe2);
+
+    oracle.refresh(logger);
+    ResourceOracleSnapshot s = new ResourceOracleSnapshot(oracle);
+    s.assertCollectionsConsistent(9);
+    s.assertPathIncluded("com/google/gwt/user/client/Command.java", cpe1);
+    s.assertPathIncluded("com/google/gwt/i18n/client/Messages.java", cpe2);
+
+    {
+      /*
+       * Read a resource in cpe1.
+       */
+      AbstractResource res = s.findResourceWithPath("com/google/gwt/user/client/Command.java");
+      BufferedReader rdr = null;
+      try {
+        InputStream is = res.openContents();
+        assertNotNull(is);
+        rdr = new BufferedReader(new InputStreamReader(is));
+        assertTrue(rdr.readLine().indexOf(
+            "package com.google.gwt.dev.resource.impl.testdata.cpe1.com.google.gwt.user.client;") >= 0);
+      } finally {
+        Utility.close(rdr);
+      }
+    }
+
+    {
+      /*
+       * Read a resource in cpe2.
+       */
+      AbstractResource res = s.findResourceWithPath("com/google/gwt/i18n/client/Messages.java");
+      BufferedReader rdr = null;
+      try {
+        InputStream is = res.openContents();
+        assertNotNull(is);
+        rdr = new BufferedReader(new InputStreamReader(is));
+        assertTrue(rdr.readLine().indexOf(
+            "package com.google.gwt.dev.resource.impl.testdata.cpe2.com.google.gwt.i18n.client;") >= 0);
+      } finally {
+        Utility.close(rdr);
+      }
+    }
+
+    {
+      /*
+       * TODO: Try to read an invalid resource and watch it fail as intended.
+       */
+    }
+  }
+
+  private void testResourceAddition(ClassPathEntry cpe1, ClassPathEntry cpe2) {
+    TreeLogger logger = createTestTreeLogger();
+
+    MOCK_CPE0 cpe0 = new MOCK_CPE0();
+    MOCK_CPE3 cpe3 = new MOCK_CPE3();
+    ResourceOracleImpl oracle = createResourceOracle(cpe0, cpe1, cpe2, cpe3);
+
+    {
+      /*
+       * Ensure it's correct as a baseline. These tests have hard-coded
+       * assumptions about the contents of each classpath entry.
+       */
+      ResourceOracleSnapshot s = refreshAndSnapshot(logger, oracle);
+      s.assertCollectionsConsistent(9);
+      s.assertPathIncluded("com/google/gwt/user/client/Command.java");
+      s.assertPathIncluded("com/google/gwt/user/client/Timer.java");
+      s.assertPathIncluded("com/google/gwt/user/client/ui/Widget.java");
+      s.assertPathIncluded("org/example/bar/client/BarClient1.txt");
+      s.assertPathIncluded("org/example/bar/client/BarClient2.txt");
+      s.assertPathIncluded("org/example/bar/client/BarClient3.txt");
+      s.assertPathIncluded("org/example/bar/client/etc/BarEtc.txt");
+      s.assertPathIncluded("org/example/foo/client/FooClient.java");
+      s.assertPathIncluded("com/google/gwt/i18n/client/Messages.java");
+    }
+
+    {
+      /*
+       * Add duplicate resources later in the classpath, which won't be found.
+       * Consequently, the collections' identities should not change.
+       */
+      cpe3.addResource("com/google/gwt/user/client/Command.java");
+      cpe3.addResource("com/google/gwt/user/client/Timer.java");
+      cpe3.addResource("com/google/gwt/bar/client/etc/BarEtc.txt");
+
+      ResourceOracleSnapshot before = new ResourceOracleSnapshot(oracle);
+      ResourceOracleSnapshot after = refreshAndSnapshot(logger, oracle);
+      after.assertCollectionsConsistent(9);
+      after.assertSameCollections(before);
+    }
+
+    {
+      /*
+       * Add a unique resource later in the classpath, which will be found.
+       * Consequently, the collections' identities should change.
+       */
+      cpe3.addResource("com/google/gwt/i18n/client/Constants.java");
+
+      ResourceOracleSnapshot before = new ResourceOracleSnapshot(oracle);
+      ResourceOracleSnapshot after = refreshAndSnapshot(logger, oracle);
+      after.assertCollectionsConsistent(10);
+      after.assertNotSameCollections(before);
+      after.assertPathIncluded("com/google/gwt/i18n/client/Constants.java");
+    }
+  }
+
+  private void testResourceDeletion(ClassPathEntry cpe1, ClassPathEntry cpe2) {
+    TreeLogger logger = createTestTreeLogger();
+
+    MOCK_CPE0 cpe0 = new MOCK_CPE0();
+    MOCK_CPE3 cpe3 = new MOCK_CPE3();
+    ResourceOracleImpl oracle = createResourceOracle(cpe0, cpe1, cpe2, cpe3);
+
+    /*
+     * Intentionally add some duplicate resources.
+     */
+    cpe0.addResource("com/google/gwt/user/client/Command.java");
+    cpe0.addResource("com/google/gwt/i18n/client/Constants.java");
+    cpe3.addResource("com/google/gwt/user/client/Command.java");
+    cpe3.addResource("com/google/gwt/i18n/client/Constants.java");
+
+    {
+      /*
+       * Ensure it's correct as a baseline. These tests have hard-coded
+       * assumptions about the contents of each classpath entry.
+       */
+      ResourceOracleSnapshot s = refreshAndSnapshot(logger, oracle);
+      s.assertCollectionsConsistent(10);
+      s.assertPathIncluded("com/google/gwt/user/client/Command.java");
+      s.assertPathIncluded("com/google/gwt/user/client/Timer.java");
+      s.assertPathIncluded("com/google/gwt/user/client/ui/Widget.java");
+      s.assertPathIncluded("org/example/bar/client/BarClient1.txt");
+      s.assertPathIncluded("org/example/bar/client/BarClient2.txt");
+      s.assertPathIncluded("org/example/bar/client/BarClient3.txt");
+      s.assertPathIncluded("org/example/bar/client/etc/BarEtc.txt");
+      s.assertPathIncluded("org/example/foo/client/FooClient.java");
+      s.assertPathIncluded("com/google/gwt/i18n/client/Messages.java");
+      s.assertPathIncluded("com/google/gwt/i18n/client/Constants.java");
+    }
+
+    {
+      /*
+       * Remove a shadowed resource, which shouldn't have been found anyway.
+       * Consequently, the collections' identities should not change.
+       */
+      cpe3.removeResource("com/google/gwt/user/client/Command.java");
+      cpe3.removeResource("com/google/gwt/i18n/client/Constants.java");
+
+      ResourceOracleSnapshot before = new ResourceOracleSnapshot(oracle);
+      ResourceOracleSnapshot after = refreshAndSnapshot(logger, oracle);
+      after.assertCollectionsConsistent(10);
+      after.assertSameCollections(before);
+    }
+
+    {
+      /*
+       * Remove a unique resource, which will no longer be found. We also add a
+       * new one to ensure that lack of size change doesn't confuse anything.
+       * Consequently, the collections' identities should change.
+       */
+      cpe0.removeResource("com/google/gwt/i18n/client/Constants.java");
+      cpe3.addResource("com/google/gwt/user/client/Window.java");
+
+      ResourceOracleSnapshot before = new ResourceOracleSnapshot(oracle);
+      ResourceOracleSnapshot after = refreshAndSnapshot(logger, oracle);
+      after.assertCollectionsConsistent(10);
+      after.assertNotSameCollections(before);
+      after.assertPathIncluded("com/google/gwt/user/client/Window.java");
+      after.assertPathNotIncluded("com/google/gwt/i18n/client/Constants.java");
+    }
+  }
+
+  private void testResourceModification(ClassPathEntry cpe1, ClassPathEntry cpe2) {
+    TreeLogger logger = createTestTreeLogger();
+
+    MOCK_CPE0 cpe0 = new MOCK_CPE0();
+    MOCK_CPE3 cpe3 = new MOCK_CPE3();
+    ResourceOracleImpl oracle = createResourceOracle(cpe0, cpe1, cpe2, cpe3);
+
+    {
+      /*
+       * Baseline assumptions about the set of resources present by default.
+       */
+      oracle.refresh(logger);
+      ResourceOracleSnapshot s = new ResourceOracleSnapshot(oracle);
+      s.assertPathIncluded("com/google/gwt/user/client/Command.java", cpe1);
+      s.assertPathIncluded("com/google/gwt/user/client/Timer.java", cpe1);
+    }
+
+    // Add intentionally duplicate resources.
+    cpe0.addResource("com/google/gwt/user/client/Command.java");
+    cpe3.addResource("com/google/gwt/user/client/Timer.java");
+
+    {
+      /*
+       * Ensure that the dups have the effect we expect.
+       */
+      oracle.refresh(logger);
+      ResourceOracleSnapshot s = new ResourceOracleSnapshot(oracle);
+      s.assertPathIncluded("com/google/gwt/user/client/Command.java", cpe0);
+      s.assertPathIncluded("com/google/gwt/user/client/Timer.java", cpe1);
+    }
+
+    {
+      /*
+       * Change a mock resource that was shadowed to ensure that the change
+       * isn't observed.
+       */
+      ResourceOracleSnapshot before = new ResourceOracleSnapshot(oracle);
+      before.assertCollectionsConsistent(9);
+
+      cpe3.updateResource("com/google/gwt/user/client/Timer.java");
+
+      ResourceOracleSnapshot after = refreshAndSnapshot(logger, oracle);
+      after.assertSameCollections(before);
+    }
+
+    {
+      /*
+       * Change a mock resource that was not shadowed to ensure that the change
+       * is observed.
+       */
+      ResourceOracleSnapshot before = new ResourceOracleSnapshot(oracle);
+      before.assertCollectionsConsistent(9);
+
+      cpe0.updateResource("com/google/gwt/user/client/Command.java");
+
+      ResourceOracleSnapshot after = refreshAndSnapshot(logger, oracle);
+      after.assertNotSameCollections(before);
+    }
+  }
+}
diff --git a/dev/core/test/com/google/gwt/dev/resource/impl/testdata/cpe1.jar b/dev/core/test/com/google/gwt/dev/resource/impl/testdata/cpe1.jar
new file mode 100644
index 0000000..5045c12
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/resource/impl/testdata/cpe1.jar
Binary files differ
diff --git a/dev/core/test/com/google/gwt/dev/resource/impl/testdata/cpe1/com/google/gwt/user/User.gwt.xml b/dev/core/test/com/google/gwt/dev/resource/impl/testdata/cpe1/com/google/gwt/user/User.gwt.xml
new file mode 100644
index 0000000..bdf08de
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/resource/impl/testdata/cpe1/com/google/gwt/user/User.gwt.xml
@@ -0,0 +1 @@
+test file
\ No newline at end of file
diff --git a/dev/core/test/com/google/gwt/dev/resource/impl/testdata/cpe1/com/google/gwt/user/client/Command.java b/dev/core/test/com/google/gwt/dev/resource/impl/testdata/cpe1/com/google/gwt/user/client/Command.java
new file mode 100644
index 0000000..42ba14b
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/resource/impl/testdata/cpe1/com/google/gwt/user/client/Command.java
@@ -0,0 +1,5 @@
+package com.google.gwt.dev.resource.impl.testdata.cpe1.com.google.gwt.user.client;
+
+public class Command {
+  // test class
+}
\ No newline at end of file
diff --git a/dev/core/test/com/google/gwt/dev/resource/impl/testdata/cpe1/com/google/gwt/user/client/Timer.java b/dev/core/test/com/google/gwt/dev/resource/impl/testdata/cpe1/com/google/gwt/user/client/Timer.java
new file mode 100644
index 0000000..d845e45
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/resource/impl/testdata/cpe1/com/google/gwt/user/client/Timer.java
@@ -0,0 +1,5 @@
+package com.google.gwt.dev.resource.impl.testdata.cpe1.com.google.gwt.user.client;
+
+public class Timer {
+  // test class
+}
\ No newline at end of file
diff --git a/dev/core/test/com/google/gwt/dev/resource/impl/testdata/cpe1/com/google/gwt/user/client/ui/Widget.java b/dev/core/test/com/google/gwt/dev/resource/impl/testdata/cpe1/com/google/gwt/user/client/ui/Widget.java
new file mode 100644
index 0000000..79acdc7
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/resource/impl/testdata/cpe1/com/google/gwt/user/client/ui/Widget.java
@@ -0,0 +1,5 @@
+package com.google.gwt.dev.resource.impl.testdata.cpe1.com.google.gwt.user.client.ui;
+
+public class Widget {
+  // test class
+}
\ No newline at end of file
diff --git a/dev/core/test/com/google/gwt/dev/resource/impl/testdata/cpe1/org/example/bar/client/BarClient1.txt b/dev/core/test/com/google/gwt/dev/resource/impl/testdata/cpe1/org/example/bar/client/BarClient1.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/resource/impl/testdata/cpe1/org/example/bar/client/BarClient1.txt
diff --git a/dev/core/test/com/google/gwt/dev/resource/impl/testdata/cpe1/org/example/bar/client/BarClient2.txt b/dev/core/test/com/google/gwt/dev/resource/impl/testdata/cpe1/org/example/bar/client/BarClient2.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/resource/impl/testdata/cpe1/org/example/bar/client/BarClient2.txt
diff --git a/dev/core/test/com/google/gwt/dev/resource/impl/testdata/cpe1/org/example/bar/client/etc/BarEtc.txt b/dev/core/test/com/google/gwt/dev/resource/impl/testdata/cpe1/org/example/bar/client/etc/BarEtc.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/resource/impl/testdata/cpe1/org/example/bar/client/etc/BarEtc.txt
diff --git a/dev/core/test/com/google/gwt/dev/resource/impl/testdata/cpe1/org/example/foo/client/FooClient.java b/dev/core/test/com/google/gwt/dev/resource/impl/testdata/cpe1/org/example/foo/client/FooClient.java
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/resource/impl/testdata/cpe1/org/example/foo/client/FooClient.java
diff --git a/dev/core/test/com/google/gwt/dev/resource/impl/testdata/cpe1/org/example/foo/server/FooServer.java b/dev/core/test/com/google/gwt/dev/resource/impl/testdata/cpe1/org/example/foo/server/FooServer.java
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/resource/impl/testdata/cpe1/org/example/foo/server/FooServer.java
diff --git a/dev/core/test/com/google/gwt/dev/resource/impl/testdata/cpe2.jar b/dev/core/test/com/google/gwt/dev/resource/impl/testdata/cpe2.jar
new file mode 100644
index 0000000..08761bb
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/resource/impl/testdata/cpe2.jar
Binary files differ
diff --git a/dev/core/test/com/google/gwt/dev/resource/impl/testdata/cpe2/com/google/gwt/i18n/I18N.gwt.xml b/dev/core/test/com/google/gwt/dev/resource/impl/testdata/cpe2/com/google/gwt/i18n/I18N.gwt.xml
new file mode 100644
index 0000000..bdf08de
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/resource/impl/testdata/cpe2/com/google/gwt/i18n/I18N.gwt.xml
@@ -0,0 +1 @@
+test file
\ No newline at end of file
diff --git a/dev/core/test/com/google/gwt/dev/resource/impl/testdata/cpe2/com/google/gwt/i18n/client/Messages.java b/dev/core/test/com/google/gwt/dev/resource/impl/testdata/cpe2/com/google/gwt/i18n/client/Messages.java
new file mode 100644
index 0000000..dff41a8
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/resource/impl/testdata/cpe2/com/google/gwt/i18n/client/Messages.java
@@ -0,0 +1,5 @@
+package com.google.gwt.dev.resource.impl.testdata.cpe2.com.google.gwt.i18n.client;
+
+public class Messages {
+  // test class
+}
\ No newline at end of file
diff --git a/dev/core/test/com/google/gwt/dev/resource/impl/testdata/cpe2/com/google/gwt/i18n/rebind/LocalizableGenerator.java b/dev/core/test/com/google/gwt/dev/resource/impl/testdata/cpe2/com/google/gwt/i18n/rebind/LocalizableGenerator.java
new file mode 100644
index 0000000..dae8c55
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/resource/impl/testdata/cpe2/com/google/gwt/i18n/rebind/LocalizableGenerator.java
@@ -0,0 +1,5 @@
+package com.google.gwt.dev.resource.impl.testdata.cpe2.com.google.gwt.i18n.rebind;
+
+public class LocalizableGenerator {
+  // test class
+}
diff --git a/dev/core/test/com/google/gwt/dev/resource/impl/testdata/cpe2/org/example/bar/client/BarClient2.txt b/dev/core/test/com/google/gwt/dev/resource/impl/testdata/cpe2/org/example/bar/client/BarClient2.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/resource/impl/testdata/cpe2/org/example/bar/client/BarClient2.txt
diff --git a/dev/core/test/com/google/gwt/dev/resource/impl/testdata/cpe2/org/example/bar/client/BarClient3.txt b/dev/core/test/com/google/gwt/dev/resource/impl/testdata/cpe2/org/example/bar/client/BarClient3.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/resource/impl/testdata/cpe2/org/example/bar/client/BarClient3.txt
diff --git a/dev/core/test/com/google/gwt/dev/resource/impl/testdata/rebuild_jars b/dev/core/test/com/google/gwt/dev/resource/impl/testdata/rebuild_jars
new file mode 100644
index 0000000..630dc1a
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/resource/impl/testdata/rebuild_jars
@@ -0,0 +1,7 @@
+cd cpe1
+find . -type f | grep -v "\.svn" | xargs jar cvf ../cpe1.jar
+cd ..
+cd cpe2
+find . -type f | grep -v "\.svn" | xargs jar cvf ../cpe2.jar
+cd ..
+
diff --git a/dev/core/test/com/google/gwt/dev/shell/StandardGeneratorContextTest.java b/dev/core/test/com/google/gwt/dev/shell/StandardGeneratorContextTest.java
index 4b556f4..7b06e39 100644
--- a/dev/core/test/com/google/gwt/dev/shell/StandardGeneratorContextTest.java
+++ b/dev/core/test/com/google/gwt/dev/shell/StandardGeneratorContextTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2008 Google Inc.
+ * Copyright 2007 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
@@ -18,59 +18,67 @@
 import com.google.gwt.core.ext.BadPropertyValueException;
 import com.google.gwt.core.ext.Generator;
 import com.google.gwt.core.ext.GeneratorContext;
-import com.google.gwt.core.ext.Linker;
 import com.google.gwt.core.ext.PropertyOracle;
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.linker.Artifact;
 import com.google.gwt.core.ext.linker.ArtifactSet;
 import com.google.gwt.core.ext.linker.GeneratedResource;
-import com.google.gwt.core.ext.typeinfo.TypeOracle;
 import com.google.gwt.dev.cfg.PublicOracle;
-import com.google.gwt.dev.jdt.CacheManager;
+import com.google.gwt.dev.javac.CompilationState;
+import com.google.gwt.dev.javac.JavaSourceFile;
+import com.google.gwt.dev.javac.JavaSourceOracle;
+import com.google.gwt.dev.resource.Resource;
 import com.google.gwt.dev.util.Util;
 
 import junit.framework.TestCase;
 
+import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.UnsupportedEncodingException;
-import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 /**
  * A wide variety of tests on {@link StandardGeneratorContext}.
  */
 public class StandardGeneratorContextTest extends TestCase {
 
-  private static class MockArtifact extends Artifact<MockArtifact> {
+  public static class MockCompilationState extends CompilationState {
 
-    public MockArtifact() {
-      super(Linker.class);
+    public MockCompilationState() {
+      super(new JavaSourceOracle() {
+        public Set<String> getClassNames() {
+          return Collections.emptySet();
+        }
+
+        public Set<JavaSourceFile> getSourceFiles() {
+          return Collections.emptySet();
+        }
+
+        public Map<String, JavaSourceFile> getSourceMap() {
+          return Collections.emptyMap();
+        }
+      });
     }
 
-    @Override
-    protected int compareToComparableArtifact(MockArtifact o) {
-      return 0;
-    }
-
-    @Override
-    protected Class<MockArtifact> getComparableArtifactType() {
-      return MockArtifact.class;
-    }
-
-    @Override
-    public int hashCode() {
-      return 0;
-    }
   }
 
-  private static class MockCacheManager extends CacheManager {
+  private static class MockGenerator extends Generator {
+    @Override
+    public String generate(TreeLogger logger, GeneratorContext context,
+        String typeName) throws UnableToCompleteException {
+      return typeName;
+    }
   }
 
   private static class MockPropertyOracle implements PropertyOracle {
@@ -85,23 +93,32 @@
     }
   }
 
-  private static class MockGenerator extends Generator {
-    @Override
-    public String generate(TreeLogger logger, GeneratorContext context,
-        String typeName) throws UnableToCompleteException {
-      return typeName;
-    }
-  }
-
   private static class MockPublicOracle implements PublicOracle {
 
-    public URL findPublicFile(String partialPath) {
+    public Resource findPublicFile(String partialPath) {
       if ("onPublicPath.txt".equals(partialPath)) {
-        try {
-          return new File("").toURL();
-        } catch (MalformedURLException e) {
-          throw new RuntimeException(e);
-        }
+        return new Resource() {
+
+          @Override
+          public String getLocation() {
+            return "/mock/onPublicPath.txt";
+          }
+
+          @Override
+          public String getPath() {
+            return "onPublicPath.txt";
+          }
+
+          @Override
+          public URL getURL() {
+            return null;
+          }
+
+          @Override
+          public InputStream openContents() {
+            return new ByteArrayInputStream(Util.getBytes("w00t!"));
+          }
+        };
       }
       return null;
     }
@@ -112,31 +129,27 @@
 
   }
 
-  private static class MockTypeOracle extends TypeOracle {
-  }
-
   private final ArtifactSet artifactSet = new ArtifactSet();
 
+  private final StandardGeneratorContext genCtx;
+  private final CompilationState mockCompilationState = new MockCompilationState();
+  private final TreeLogger mockLogger = TreeLogger.NULL;
+  private final PropertyOracle mockPropOracle = new MockPropertyOracle();
+  private final PublicOracle mockPublicOracle = new MockPublicOracle();
+  private int tempFileCounter;
+  private final File tempGenDir;
+  private final File tempOutDir;
   /**
    * Stores the File objects to delete in the order they were created. Delete
    * them in reverse order.
    */
   private final List<File> toDelete = new ArrayList<File>();
-  private final TypeOracle mockTypeOracle = new MockTypeOracle();
-  private final PropertyOracle mockPropOracle = new MockPropertyOracle();
-  private final PublicOracle mockPublicOracle = new MockPublicOracle();
-  private final File tempGenDir;
-  private final File tempOutDir;
-  private final CacheManager mockCacheManager = new MockCacheManager();
-  private final TreeLogger mockLogger = TreeLogger.NULL;
-  private final StandardGeneratorContext genCtx;
-  private int tempFileCounter;
 
   public StandardGeneratorContextTest() {
     tempGenDir = createTempDir("gwt-gen-");
     tempOutDir = createTempDir("gwt-out-");
-    genCtx = new StandardGeneratorContext(mockTypeOracle, mockPropOracle,
-        mockPublicOracle, tempGenDir, tempOutDir, mockCacheManager, artifactSet);
+    genCtx = new StandardGeneratorContext(mockCompilationState, mockPropOracle,
+        mockPublicOracle, tempGenDir, tempOutDir, artifactSet);
   }
 
   public void testTryCreateResource_badFileName() {
@@ -267,15 +280,6 @@
     genCtx.finish(mockLogger);
   }
 
-  public void testTryCreateResource_duplicateCreationAttempt()
-      throws UnableToCompleteException {
-    String path = createTempOutFilename();
-    OutputStream os1 = genCtx.tryCreateResource(mockLogger, path);
-    assertNotNull(os1);
-    OutputStream os2 = genCtx.tryCreateResource(mockLogger, path);
-    assertNull(os2);
-  }
-
   public void testTryCreateResource_duplicateCreationAfterCommit()
       throws UnableToCompleteException, UnsupportedEncodingException,
       IOException {
@@ -291,6 +295,15 @@
     assertNull(os2);
   }
 
+  public void testTryCreateResource_duplicateCreationAttempt()
+      throws UnableToCompleteException {
+    String path = createTempOutFilename();
+    OutputStream os1 = genCtx.tryCreateResource(mockLogger, path);
+    assertNotNull(os1);
+    OutputStream os2 = genCtx.tryCreateResource(mockLogger, path);
+    assertNull(os2);
+  }
+
   public void testTryCreateResource_finishCalledTwice()
       throws UnableToCompleteException, IOException {
     // Borrow impl.
@@ -334,12 +347,6 @@
   }
 
   @Override
-  protected void setUp() throws Exception {
-    mockCacheManager.invalidateVolatileFiles();
-    artifactSet.clear();
-  }
-
-  @Override
   protected void tearDown() throws Exception {
     for (int i = toDelete.size() - 1; i >= 0; --i) {
       File f = toDelete.get(i);
diff --git a/dev/core/test/com/google/gwt/dev/typeinfo/test/InteractiveTypeOracle.java b/dev/core/test/com/google/gwt/dev/typeinfo/test/InteractiveTypeOracle.java
index 66047b2..f20de04 100644
--- a/dev/core/test/com/google/gwt/dev/typeinfo/test/InteractiveTypeOracle.java
+++ b/dev/core/test/com/google/gwt/dev/typeinfo/test/InteractiveTypeOracle.java
@@ -27,7 +27,7 @@
 import com.google.gwt.core.ext.typeinfo.ParseException;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
 import com.google.gwt.core.ext.typeinfo.TypeOracleException;
-import com.google.gwt.dev.jdt.TypeOracleBuilder;
+import com.google.gwt.dev.javac.TypeOracleMediator;
 import com.google.gwt.dev.util.log.AbstractTreeLogger;
 import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
 
@@ -81,9 +81,9 @@
 
     // Build an oracle.
     //
-    TypeOracleBuilder builder = new TypeOracleBuilder();
+    TypeOracleMediator mediator = new TypeOracleMediator();
+    TypeOracle oracle = mediator.getTypeOracle();
     // TODO: add compilation units
-    TypeOracle oracle = builder.build(logger);
 
     // Create an interactive wrapper around the oracle.
     //
diff --git a/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiContainer.java b/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiContainer.java
index c7d1c20..2a89cb9 100644
--- a/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiContainer.java
+++ b/tools/api-checker/src/com/google/gwt/tools/apichecker/ApiContainer.java
@@ -18,13 +18,13 @@
 
 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.JPackage;
 import com.google.gwt.core.ext.typeinfo.NotFoundException;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
-import com.google.gwt.dev.jdt.CacheManager;
-import com.google.gwt.dev.jdt.TypeOracleBuilder;
-import com.google.gwt.dev.jdt.URLCompilationUnitProvider;
+import com.google.gwt.dev.javac.CompilationUnit;
+import com.google.gwt.dev.javac.JdtCompiler;
+import com.google.gwt.dev.javac.TypeOracleMediator;
+import com.google.gwt.dev.javac.impl.FileCompilationUnit;
 
 import java.io.BufferedReader;
 import java.io.File;
@@ -33,13 +33,13 @@
 import java.io.FileReader;
 import java.io.IOException;
 import java.net.MalformedURLException;
-import java.net.URL;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Properties;
+import java.util.Set;
 import java.util.Vector;
 
 /**
@@ -47,6 +47,7 @@
  * 
  */
 public class ApiContainer {
+
   private HashMap<String, ApiPackage> apiPackages = new HashMap<String, ApiPackage>();
   private HashMap<String, String> excludedFiles = null;
   private TreeLogger logger = null;
@@ -119,8 +120,8 @@
   public TreeLogger getLogger() {
     return logger;
   }
- 
-  private void addCompilationUnitsInPath(TypeOracleBuilder builder,
+
+  private void addCompilationUnitsInPath(Set<CompilationUnit> units,
       File sourcePathEntry) throws NotFoundException, IOException,
       UnableToCompleteException {
     File[] files = sourcePathEntry.listFiles();
@@ -130,7 +131,7 @@
     }
 
     for (int i = 0; i < files.length; i++) {
-      File file = files[i];
+      final File file = files[i];
       // Ignore files like .svn and .cvs
       if (file.getName().startsWith(".") || file.getName().equals("CVS")) {
         continue;
@@ -150,19 +151,15 @@
         }
         if (isValidPackage(pkgName, sourcePathEntry.toURL().toString())) {
           // Add if it's a source file and the package and fileNames are okay
-          URL location = file.toURL();
-          CompilationUnitProvider cup = new URLCompilationUnitProvider(
-              location, pkgName);
-          logger.log(TreeLogger.DEBUG, "+ to CompilationUnit" + ", location="
-              + location + ", pkgName=" + pkgName, null);
-          builder.addCompilationUnit(cup);
+          CompilationUnit unit = new FileCompilationUnit(file, pkgName);
+          units.add(unit);
           numFilesCount++;
         } else {
           logger.log(TreeLogger.SPAM, " not adding file " + file.toURL(), null);
         }
       } else {
         // Recurse into subDirs
-        addCompilationUnitsInPath(builder, file);
+        addCompilationUnitsInPath(units, file);
       }
     }
   }
@@ -171,12 +168,14 @@
       IOException, UnableToCompleteException {
 
     numFilesCount = 0;
-    TypeOracleBuilder builder = new TypeOracleBuilder(new CacheManager(null,
-        null, ApiCompatibilityChecker.DISABLE_CHECKS));
+    TypeOracleMediator mediator = new TypeOracleMediator();
+    Set<CompilationUnit> units = new HashSet<CompilationUnit>();
     for (Iterator<File> i = sourceTrees.iterator(); i.hasNext();) {
-      addCompilationUnitsInPath(builder, i.next());
+      addCompilationUnitsInPath(units, i.next());
     }
-    typeOracle = builder.build(logger);
+    JdtCompiler.compile(units);
+    mediator.refresh(logger, units);
+    typeOracle = mediator.getTypeOracle();
     logger.log(TreeLogger.INFO, "API " + name
         + ", Finished with building typeOracle, added " + numFilesCount
         + " files", null);
diff --git a/tools/api-checker/test/com/google/gwt/tools/apichecker/ApiCompatibilityTest.java b/tools/api-checker/test/com/google/gwt/tools/apichecker/ApiCompatibilityTest.java
index f6fdbd9..b643388 100644
--- a/tools/api-checker/test/com/google/gwt/tools/apichecker/ApiCompatibilityTest.java
+++ b/tools/api-checker/test/com/google/gwt/tools/apichecker/ApiCompatibilityTest.java
@@ -19,15 +19,16 @@
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.typeinfo.NotFoundException;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
-import com.google.gwt.dev.jdt.CacheManager;
-import com.google.gwt.dev.jdt.TypeOracleBuilder;
+import com.google.gwt.dev.javac.CompilationUnit;
+import com.google.gwt.dev.javac.JdtCompiler;
+import com.google.gwt.dev.javac.TypeOracleMediator;
 import com.google.gwt.dev.util.log.AbstractTreeLogger;
 import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
-import com.google.gwt.dev.jdt.StaticCompilationUnitProvider;
 
 import junit.framework.TestCase;
 
 import java.util.HashSet;
+import java.util.Set;
 
 /**
  * 
@@ -40,18 +41,49 @@
  * test white-list support.
  */
 public class ApiCompatibilityTest extends TestCase {
-  // These cups are slightly different from the cups in ApiContainerTest
-  static StaticCompilationUnitProvider cuApiClass = new StaticCompilationUnitProvider(
-      "test.apicontainer", "ApiClass", getSourceForApiClass());
-  static StaticCompilationUnitProvider cuNonApiClass = new StaticCompilationUnitProvider(
-      "test.apicontainer", "NonApiClass", getSourceForNonApiClass());
-  static StaticCompilationUnitProvider cuNonApiPackage = new StaticCompilationUnitProvider(
-      "test.nonapipackage", "TestClass", getSourceForTestClass());
-  static StaticCompilationUnitProvider cuObject = new StaticCompilationUnitProvider(
-      "java.lang", "Object", getSourceForObject());
 
-  static StaticCompilationUnitProvider cuThrowable = new StaticCompilationUnitProvider(
-      "java.lang", "Throwable", getSourceForThrowable());
+  static class StaticCompilationUnit extends CompilationUnit {
+
+    private final char[] source;
+    private final String typeName;
+
+    public StaticCompilationUnit(String typeName, char[] source) {
+      this.typeName = typeName;
+      this.source = source;
+    }
+
+    @Override
+    public String getDisplayLocation() {
+      return "/mock/" + typeName;
+    }
+
+    @Override
+    public String getSource() {
+      return String.valueOf(source);
+    }
+
+    @Override
+    public String getTypeName() {
+      return typeName;
+    }
+
+    @Override
+    public boolean isGenerated() {
+      return false;
+    }
+  }
+
+  // These cups are slightly different from the cups in ApiContainerTest
+  static StaticCompilationUnit cuApiClass = new StaticCompilationUnit(
+      "test.apicontainer.ApiClass", getSourceForApiClass());
+  static StaticCompilationUnit cuNonApiClass = new StaticCompilationUnit(
+      "test.apicontainer.NonApiClass", getSourceForNonApiClass());
+  static StaticCompilationUnit cuNonApiPackage = new StaticCompilationUnit(
+      "test.nonapipackage.TestClass", getSourceForTestClass());
+  static StaticCompilationUnit cuObject = new StaticCompilationUnit(
+      "java.lang.Object", getSourceForObject());
+  static StaticCompilationUnit cuThrowable = new StaticCompilationUnit(
+      "java.lang.Throwable", getSourceForThrowable());
 
   private static char[] getSourceForApiClass() {
     StringBuffer sb = new StringBuffer();
@@ -115,14 +147,16 @@
 
   public TypeOracle getNewTypeOracleWithCompilationUnitsAdded(
       AbstractTreeLogger logger) throws UnableToCompleteException {
-    TypeOracleBuilder builder1 = new TypeOracleBuilder(new CacheManager(null,
-        null, ApiCompatibilityChecker.DISABLE_CHECKS));
-    builder1.addCompilationUnit(cuObject);
-    builder1.addCompilationUnit(cuNonApiClass);
-    builder1.addCompilationUnit(cuApiClass);
-    builder1.addCompilationUnit(cuNonApiPackage);
-    builder1.addCompilationUnit(cuThrowable);
-    return builder1.build(logger);
+    TypeOracleMediator mediator = new TypeOracleMediator();
+    Set<CompilationUnit> units = new HashSet<CompilationUnit>();
+    units.add(cuObject);
+    units.add(cuNonApiClass);
+    units.add(cuApiClass);
+    units.add(cuNonApiPackage);
+    units.add(cuThrowable);
+    JdtCompiler.compile(units);
+    mediator.refresh(logger, units);
+    return mediator.getTypeOracle();
   }
 
   @Override
diff --git a/tools/api-checker/test/com/google/gwt/tools/apichecker/ApiContainerTest.java b/tools/api-checker/test/com/google/gwt/tools/apichecker/ApiContainerTest.java
index d3dc300..a390b08 100644
--- a/tools/api-checker/test/com/google/gwt/tools/apichecker/ApiContainerTest.java
+++ b/tools/api-checker/test/com/google/gwt/tools/apichecker/ApiContainerTest.java
@@ -19,14 +19,18 @@
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.typeinfo.JAbstractMethod;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
-import com.google.gwt.dev.jdt.CacheManager;
-import com.google.gwt.dev.jdt.StaticCompilationUnitProvider;
-import com.google.gwt.dev.jdt.TypeOracleBuilder;
+import com.google.gwt.dev.javac.CompilationUnit;
+import com.google.gwt.dev.javac.JdtCompiler;
+import com.google.gwt.dev.javac.TypeOracleMediator;
 import com.google.gwt.dev.util.log.AbstractTreeLogger;
 import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
+import com.google.gwt.tools.apichecker.ApiCompatibilityTest.StaticCompilationUnit;
 
 import junit.framework.TestCase;
 
+import java.util.HashSet;
+import java.util.Set;
+
 /**
  * Test ApiContainer.
  */
@@ -61,19 +65,16 @@
     }
   }
 
-  static StaticCompilationUnitProvider cuApiClass = new StaticCompilationUnitProvider(
-      "test.apicontainer", "ApiClass", getSourceForApiClass());
-
-  static StaticCompilationUnitProvider cuNewPackage = new StaticCompilationUnitProvider(
-      "java.newpackage", "Test", getSourceForTest());
-
-  static StaticCompilationUnitProvider cuNonApiClass = new StaticCompilationUnitProvider(
-      "test.apicontainer", "NonApiClass", getSourceForNonApiClass());
-  static StaticCompilationUnitProvider cuNonApiPackage = new StaticCompilationUnitProvider(
-      "test.nonapipackage", "TestClass", getSourceForTestClass());
-
-  static StaticCompilationUnitProvider cuObject = new StaticCompilationUnitProvider(
-      "java.lang", "Object", getSourceForObject());
+  static StaticCompilationUnit cuApiClass = new StaticCompilationUnit(
+      "test.apicontainer.ApiClass", getSourceForApiClass());
+  static StaticCompilationUnit cuNonApiClass = new StaticCompilationUnit(
+      "test.apicontainer.NonApiClass", getSourceForNonApiClass());
+  static StaticCompilationUnit cuNonApiPackage = new StaticCompilationUnit(
+      "test.nonapipackage.TestClass", getSourceForTestClass());
+  static StaticCompilationUnit cuObject = new StaticCompilationUnit(
+      "java.lang.Object", getSourceForObject());
+  static StaticCompilationUnit cuNewPackage = new StaticCompilationUnit(
+      "java.newpackage.Test", getSourceForTest());
 
   private static JAbstractMethod getMethodByName(String name, ApiClass apiClass) {
     return (apiClass.getApiMethodsByName(name, ApiClass.MethodType.METHOD).toArray(
@@ -142,18 +143,20 @@
   public TypeOracle getNewTypeOracleWithCompilationUnitsAdded()
       throws UnableToCompleteException {
 
-    // Build onto an empty type oracle.
-    TypeOracleBuilder builder1 = new TypeOracleBuilder(new CacheManager(null,
-        null, ApiCompatibilityChecker.DISABLE_CHECKS));
-    builder1.addCompilationUnit(cuObject);
-    builder1.addCompilationUnit(cuNonApiClass);
-    builder1.addCompilationUnit(cuApiClass);
-    builder1.addCompilationUnit(cuNonApiPackage);
-    builder1.addCompilationUnit(cuNewPackage);
     AbstractTreeLogger logger = new PrintWriterTreeLogger();
     logger.setMaxDetail(TreeLogger.ERROR);
-    TypeOracle typeOracle = builder1.build(logger);
-    return typeOracle;
+
+    // Build onto an empty type oracle.
+    TypeOracleMediator mediator = new TypeOracleMediator();
+    Set<CompilationUnit> units = new HashSet<CompilationUnit>();
+    units.add(cuObject);
+    units.add(cuNonApiClass);
+    units.add(cuApiClass);
+    units.add(cuNonApiPackage);
+    units.add(cuNewPackage);
+    JdtCompiler.compile(units);
+    mediator.refresh(logger, units);
+    return mediator.getTypeOracle();
   }
 
   /**
diff --git a/tools/benchmark-viewer/build.xml b/tools/benchmark-viewer/build.xml
index 8d187fe..3fa38f6 100755
--- a/tools/benchmark-viewer/build.xml
+++ b/tools/benchmark-viewer/build.xml
@@ -96,6 +96,7 @@
 			</targetfiles>
 			<sequential>
 				<java dir="${tools.build}" classname="com.google.gwt.dev.GWTCompiler" classpath="src:${gwt.user.jar}:${gwt.dev.jar}" fork="yes" failonerror="true">
+					<jvmarg value="-Xmx256M"/>
 					<arg value="-out" />
 					<arg file="${tools.build}/www" />
 					<arg value="${tools.module}" />
diff --git a/user/src/com/google/gwt/benchmarks/BenchmarkReport.java b/user/src/com/google/gwt/benchmarks/BenchmarkReport.java
index 7f17f3a..3094d33 100644
--- a/user/src/com/google/gwt/benchmarks/BenchmarkReport.java
+++ b/user/src/com/google/gwt/benchmarks/BenchmarkReport.java
@@ -193,16 +193,8 @@
         return source;
       }
 
-      try {
-        return source.substring(method.getDeclStart(), method.getDeclEnd() + 1);
-      } catch (IndexOutOfBoundsException e) {
-        logger.log(TreeLogger.WARN, "Unable to parse " + method.getName(), e);
-        // Have seen this happen when the compiler read the source using one
-        // character encoding, and then this Parser read it in a different
-        // encoding. I don't know if there are other cases in which this can
-        // occur.
-        return null;
-      }
+      // TODO: search for the method manually?
+      return null;
     }
   }
 
diff --git a/user/src/com/google/gwt/core/client/GWT.java b/user/src/com/google/gwt/core/client/GWT.java
index b4652d7..b00f5b5 100644
--- a/user/src/com/google/gwt/core/client/GWT.java
+++ b/user/src/com/google/gwt/core/client/GWT.java
@@ -42,7 +42,29 @@
     void onUncaughtException(Throwable e);
   }
 
-  // web mode default is to let the exception go
+  /**
+   * An {@link UncaughtExceptionHandler} that logs errors to
+   * {@link GWT#log(String, Throwable)}. This is the default exception handler
+   * in hosted mode. In web mode, the default exception handler is
+   * <code>null</code>.
+   */
+  private static final class DefaultUncaughtExceptionHandler implements
+      UncaughtExceptionHandler {
+    public void onUncaughtException(Throwable e) {
+      log("Uncaught exception escaped", e);
+    }
+  }
+
+  /**
+   * Always <code>null</code> in web mode; in hosted mode provides the
+   * implementation for certain methods.
+   */
+  private static GWTBridge sGWTBridge = null;
+
+  /**
+   * Defaults to <code>null</code> in web mode and an instance of
+   * {@link DefaultUncaughtExceptionHandler} in hosted mode.
+   */
   private static UncaughtExceptionHandler sUncaughtExceptionHandler = null;
 
   /**
@@ -61,16 +83,20 @@
    */
   @SuppressWarnings("unused")
   public static <T> T create(Class<?> classLiteral) {
-    /*
-     * In web mode, the compiler directly replaces calls to this method with a
-     * new Object() type expression of the correct rebound type.
-     */
-    throw new UnsupportedOperationException(
-        "ERROR: GWT.create() is only usable in client code!  It cannot be called, "
-            + "for example, from server code.  If you are running a unit test, "
-            + "check that your test case extends GWTTestCase and that GWT.create() "
-            + "is not called from within an initializer, constructor, or "
-            + "setUp()/tearDown().");
+    if (sGWTBridge == null) {
+      /*
+       * In web mode, the compiler directly replaces calls to this method with a
+       * new Object() type expression of the correct rebound type.
+       */
+      throw new UnsupportedOperationException(
+          "ERROR: GWT.create() is only usable in client code!  It cannot be called, "
+              + "for example, from server code.  If you are running a unit test, "
+              + "check that your test case extends GWTTestCase and that GWT.create() "
+              + "is not called from within an initializer, constructor, or "
+              + "setUp()/tearDown().");
+    } else {
+      return sGWTBridge.<T> create(classLiteral);
+    }
   }
 
   /**
@@ -126,9 +152,13 @@
     return sUncaughtExceptionHandler;
   }
 
-  public static native String getVersion() /*-{
-    return $gwt_version;
-  }-*/;
+  public static String getVersion() {
+    if (sGWTBridge == null) {
+      return getVersion0();
+    } else {
+      return sGWTBridge.getVersion();
+    }
+  }
 
   /**
    * Returns <code>true</code> when running inside the normal GWT environment,
@@ -137,16 +167,15 @@
    * on the server, or during the bootstrap sequence of a GWTTestCase test.
    */
   public static boolean isClient() {
-    // Replaced with "true" by compiler and hosted mode.
-    return false;
+    // Replaced with "true" by compiler.
+    return sGWTBridge != null;
   }
 
   /**
    * Determines whether or not the running program is script or bytecode.
    */
   public static boolean isScript() {
-    // Will return false in hosted mode.
-    return isClient() && true;
+    return isClient() && sGWTBridge == null;
   }
 
   /**
@@ -155,7 +184,9 @@
    */
   @SuppressWarnings("unused")
   public static void log(String message, Throwable e) {
-    // intentionally empty in web mode.
+    if (sGWTBridge != null) {
+      sGWTBridge.log(message, e);
+    }
   }
 
   /**
@@ -170,4 +201,19 @@
       UncaughtExceptionHandler handler) {
     sUncaughtExceptionHandler = handler;
   }
+
+  /**
+   * Called via reflection in hosted mode; do not every call this method in web
+   * mode.
+   */
+  static void setBridge(GWTBridge bridge) {
+    sGWTBridge = bridge;
+    if (bridge != null) {
+      setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler());
+    }
+  }
+
+  private static native String getVersion0() /*-{
+    return $gwt_version;
+  }-*/;
 }
diff --git a/user/src/com/google/gwt/core/client/GWT.java-hosted b/user/src/com/google/gwt/core/client/GWT.java-hosted
deleted file mode 100644
index 0c1dc4b..0000000
--- a/user/src/com/google/gwt/core/client/GWT.java-hosted
+++ /dev/null
@@ -1,86 +0,0 @@
-/*

- * Copyright 2008 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.core.client;

-

-import com.google.gwt.core.client.impl.Impl;

-import com.google.gwt.dev.shell.ShellGWT;

-

-/**

- * The hosted mode implementation of the magic GWT class, with different

- * implementations of certain core methods.

- */

-public final class GWT {

-  public interface UncaughtExceptionHandler {

-    void onUncaughtException(Throwable e);

-  }

-

-  // hosted mode default is to log the exception to the log window

-  private static UncaughtExceptionHandler sUncaughtExceptionHandler = new UncaughtExceptionHandler() {

-    public void onUncaughtException(Throwable e) {

-      log("Uncaught exception escaped", e);

-    }

-  };

-

-  public static <T> T create(Class<?> classLiteral) {

-    // deferred binding at runtime

-    return ShellGWT.create(classLiteral);

-  }

-

-  public static String getHostPageBaseURL() {

-    return Impl.getHostPageBaseURL();

-  }

-

-  public static String getModuleBaseURL() {

-    return Impl.getModuleBaseURL();

-  }

-

-  public static String getModuleName() {

-    return Impl.getModuleName();

-  }

-

-  public static String getTypeName(Object o) {

-    // uses reflection in hosted mode

-    return ShellGWT.getTypeName(o);

-  }

-

-  public static UncaughtExceptionHandler getUncaughtExceptionHandler() {

-    return sUncaughtExceptionHandler;

-  }

-

-  public static String getVersion() {

-    return ShellGWT.getVersion();

-  };

-

-  public static boolean isClient() {

-    // true in hosted mode

-    return true;

-  }

-

-  public static boolean isScript() {

-    // false in hosted mode

-    return false;

-  }

-

-  public static void log(String message, Throwable e) {

-    // logs to the shell logger in hosted mode

-    ShellGWT.log(message, e);

-  }

-

-  public static void setUncaughtExceptionHandler(

-      UncaughtExceptionHandler handler) {

-    sUncaughtExceptionHandler = handler;

-  }

-}

diff --git a/user/src/com/google/gwt/user/client/impl/HTTPRequestImplIE6.java b/user/src/com/google/gwt/user/client/impl/HTTPRequestImplIE6.java
index 35f38d5..ea4356d 100644
--- a/user/src/com/google/gwt/user/client/impl/HTTPRequestImplIE6.java
+++ b/user/src/com/google/gwt/user/client/impl/HTTPRequestImplIE6.java
@@ -18,8 +18,7 @@
 import com.google.gwt.core.client.JavaScriptObject;
 
 /**
- * Internet Explorer 6 implementation of
- * {@link com.google.gwt.user.client.impl.HttpRequestImpl}.
+ * Internet Explorer 6 implementation of {@link HTTPRequestImpl}.
  */
 class HTTPRequestImplIE6 extends HTTPRequestImpl {
 
diff --git a/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleImpl.java b/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleImpl.java
index faf66dc..41f1653 100644
--- a/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleImpl.java
+++ b/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleImpl.java
@@ -21,7 +21,7 @@
 import com.google.gwt.core.ext.typeinfo.JParameterizedType;
 import com.google.gwt.core.ext.typeinfo.JType;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
-import com.google.gwt.dev.jdt.TypeOracleBuilder;
+import com.google.gwt.dev.javac.TypeOracleMediator;
 import com.google.gwt.dev.util.Util;
 
 import java.io.UnsupportedEncodingException;
@@ -144,7 +144,7 @@
   }
 
   public String getSerializedTypeName(JType type) {
-    return TypeOracleBuilder.computeBinaryClassName(type);
+    return TypeOracleMediator.computeBinaryClassName(type);
   }
 
   public String getTypeSerializerQualifiedName(JClassType serviceIntf) {
diff --git a/user/src/com/google/gwt/user/rebind/ui/ImageBundleGenerator.java b/user/src/com/google/gwt/user/rebind/ui/ImageBundleGenerator.java
index 1513790..258d001 100644
--- a/user/src/com/google/gwt/user/rebind/ui/ImageBundleGenerator.java
+++ b/user/src/com/google/gwt/user/rebind/ui/ImageBundleGenerator.java
@@ -19,7 +19,6 @@
 import com.google.gwt.core.ext.GeneratorContext;
 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.JClassType;
 import com.google.gwt.core.ext.typeinfo.JMethod;
 import com.google.gwt.core.ext.typeinfo.NotFoundException;
@@ -190,16 +189,6 @@
     return baseName + "_generatedBundle";
   }
 
-  private int countLines(char[] source, int declStartIndex) {
-    int lineNum = 1;
-    for (int i = 0; i < declStartIndex; ++i) {
-      if (source[i] == '\n') {
-        ++lineNum;
-      }
-    }
-    return lineNum;
-  }
-
   private void generateImageMethod(ImageBundleBuilder compositeImage,
       SourceWriter sw, JMethod method, String imgResName) {
 
@@ -287,11 +276,8 @@
       List<String> imageResNames = new ArrayList<String>();
 
       for (JMethod method : imageMethods) {
-        CompilationUnitProvider unit = method.getEnclosingType().getCompilationUnit();
-        String sourceFile = unit.getLocation();
-        int lineNum = countLines(unit.getSource(), method.getDeclStart());
         String branchMsg = "Analyzing method '" + method.getName()
-            + "' beginning on line " + lineNum + " of " + sourceFile;
+            + "' in type " + userType.getQualifiedSourceName();
         TreeLogger branch = logger.branch(TreeLogger.DEBUG, branchMsg, null);
 
         // Verify that this method is valid on an image bundle.
diff --git a/user/test/com/google/gwt/dev/cfg/PublicTagTest.java b/user/test/com/google/gwt/dev/cfg/PublicTagTest.java
index 1af2ad6..03e189d 100644
--- a/user/test/com/google/gwt/dev/cfg/PublicTagTest.java
+++ b/user/test/com/google/gwt/dev/cfg/PublicTagTest.java
@@ -15,15 +15,13 @@
  */
 package com.google.gwt.dev.cfg;
 
+import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
-import com.google.gwt.dev.GWTCompiler;
-import com.google.gwt.dev.util.Util;
-import com.google.gwt.dev.util.log.AbstractTreeLogger;
 import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
 
 import junit.framework.TestCase;
 
-import java.io.File;
+import java.io.PrintWriter;
 
 /**
  * Tests various permutations of the GWT module's &amp;public&amp; tag,
@@ -31,82 +29,50 @@
  */
 public class PublicTagTest extends TestCase {
 
-  /**
-   * Provides a convenient interface to the {@link GWTCompiler}. This test
-   * cannot simply call {@link GWTCompiler#main(String[])} since it always
-   * terminates with a call to {@link System#exit(int)}.
-   */
-  private static class Compiler extends GWTCompiler {
-    /**
-     * Run the {@link GWTCompiler} with the specified arguments.
-     * 
-     * @param args arguments passed to the compiler.
-     */
-    static void compile(String[] args) {
-      try {
-        final Compiler compiler = new Compiler();
-        if (compiler.processArgs(args)) {
-          final AbstractTreeLogger logger = new PrintWriterTreeLogger();
-          logger.setMaxDetail(compiler.getLogLevel());
-          compiler.distill(logger, ModuleDefLoader.loadFromClassPath(logger,
-              compiler.getModuleName()));
-        }
-      } catch (UnableToCompleteException e) {
-        throw new RuntimeException("Compilation failed.", e);
-      }
-    }
+  private static TreeLogger getRootLogger() {
+    PrintWriterTreeLogger logger = new PrintWriterTreeLogger(new PrintWriter(
+        System.err, true));
+    logger.setMaxDetail(TreeLogger.ERROR);
+    return logger;
+  }
+
+  private final ModuleDef moduleDef;
+
+  public PublicTagTest() throws UnableToCompleteException {
+    // Module has the same name as this class.
+    String moduleName = getClass().getCanonicalName();
+    moduleDef = ModuleDefLoader.loadFromClassPath(getRootLogger(), moduleName);
   }
 
   public void testPublicTag() {
-    // Find the current directory
-    String userDir = System.getProperty("user.dir");
-    assertNotNull(userDir);
-    File curDir = new File(userDir);
-    assertTrue(curDir.isDirectory());
+    assertNotNull(moduleDef.findPublicFile("good0.html"));
+    assertNotNull(moduleDef.findPublicFile("good1.html"));
+    assertNotNull(moduleDef.findPublicFile("bar/good.html"));
+    assertNotNull(moduleDef.findPublicFile("good2.html"));
+    assertNotNull(moduleDef.findPublicFile("good3.html"));
+    assertNotNull(moduleDef.findPublicFile("good4.html"));
+    assertNotNull(moduleDef.findPublicFile("good5.html"));
+    assertNotNull(moduleDef.findPublicFile("good6.html"));
+    assertNotNull(moduleDef.findPublicFile("good7.html"));
+    assertNotNull(moduleDef.findPublicFile("good8.html"));
+    assertNotNull(moduleDef.findPublicFile("good10.html"));
+    assertNotNull(moduleDef.findPublicFile("good11.html"));
+    assertNotNull(moduleDef.findPublicFile("good9.html"));
+    assertNotNull(moduleDef.findPublicFile("bar/CVS/good.html"));
+    assertNotNull(moduleDef.findPublicFile("CVS/good.html"));
+    assertNotNull(moduleDef.findPublicFile("GOOD/bar/GOOD/good.html"));
+    assertNotNull(moduleDef.findPublicFile("GOOD/good.html"));
 
-    // Our module name is the same as this class's name
-    String moduleName = PublicTagTest.class.getName();
-
-    // Find our module output directory and delete it
-    File moduleDir = new File(curDir, "www/" + moduleName);
-    if (moduleDir.exists()) {
-      Util.recursiveDelete(moduleDir, false);
-    }
-    assertFalse(moduleDir.exists());
-
-    // Compile the dummy app; suppress output to stdout
-    Compiler.compile(new String[] {
-        moduleName, "-logLevel", "ERROR", "-out", "www"});
-
-    // Check the output folder
-    assertTrue(new File(moduleDir, "good0.html").exists());
-    assertTrue(new File(moduleDir, "good1.html").exists());
-    assertTrue(new File(moduleDir, "bar/good.html").exists());
-    assertTrue(new File(moduleDir, "good2.html").exists());
-    assertTrue(new File(moduleDir, "good3.html").exists());
-    assertTrue(new File(moduleDir, "good4.html").exists());
-    assertTrue(new File(moduleDir, "good5.html").exists());
-    assertTrue(new File(moduleDir, "good6.html").exists());
-    assertTrue(new File(moduleDir, "good7.html").exists());
-    assertTrue(new File(moduleDir, "good8.html").exists());
-    assertTrue(new File(moduleDir, "good10.html").exists());
-    assertTrue(new File(moduleDir, "good11.html").exists());
-    assertTrue(new File(moduleDir, "good9.html").exists());
-    assertTrue(new File(moduleDir, "bar/CVS/good.html").exists());
-    assertTrue(new File(moduleDir, "CVS/good.html").exists());
-    assertTrue(new File(moduleDir, "GOOD/bar/GOOD/good.html").exists());
-    assertTrue(new File(moduleDir, "GOOD/good.html").exists());
-
-    assertFalse(new File(moduleDir, "bad.Html").exists());
-    assertFalse(new File(moduleDir, "bar/CVS/bad.html").exists());
-    assertFalse(new File(moduleDir, "CVS/bad.html").exists());
-    assertFalse(new File(moduleDir, "bad1.html").exists());
-    assertFalse(new File(moduleDir, "bad2.html").exists());
-    assertFalse(new File(moduleDir, "bad3.html").exists());
-    assertFalse(new File(moduleDir, "bad.html").exists());
-    assertFalse(new File(moduleDir, "bar/bad.html").exists());
-    assertFalse(new File(moduleDir, "GOOD/bar/bad.html").exists());
-    assertFalse(new File(moduleDir, "GOOD/bar/GOOD/bar/bad.html").exists());
+    assertNull(moduleDef.findPublicFile("bad.Html"));
+    assertNull(moduleDef.findPublicFile("bar/CVS/bad.html"));
+    assertNull(moduleDef.findPublicFile("CVS/bad.html"));
+    assertNull(moduleDef.findPublicFile("bad1.html"));
+    assertNull(moduleDef.findPublicFile("bad2.html"));
+    assertNull(moduleDef.findPublicFile("bad3.html"));
+    assertNull(moduleDef.findPublicFile("bad.html"));
+    assertNull(moduleDef.findPublicFile("bar/bad.html"));
+    assertNull(moduleDef.findPublicFile("GOOD/bar/bad.html"));
+    assertNull(moduleDef.findPublicFile("GOOD/bar/GOOD/bar/bad.html"));
   }
 
 }
diff --git a/user/test/com/google/gwt/dev/cfg/SourceTagTest.java b/user/test/com/google/gwt/dev/cfg/SourceTagTest.java
index 1939a32..70bda16 100644
--- a/user/test/com/google/gwt/dev/cfg/SourceTagTest.java
+++ b/user/test/com/google/gwt/dev/cfg/SourceTagTest.java
@@ -35,6 +35,6 @@
    * logical path would be java/lang/Object.
    */
   protected String getLogicalPath(Class<?> clazz) {
-    return clazz.getCanonicalName().replace('.', '/') + ".java";
+    return clazz.getCanonicalName();
   }
 }
diff --git a/user/test/com/google/gwt/dev/cfg/SuperSourceTagTest.java b/user/test/com/google/gwt/dev/cfg/SuperSourceTagTest.java
index 7fdc6af..80cedff 100644
--- a/user/test/com/google/gwt/dev/cfg/SuperSourceTagTest.java
+++ b/user/test/com/google/gwt/dev/cfg/SuperSourceTagTest.java
@@ -37,8 +37,7 @@
   protected String getLogicalPath(Class<?> clazz) {
     String name = clazz.getCanonicalName();
     name = name.substring(getClass().getPackage().getName().length() + 1);
-    name = name.replace('.', '/') + ".java";
-    name = name.replaceFirst("test/\\w+/", "");
+    name = name.replaceFirst("test\\.\\w+\\.", "");
     return name;
   }
 }
diff --git a/user/test/com/google/gwt/dev/cfg/TestSuperAndSourceTags.java b/user/test/com/google/gwt/dev/cfg/TestSuperAndSourceTags.java
index e4c326c..9a76d25 100644
--- a/user/test/com/google/gwt/dev/cfg/TestSuperAndSourceTags.java
+++ b/user/test/com/google/gwt/dev/cfg/TestSuperAndSourceTags.java
@@ -70,8 +70,8 @@
   }
 
   /**
-   * Returns the logical path for a class.  This method is implemented by the 
-   * subclasses because source and super-source compute logical paths 
+   * Returns the logical path for a class. This method is implemented by the
+   * subclasses because source and super-source compute logical paths
    * differently.
    */
   protected abstract String getLogicalPath(Class<?> clazz);