Merged changed/mmendez/STOB r2670:2814 into the trunk.  Updated STOB to properly deal with parameterized and arrays types that can lead to infinite recursions if you are not careful.

Updated the TypeParamaterExposeComputer to match RPC's field serialization policy.

Review by: mmendez, spoon (pair prog)

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@2848 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/core/ext/typeinfo/JClassType.java b/dev/core/src/com/google/gwt/core/ext/typeinfo/JClassType.java
index eeb18ce..ecbfd30 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
@@ -325,6 +325,18 @@
 
   public abstract void addModifierBits(int bits);
 
+  public JParameterizedType asParameterizationOf(JGenericType type) {
+    Set<JClassType> supertypes = getFlattenedSuperTypeHierarchy(this);
+    for (JClassType supertype : supertypes) {
+      JParameterizedType isParameterized = supertype.isParameterized();
+      if (isParameterized != null && isParameterized.getBaseType() == type) {
+        return isParameterized;
+      }
+    }
+
+    return null;
+  }
+
   public abstract JConstructor findConstructor(JType[] paramTypes);
 
   public abstract JField findField(String name);
diff --git a/dev/core/src/com/google/gwt/core/ext/typeinfo/JField.java b/dev/core/src/com/google/gwt/core/ext/typeinfo/JField.java
index 6e77701..069a06d 100644
--- a/dev/core/src/com/google/gwt/core/ext/typeinfo/JField.java
+++ b/dev/core/src/com/google/gwt/core/ext/typeinfo/JField.java
@@ -45,7 +45,7 @@
     this.enclosingType = enclosingType;
     this.name = name;
     this.enclosingType.addField(this);
-    annotations =  new Annotations();
+    annotations = new Annotations();
     annotations.addAnnotations(declaredAnnotations);
   }
 
@@ -152,7 +152,7 @@
     if (names.length > 0) {
       sb.append(" ");
     }
-    sb.append(type.getQualifiedSourceName());
+    sb.append(type.getParameterizedQualifiedSourceName());
     sb.append(" ");
     sb.append(getName());
     return sb.toString();
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 a1286dc..68d3f1f 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
@@ -99,5 +99,6 @@
 
   private void addTypeParameter(JTypeParameter typeParameter) {
     typeParams.add(typeParameter);
+    typeParameter.setDeclaringClass(this);
   }
 }
diff --git a/dev/core/src/com/google/gwt/core/ext/typeinfo/JTypeParameter.java b/dev/core/src/com/google/gwt/core/ext/typeinfo/JTypeParameter.java
index b6ab8e7..ecb9c41 100644
--- a/dev/core/src/com/google/gwt/core/ext/typeinfo/JTypeParameter.java
+++ b/dev/core/src/com/google/gwt/core/ext/typeinfo/JTypeParameter.java
@@ -23,6 +23,7 @@
  */
 public class JTypeParameter extends JDelegatingClassType {
   private JClassType[] bounds;
+  private JGenericType declaringClass;
   private final int ordinal;
   private final String typeName;
 
@@ -45,6 +46,10 @@
     return bounds;
   }
 
+  public JGenericType getDeclaringClass() {
+    return declaringClass;
+  }
+
   @Override
   public JClassType getEnclosingType() {
     // Type parameters do not have an enclosing type.
@@ -81,6 +86,10 @@
     return typeName;
   }
 
+  public int getOrdinal() {
+    return ordinal;
+  }
+
   @Override
   public String getParameterizedQualifiedSourceName() {
     return typeName;
@@ -157,15 +166,15 @@
     }
   }
 
-  int getOrdinal() {
-    return ordinal;
-  }
-
   @Override
   JClassType getSubstitutedType(JParameterizedType parameterizedType) {
     return parameterizedType.getTypeParameterSubstitution(this);
   }
 
+  void setDeclaringClass(JGenericType declaringClass) {
+    this.declaringClass = declaringClass;
+  }
+
   private String toString(boolean simpleName) {
     StringBuffer sb = new StringBuffer();
     sb.append(typeName);
diff --git a/user/src/com/google/gwt/user/rebind/rpc/ProxyCreator.java b/user/src/com/google/gwt/user/rebind/rpc/ProxyCreator.java
index e8c04c9..7d7131e 100644
--- a/user/src/com/google/gwt/user/rebind/rpc/ProxyCreator.java
+++ b/user/src/com/google/gwt/user/rebind/rpc/ProxyCreator.java
@@ -19,18 +19,21 @@
 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.linker.GeneratedResource;
 import com.google.gwt.core.ext.typeinfo.JClassType;
 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.JPrimitiveType;
 import com.google.gwt.core.ext.typeinfo.JType;
+import com.google.gwt.core.ext.typeinfo.NotFoundException;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
 import com.google.gwt.dev.generator.GenUtil;
 import com.google.gwt.dev.generator.NameFactory;
 import com.google.gwt.dev.util.Util;
 import com.google.gwt.http.client.Request;
 import com.google.gwt.http.client.RequestBuilder;
+import com.google.gwt.user.client.rpc.IncompatibleRemoteServiceException;
 import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;
 import com.google.gwt.user.client.rpc.SerializationException;
 import com.google.gwt.user.client.rpc.impl.ClientSerializationStreamReader;
@@ -62,6 +65,80 @@
 
   private static final String PROXY_SUFFIX = "_Proxy";
 
+  /**
+   * Adds a root type for each type that appears in the RemoteService interface
+   * methods.
+   */
+  private static void addRemoteServiceRootTypes(TreeLogger logger,
+      TypeOracle typeOracle, SerializableTypeOracleBuilder stob,
+      JClassType remoteService) throws NotFoundException {
+    logger = logger.branch(TreeLogger.DEBUG, "Analyzing '"
+        + remoteService.getParameterizedQualifiedSourceName()
+        + "' for serializable types", null);
+
+    JMethod[] methods = remoteService.getOverridableMethods();
+
+    JClassType exceptionClass = typeOracle.getType(Exception.class.getName());
+
+    TreeLogger validationLogger = logger.branch(TreeLogger.DEBUG,
+        "Analyzing methods:", null);
+    for (JMethod method : methods) {
+      TreeLogger methodLogger = validationLogger.branch(TreeLogger.DEBUG,
+          method.toString(), null);
+      JType returnType = method.getReturnType();
+      if (returnType != JPrimitiveType.VOID) {
+        TreeLogger returnTypeLogger = methodLogger.branch(TreeLogger.DEBUG,
+            "Return type: " + returnType.getParameterizedQualifiedSourceName(),
+            null);
+        stob.addRootType(returnTypeLogger, returnType);
+      }
+
+      JParameter[] params = method.getParameters();
+      for (JParameter param : params) {
+        TreeLogger paramLogger = methodLogger.branch(TreeLogger.DEBUG,
+            "Parameter: " + param.toString(), null);
+        JType paramType = param.getType();
+        stob.addRootType(paramLogger, paramType);
+      }
+
+      JType[] exs = method.getThrows();
+      if (exs.length > 0) {
+        TreeLogger throwsLogger = methodLogger.branch(TreeLogger.DEBUG,
+            "Throws:", null);
+        for (JType ex : exs) {
+          if (!exceptionClass.isAssignableFrom(ex.isClass())) {
+            throwsLogger = throwsLogger.branch(
+                TreeLogger.WARN,
+                "'"
+                    + ex.getQualifiedSourceName()
+                    + "' is not a checked exception; only checked exceptions may be used",
+                null);
+          }
+
+          stob.addRootType(throwsLogger, ex);
+        }
+      }
+    }
+  }
+
+  /**
+   * Add the implicit root types that are needed to make RPC work. These would
+   * be {@link String} and {@link IncompatibleRemoteServiceException}.
+   */
+  private static void addRequiredRoots(TreeLogger logger,
+      TypeOracle typeOracle, SerializableTypeOracleBuilder stob)
+      throws NotFoundException {
+    logger = logger.branch(TreeLogger.DEBUG, "Analyzing implicit types");
+
+    // String is always instantiable.
+    JClassType stringType = typeOracle.getType(String.class.getName());
+    stob.addRootType(logger, stringType);
+
+    // IncompatibleRemoteServiceException is always serializable
+    JClassType icseType = typeOracle.getType(IncompatibleRemoteServiceException.class.getName());
+    stob.addRootType(logger, icseType);
+  }
+
   private boolean enforceTypeVersioning;
 
   private JClassType serviceIntf;
@@ -135,8 +212,24 @@
     // Determine the set of serializable types
     SerializableTypeOracleBuilder stob = new SerializableTypeOracleBuilder(
         logger, typeOracle);
-    SerializableTypeOracle sto = stob.build(context.getPropertyOracle(),
-        serviceIntf);
+    try {
+      addRequiredRoots(logger, typeOracle, stob);
+
+      addRemoteServiceRootTypes(logger, typeOracle, stob, serviceIntf);
+    } catch (NotFoundException e) {
+      logger.log(TreeLogger.ERROR, "", e);
+      throw new UnableToCompleteException();
+    }
+
+    // Create a resource file to receive all of the serialization information
+    // computed by STOB and mark it as private so it does not end up in the
+    // output.
+    OutputStream pathInfo = context.tryCreateResource(logger,
+        serviceIntf.getQualifiedSourceName() + ".rpc.log");
+    stob.setLogOutputStream(pathInfo);
+    SerializableTypeOracle sto = stob.build(logger);
+    GeneratedResource rpcLog = context.commitResource(logger, pathInfo);
+    rpcLog.setPrivate(true);
 
     TypeSerializerCreator tsc = new TypeSerializerCreator(logger, sto, context,
         serviceIntf);
diff --git a/user/src/com/google/gwt/user/rebind/rpc/RemoteServiceAsyncValidator.java b/user/src/com/google/gwt/user/rebind/rpc/RemoteServiceAsyncValidator.java
index 655ee0e..97c6f70 100644
--- a/user/src/com/google/gwt/user/rebind/rpc/RemoteServiceAsyncValidator.java
+++ b/user/src/com/google/gwt/user/rebind/rpc/RemoteServiceAsyncValidator.java
@@ -238,6 +238,8 @@
       validationFailed(branch, remoteService);
     }
 
+    branch.log(TreeLogger.DEBUG, "Interfaces are in sync");
+
     return syncMethodToAsyncMethodMap;
   }
 }
diff --git a/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java b/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java
index 90203ac..71310d4 100644
--- a/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java
+++ b/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java
@@ -15,63 +15,69 @@
  */
 package com.google.gwt.user.rebind.rpc;
 
-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.TreeLogger.Type;
 import com.google.gwt.core.ext.typeinfo.JArrayType;
 import com.google.gwt.core.ext.typeinfo.JClassType;
 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.JGenericType;
 import com.google.gwt.core.ext.typeinfo.JParameterizedType;
-import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
+import com.google.gwt.core.ext.typeinfo.JRawType;
+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.JWildcardType;
 import com.google.gwt.core.ext.typeinfo.NotFoundException;
 import com.google.gwt.core.ext.typeinfo.TypeOracle;
-import com.google.gwt.user.client.rpc.IncompatibleRemoteServiceException;
+import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
 import com.google.gwt.user.client.rpc.IsSerializable;
+import com.google.gwt.user.rebind.rpc.TypeParameterExposureComputer.TypeParameterFlowInfo;
 
+import java.io.OutputStream;
+import java.io.PrintWriter;
 import java.io.Serializable;
-import java.text.MessageFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Comparator;
+import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.Stack;
 import java.util.TreeSet;
+import java.util.Map.Entry;
 
 /**
- * Builds a {@link SerializableTypeOracle} for a given
- * {@link com.google.gwt.user.client.rpc.RemoteService RemoteService} interface.
+ * Builds a {@link SerializableTypeOracle} for a given set of root types.
  * 
- * <h4>Background</h4>
+ * <p>
+ * There are two goals for this builder. First, discover the set of serializable
+ * types that can be serialized if you serialize one of the root types. Second,
+ * to make sure that all root types can actually be serialized by GWT.
  * </p>
- * There are two goals for this builder. First, discover the set serializable
- * types that can be exchanged between client and server code over a given
- * {@link com.google.gwt.user.client.rpc.RemoteService RemoteService} interface.
- * Second, to make sure that all types which qualify for serializability
- * actually adhere to the constraints for the particular type of serializability
- * which applies to them.
  * 
+ * <p>
+ * It then traverses the type hierarchy of each of the types in these method
+ * signatures in order to discover additional types which it might need to
+ * include. For the purposes of this explanation we define a root type to be any
+ * type which appears in the RemoteService method signatures or the type of any
+ * non-final, instance field which is part of a type that qualifies for
+ * serialization. The builder will fail if a root is not serializable and it has
+ * no subtypes that are.
  * </p>
- * This builder starts from the set of methods that are declared or inherited by
- * the RemoteService interface. It then traverses the type hierarchy of each of
- * the types in these method signatures in order to discover additional types
- * which it might need to include. For the purposes of this explanation we
- * define a root type to be any type which appears in the RemoteService method
- * signatures or the type of any non-final, instance field which is part of a
- * type that qualifies for serialization. The builder will fail if a root is not
- * serializable and it has no subtypes that are.
  * 
+ * <p>
+ * To improve the accuracy of the traversal there is a computations of the
+ * exposure of type parameters. When the traversal reaches a paramaterized type,
+ * these exposure values are used to determine how to treat the arguments.
  * </p>
+ * 
+ * <p>
  * A type qualifies for serialization if it is automatically or manually
  * serializable. Automatic serialization is selected if the type is assignable
  * to {@link IsSerializable} or {@link Serializable} or if the type is a
@@ -79,66 +85,44 @@
  * there exists another type with the same fully qualified name concatenated
  * with "_CustomFieldSerializer". If a type qualifies for both manual and
  * automatic serialization, manual serialization is preferred.
- * 
  * </p>
- * If any of the checks described in the following sections fail, the build
- * process will fail.
- * 
- * <h4>Root Types</h4>
- * <ul>
- * <li>If not parameterized and it is assignable to Map or Collection, emit a
- * warning and check all serializable subtypes of object.</li>
- * <li>If parameterized check all subtypes of the type arguments.</li>
- * <li>Check all subtypes of the raw type</li>
- * <li>If not parameterized and not assignable to Map or Collection, check the
- * root type and all of its subtypes</li>
- * </ul>
- * 
- * <h4>Classes</h4>
- * <ul>
- * <li>Does not qualify for serialization</li>
- * <ul>
- * <li>If asked to check subtypes, check all subclasses; must have one
- * serializable subtype</li>
- * </ul>
- * <li>Qualifies for Auto Serialization
- * <ul>
- * <li>If superclass qualifies for serialization check it</li>
- * <li>Check the type of every non-final instance field</li>
- * <li>If class is local or nested and not static ignore it</li>
- * <li>If class is not abstract and not default instantiable fail</li>
- * <li>If class is not part of the JRE, warn if it uses native methods</li>
- * <li>If asked to check subtypes, check all subclasses</li>
- * </ul>
- * </li>
- * <li>Qualifies for Manual Serialization
- * <ul>
- * <li>If superclass qualifies for serialization check it</li>
- * <li>Check the type of every non-final instance field</li>
- * <li>Check that the CustomFieldSerializer meets the following criteria:
- * <ul>
- * <li>A deserialize method whose signature is: 'public static void
- * deserialize({@link SerializationStreamReader}, &lt;T&gt; instance)'</li>
- * <li>A serialize method whose signature is: 'public static void serialize({@link SerializationStreamWriter},
- * &lt;T&gt; instance)'</li>
- * <li>It the class is not abstract, not default instantiable the custom field
- * serializer must implement an instantiate method whose signature is: 'public
- * static &lt;T&gt; instantiate(SerializationStreamReader)'</li>
- * </ul>
- * </ul>
- * </li>
- * </ul>
- * 
- * <h4>Arrays</h4>
- * <ul>
- * <li>Check the leaf type of the array; it must be serializable or have a
- * subtype that is.</li>
- * <li>All covariant array types are included.</li>
- * </ul>
  */
 public class SerializableTypeOracleBuilder {
 
-  private class TypeInfo {
+  interface Path {
+    Path getParent();
+
+    String toString();
+  }
+
+  enum TypeState {
+    /**
+     * The instantiability of a type has been determined.
+     */
+    CHECK_DONE("Check succeeded"),
+    /**
+     * The instantiability of a type is being checked.
+     */
+    CHECK_IN_PROGRESS("Check in progress"),
+    /**
+     * The instantiability of a type has not been checked.
+     */
+    NOT_CHECKED("Not checked");
+
+    private final String message;
+
+    TypeState(String message) {
+      this.message = message;
+    }
+
+    @Override
+    public String toString() {
+      return message;
+    }
+  }
+
+  private class TypeInfoComputed {
+
     /**
      * <code>true</code> if the type is assignable to {@link IsSerializable}
      * or {@link java.io.Serializable Serializable}.
@@ -152,11 +136,45 @@
     private final boolean directlyImplementsMarker;
 
     /**
+     * <code>true</code> if the type is automatically or manually serializable
+     * and the corresponding checks succeed.
+     */
+    private boolean fieldSerializable = false;
+
+    /**
+     * <code>true</code> if this type might be instantiated.
+     */
+    private boolean instantiable = false;
+
+    /**
+     * <code>true</code> if there are instantiable subtypes assignable to this
+     * one.
+     */
+    private boolean instantiableSubtypes;
+
+    /**
      * Custom field serializer or <code>null</code> if there isn't one.
      */
     private final JClassType manualSerializer;
 
-    TypeInfo(JClassType type) {
+    /**
+     * Path used to discover this type.
+     */
+    private final Path path;
+
+    /**
+     * The state that this type is currently in.
+     */
+    private TypeState state = TypeState.NOT_CHECKED;
+
+    /**
+     * {@link JClassType} associated with this metadata.
+     */
+    private final JClassType type;
+
+    public TypeInfoComputed(JClassType type, Path path) {
+      this.type = type;
+      this.path = path;
       autoSerializable = type.isAssignableTo(isSerializableClass)
           || type.isAssignableTo(serializableClass);
       manualSerializer = findCustomFieldSerializer(typeOracle, type);
@@ -169,6 +187,18 @@
       return manualSerializer;
     }
 
+    public Path getPath() {
+      return path;
+    }
+
+    public JClassType getType() {
+      return type;
+    }
+
+    public boolean hasInstantiableSubtypes() {
+      return isInstantiable() || instantiableSubtypes;
+    }
+
     public boolean isAutoSerializable() {
       return autoSerializable;
     }
@@ -181,113 +211,8 @@
       return directlyImplementsMarker || isManuallySerializable();
     }
 
-    public boolean isManuallySerializable() {
-      return manualSerializer != null;
-    }
-  }
-
-  private static class TypeInfoComputed {
-    /**
-     * An issue that prevents a type from being serializable.
-     */
-    private static class SerializationIssue implements
-        Comparable<SerializationIssue> {
-      public final boolean isSpeculative;
-      public final String issueMessage;
-
-      SerializationIssue(boolean isSpeculative, String issueMessage) {
-        this.isSpeculative = isSpeculative;
-        this.issueMessage = issueMessage;
-      }
-
-      public int compareTo(SerializationIssue other) {
-        if (isSpeculative == other.isSpeculative) {
-          return issueMessage.compareTo(other.issueMessage);
-        }
-
-        if (isSpeculative && !other.isSpeculative) {
-          return -1;
-        }
-
-        return 1;
-      }
-    }
-
-    /**
-     * Represents the state of a type while we are determining the set of
-     * serializable types.
-     */
-    private static final class TypeState {
-      private final String state;
-
-      protected TypeState(String state) {
-        this.state = state;
-      }
-
-      @Override
-      public String toString() {
-        return state;
-      }
-    }
-
-    /**
-     * The instantiability of a type has been determined.
-     */
-    static final TypeState CHECK_DONE = new TypeState("Check succeeded");
-
-    /**
-     * The instantiability of a type is being checked.
-     */
-    static final TypeState CHECK_IN_PROGRESS = new TypeState(
-        "Check in progress");
-
-    /**
-     * The instantiability of a type has not been checked.
-     */
-    static final TypeState NOT_CHECKED = new TypeState("Not checked");
-
-    /**
-     * <code>true</code> if the type is automatically or manually serializable
-     * and the corresponding checks succeed.
-     */
-    private boolean fieldSerializable = false;
-
-    /**
-     * <code>true</code> if this type might be instantiated.
-     */
-    private boolean instantiable = false;
-
-    /**
-     * List of serialization warnings or errors that prevent this type from
-     * being serializable.
-     */
-    private Set<SerializationIssue> serializationIssues = new TreeSet<SerializationIssue>();
-
-    /**
-     * The state that this type is currently in.
-     */
-    private TypeState state = NOT_CHECKED;
-
-    /**
-     * {@link JClassType} associated with this metadata.
-     */
-    private final JClassType type;
-
-    public TypeInfoComputed(JClassType type) {
-      this.type = type;
-    }
-
-    public void addSerializationIssue(boolean isSpeculative, String issueMessage) {
-      serializationIssues.add(new SerializationIssue(isSpeculative,
-          issueMessage));
-    }
-
-    public JClassType getType() {
-      return type;
-    }
-
     public boolean isDone() {
-      return state == CHECK_DONE;
+      return state == TypeState.CHECK_DONE;
     }
 
     public boolean isFieldSerializable() {
@@ -298,15 +223,12 @@
       return instantiable;
     }
 
-    public boolean isPendingInstantiable() {
-      return state == CHECK_IN_PROGRESS;
+    public boolean isManuallySerializable() {
+      return manualSerializer != null;
     }
 
-    public void logReasonsForUnserializability(TreeLogger logger) {
-      for (SerializationIssue serializationIssue : serializationIssues) {
-        logger.branch(getLogLevel(serializationIssue.isSpeculative),
-            serializationIssue.issueMessage, null);
-      }
+    public boolean isPendingInstantiable() {
+      return state == TypeState.CHECK_IN_PROGRESS;
     }
 
     public void setFieldSerializable() {
@@ -318,24 +240,59 @@
       if (instantiable) {
         fieldSerializable = true;
       }
-      state = CHECK_DONE;
+      state = TypeState.CHECK_DONE;
+    }
+
+    public void setInstantiableSubytpes(boolean instantiableSubtypes) {
+      this.instantiableSubtypes = instantiableSubtypes;
     }
 
     public void setPendingInstantiable() {
-      state = CHECK_IN_PROGRESS;
+      state = TypeState.CHECK_IN_PROGRESS;
     }
   }
 
   /**
-   * Compares {@link JClassType}s according to their qualified source names.
+   * Type parameter is exposed.
    */
-  private static final Comparator<JClassType> JCLASS_TYPE_COMPARATOR = new Comparator<JClassType>() {
-    public int compare(JClassType t1, JClassType t2) {
+  static final int EXPOSURE_DIRECT = 0;
+
+  /**
+   * Type parameter is exposed as a bounded array. The value is the max bound of
+   * the exposure.
+   */
+  static final int EXPOSURE_MIN_BOUNDED_ARRAY = EXPOSURE_DIRECT + 1;
+
+  /**
+   * Type parameter is not exposed.
+   */
+  static final int EXPOSURE_NONE = -1;
+
+  /**
+   * Compares {@link JType}s according to their qualified source names.
+   */
+  static final Comparator<JType> JTYPE_COMPARATOR = new Comparator<JType>() {
+    public int compare(JType t1, JType t2) {
       return t1.getQualifiedSourceName().compareTo(t2.getQualifiedSourceName());
     }
   };
 
   /**
+   * No type filtering by default..
+   */
+  private static final TypeFilter DEFAULT_TYPE_FILTER = new TypeFilter() {
+    public String getName() {
+      return "Default";
+    }
+
+    public boolean isAllowed(JClassType type) {
+      return true;
+    }
+  };
+
+  private static final JClassType[] NO_JCLASSES = new JClassType[0];
+
+  /**
    * Finds the custom field serializer for a given type.
    * 
    * @param typeOracle
@@ -362,32 +319,127 @@
     return customSerializer;
   }
 
-  private static void addEdge(Map<JClassType, List<JClassType>> adjList,
-      JClassType subclass, JClassType clazz) {
-    List<JClassType> edges = adjList.get(subclass);
-    if (edges == null) {
-      edges = new ArrayList<JClassType>();
-      adjList.put(subclass, edges);
+  /**
+   * Returns <code>true</code> if the field qualifies for serialization
+   * without considering its type.
+   */
+  static boolean qualfiesForSerialization(TreeLogger logger, JField field) {
+    if (field.isStatic() || field.isTransient()) {
+      return false;
     }
 
-    edges.add(clazz);
+    if (field.isFinal()) {
+      logger.branch(TreeLogger.DEBUG, "Field '" + field.toString()
+          + "' will not be serialized because it is final", null);
+      return false;
+    }
+
+    return true;
   }
 
-  private static void depthFirstSearch(Set<JClassType> seen,
-      Map<JClassType, List<JClassType>> adjList, JClassType type) {
-    if (seen.contains(type)) {
-      return;
+  static void recordTypeParametersIn(JType type, Set<JTypeParameter> params) {
+    JTypeParameter isTypeParameter = type.isTypeParameter();
+    if (isTypeParameter != null) {
+      params.add(isTypeParameter);
     }
-    seen.add(type);
 
-    List<JClassType> children = adjList.get(type);
-    if (children != null) {
-      for (JClassType child : children) {
-        if (!seen.contains(child)) {
-          depthFirstSearch(seen, adjList, child);
-        }
+    JArrayType isArray = type.isArray();
+    if (isArray != null) {
+      recordTypeParametersIn(isArray.getComponentType(), params);
+    }
+
+    JWildcardType isWildcard = type.isWildcard();
+    if (isWildcard != null) {
+      for (JClassType bound : isWildcard.getUpperBounds()) {
+        recordTypeParametersIn(bound, params);
       }
     }
+
+    JParameterizedType isParameterized = type.isParameterized();
+    if (isParameterized != null) {
+      for (JClassType arg : isParameterized.getTypeArgs()) {
+        recordTypeParametersIn(arg, params);
+      }
+    }
+  }
+
+  private static Path createArrayComponentPath(final JArrayType arrayType,
+      final Path parent) {
+    return new Path() {
+      public Path getParent() {
+        return parent;
+      }
+
+      @Override
+      public String toString() {
+        return "Type '"
+            + arrayType.getComponentType().getParameterizedQualifiedSourceName()
+            + "' is reachable from array type '"
+            + arrayType.getParameterizedQualifiedSourceName() + "'";
+      }
+    };
+  }
+
+  private static Path createFieldPath(final Path parent, final JField field) {
+    return new Path() {
+      public Path getParent() {
+        return parent;
+      }
+
+      @Override
+      public String toString() {
+        JType type = field.getType();
+        JClassType enclosingType = field.getEnclosingType();
+        return "'" + type.getParameterizedQualifiedSourceName()
+            + "' is reachable from field '" + field.getName() + "' of type '"
+            + enclosingType.getParameterizedQualifiedSourceName() + "'";
+      }
+    };
+  }
+
+  private static Path createRootPath(final JType type) {
+    return new Path() {
+      public Path getParent() {
+        return null;
+      }
+
+      @Override
+      public String toString() {
+        return "Started from '" + type.getParameterizedQualifiedSourceName()
+            + "'";
+      }
+    };
+  }
+
+  private static Path createSubtypePath(final Path parent, final JType type,
+      final JClassType supertype) {
+    return new Path() {
+      public Path getParent() {
+        return parent;
+      }
+
+      @Override
+      public String toString() {
+        return "'" + type.getParameterizedQualifiedSourceName()
+            + "' is reachable as a subtype of type '" + supertype + "'";
+      }
+    };
+  }
+
+  private static Path createTypeArgumentPath(final Path parent,
+      final JClassType type, final int typeArgIndex, final JClassType typeArg) {
+    return new Path() {
+      public Path getParent() {
+        return parent;
+      }
+
+      @Override
+      public String toString() {
+        return "'" + typeArg.getParameterizedQualifiedSourceName()
+            + "' is reachable from type argument " + typeArgIndex
+            + " of type '" + type.getParameterizedQualifiedSourceName() + "'";
+      }
+    };
   }
 
   /**
@@ -427,26 +479,6 @@
     return false;
   }
 
-  /**
-   * Returns all types on the path from the root type to the serializable
-   * leaves.
-   * 
-   * @param root the root type
-   * @param leaves the set of serializable leaf types
-   * @return all types on the path from the root type to the serializable leaves
-   */
-  private static List<JClassType> getAllTypesBetweenRootTypeAndLeaves(
-      JClassType root, List<JClassType> leaves) {
-    Map<JClassType, List<JClassType>> adjList = getInvertedTypeHierarchy(root);
-    Set<JClassType> types = new HashSet<JClassType>();
-
-    for (JClassType type : leaves) {
-      depthFirstSearch(types, adjList, type);
-    }
-
-    return Arrays.asList(types.toArray(new JClassType[0]));
-  }
-
   private static JArrayType getArrayType(TypeOracle typeOracle, int rank,
       JType component) {
     assert (rank > 0);
@@ -461,43 +493,6 @@
     return array;
   }
 
-  /**
-   * Given a root type return an adjacency list that is the inverted type
-   * hierarchy.
-   */
-  private static Map<JClassType, List<JClassType>> getInvertedTypeHierarchy(
-      JClassType root) {
-    Map<JClassType, List<JClassType>> adjList = new HashMap<JClassType, List<JClassType>>();
-    Set<JClassType> seen = new HashSet<JClassType>();
-    Stack<JClassType> queue = new Stack<JClassType>();
-    queue.push(root);
-    while (!queue.isEmpty()) {
-      JClassType clazz = queue.pop();
-      JClassType[] subclasses = clazz.getSubtypes();
-
-      if (seen.contains(clazz)) {
-        continue;
-      }
-      seen.add(clazz);
-
-      for (JClassType subclass : subclasses) {
-        if (clazz.isInterface() != null) {
-          if (directlyImplementsInterface(subclass, clazz)) {
-            addEdge(adjList, subclass, clazz);
-            queue.push(subclass);
-          }
-        } else {
-          if (subclass.getSuperclass() == clazz) {
-            addEdge(adjList, subclass, clazz);
-            queue.push(subclass);
-          }
-        }
-      }
-    }
-
-    return adjList;
-  }
-
   private static Type getLogLevel(boolean isSpeculative) {
     return isSpeculative ? TreeLogger.WARN : TreeLogger.ERROR;
   }
@@ -522,11 +517,6 @@
   private final JClassType collectionClass;
 
   /**
-   * Cache of the {@link JClassType} for {@link Exception}.
-   */
-  private final JClassType exceptionClass;
-
-  /**
    * Cache of the {@link JClassType} for {@link IsSerializable}.
    */
   private final JClassType isSerializableClass;
@@ -536,7 +526,7 @@
    */
   private final JClassType mapClass;
 
-  private final TreeLogger rootLogger;
+  private final Map<JClassType, TreeLogger> rootTypes = new LinkedHashMap<JClassType, TreeLogger>();
 
   /**
    * Cache of the {@link JClassType} for
@@ -544,150 +534,119 @@
    */
   private final JClassType serializableClass;
 
+  private TypeFilter typeFilter = DEFAULT_TYPE_FILTER;
+
   private final TypeOracle typeOracle;
 
-  /**
-   * Map of {@link JClassType} to {@link TypeInfo}.
-   */
-  private final Map<JClassType, TypeInfo> typeToTypeInfo = new HashMap<JClassType, TypeInfo>();
+  private final TypeParameterExposureComputer typeParameterExposureComputer = new TypeParameterExposureComputer();
+
+  private Set<JTypeParameter> typeParametersInRootTypes = new HashSet<JTypeParameter>();
 
   /**
    * Map of {@link JType} to {@link TypeInfoComputed}.
    */
   private final Map<JType, TypeInfoComputed> typeToTypeInfoComputed = new HashMap<JType, TypeInfoComputed>();
 
+  private OutputStream logOutputStream;
+
   /**
    * Constructs a builder.
    * 
-   * @param rootLogger
+   * @param logger
    * @param typeOracle
+   * 
    * @throws UnableToCompleteException if we fail to find one of our special
    *           types
    */
-  public SerializableTypeOracleBuilder(TreeLogger rootLogger,
-      TypeOracle typeOracle) throws UnableToCompleteException {
-    this.rootLogger = rootLogger;
+  public SerializableTypeOracleBuilder(TreeLogger logger, TypeOracle typeOracle)
+      throws UnableToCompleteException {
     this.typeOracle = typeOracle;
 
     try {
       collectionClass = typeOracle.getType(Collection.class.getName());
-      exceptionClass = typeOracle.getType(Exception.class.getName());
       isSerializableClass = typeOracle.getType(IsSerializable.class.getName());
       mapClass = typeOracle.getType(Map.class.getName());
       serializableClass = typeOracle.getType(Serializable.class.getName());
     } catch (NotFoundException e) {
-      rootLogger.log(TreeLogger.ERROR, null, e);
+      logger.log(TreeLogger.ERROR, null, e);
       throw new UnableToCompleteException();
     }
   }
 
-  /**
-   * Builds a {@link SerializableTypeOracle} for a give
-   * {@link com.google.gwt.user.client.rpc.RemoteService} interface.
-   * 
-   * @param propertyOracle property oracle used for initializing properties
-   * @param remoteService
-   *          {@link com.google.gwt.user.client.rpc.RemoteService RemoteService}
-   *          interface to build the oracle for
-   * @return a {@link SerializableTypeOracle} for the specified
-   *         {@link com.google.gwt.user.client.rpc.RemoteService RemoteService}
-   *         interface
-   * 
-   * @throws UnableToCompleteException if the the remote service is considered
-   *           invalid due to serialization problem or a missing or ill formed
-   *           remote service asynchronous interface
-   */
-  public SerializableTypeOracle build(PropertyOracle propertyOracle,
-      JClassType remoteService) throws UnableToCompleteException {
+  public void addRootType(TreeLogger logger, JType type) {
+    if (type.isPrimitive() != null) {
+      return;
+    }
 
-    try {
-      // String is always instantiable.
-      JClassType stringType = typeOracle.getType(String.class.getName());
-      if (!checkTypeInstantiable(rootLogger, stringType, false)) {
-        throw new UnableToCompleteException();
-      }
-      // IncompatibleRemoteServiceException is always serializable
-      JClassType icseType = typeOracle.getType(IncompatibleRemoteServiceException.class.getName());
-      if (!checkTypeInstantiable(rootLogger, icseType, false)) {
-        throw new UnableToCompleteException();
-      }
-    } catch (NotFoundException e) {
-      rootLogger.log(TreeLogger.ERROR, null, e);
+    JClassType clazz = (JClassType) type;
+    if (!rootTypes.containsKey(clazz)) {
+      recordTypeParametersIn(type, typeParametersInRootTypes);
+
+      rootTypes.put(clazz, logger);
+    } else {
+      logger.log(TreeLogger.TRACE, clazz.getParameterizedQualifiedSourceName()
+          + " is already a root type.");
+    }
+  }
+
+  /**
+   * Builds a {@link SerializableTypeOracle} for a given set of root types.
+   * 
+   * @param logger
+   * 
+   * @return a {@link SerializableTypeOracle} for the specified set of root
+   *         types
+   * 
+   * @throws UnableToCompleteException if there was not at least one
+   *           instantiable type assignable to each of the specified root types
+   */
+  public SerializableTypeOracle build(TreeLogger logger)
+      throws UnableToCompleteException {
+    alreadyCheckedObject = false;
+
+    boolean allSucceeded = true;
+    for (Entry<JClassType, TreeLogger> entry : rootTypes.entrySet()) {
+      allSucceeded &= checkTypeInstantiable(entry.getValue(), entry.getKey(),
+          false, createRootPath(entry.getKey()));
+    }
+
+    if (!allSucceeded) {
+      // the validation code has already logged why
       throw new UnableToCompleteException();
     }
 
-    TreeLogger logger = rootLogger.branch(TreeLogger.DEBUG, "Analyzing '"
-        + remoteService.getParameterizedQualifiedSourceName()
-        + "' for serializable types", null);
+    pruneUnreachableTypes();
 
-    alreadyCheckedObject = false;
-
-    validateRemoteService(logger, remoteService);
-
-    // Compute covariant arrays.
-    // Cache a list to prevent comodification.
-    List<TypeInfoComputed> typeInfoComputed = new ArrayList<TypeInfoComputed>(
-        typeToTypeInfoComputed.values());
-    for (TypeInfoComputed tic : typeInfoComputed) {
-      if (tic.isInstantiable()) {
-        JArrayType arrayType = tic.getType().isArray();
-        if (arrayType != null) {
-          JType leafType = arrayType.getLeafType();
-          int rank = arrayType.getRank();
-          JClassType classType = leafType.isClassOrInterface();
-          if (classType != null) {
-            List<JClassType> instantiableSubTypes = new ArrayList<JClassType>();
-            JClassType[] subTypes = classType.getSubtypes();
-            for (int i = 0; i < subTypes.length; ++i) {
-              if (getTypeInfoComputed(subTypes[i]).isInstantiable()) {
-                instantiableSubTypes.add(subTypes[i]);
-              }
-            }
-            List<JClassType> covariantTypes = getAllTypesBetweenRootTypeAndLeaves(
-                classType, instantiableSubTypes);
-            for (int i = 0, c = covariantTypes.size(); i < c; ++i) {
-              JArrayType covariantArray = getArrayType(typeOracle, rank,
-                  covariantTypes.get(i));
-              getTypeInfoComputed(covariantArray).setInstantiable(true);
-            }
-          }
-        }
-      }
+    TreeLogger reachableTypesLogger = logger;
+    if (logOutputStream != null) {
+      PrintWriterTreeLogger printWriterTreeLogger = new PrintWriterTreeLogger(
+          new PrintWriter(logOutputStream));
+      printWriterTreeLogger.setMaxDetail(TreeLogger.ALL);
+      reachableTypesLogger = printWriterTreeLogger;
     }
 
+    logReachableTypes(reachableTypesLogger);
+
     Set<JClassType> possiblyInstantiatedTypes = new TreeSet<JClassType>(
-        JCLASS_TYPE_COMPARATOR);
+        JTYPE_COMPARATOR);
 
     Set<JClassType> fieldSerializableTypes = new TreeSet<JClassType>(
-        JCLASS_TYPE_COMPARATOR);
+        JTYPE_COMPARATOR);
 
     for (TypeInfoComputed tic : typeToTypeInfoComputed.values()) {
       JClassType type = tic.getType();
 
-      if (type.isTypeParameter() != null || type.isWildcard() != null) {
-        /*
-         * Wildcard and type parameters types are ignored here. Their
-         * corresponding subtypes will be part of the typeToTypeInfoComputed
-         * set. TypeParameters can reach here if there are methods on the
-         * RemoteService that declare their own type parameters.
-         */
-        continue;
-      }
-
-      // Only record real types
-      if (type.isParameterized() != null) {
-        type = type.isParameterized().getRawType();
-      } else if (type.isGenericType() != null) {
-        type = type.isGenericType().getRawType();
-      }
-
-      assert (type == type.getErasedType());
+      type = type.getErasedType();
 
       if (tic.isInstantiable()) {
+        assert (!type.isAbstract() || type.isEnum() != null);
+
         possiblyInstantiatedTypes.add(type);
       }
 
       if (tic.isFieldSerializable()) {
+        assert (type.isInterface() == null);
+
         fieldSerializableTypes.add(type);
       }
     }
@@ -698,11 +657,33 @@
         possiblyInstantiatedTypes);
   }
 
-  /*
-   * Exposed using default access to enable testing.
+  /**
+   * Set the {@link OutputStream} which will receive a detailed log of the types
+   * which were examined in order to determine serializability.
+   */
+  public void setLogOutputStream(OutputStream logOutputStream) {
+    this.logOutputStream = logOutputStream;
+  }
+
+  public void setTypeFilter(TypeFilter typeFilter) {
+    this.typeFilter = typeFilter;
+  }
+
+  /**
+   * This method determines whether a type can be serialized by GWT. To do so,
+   * it must traverse all subtypes as well as all field types of those types,
+   * transitively.
+   * 
+   * It returns a boolean indicating whether this type or any of its subtypes
+   * are instantiable.
+   * 
+   * As a side effect, all types needed--plus some--to serialize this type are
+   * accumulated in {@link #typeToTypeInfoComputed}.
+   * 
+   * The method is exposed using default access to enable testing.
    */
   final boolean checkTypeInstantiable(TreeLogger logger, JType type,
-      boolean isSpeculative) {
+      boolean isSpeculative, final Path path) {
     assert (type != null);
     if (type.isPrimitive() != null) {
       return true;
@@ -715,113 +696,272 @@
     TreeLogger localLogger = logger.branch(TreeLogger.DEBUG,
         classType.getParameterizedQualifiedSourceName(), null);
 
-    TypeInfoComputed tic = getTypeInfoComputed(classType);
+    if (!isAllowedByFilter(localLogger, classType, isSpeculative)) {
+      return false;
+    }
+
+    JTypeParameter isTypeParameter = classType.isTypeParameter();
+    if (isTypeParameter != null) {
+      if (typeParametersInRootTypes.contains(isTypeParameter)) {
+        return checkTypeInstantiable(localLogger,
+            isTypeParameter.getFirstBound(), isSpeculative, path);
+      }
+
+      /*
+       * This type parameter was not in a root type and therefore it is the
+       * caller's responsibility to deal with it. We assume that it is
+       * instantiable here.
+       */
+      return true;
+    }
+
+    JWildcardType isWildcard = classType.isWildcard();
+    if (isWildcard != null) {
+      boolean success = true;
+      for (JClassType bound : isWildcard.getUpperBounds()) {
+        success &= checkTypeInstantiable(localLogger, bound, isSpeculative,
+            path);
+      }
+      return success;
+    }
+
+    TypeInfoComputed tic = getTypeInfoComputed(classType, path);
     if (tic.isPendingInstantiable()) {
       // just early out and pretend we will succeed
       return true;
     } else if (tic.isDone()) {
-      return tic.isInstantiable();
+      return tic.hasInstantiableSubtypes();
     }
-
     tic.setPendingInstantiable();
 
-    if (classType.isArray() != null) {
-      return checkArrayInstantiable(logger, classType.isArray(), tic,
-          isSpeculative);
-    } else if (classType.isWildcard() != null) {
-      return checkWildcardInstantiable(logger, classType.isWildcard(), tic,
-          isSpeculative);
-    } else if (classType.isClassOrInterface() != null) {
-      TypeInfo typeInfo = getTypeInfo(classType);
-      if (isSpeculative && typeInfo.isDirectlySerializable()) {
-        isSpeculative = false;
+    JArrayType isArray = classType.isArray();
+    if (isArray != null) {
+      JType leafType = isArray.getLeafType();
+      JTypeParameter isLeafTypeParameter = leafType.isTypeParameter();
+      if (isLeafTypeParameter != null
+          && !typeParametersInRootTypes.contains(isLeafTypeParameter)) {
+        // Don't deal with non root type parameters
+        tic.setInstantiable(false);
+        tic.setInstantiableSubytpes(true);
+        return true;
       }
 
-      JClassType javaLangObject = typeOracle.getJavaLangObject();
-      if (classType == javaLangObject
-          || classType.getErasedType() == javaLangObject) {
+      boolean succeeded = checkArrayInstantiable(localLogger, isArray,
+          isSpeculative, path);
+      if (succeeded) {
+        JClassType leafClass = leafType.isClassOrInterface();
+        if (leafClass != null) {
+          JClassType[] leafSubtypes = leafClass.getErasedType().getSubtypes();
+          for (JClassType leafSubtype : leafSubtypes) {
+            JArrayType covariantArray = getArrayType(typeOracle,
+                isArray.getRank(), leafSubtype);
+            checkTypeInstantiable(localLogger, covariantArray, true, path);
+          }
+        }
+      }
+
+      tic.setInstantiable(succeeded);
+      return succeeded;
+    }
+
+    if (classType == typeOracle.getJavaLangObject()) {
+      /*
+       * Report an error if the type is or erases to Object since this violates
+       * our restrictions on RPC.
+       */
+      localLogger.branch(
+          getLogLevel(isSpeculative),
+          "In order to produce smaller client-side code, 'Object' is not allowed; consider using a more specific type",
+          null);
+      tic.setInstantiable(false);
+      return false;
+    }
+
+    if (classType.isRawType() != null) {
+      TreeLogger rawTypeLogger = localLogger.branch(
+          TreeLogger.DEBUG,
+          "Type '"
+              + classType.getQualifiedSourceName()
+              + "' should be parameterized to help the compiler produce the smallest code size possible for your module",
+          null);
+
+      if (classType.isAssignableTo(collectionClass)
+          || classType.isAssignableTo(mapClass)) {
         /*
-         * Report an error if the type is or erases to Object since this
-         * violates our restrictions on RPC.
+         * Backwards compatibility. Raw collections or maps force all object
+         * subtypes to be considered. Fall through to the normal class handling.
          */
-        markAsUninstantiableAndLog(
-            localLogger,
-            isSpeculative,
-            "In order to produce smaller client-side code, 'Object' is not allowed; consider using a more specific type",
-            tic);
-        return false;
+        checkAllSubtypesOfObject(rawTypeLogger, path);
       }
+    }
 
-      boolean anySubtypes = false;
-      if (checkClassOrInterfaceInstantiable(localLogger, classType,
-          isSpeculative)) {
-        tic.setInstantiable(true);
-        anySubtypes = true;
+    JClassType originalType = (JClassType) type;
+
+    JRealClassType baseType;
+    if (type.isRawType() != null) {
+      baseType = type.isRawType().getBaseType();
+    } else if (type.isParameterized() != null) {
+      baseType = type.isParameterized().getBaseType();
+    } else {
+      baseType = (JRealClassType) originalType;
+    }
+
+    if (isSpeculative && tic.isDirectlySerializable()) {
+      isSpeculative = false;
+    }
+
+    boolean isInstantiable = checkTypeInstantiableNoSubtypes(localLogger,
+        baseType, isSpeculative, path);
+
+    JClassType[] typeArgs = NO_JCLASSES;
+    JParameterizedType isParameterized = originalType.isParameterized();
+    JGenericType baseAsGenericType = baseType.isGenericType();
+
+    if (isParameterized != null) {
+      typeArgs = isParameterized.getTypeArgs();
+    } else if (baseAsGenericType != null) {
+      List<JClassType> arguments = new ArrayList<JClassType>();
+      for (JTypeParameter typeParameter : baseAsGenericType.getTypeParameters()) {
+        arguments.add(typeParameter.getFirstBound());
       }
+      typeArgs = arguments.toArray(NO_JCLASSES);
+    }
 
-      if (classType.isParameterized() != null) {
+    boolean parametersOkay = true;
+    JRawType isRaw = originalType.isRawType();
+    if (isParameterized != null || isRaw != null) {
+      assert (baseAsGenericType != null);
+      int numDeclaredParams = baseAsGenericType.getTypeParameters().length;
+      if (numDeclaredParams == typeArgs.length) {
+        for (int i = 0; i < numDeclaredParams; ++i) {
+          JClassType typeArg = typeArgs[i];
+          parametersOkay &= checkTypeArgument(localLogger, baseAsGenericType,
+              i, typeArg, isSpeculative, path);
+        }
+      } else {
         /*
+         * TODO: Does anyone actually depend on this odious behavior?
+         * 
          * Backwards compatibility. The number of parameterization arguments
          * specified via gwt.typeArgs could exceed the number of formal
          * parameters declared on the generic type. Therefore have to explicitly
          * visit them here and they must all be instantiable.
          */
-        JParameterizedType parameterizedType = classType.isParameterized();
-        if (!checkTypeArgumentsInstantiable(localLogger, parameterizedType,
-            isSpeculative)) {
-          return false;
-        }
-      } else if (classType.isRawType() != null) {
-        TreeLogger rawTypeLogger = logger.branch(
-            TreeLogger.DEBUG,
-            "Type '"
-                + classType.getQualifiedSourceName()
-                + "' should be parameterized to help the compiler produce the smallest code size possible for your module",
-            null);
-
-        if (classType.isAssignableTo(collectionClass)
-            || classType.isAssignableTo(mapClass)) {
-          /*
-           * Backwards compatibility. Raw collections or maps force all object
-           * subtypes to be considered. Fall through to the normal class
-           * handling.
-           */
-          checkAllSubtypesOfObject(rawTypeLogger);
+        for (int i = 0; i < numDeclaredParams; ++i) {
+          JClassType typeArg = typeArgs[i];
+          parametersOkay &= checkTypeInstantiable(localLogger, typeArg,
+              isSpeculative, path);
         }
       }
+    }
 
+    isInstantiable &= parametersOkay;
+
+    boolean anySubtypes = false;
+    if (parametersOkay) {
       // Speculatively check all subtypes.
-      JClassType[] subtypes = classType.getSubtypes();
+      JClassType[] subtypes = baseType.getSubtypes();
       if (subtypes.length > 0) {
-        TreeLogger subLogger = localLogger.branch(TreeLogger.DEBUG,
+        TreeLogger subtypesLogger = localLogger.branch(TreeLogger.DEBUG,
             "Analyzing subclasses:", null);
 
-        for (JClassType subType : subtypes) {
-          if (checkClassOrInterfaceInstantiable(subLogger.branch(
-              TreeLogger.DEBUG, subType.getParameterizedQualifiedSourceName(),
-              null), subType, true)) {
-            getTypeInfoComputed(subType).setInstantiable(true);
+        for (JClassType subtype : subtypes) {
+          TreeLogger subtypeLogger = subtypesLogger.branch(TreeLogger.DEBUG,
+              subtype.getParameterizedQualifiedSourceName(), null);
+          Path subtypePath = createSubtypePath(path, subtype, originalType);
+          boolean subInstantiable = checkTypeInstantiableNoSubtypes(
+              subtypeLogger, subtype, true, subtypePath);
+          JGenericType genericSub = subtype.isGenericType();
+          if (genericSub != null) {
+            TreeLogger paramsLogger = subtypeLogger.branch(TreeLogger.DEBUG,
+                "Checking parameters of '"
+                    + genericSub.getParameterizedQualifiedSourceName() + "'");
+            Map<JTypeParameter, Set<JTypeParameter>> subParamsConstrainedBy = subParamsConstrainedBy(
+                baseType, genericSub);
+            for (int i = 0; i < genericSub.getTypeParameters().length; i++) {
+              JTypeParameter param = genericSub.getTypeParameters()[i];
+              TreeLogger paramLogger = paramsLogger.branch(TreeLogger.DEBUG,
+                  "Checking param '"
+                      + param.getParameterizedQualifiedSourceName() + "'");
+              Set<JTypeParameter> constBy = subParamsConstrainedBy.get(param);
+              if (constBy == null) {
+                subInstantiable &= checkTypeArgument(paramLogger, genericSub,
+                    i, param.getFirstBound(), true, path);
+              } else {
+                boolean paramOK = false;
+                for (JTypeParameter constrained : constBy) {
+                  paramOK |= checkTypeArgument(paramLogger, genericSub, i,
+                      typeArgs[constrained.getOrdinal()], true, path);
+                }
+                subInstantiable &= paramOK;
+              }
+            }
+          } else {
+            /*
+             * The subtype is not generic so it must be a concrete
+             * parameterization of the query type. If the query type is also a
+             * concrete parameterization then we should be able to exclude the
+             * subtype based on an assignability check. If the query type does
+             * contain type parameters then we assume that the subtype should be
+             * included. In order to be certain, we would need to perform a full
+             * unification between the query type and subtype.
+             */
+            if (subInstantiable && isParameterized != null) {
+              HashSet<JTypeParameter> typeParamsInQueryType = new HashSet<JTypeParameter>();
+              recordTypeParametersIn(isParameterized, typeParamsInQueryType);
+
+              if (typeParamsInQueryType.isEmpty()) {
+                if (!isParameterized.isAssignableFrom(subtype)) {
+                  // Only emit a warning if the supertype is not instantiable.
+                  Type logLevel = isInstantiable ? TreeLogger.DEBUG
+                      : getLogLevel(true);
+                  subtypeLogger.log(logLevel, "Excluding type '"
+                      + subtype.getParameterizedQualifiedSourceName()
+                      + "' because it is not assignable to '"
+                      + isParameterized.getParameterizedQualifiedSourceName()
+                      + "'");
+                  subInstantiable = false;
+                }
+              }
+            }
+          }
+
+          if (subInstantiable) {
+            // TODO: This is suspect.
+            getTypeInfoComputed(subtype, path).setInstantiable(true);
             anySubtypes = true;
           }
         }
       }
-
-      if (!anySubtypes && !isSpeculative) {
-        // No instantiable types were found
-        markAsUninstantiableAndLog(
-            logger,
-            isSpeculative,
-            "Type '"
-                + classType.getParameterizedQualifiedSourceName()
-                + "' was not serializable and has no concrete serializable subtypes",
-            tic);
-      }
-
-      return anySubtypes;
-    } else {
-      assert (false);
-      return false;
     }
+
+    anySubtypes |= isInstantiable;
+
+    tic.setInstantiable(isInstantiable);
+    tic.setInstantiableSubytpes(anySubtypes);
+
+    if (!anySubtypes && !isSpeculative) {
+      // No instantiable types were found
+      localLogger.branch(getLogLevel(isSpeculative), "Type '"
+          + classType.getParameterizedQualifiedSourceName()
+          + "' was not serializable and has no concrete serializable subtypes",
+          null);
+    }
+
+    return tic.hasInstantiableSubtypes();
+  }
+
+  final boolean checkTypeInstantiableNoSubtypes(TreeLogger logger,
+      JClassType type, boolean isSpeculative, Path path) {
+    if (qualifiesForSerialization(logger, type, isSpeculative, path)) {
+      return checkFields(logger, type, isSpeculative, path);
+    }
+
+    return false;
+  }
+
+  int getTypeParameterExposure(JGenericType type, int index) {
+    return getFlowInfo(type, index).getExposure();
   }
 
   /**
@@ -829,7 +969,7 @@
    * 
    * @param logger
    */
-  private void checkAllSubtypesOfObject(TreeLogger logger) {
+  private void checkAllSubtypesOfObject(TreeLogger logger, Path parent) {
     if (alreadyCheckedObject) {
       return;
     }
@@ -845,124 +985,30 @@
         "Checking all subtypes of Object which qualify for serialization", null);
     JClassType[] allTypes = typeOracle.getJavaLangObject().getSubtypes();
     for (JClassType cls : allTypes) {
-      if (getTypeInfo(cls).isDeclaredSerializable()) {
-        checkTypeInstantiable(localLogger, cls, true);
+      if (getTypeInfoComputed(cls, parent).isDeclaredSerializable()) {
+        checkTypeInstantiable(localLogger, cls, true, parent);
       }
     }
   }
 
   private boolean checkArrayInstantiable(TreeLogger logger,
-      JArrayType arrayType, TypeInfoComputed tic, boolean isSpeculative) {
+      final JArrayType arrayType, boolean isSpeculative, final Path parent) {
     TreeLogger branch = logger.branch(TreeLogger.DEBUG,
         "Analyzing component type:", null);
-    boolean success = checkTypeInstantiable(branch,
-        arrayType.getComponentType(), isSpeculative);
-    tic.setInstantiable(success);
-    return success;
-  }
-
-  private boolean checkClassOrInterfaceInstantiable(TreeLogger logger,
-      JClassType type, boolean isSpeculative) {
-
-    TypeInfo typeInfo = getTypeInfo(type);
-    TypeInfoComputed tic = getTypeInfoComputed(type);
-
-    if (!typeInfo.isDeclaredSerializable()) {
-      logger.branch(TreeLogger.DEBUG, "Type '"
-          + type.getParameterizedQualifiedSourceName()
-          + "' is not assignable to '" + IsSerializable.class.getName()
-          + "' or '" + Serializable.class.getName()
-          + "' nor does it have a custom field serializer", null);
-      return false;
-    }
-
-    if (typeInfo.isManuallySerializable()) {
-      List<String> failures = CustomFieldSerializerValidator.validate(
-          typeInfo.getManualSerializer(), type);
-      if (!failures.isEmpty()) {
-        markAsUninstantiableAndLog(logger, isSpeculative, failures, tic);
-        return false;
-      }
-    } else {
-      assert (typeInfo.isAutoSerializable());
-
-      if (type.isEnum() != null) {
-        if (type.isLocalType()) {
-          /*
-           * Quietly ignore local enum types.
-           */
-          tic.setInstantiable(false);
-          return false;
-        } else {
-          /*
-           * Enumerated types are serializable by default, but they do not have
-           * their state automatically or manually serialized. So, consider it
-           * serializable but do not check its fields.
-           */
-          return !type.isPrivate();
-        }
-      }
-
-      if (type.isPrivate()) {
-        /*
-         * Quietly ignore private types since these cannot be instantiated from
-         * the generated field serializers.
-         */
-        tic.setInstantiable(false);
-        return false;
-      }
-
-      if (type.isLocalType()) {
-        markAsUninstantiableAndLog(
-            logger,
-            isSpeculative,
-            type.getParameterizedQualifiedSourceName()
-                + " is a local type; it will be excluded from the set of serializable types",
-            tic);
-        return false;
-      }
-
-      if (type.isMemberType() && !type.isStatic()) {
-        markAsUninstantiableAndLog(
-            logger,
-            isSpeculative,
-            type.getParameterizedQualifiedSourceName()
-                + " is nested but not static; it will be excluded from the set of serializable types",
-            tic);
-        return false;
-      }
-
-      if (type.isInterface() != null || type.isAbstract()) {
-        // Quietly return false.
-        return false;
-      }
-
-      if (!type.isDefaultInstantiable()) {
-        // Warn and return false.
-        logger.log(
-            TreeLogger.WARN,
-            "Was not default instantiable (it must have a zero-argument constructor or no constructors at all)",
-            null);
-        return false;
-      }
-    }
-
-    if (!checkFields(logger, type, isSpeculative)) {
-      return false;
-    }
-
-    checkMethods(logger, type);
-    return true;
+    return checkTypeInstantiable(branch, arrayType.getComponentType(),
+        isSpeculative, createArrayComponentPath(arrayType, parent));
   }
 
   private boolean checkFields(TreeLogger logger, JClassType classOrInterface,
-      boolean isSpeculative) {
-    TypeInfo typeInfo = getTypeInfo(classOrInterface);
+      boolean isSpeculative, Path parent) {
+    TypeInfoComputed typeInfo = getTypeInfoComputed(classOrInterface, parent);
 
     // Check all super type fields first (recursively).
     JClassType superType = classOrInterface.getSuperclass();
-    if (superType != null && getTypeInfo(superType).isDeclaredSerializable()) {
-      boolean superTypeOk = checkFields(logger, superType, isSpeculative);
+    if (superType != null
+        && getTypeInfoComputed(superType, parent).isDeclaredSerializable()) {
+      boolean superTypeOk = checkFields(logger, superType, isSpeculative,
+          parent);
       /*
        * If my super type did not check out, then I am not instantiable and we
        * should error out... UNLESS I am *directly* serializable myself, in
@@ -986,13 +1032,7 @@
           "Analyzing Fields:", null);
 
       for (JField field : fields) {
-        if (field.isStatic() || field.isTransient()) {
-          continue;
-        }
-
-        if (field.isFinal()) {
-          localLogger.branch(TreeLogger.DEBUG, "Field '" + field.toString()
-              + "' will not be serialized because it is final", null);
+        if (!qualfiesForSerialization(localLogger, field)) {
           continue;
         }
 
@@ -1000,173 +1040,326 @@
             field.toString(), null);
         JType fieldType = field.getType();
 
+        Path path = createFieldPath(parent, field);
         if (typeInfo.isManuallySerializable()
             && fieldType.getLeafType() == typeOracle.getJavaLangObject()) {
           checkAllSubtypesOfObject(fieldLogger.branch(TreeLogger.WARN,
-              "Object was reached from a manually serializable type", null));
+              "Object was reached from a manually serializable type", null),
+              path);
         } else {
           allSucceeded &= checkTypeInstantiable(fieldLogger, fieldType,
-              isSpeculative);
+              isSpeculative, path);
         }
       }
     }
 
     boolean succeeded = allSucceeded || typeInfo.isManuallySerializable();
     if (succeeded) {
-      getTypeInfoComputed(classOrInterface).setFieldSerializable();
+      getTypeInfoComputed(classOrInterface, parent).setFieldSerializable();
     }
     return succeeded;
   }
 
-  private void checkMethods(TreeLogger logger, JClassType classOrInterface) {
-    if (isDefinedInJREEmulation(classOrInterface)) {
-      // JRE emulation classes are never used on the server; skip the check
-      return;
-    }
-
-    // TODO: consider looking up type hierarchy.
-    JMethod[] methods = classOrInterface.getMethods();
-    for (JMethod method : methods) {
-      if (method.isNative()) {
-        logger.branch(
-            TreeLogger.WARN,
-            MessageFormat.format(
-                "Method ''{0}'' is native, calling this method in server side code will result in an UnsatisfiedLinkError",
-                method.toString()), null);
+  /**
+   * Check the argument to a parameterized type to see if it will make the type
+   * it is applied to be serializable. As a side effect, populates
+   * {@link #typeToTypeInfoComputed} in the same way as
+   * {@link #checkTypeInstantiable(TreeLogger, JType, boolean)}.
+   * 
+   * @param logger
+   * @param baseType - The generic type the parameter is on
+   * @param paramIndex - The index of the parameter in the generic type
+   * @param typeArg - An upper bound on the actual argument being applied to the
+   *          generic type
+   * @param isSpeculative
+   * 
+   * @return Whether the a parameterized type can be serializable if
+   *         <code>baseType</code> is the base type and the
+   *         <code>paramIndex</code>th type argument is a subtype of
+   *         <code>typeArg</code>.
+   */
+  private boolean checkTypeArgument(TreeLogger logger, JGenericType baseType,
+      int paramIndex, JClassType typeArg, boolean isSpeculative, Path parent) {
+    JArrayType typeArgAsArray = typeArg.isArray();
+    if (typeArgAsArray != null) {
+      JTypeParameter parameterOfTypeArgArray = typeArgAsArray.getLeafType().isTypeParameter();
+      if (parameterOfTypeArgArray != null) {
+        JGenericType declaringClass = parameterOfTypeArgArray.getDeclaringClass();
+        if (declaringClass != null) {
+          TypeParameterFlowInfo flowInfoForArrayParam = getFlowInfo(
+              declaringClass, parameterOfTypeArgArray.getOrdinal());
+          TypeParameterFlowInfo otherFlowInfo = getFlowInfo(baseType,
+              paramIndex);
+          if (otherFlowInfo.getExposure() >= 0
+              && flowInfoForArrayParam.infiniteArrayExpansionPathBetween(otherFlowInfo)) {
+            logger.branch(
+                getLogLevel(isSpeculative),
+                "Cannot serialize type '"
+                    + baseType.getParameterizedQualifiedSourceName()
+                    + "' when given an argument of type '"
+                    + typeArg.getParameterizedQualifiedSourceName()
+                    + "' because it appears to require serializing arrays of unbounded dimension");
+            return false;
+          }
+        }
       }
     }
-  }
 
-  /**
-   * Returns <code>true</code> if all of the type arguments of the
-   * parameterized type are themselves instantiable.
-   */
-  private boolean checkTypeArgumentsInstantiable(TreeLogger logger,
-      JParameterizedType parameterizedType, boolean isSpeculative) {
-    TreeLogger branch = logger.branch(TreeLogger.DEBUG, "Analyzing type args",
-        null);
-    JClassType[] typeArgs = parameterizedType.getTypeArgs();
-    boolean allSucceeded = true;
-    for (JClassType typeArg : typeArgs) {
-      allSucceeded &= checkTypeInstantiable(branch, typeArg, isSpeculative);
+    Path path = createTypeArgumentPath(parent, baseType, paramIndex, typeArg);
+    int exposure = getTypeParameterExposure(baseType, paramIndex);
+    switch (exposure) {
+      case EXPOSURE_DIRECT:
+        return checkTypeInstantiable(logger, typeArg, true, path)
+            || mightNotBeExposed(baseType, paramIndex);
+
+      case EXPOSURE_NONE:
+        // Ignore this argument
+        return true;
+
+      default:
+        assert (exposure >= EXPOSURE_MIN_BOUNDED_ARRAY);
+        return checkTypeInstantiable(logger, getArrayType(typeOracle, exposure,
+            typeArg), true, path)
+            || mightNotBeExposed(baseType, paramIndex);
     }
-
-    return allSucceeded;
   }
 
-  private boolean checkWildcardInstantiable(TreeLogger logger,
-      JWildcardType wildcard, TypeInfoComputed tic, boolean isSpeculative) {
-    boolean success;
-    if (wildcard.getLowerBounds().length > 0) {
-      // Fail since ? super T for any T implies object also
-      markAsUninstantiableAndLog(logger, isSpeculative,
-          "In order to produce smaller client-side code, 'Object' is not allowed; '"
-              + wildcard.getQualifiedSourceName() + "' includes Object", tic);
-
-      success = false;
-    } else {
-      JClassType firstBound = wildcard.getFirstBound();
-      success = checkTypeInstantiable(logger, firstBound, isSpeculative);
-    }
-
-    tic.setInstantiable(success);
-    return success;
+  private TypeParameterFlowInfo getFlowInfo(JGenericType type, int index) {
+    return typeParameterExposureComputer.computeTypeParameterExposure(type,
+        index);
   }
 
-  private TypeInfo getTypeInfo(JClassType type) {
-    TypeInfo ti = typeToTypeInfo.get(type);
-    if (ti == null) {
-      ti = new TypeInfo(type);
-      typeToTypeInfo.put(type, ti);
-    }
-    return ti;
-  }
-
-  private TypeInfoComputed getTypeInfoComputed(JClassType type) {
+  private TypeInfoComputed getTypeInfoComputed(JClassType type, Path path) {
     TypeInfoComputed tic = typeToTypeInfoComputed.get(type);
     if (tic == null) {
-      tic = new TypeInfoComputed(type);
+      tic = new TypeInfoComputed(type, path);
       typeToTypeInfoComputed.put(type, tic);
     }
     return tic;
   }
 
+  private boolean isAllowedByFilter(TreeLogger logger, JClassType classType,
+      boolean isSpeculative) {
+    if (!typeFilter.isAllowed(classType)) {
+      logger.log(getLogLevel(isSpeculative), "Excluded by type filter ");
+      return false;
+    }
+
+    return true;
+  }
+
+  private void logPath(TreeLogger logger, Path path) {
+    if (path == null) {
+      return;
+    }
+
+    logger.log(TreeLogger.INFO, path.toString());
+    logPath(logger, path.getParent());
+  }
+
+  private void logReachableTypes(TreeLogger logger) {
+    logger.log(TreeLogger.INFO, "Reachable types computed on: "
+        + new Date().toString());
+    Set<JType> keySet = typeToTypeInfoComputed.keySet();
+    JType[] types = keySet.toArray(new JType[0]);
+    Arrays.sort(types, JTYPE_COMPARATOR);
+
+    for (JType type : types) {
+      TypeInfoComputed tic = typeToTypeInfoComputed.get(type);
+      assert (tic != null);
+
+      TreeLogger typeLogger = logger.branch(TreeLogger.INFO,
+          tic.getType().getParameterizedQualifiedSourceName());
+      TreeLogger serializationStatus = typeLogger.branch(TreeLogger.INFO,
+          "Serialization status");
+      if (tic.isInstantiable()) {
+        serializationStatus.branch(TreeLogger.INFO, "Instantiable");
+      } else {
+        if (tic.isFieldSerializable()) {
+          serializationStatus.branch(TreeLogger.INFO, "Field serializable");
+        } else {
+          serializationStatus.branch(TreeLogger.INFO, "Not serializable");
+        }
+      }
+
+      TreeLogger pathLogger = typeLogger.branch(TreeLogger.INFO, "Path");
+
+      logPath(pathLogger, tic.getPath());
+    }
+  }
+
+  private boolean mightNotBeExposed(JGenericType baseType, int paramIndex) {
+    TypeParameterFlowInfo flowInfo = getFlowInfo(baseType, paramIndex);
+    return flowInfo.getMightNotBeExposed();
+  }
+
   /**
-   * Returns <code>true</code> if the type is defined by the JRE.
+   * Remove serializable types that were visited due to speculative paths but
+   * are not really needed for serialization.
+   * 
+   * NOTE: This is currently much more limited than it should be. For example, a
+   * path sensitive prune could remove instantiable types also.
    */
-  private boolean isDefinedInJREEmulation(JClassType type) {
-    JPackage pkg = type.getPackage();
-    if (pkg != null) {
-      return pkg.getName().startsWith("java.");
-    }
-
-    return false;
-  }
-
-  private void markAsUninstantiableAndLog(TreeLogger logger,
-      boolean isSpeculative, List<String> es, TypeInfoComputed tic) {
-    for (String s : es) {
-      markAsUninstantiableAndLog(logger, isSpeculative, s, tic);
-    }
-  }
-
-  private void markAsUninstantiableAndLog(TreeLogger logger,
-      boolean isSpeculative, String logMessage, TypeInfoComputed tic) {
-    tic.setInstantiable(false);
-    tic.addSerializationIssue(isSpeculative, logMessage);
-    logger.branch(getLogLevel(isSpeculative), logMessage, null);
-  }
-
-  private void validateRemoteService(TreeLogger logger, JClassType remoteService)
-      throws UnableToCompleteException {
-    JMethod[] methods = remoteService.getOverridableMethods();
-
-    TreeLogger validationLogger = logger.branch(TreeLogger.DEBUG,
-        "Analyzing methods:", null);
-
-    boolean allSucceeded = true;
-    for (JMethod method : methods) {
-      TreeLogger methodLogger = validationLogger.branch(TreeLogger.DEBUG,
-          method.toString(), null);
-      JType returnType = method.getReturnType();
-      if (returnType != JPrimitiveType.VOID) {
-        TreeLogger returnTypeLogger = methodLogger.branch(TreeLogger.DEBUG,
-            "Return type: " + returnType.getParameterizedQualifiedSourceName(),
-            null);
-        allSucceeded &= checkTypeInstantiable(returnTypeLogger, returnType,
-            false);
-      }
-
-      JParameter[] params = method.getParameters();
-      for (JParameter param : params) {
-        TreeLogger paramLogger = methodLogger.branch(TreeLogger.DEBUG,
-            "Parameter: " + param.toString(), null);
-        JType paramType = param.getType();
-        allSucceeded &= checkTypeInstantiable(paramLogger, paramType, false);
-      }
-
-      JType[] exs = method.getThrows();
-      if (exs.length > 0) {
-        TreeLogger throwsLogger = methodLogger.branch(TreeLogger.DEBUG,
-            "Throws:", null);
-        for (JType ex : exs) {
-          if (!exceptionClass.isAssignableFrom(ex.isClass())) {
-            throwsLogger = throwsLogger.branch(
-                TreeLogger.WARN,
-                "'"
-                    + ex.getQualifiedSourceName()
-                    + "' is not a checked exception; only checked exceptions may be used",
-                null);
-          }
-
-          allSucceeded &= checkTypeInstantiable(throwsLogger, ex, false);
+  private void pruneUnreachableTypes() {
+    /*
+     * Record all supertypes of any instantiable type, whether or not they are
+     * field serialziable.
+     */
+    Set<JType> supersOfInstantiableTypes = new LinkedHashSet<JType>();
+    for (TypeInfoComputed tic : typeToTypeInfoComputed.values()) {
+      if (tic.isInstantiable()) {
+        JClassType type = tic.getType().getErasedType();
+        JClassType sup = type;
+        while (sup != null) {
+          supersOfInstantiableTypes.add(sup.getErasedType());
+          sup = sup.getErasedType().getSuperclass();
         }
       }
     }
 
-    if (!allSucceeded) {
-      // the validation code has already logged why
-      throw new UnableToCompleteException();
+    /*
+     * Record any field serializable type that is not in the supers of any
+     * instantiable type.
+     */
+    Set<JType> toKill = new LinkedHashSet<JType>();
+    for (TypeInfoComputed tic : typeToTypeInfoComputed.values()) {
+      if (tic.isFieldSerializable()
+          && !supersOfInstantiableTypes.contains(tic.getType().getErasedType())) {
+        toKill.add(tic.getType());
+      }
     }
+
+    /*
+     * Remove any field serializable supers that cannot be reached from an
+     * instantiable type.
+     */
+    for (JType type : toKill) {
+      typeToTypeInfoComputed.remove(type);
+    }
+  }
+
+  /**
+   * Returns <code>true</code> if the type qualifies for serialization.
+   */
+  private boolean qualifiesForSerialization(TreeLogger logger, JClassType type,
+      boolean isSpeculative, Path parent) {
+    TypeInfoComputed typeInfo = getTypeInfoComputed(type, parent);
+
+    if (!isAllowedByFilter(logger, type, isSpeculative)) {
+      return false;
+    }
+
+    if (!typeInfo.isDeclaredSerializable()) {
+      logger.branch(TreeLogger.DEBUG, "Type '"
+          + type.getParameterizedQualifiedSourceName()
+          + "' is not assignable to '" + IsSerializable.class.getName()
+          + "' or '" + Serializable.class.getName()
+          + "' nor does it have a custom field serializer", null);
+      return false;
+    }
+
+    if (typeInfo.isManuallySerializable()) {
+      List<String> problems = CustomFieldSerializerValidator.validate(
+          typeInfo.getManualSerializer(), type);
+      if (!problems.isEmpty()) {
+        for (String problem : problems) {
+          logger.branch(getLogLevel(isSpeculative), problem, null);
+        }
+        return false;
+      }
+    } else {
+      assert (typeInfo.isAutoSerializable());
+
+      if (type.isEnum() != null) {
+        if (type.isLocalType()) {
+          /*
+           * Quietly ignore local enum types.
+           */
+          return false;
+        } else {
+          /*
+           * Enumerated types are serializable by default, but they do not have
+           * their state automatically or manually serialized. So, consider it
+           * serializable but do not check its fields.
+           */
+          return !type.isPrivate();
+        }
+      }
+
+      if (type.isPrivate()) {
+        /*
+         * Quietly ignore private types since these cannot be instantiated from
+         * the generated field serializers.
+         */
+        return false;
+      }
+
+      if (type.isLocalType()) {
+        logger.branch(
+            getLogLevel(isSpeculative),
+            type.getParameterizedQualifiedSourceName()
+                + " is a local type; it will be excluded from the set of serializable types",
+            null);
+        return false;
+      }
+
+      if (type.isMemberType() && !type.isStatic()) {
+        logger.branch(
+            getLogLevel(isSpeculative),
+            type.getParameterizedQualifiedSourceName()
+                + " is nested but not static; it will be excluded from the set of serializable types",
+            null);
+        return false;
+      }
+
+      if (type.isAbstract()) {
+        // Abstract types will be picked up if there is an instantiable subtype.
+        return false;
+      }
+
+      if (!type.isDefaultInstantiable()) {
+        // Warn and return false.
+        logger.log(
+            TreeLogger.WARN,
+            "Was not default instantiable (it must have a zero-argument constructor or no constructors at all)",
+            null);
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+  /**
+   * 
+   * Returns a map from each parameter in the subtype to the set of parameters,
+   * if any, in the supertype which constrain that parameter.
+   */
+  private Map<JTypeParameter, Set<JTypeParameter>> subParamsConstrainedBy(
+      JClassType superclass, JGenericType subclass) {
+
+    Map<JTypeParameter, Set<JTypeParameter>> newTypeParameters = new LinkedHashMap<JTypeParameter, Set<JTypeParameter>>();
+
+    JGenericType isGenericSuper = superclass.isGenericType();
+    if (isGenericSuper != null) {
+      JParameterizedType parameterization = subclass.asParameterizationOf(isGenericSuper);
+      JClassType[] paramArgs = parameterization.getTypeArgs();
+      for (int i = 0; i < paramArgs.length; ++i) {
+        Set<JTypeParameter> typeParamsInParamArg = new HashSet<JTypeParameter>();
+        recordTypeParametersIn(paramArgs[i], typeParamsInParamArg);
+
+        for (JTypeParameter arg : typeParamsInParamArg) {
+          Set<JTypeParameter> constBy = newTypeParameters.get(arg);
+          if (constBy == null) {
+            constBy = new LinkedHashSet<JTypeParameter>();
+            newTypeParameters.put(arg, constBy);
+          }
+
+          constBy.add(isGenericSuper.getTypeParameters()[i]);
+        }
+      }
+    }
+
+    return newTypeParameters;
   }
 }
diff --git a/user/src/com/google/gwt/user/rebind/rpc/TypeFilter.java b/user/src/com/google/gwt/user/rebind/rpc/TypeFilter.java
new file mode 100644
index 0000000..4568c66
--- /dev/null
+++ b/user/src/com/google/gwt/user/rebind/rpc/TypeFilter.java
@@ -0,0 +1,40 @@
+/*
+ * 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.user.rebind.rpc;
+
+import com.google.gwt.core.ext.typeinfo.JClassType;
+
+/**
+ * Used to filter types out of serialization.
+ */
+interface TypeFilter {
+  /**
+   * Returns the name of this filter.
+   * 
+   * 
+   * @return the name of this filter
+   */
+  String getName();
+
+  /**
+   * Returns <code>true</code> if the type should be included.
+   * 
+   * @param type
+   * @return <code>true</code> if the type should be included
+   */
+  boolean isAllowed(JClassType type);
+}
diff --git a/user/src/com/google/gwt/user/rebind/rpc/TypeParameterExposureComputer.java b/user/src/com/google/gwt/user/rebind/rpc/TypeParameterExposureComputer.java
new file mode 100644
index 0000000..e38f360
--- /dev/null
+++ b/user/src/com/google/gwt/user/rebind/rpc/TypeParameterExposureComputer.java
@@ -0,0 +1,388 @@
+/*
+ * 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.user.rebind.rpc;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.typeinfo.JArrayType;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JField;
+import com.google.gwt.core.ext.typeinfo.JGenericType;
+import com.google.gwt.core.ext.typeinfo.JParameterizedType;
+import com.google.gwt.core.ext.typeinfo.JTypeParameter;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.Map.Entry;
+
+/**
+ * This class is used to compute type parameter exposure using a flow algorithm.
+ */
+class TypeParameterExposureComputer {
+  /**
+   * Helper class for type parameter flow information.
+   */
+  class TypeParameterFlowInfo {
+    private final JGenericType baseType;
+
+    /**
+     * The keys are the set of type parameters that, if exposed, cause this type
+     * parameter to be exposed. The value for each key is the dimensionality
+     * that the exposure will cause. If the key is exposed as an array, then the
+     * dimensionality should be added to the dimensionality that the key is
+     * already exposed as.
+     */
+    private final Map<TypeParameterFlowInfo, Integer> causesExposure = new LinkedHashMap<TypeParameterFlowInfo, Integer>();
+
+    private int exposure = SerializableTypeOracleBuilder.EXPOSURE_NONE;
+
+    private final Map<TypeParameterFlowInfo, Boolean> isTransitivelyAffectedByCache = new HashMap<TypeParameterFlowInfo, Boolean>();
+
+    /**
+     * Type parameters that need to be notified when my exposure changes.
+     */
+    private final Set<TypeParameterFlowInfo> listeners = new LinkedHashSet<TypeParameterFlowInfo>();
+
+    private boolean mightNotBeExposed = true;
+
+    /**
+     * Ordinal of this type parameter.
+     */
+    private final int ordinal;
+
+    private boolean visited;
+
+    TypeParameterFlowInfo(JGenericType baseType, int ordinal) {
+      this.baseType = baseType;
+      this.ordinal = ordinal;
+    }
+
+    /**
+     * TODO: We only need to go up the hierarchy until we find the first non RPC
+     * qualifying type.
+     * 
+     * TODO: We need to ignore fields that would be ignored by serialization.
+     * 
+     * @return
+     */
+    public boolean checkDirectExposure() {
+      boolean didChange = false;
+      JClassType type = baseType;
+      while (type != null) {
+        JField[] fields = type.getFields();
+        for (JField field : fields) {
+          if (!SerializableTypeOracleBuilder.qualfiesForSerialization(
+              TreeLogger.NULL, field)) {
+            continue;
+          }
+
+          if (field.getType().getLeafType() == getTypeParameter()) {
+            /*
+             * If the type parameter is referenced explicitly or as the leaf
+             * type of an array, then it will be considered directly exposed.
+             */
+            markExposedAsArray(0);
+            mightNotBeExposed = false;
+            didChange = true;
+
+            JArrayType fieldTypeAsArray = field.getType().isArray();
+            if (fieldTypeAsArray != null) {
+              markExposedAsArray(fieldTypeAsArray.getRank());
+            }
+          }
+        }
+
+        /*
+         * Counting on substitution to propagate the type parameter.
+         */
+        type = type.getSuperclass();
+      }
+
+      return didChange;
+    }
+
+    public Map<TypeParameterFlowInfo, Integer> getCausesExposure() {
+      return causesExposure;
+    }
+
+    public int getExposure() {
+      return exposure;
+    }
+
+    public Set<TypeParameterFlowInfo> getListeners() {
+      return listeners;
+    }
+
+    public boolean getMightNotBeExposed() {
+      return mightNotBeExposed;
+    }
+
+    /**
+     * Determine whether there is an infinite array exposure if this type
+     * parameter is used in an array type which is then passed as an actual type
+     * argument for the formal type parameter <code>other</code>.
+     */
+    public boolean infiniteArrayExpansionPathBetween(TypeParameterFlowInfo other) {
+      Integer dimensionDelta = getCausesExposure().get(other);
+      if (dimensionDelta == null) {
+        return false;
+      }
+      return dimensionDelta > 0 && other.isTransitivelyAffectedBy(this);
+    }
+
+    @Override
+    public String toString() {
+      return getTypeParameter().getName() + " in " + baseType.getName();
+    }
+
+    public boolean updateFlowInfo() {
+      boolean didChange = false;
+      if (!wasVisited()) {
+        didChange |= initializeExposure();
+        markVisited();
+      }
+
+      for (Entry<TypeParameterFlowInfo, Integer> entry : getCausesExposure().entrySet()) {
+        TypeParameterFlowInfo info2 = entry.getKey();
+        int dimensionDelta = entry.getValue();
+        if (info2.getExposure() >= 0) {
+          if (!infiniteArrayExpansionPathBetween(info2)) {
+            didChange |= markExposedAsArray(dimensionDelta
+                + info2.getExposure());
+          }
+        }
+      }
+
+      return didChange;
+    }
+
+    void addListener(TypeParameterFlowInfo listener) {
+      listeners.add(listener);
+    }
+
+    JTypeParameter getTypeParameter() {
+      return baseType.getTypeParameters()[ordinal];
+    }
+
+    boolean initializeExposure() {
+      computeIndirectExposureCauses();
+      return checkDirectExposure();
+    }
+
+    boolean markExposedAsArray(int dim) {
+      if (exposure >= dim) {
+        return false;
+      }
+
+      exposure = dim;
+      return true;
+    }
+
+    void markVisited() {
+      visited = true;
+    }
+
+    boolean wasVisited() {
+      return visited;
+    }
+
+    private void computeIndirectExposureCauses() {
+      JClassType[] subtypes = baseType.getSubtypes();
+      for (JClassType subtype : subtypes) {
+        JGenericType isGeneric = subtype.isGenericType();
+        if (isGeneric == null || isGeneric.isLocalType()
+            || (isGeneric.isMemberType() && !isGeneric.isStatic())) {
+          // Only generic types can cause a type parameter to be exposed,
+          // we exclude local and non-static member types that we know cannot
+          // be serialized.
+          // TODO: Unify this check with the checkTypeInstantiableNoSubtypes.
+          continue;
+        }
+
+        JParameterizedType asParameterizationOf = subtype.asParameterizationOf(baseType);
+        Set<JTypeParameter> paramsUsed = new LinkedHashSet<JTypeParameter>();
+        SerializableTypeOracleBuilder.recordTypeParametersIn(
+            asParameterizationOf.getTypeArgs()[ordinal], paramsUsed);
+
+        for (JTypeParameter paramUsed : paramsUsed) {
+          recordCausesExposure(isGeneric, paramUsed.getOrdinal(), 0);
+        }
+      }
+
+      JClassType type = baseType;
+      while (type != null) {
+        JField[] fields = type.getFields();
+        for (JField field : fields) {
+          if (!SerializableTypeOracleBuilder.qualfiesForSerialization(
+              TreeLogger.NULL, field)) {
+            continue;
+          }
+
+          JParameterizedType isParameterized = field.getType().isParameterized();
+          if (isParameterized == null) {
+            continue;
+          }
+
+          JClassType[] typeArgs = isParameterized.getTypeArgs();
+          for (int i = 0; i < typeArgs.length; ++i) {
+            if (referencesTypeParameter(typeArgs[i], getTypeParameter())) {
+              JGenericType genericFieldType = isParameterized.getBaseType();
+              recordCausesExposure(genericFieldType, i, 0);
+              JArrayType typeArgIsArray = typeArgs[i].isArray();
+              if (typeArgIsArray != null
+                  && typeArgIsArray.getLeafType() == getTypeParameter()) {
+                int dims = typeArgIsArray.getRank();
+                recordCausesExposure(genericFieldType, i, dims);
+              }
+            }
+          }
+        }
+
+        /*
+         * Counting on substitution to propagate the type parameter.
+         */
+        type = type.getSuperclass();
+      }
+    }
+
+    private Collection<? extends TypeParameterFlowInfo> getAffectedBy() {
+      return causesExposure.keySet();
+    }
+
+    /**
+     * The same as
+     * {@link TypeParameterExposureComputer#getFlowInfo(JGenericType, int)},
+     * except that it additionally adds <code>this</code> as a listener to the
+     * returned flow info.
+     */
+    private TypeParameterFlowInfo getFlowInfo(JGenericType type, int index) {
+      TypeParameterFlowInfo flowInfo = TypeParameterExposureComputer.this.getFlowInfo(
+          type, index);
+      flowInfo.addListener(this);
+      return flowInfo;
+    }
+
+    private boolean isTransitivelyAffectedBy(TypeParameterFlowInfo flowInfo) {
+      Boolean result = isTransitivelyAffectedByCache.get(flowInfo);
+      if (result != null) {
+        return result;
+      }
+
+      HashSet<TypeParameterFlowInfo> affectedBy = new HashSet<TypeParameterFlowInfo>();
+      Set<TypeParameterFlowInfo> affectedByWorklist = new LinkedHashSet<TypeParameterFlowInfo>();
+      affectedByWorklist.add(this);
+
+      result = false;
+      while (!affectedByWorklist.isEmpty()) {
+        TypeParameterFlowInfo currFlowInfo = affectedByWorklist.iterator().next();
+        affectedByWorklist.remove(currFlowInfo);
+
+        if (currFlowInfo == flowInfo) {
+          result = true;
+          break;
+        }
+
+        if (affectedBy.add(currFlowInfo)) {
+          affectedByWorklist.addAll(currFlowInfo.getAffectedBy());
+        }
+      }
+
+      isTransitivelyAffectedByCache.put(flowInfo, result);
+      return result;
+    }
+
+    private void recordCausesExposure(JGenericType type, int index, int level) {
+      assert (index < type.getTypeParameters().length);
+      TypeParameterFlowInfo flowInfo = getFlowInfo(type, index);
+      Integer oldLevel = causesExposure.get(flowInfo);
+      if (oldLevel == null || oldLevel < level) {
+        causesExposure.put(flowInfo, level);
+      }
+    }
+
+    private boolean referencesTypeParameter(JClassType classType,
+        JTypeParameter typeParameter) {
+      Set<JTypeParameter> typeParameters = new LinkedHashSet<JTypeParameter>();
+      SerializableTypeOracleBuilder.recordTypeParametersIn(classType,
+          typeParameters);
+      return typeParameters.contains(typeParameter);
+    }
+  }
+
+  private final Map<JTypeParameter, TypeParameterFlowInfo> typeParameterToFlowInfo = new IdentityHashMap<JTypeParameter, TypeParameterFlowInfo>();
+
+  private final Set<TypeParameterFlowInfo> worklist = new LinkedHashSet<TypeParameterFlowInfo>();
+
+  /**
+   * Computes flow information for the specified type parameter. If it has
+   * already been computed just return the value of the previous computation.
+   * 
+   * @param type the generic type whose type parameter flow we are interested in
+   * @param index the index of the type parameter whose flow we want to compute
+   */
+  public TypeParameterFlowInfo computeTypeParameterExposure(JGenericType type,
+      int index) {
+    // check if it has already been computed
+    JTypeParameter[] typeParameters = type.getTypeParameters();
+    assert (index < typeParameters.length);
+    JTypeParameter typeParameter = typeParameters[index];
+    TypeParameterFlowInfo queryFlow = typeParameterToFlowInfo.get(typeParameter);
+    if (queryFlow != null) {
+      return queryFlow;
+    }
+
+    // not already computed; compute it
+    queryFlow = getFlowInfo(type, index); // adds it to the work list as a
+    // side effect
+
+    while (!worklist.isEmpty()) {
+      TypeParameterFlowInfo info = worklist.iterator().next();
+      worklist.remove(info);
+
+      boolean didChange = info.updateFlowInfo();
+
+      if (didChange) {
+        for (TypeParameterFlowInfo listener : info.getListeners()) {
+          worklist.add(listener);
+        }
+      }
+    }
+
+    return queryFlow;
+  }
+
+  /**
+   * Return the parameter flow info for a type parameter specified by class and
+   * index. If the flow info did not previously exist, create it and add it to
+   * the work list. TODO(spoon): if the flow info has already been computed,
+   * then make a perfect/no-search flow info for this type parameter.
+   */
+  private TypeParameterFlowInfo getFlowInfo(JGenericType type, int index) {
+    JTypeParameter typeParameter = type.getTypeParameters()[index];
+    TypeParameterFlowInfo info = typeParameterToFlowInfo.get(typeParameter);
+    if (info == null) {
+      info = new TypeParameterFlowInfo(type, index);
+      typeParameterToFlowInfo.put(typeParameter, info);
+      worklist.add(info);
+    }
+    return info;
+  }
+}
\ No newline at end of file
diff --git a/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java b/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java
index 1d7fe84..bec1bbd 100644
--- a/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java
+++ b/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java
@@ -15,39 +15,47 @@
  */
 package com.google.gwt.user.rebind.rpc;
 
-import com.google.gwt.core.ext.BadPropertyValueException;
-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.typeinfo.JArrayType;
 import com.google.gwt.core.ext.typeinfo.JClassType;
 import com.google.gwt.core.ext.typeinfo.JGenericType;
+import com.google.gwt.core.ext.typeinfo.JParameterizedType;
+import com.google.gwt.core.ext.typeinfo.JRawType;
 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.dev.cfg.ModuleDef;
 import com.google.gwt.dev.cfg.ModuleDefLoader;
-import com.google.gwt.dev.util.log.AbstractTreeLogger;
+import com.google.gwt.dev.javac.CompilationUnit;
+import com.google.gwt.dev.javac.JavaSourceCodeBase;
+import com.google.gwt.dev.javac.MockCompilationUnit;
+import com.google.gwt.dev.javac.TypeOracleTestingUtils;
+import com.google.gwt.dev.javac.impl.SourceFileCompilationUnit;
 import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
-import com.google.gwt.user.client.rpc.IncompatibleRemoteServiceException;
 import com.google.gwt.user.rebind.rpc.testcases.client.AbstractSerializableTypes;
+import com.google.gwt.user.rebind.rpc.testcases.client.ClassWithTypeParameterThatErasesToObject;
 import com.google.gwt.user.rebind.rpc.testcases.client.CovariantArrays;
 import com.google.gwt.user.rebind.rpc.testcases.client.ManualSerialization;
-import com.google.gwt.user.rebind.rpc.testcases.client.MissingGwtTypeArgs;
-import com.google.gwt.user.rebind.rpc.testcases.client.ClassWithTypeParameterThatErasesToObject;
 import com.google.gwt.user.rebind.rpc.testcases.client.NoSerializableTypes;
 import com.google.gwt.user.rebind.rpc.testcases.client.NotAllSubtypesAreSerializable;
-import com.google.gwt.user.rebind.rpc.testcases.client.ObjectArrayInMethodSignature;
-import com.google.gwt.user.rebind.rpc.testcases.client.ObjectInMethodSignature;
 
 import junit.framework.TestCase;
 
+import java.io.PrintWriter;
 import java.util.Arrays;
 import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
 
 /**
  * Used to test the {@link SerializableTypeOracleBuilder}.
  */
 public class SerializableTypeOracleBuilderTest extends TestCase {
+
   /**
    * Used to test the results produced by the {@link SerializableTypeOracle}.
    */
@@ -74,28 +82,140 @@
           && maybeInstantiated == other.maybeInstantiated;
     }
 
+    @Override
     public String toString() {
       return "{ " + sourceName + ", " + Boolean.toString(maybeInstantiated)
           + " }";
     }
   }
 
-  private static class MockPropertyOracle implements PropertyOracle {
-    public String getPropertyValue(TreeLogger logger, String propertyName) {
-      // Could mock "gwt.enforceRPCTypeVersioning" etc here
-      return "";
-    }
-
-    public String[] getPropertyValueSet(TreeLogger logger, String propertyName)
-        throws BadPropertyValueException {
-      return new String[] { "" };
-    }
+  private static void addICRSE(Set<CompilationUnit> units) {
+    StringBuffer code = new StringBuffer();
+    code.append("package com.google.gwt.user.client.rpc;\n");
+    code.append("public class IncompatibleRemoteServiceException extends Throwable {\n");
+    code.append("}\n");
+    units.add(createMockCompilationUnit(
+        "com.google.gwt.user.client.rpc.IncompatibleRemoteServiceException",
+        code));
   }
 
-  /**
-   * No logger output will be written to the console.
-   */
-  private static final boolean SUPPRESS_LOGGER_OUTPUT = true;
+  private static void addIsSerializable(Set<CompilationUnit> units) {
+    StringBuffer code = new StringBuffer();
+    code.append("package com.google.gwt.user.client.rpc;\n");
+    code.append("public interface IsSerializable {\n");
+    code.append("}\n");
+    units.add(createMockCompilationUnit(
+        "com.google.gwt.user.client.rpc.IsSerializable", code));
+  }
+
+  private static void addJavaIoSerializable(Set<CompilationUnit> units) {
+    units.add(new SourceFileCompilationUnit(JavaSourceCodeBase.SERIALIZABLE));
+  }
+
+  private static void addJavaLangException(Set<CompilationUnit> units) {
+    StringBuffer code = new StringBuffer();
+    code.append("package java.lang;\n");
+    code.append("public class Exception extends Throwable {\n");
+    code.append("}\n");
+
+    units.add(createMockCompilationUnit("java.lang.Exception", code));
+  }
+
+  private static void addJavaLangObject(Set<CompilationUnit> units) {
+    units.add(new SourceFileCompilationUnit(JavaSourceCodeBase.OBJECT));
+  }
+
+  private static void addJavaLangString(Set<CompilationUnit> units) {
+    units.add(new SourceFileCompilationUnit(JavaSourceCodeBase.STRING));
+  }
+
+  private static void addJavaLangThrowable(Set<CompilationUnit> units) {
+    StringBuffer code = new StringBuffer();
+    code.append("package java.lang;\n");
+    code.append("import java.io.Serializable;\n");
+    code.append("public class Throwable implements Serializable {\n");
+    code.append("}\n");
+    units.add(createMockCompilationUnit("java.lang.Throwable", code));
+  }
+
+  private static void addJavaUtilCollection(Set<CompilationUnit> units) {
+    StringBuffer code = new StringBuffer();
+    code.append("package java.util;\n");
+    code.append("public interface Collection<E> {\n");
+    code.append("}\n");
+    units.add(createMockCompilationUnit("java.util.Collection", code));
+  }
+
+  private static void addJavaUtilMap(Set<CompilationUnit> units) {
+    units.add(new SourceFileCompilationUnit(JavaSourceCodeBase.MAP));
+  }
+
+  private static void addStandardClasses(Set<CompilationUnit> units) {
+    addJavaIoSerializable(units);
+    addJavaLangObject(units);
+    addJavaLangString(units);
+    addJavaUtilMap(units);
+    addICRSE(units);
+    addJavaLangException(units);
+    addJavaLangThrowable(units);
+    addJavaUtilCollection(units);
+    addIsSerializable(units);
+  }
+
+  private static void assertFieldSerializable(SerializableTypeOracle so,
+      JClassType type) {
+    assertTrue(so.isSerializable(type));
+  }
+
+  private static void assertInstantiable(SerializableTypeOracle so,
+      JClassType type) {
+    assertTrue(so.maybeInstantiated(type));
+    assertFieldSerializable(so, type);
+  }
+
+  private static void assertNotFieldSerializable(SerializableTypeOracle so,
+      JClassType type) {
+    assertFalse(so.isSerializable(type));
+  }
+
+  private static void assertNotInstantiable(SerializableTypeOracle so,
+      JClassType type) {
+    assertFalse(so.maybeInstantiated(type));
+  }
+
+  private static void assertNotInstantiableOrFieldSerializable(
+      SerializableTypeOracle so, JClassType type) {
+    assertNotInstantiable(so, type);
+    assertNotFieldSerializable(so, type);
+  }
+
+  private static void assertSerializableTypes(SerializableTypeOracle so,
+      JClassType... expectedTypes) {
+    Set<JType> expectedSet = new TreeSet<JType>(
+        SerializableTypeOracleBuilder.JTYPE_COMPARATOR);
+    expectedSet.addAll(Arrays.asList(expectedTypes));
+
+    Set<JType> actualSet = new TreeSet<JType>(
+        SerializableTypeOracleBuilder.JTYPE_COMPARATOR);
+    JType[] actualTypes = so.getSerializableTypes();
+    actualSet.addAll(Arrays.asList(actualTypes));
+
+    assertTrue("Sets not equal.  Expected=\n" + expectedSet + ", \nactual=\n"
+        + actualSet, expectedSet.containsAll(actualSet)
+        && actualSet.containsAll(expectedSet));
+  }
+
+  private static TreeLogger createLogger() {
+    PrintWriterTreeLogger logger = new PrintWriterTreeLogger(new PrintWriter(
+        System.err));
+    logger.setMaxDetail(TreeLogger.ERROR);
+    return logger;
+  }
+
+  private static CompilationUnit createMockCompilationUnit(String qname,
+      CharSequence code) {
+    return new MockCompilationUnit(qname, code.toString());
+  }
 
   private static TypeInfo[] getActualTypeInfo(SerializableTypeOracle sto) {
     JType[] types = sto.getSerializableTypes();
@@ -149,22 +269,12 @@
         + toString(actual), Arrays.equals(expected, actual));
   }
 
-  /**
-   * This could be your own tree logger, perhaps validating the error output.
-   */
-  private final TreeLogger logger = SUPPRESS_LOGGER_OUTPUT ? TreeLogger.NULL
-      : new PrintWriterTreeLogger();
-
   private final ModuleDef moduleDef;
 
-  private final PropertyOracle propertyOracle = new MockPropertyOracle();
-
   private final TypeOracle typeOracle;
 
   public SerializableTypeOracleBuilderTest() throws UnableToCompleteException {
-    if (logger instanceof AbstractTreeLogger) {
-      ((AbstractTreeLogger) logger).setMaxDetail(TreeLogger.INFO);
-    }
+    TreeLogger logger = createLogger();
 
     moduleDef = ModuleDefLoader.createSyntheticModule(logger,
         "com.google.gwt.user.rebind.rpc.testcases.RebindRPCTestCases.JUnit",
@@ -175,6 +285,152 @@
   }
 
   /**
+   * Test with a generic class whose type parameter is exposed only in certain
+   * subclasses.
+   * 
+   * NOTE: This test has been disabled because it requires a better pruner in
+   * STOB. See SerializableTypeOracleBuilder.pruneUnreachableTypes().
+   */
+  public void disabledTestMaybeExposedParameter()
+      throws UnableToCompleteException, NotFoundException {
+    Set<CompilationUnit> units = new HashSet<CompilationUnit>();
+    addStandardClasses(units);
+
+    {
+      StringBuilder code = new StringBuilder();
+      code.append("import java.io.Serializable;\n");
+      code.append("public abstract class List<T> implements Serializable {\n");
+      code.append("}\n");
+      units.add(createMockCompilationUnit("List", code));
+    }
+
+    {
+      StringBuilder code = new StringBuilder();
+      code.append("public class EmptyList<T> extends List<T> {\n");
+      code.append("}\n");
+      units.add(createMockCompilationUnit("EmptyList", code));
+    }
+
+    {
+      StringBuilder code = new StringBuilder();
+      code.append("public class LinkedList<T> extends List<T> {\n");
+      code.append("  T head;\n");
+      code.append("  LinkedList<T> next;\n");
+      code.append("}\n");
+      units.add(createMockCompilationUnit("LinkedList", code));
+    }
+
+    {
+      StringBuilder code = new StringBuilder();
+      code.append("public class CantSerialize {\n");
+      code.append("}\n");
+      units.add(createMockCompilationUnit("CantSerialize", code));
+    }
+
+    TreeLogger logger = createLogger();
+    TypeOracle to = TypeOracleTestingUtils.buildTypeOracle(logger, units);
+
+    JGenericType list = to.getType("List").isGenericType();
+    JGenericType emptyList = to.getType("EmptyList").isGenericType();
+    JClassType cantSerialize = to.getType("CantSerialize");
+
+    JParameterizedType listOfCantSerialize = to.getParameterizedType(list,
+        new JClassType[] {cantSerialize});
+
+    SerializableTypeOracleBuilder sob = new SerializableTypeOracleBuilder(
+        logger, to);
+    sob.addRootType(logger, listOfCantSerialize);
+    SerializableTypeOracle so = sob.build(logger);
+
+    assertFieldSerializable(so, listOfCantSerialize);
+    assertSerializableTypes(so, list.getRawType(), emptyList.getRawType());
+  }
+
+  /*
+   * Tests that arrays of type variables that do not cause infinite expansion.
+   */
+  public void testArrayOfTypeParameter() throws UnableToCompleteException,
+      NotFoundException {
+    Set<CompilationUnit> units = new HashSet<CompilationUnit>();
+    addStandardClasses(units);
+
+    {
+      StringBuilder code = new StringBuilder();
+      code.append("import java.io.Serializable;\n");
+      code.append("public class A<T> implements Serializable {\n");
+      code.append("}\n");
+      units.add(createMockCompilationUnit("A", code));
+    }
+
+    {
+      StringBuilder code = new StringBuilder();
+      code.append("import java.io.Serializable;\n");
+      code.append("public class B<T> extends A<T> implements Serializable {\n");
+      code.append("  T[][] t;\n");
+      code.append("}\n");
+      units.add(createMockCompilationUnit("B", code));
+    }
+
+    {
+      StringBuilder code = new StringBuilder();
+      code.append("import java.io.Serializable;\n");
+      code.append("public class C<T> implements Serializable {\n");
+      code.append("  A<T[]> a1;\n");
+      code.append("  A<Ser> a2;\n");
+      code.append("}\n");
+      units.add(createMockCompilationUnit("C", code));
+    }
+
+    {
+      StringBuilder code = new StringBuilder();
+      code.append("import java.io.Serializable;\n");
+      code.append("public class Ser implements Serializable {\n");
+      code.append("}\n");
+      units.add(createMockCompilationUnit("Ser", code));
+    }
+
+    TreeLogger logger = createLogger();
+    TypeOracle to = TypeOracleTestingUtils.buildTypeOracle(logger, units);
+
+    JGenericType a = to.getType("A").isGenericType();
+    JGenericType b = to.getType("B").isGenericType();
+    JGenericType c = to.getType("C").isGenericType();
+    JClassType ser = to.getType("Ser");
+
+    JClassType javaLangString = to.getType(String.class.getName());
+    JParameterizedType cOfString = to.getParameterizedType(c,
+        new JClassType[] {javaLangString});
+    SerializableTypeOracleBuilder sob = new SerializableTypeOracleBuilder(
+        logger, to);
+    sob.addRootType(logger, cOfString);
+
+    assertEquals(2, sob.getTypeParameterExposure(a, 0));
+    assertEquals(2, sob.getTypeParameterExposure(b, 0));
+    assertEquals(3, sob.getTypeParameterExposure(c, 0));
+
+    SerializableTypeOracle so = sob.build(logger);
+
+    JArrayType stringArray = to.getArrayType(javaLangString);
+    JArrayType stringArrayArray = to.getArrayType(stringArray);
+    JArrayType stringArrayArrayArray = to.getArrayType(stringArrayArray);
+    JArrayType serArray = to.getArrayType(ser);
+    JArrayType serArrayArray = to.getArrayType(serArray);
+
+    assertSerializableTypes(so, a.getRawType(), b.getRawType(), c,
+        javaLangString, stringArray, stringArrayArray, stringArrayArrayArray,
+        ser, serArray, serArrayArray);
+
+    assertInstantiable(so, a.getRawType());
+    assertInstantiable(so, b.getRawType());
+    assertInstantiable(so, c);
+    assertInstantiable(so, stringArray);
+    assertInstantiable(so, stringArrayArray);
+    assertInstantiable(so, stringArrayArrayArray);
+    assertInstantiable(so, serArray);
+    assertInstantiable(so, serArrayArray);
+  }
+
+  /**
    * Tests that both the generic and raw forms of type that has a type parameter
    * that erases to object are not serializable.
    * 
@@ -182,20 +438,21 @@
    */
   public void testClassWithTypeParameterThatErasesToObject()
       throws NotFoundException, UnableToCompleteException {
-    JGenericType genericType = typeOracle.getType(
-        ClassWithTypeParameterThatErasesToObject.class.getName().replace('$',
-            '.')).isGenericType();
+    TreeLogger logger = createLogger();
 
-    // The generic form of the type should not be serializable.
-    SerializableTypeOracleBuilder genericStob = new SerializableTypeOracleBuilder(
-        logger, typeOracle);
-    assertFalse(genericStob.checkTypeInstantiable(logger, genericType, false));
+    JRawType rawType = typeOracle.getType(
+        ClassWithTypeParameterThatErasesToObject.class.getCanonicalName()).isGenericType().getRawType();
 
     // The raw form of the type should not be serializable.
-    SerializableTypeOracleBuilder rawStob = new SerializableTypeOracleBuilder(
+    SerializableTypeOracleBuilder stob = new SerializableTypeOracleBuilder(
         logger, typeOracle);
-    assertFalse(rawStob.checkTypeInstantiable(logger, genericType.getRawType(),
-        false));
+    stob.addRootType(logger, rawType);
+    try {
+      stob.build(logger);
+      fail("Expected an " + UnableToCompleteException.class.getSimpleName());
+    } catch (UnableToCompleteException ex) {
+      // Expected to reach here
+    }
   }
 
   /**
@@ -204,14 +461,15 @@
    */
   public void testCovariantArrays() throws UnableToCompleteException,
       NotFoundException {
+    TreeLogger logger = createLogger();
 
-    JClassType testServiceClass = typeOracle.getType(CovariantArrays.class.getName());
     SerializableTypeOracleBuilder stob = new SerializableTypeOracleBuilder(
         logger, typeOracle);
-    SerializableTypeOracle sto = stob.build(propertyOracle, testServiceClass);
+    JClassType rootType = typeOracle.getArrayType(typeOracle.getType(CovariantArrays.AA.class.getCanonicalName()));
+    stob.addRootType(logger, rootType);
+    SerializableTypeOracle sto = stob.build(logger);
 
     TypeInfo[] expected = new TypeInfo[] {
-        new TypeInfo(IncompatibleRemoteServiceException.class.getName(), true),
         new TypeInfo(CovariantArrays.AA.class.getName() + "[]", true),
         new TypeInfo(CovariantArrays.BB.class.getName() + "[]", true),
         new TypeInfo(CovariantArrays.CC.class.getName() + "[]", true),
@@ -221,36 +479,258 @@
         new TypeInfo(CovariantArrays.B.class.getName(), true),
         new TypeInfo(CovariantArrays.C.class.getName() + "[]", true),
         new TypeInfo(CovariantArrays.D.class.getName() + "[]", true),
-        new TypeInfo(CovariantArrays.D.class.getName(), true),
-        new TypeInfo(Exception.class.getName(), false),
-        new TypeInfo(RuntimeException.class.getName(), false),
-        new TypeInfo(String.class.getName(), true),
-        new TypeInfo(Throwable.class.getName(), false)};
+        new TypeInfo(CovariantArrays.D.class.getName(), true),};
     validateSTO(sto, expected);
   }
 
   /**
+   * Expansion via parameterized types, where the type is exposed.
+   */
+  public void testInfiniteParameterizedTypeExpansionCase1()
+      throws UnableToCompleteException, NotFoundException {
+    Set<CompilationUnit> units = new HashSet<CompilationUnit>();
+    addStandardClasses(units);
+
+    {
+      StringBuilder code = new StringBuilder();
+      code.append("import java.io.Serializable;\n");
+      code.append("public class A<T> implements Serializable {\n");
+      code.append("  B<T> b;\n");
+      code.append("  T x;\n");
+      code.append("}\n");
+      units.add(createMockCompilationUnit("A", code));
+    }
+
+    {
+      StringBuilder code = new StringBuilder();
+      code.append("import java.io.Serializable;\n");
+      code.append("public class B<T> extends A<T> implements Serializable {\n");
+      code.append("  A<B<T>> ab;\n");
+      code.append("}\n");
+      units.add(createMockCompilationUnit("B", code));
+    }
+
+    {
+      StringBuilder code = new StringBuilder();
+      code.append("import java.io.Serializable;\n");
+      code.append("public class SerializableArgument implements Serializable {\n");
+      code.append("}\n");
+      units.add(createMockCompilationUnit("SerializableArgument", code));
+    }
+
+    TreeLogger logger = createLogger();
+    TypeOracle to = TypeOracleTestingUtils.buildTypeOracle(logger, units);
+
+    JGenericType a = to.getType("A").isGenericType();
+    JGenericType b = to.getType("B").isGenericType();
+    JClassType serializableArgument = to.getType("SerializableArgument");
+
+    JParameterizedType aOfString = to.getParameterizedType(a,
+        new JClassType[] {serializableArgument});
+    SerializableTypeOracleBuilder sob = new SerializableTypeOracleBuilder(
+        logger, to);
+    sob.addRootType(logger, aOfString);
+    SerializableTypeOracle so = sob.build(logger);
+
+    assertInstantiable(so, a.getRawType());
+    assertInstantiable(so, b.getRawType());
+    assertInstantiable(so, serializableArgument);
+    assertSerializableTypes(so, a.getRawType(), b.getRawType(),
+        serializableArgument);
+  }
+
+  /**
+   * Expansion via parameterized types, where the type is not actually exposed.
+   */
+  public void testInfiniteParameterizedTypeExpansionCase2()
+      throws UnableToCompleteException, NotFoundException {
+    Set<CompilationUnit> units = new HashSet<CompilationUnit>();
+    addStandardClasses(units);
+
+    {
+      StringBuilder code = new StringBuilder();
+      code.append("import java.io.Serializable;\n");
+      code.append("public class A<T> implements Serializable {\n");
+      code.append("  B<T> b;\n");
+      code.append("}\n");
+      units.add(createMockCompilationUnit("A", code));
+    }
+
+    {
+      StringBuilder code = new StringBuilder();
+      code.append("import java.io.Serializable;\n");
+      code.append("public class B<T> extends A<T> implements Serializable {\n");
+      code.append("  A<B<T>> ab;\n");
+      code.append("}\n");
+      units.add(createMockCompilationUnit("B", code));
+    }
+
+    {
+      StringBuilder code = new StringBuilder();
+      code.append("import java.io.Serializable;\n");
+      code.append("public class UnusedSerializableArgument implements Serializable {\n");
+      code.append("}\n");
+      units.add(createMockCompilationUnit("UnusedSerializableArgument", code));
+    }
+
+    TreeLogger logger = createLogger();
+    TypeOracle to = TypeOracleTestingUtils.buildTypeOracle(logger, units);
+
+    JGenericType a = to.getType("A").isGenericType();
+    JGenericType b = to.getType("B").isGenericType();
+    JClassType unusedSerializableArgument = to.getType("UnusedSerializableArgument");
+
+    JParameterizedType aOfString = to.getParameterizedType(a,
+        new JClassType[] {unusedSerializableArgument});
+    SerializableTypeOracleBuilder sob = new SerializableTypeOracleBuilder(
+        logger, to);
+    sob.addRootType(logger, aOfString);
+    SerializableTypeOracle so = sob.build(logger);
+
+    assertInstantiable(so, a.getRawType());
+    assertInstantiable(so, b.getRawType());
+
+    assertNotInstantiableOrFieldSerializable(so, unusedSerializableArgument);
+    assertSerializableTypes(so, a.getRawType(), b.getRawType());
+  }
+
+  /*
+   * Case 3: Expansion via array dimensions, but the type arguments are not
+   * exposed so this case will succeed.
+   */
+  public void testInfiniteParameterizedTypeExpansionCase3()
+      throws UnableToCompleteException, NotFoundException {
+    Set<CompilationUnit> units = new HashSet<CompilationUnit>();
+    addStandardClasses(units);
+
+    {
+      StringBuilder code = new StringBuilder();
+      code.append("import java.io.Serializable;\n");
+      code.append("public class A<T> implements Serializable {\n");
+      code.append("  B<T> b;\n");
+      code.append("}\n");
+      units.add(createMockCompilationUnit("A", code));
+    }
+
+    {
+      StringBuilder code = new StringBuilder();
+      code.append("import java.io.Serializable;\n");
+      code.append("public class B<T> extends A<T> implements Serializable {\n");
+      code.append("  A<T[]> ab;\n");
+      code.append("}\n");
+      units.add(createMockCompilationUnit("B", code));
+    }
+
+    TreeLogger logger = createLogger();
+    TypeOracle to = TypeOracleTestingUtils.buildTypeOracle(logger, units);
+
+    JGenericType a = to.getType("A").isGenericType();
+    JGenericType b = to.getType("B").isGenericType();
+
+    JClassType javaLangString = to.getType(String.class.getName());
+    JParameterizedType aOfString = to.getParameterizedType(a,
+        new JClassType[] {javaLangString});
+    SerializableTypeOracleBuilder sob = new SerializableTypeOracleBuilder(
+        logger, to);
+    sob.addRootType(logger, aOfString);
+    SerializableTypeOracle so = sob.build(logger);
+
+    assertInstantiable(so, a.getRawType());
+    assertInstantiable(so, b.getRawType());
+
+    assertNotInstantiableOrFieldSerializable(so, javaLangString);
+    assertSerializableTypes(so, a.getRawType(), b.getRawType());
+  }
+
+  /*
+   * Case 4: Expansion via array dimensions, but the type arguments are exposed
+   * so this case will fail.
+   */
+  public void testInfiniteParameterizedTypeExpansionCase4()
+      throws UnableToCompleteException, NotFoundException {
+    Set<CompilationUnit> units = new HashSet<CompilationUnit>();
+    addStandardClasses(units);
+
+    {
+      StringBuilder code = new StringBuilder();
+      code.append("import java.io.Serializable;\n");
+      code.append("public class A<T> implements Serializable {\n");
+      code.append("  T t;\n");
+      code.append("}\n");
+      units.add(createMockCompilationUnit("A", code));
+    }
+
+    {
+      StringBuilder code = new StringBuilder();
+      code.append("import java.io.Serializable;\n");
+      code.append("public class B<T> extends A<T> implements Serializable {\n");
+      code.append("  A<T[]> ab;\n");
+      code.append("}\n");
+      units.add(createMockCompilationUnit("B", code));
+    }
+
+    TreeLogger logger = createLogger();
+    TypeOracle to = TypeOracleTestingUtils.buildTypeOracle(logger, units);
+
+    JGenericType a = to.getType("A").isGenericType();
+    JGenericType b = to.getType("B").isGenericType();
+
+    JClassType javaLangString = to.getType(String.class.getName());
+    JParameterizedType aOfString = to.getParameterizedType(a,
+        new JClassType[] {javaLangString});
+    SerializableTypeOracleBuilder sob = new SerializableTypeOracleBuilder(
+        logger, to);
+
+    assertEquals(0, sob.getTypeParameterExposure(a, 0));
+    assertEquals(0, sob.getTypeParameterExposure(b, 0));
+
+    sob.addRootType(logger, aOfString);
+    SerializableTypeOracle so = sob.build(logger);
+
+    assertNotFieldSerializable(so, b.getRawType());
+    assertNotInstantiable(so, b.getRawType());
+
+    assertSerializableTypes(so, a.getRawType(), javaLangString);
+    assertInstantiable(so, a.getRawType());
+    assertNotInstantiable(so, b.getRawType());
+    assertInstantiable(so, javaLangString);
+  }
+
+  /**
    * Tests that a manually serialized type with a field that is not serializable
    * does not cause the generator to fail.
    */
   public void testManualSerialization() throws NotFoundException,
       UnableToCompleteException {
-    JClassType testServiceClass = typeOracle.getType(ManualSerialization.class.getName());
+    TreeLogger logger = createLogger();
+
     SerializableTypeOracleBuilder stob = new SerializableTypeOracleBuilder(
         logger, typeOracle);
-    stob.build(propertyOracle, testServiceClass);
+    JClassType a = typeOracle.getType(ManualSerialization.A.class.getCanonicalName());
+    JClassType b = typeOracle.getType(ManualSerialization.B.class.getCanonicalName());
+    stob.addRootType(logger, a);
+    SerializableTypeOracle sto = stob.build(logger);
+    assertInstantiable(sto, a);
+    assertNotInstantiableOrFieldSerializable(sto, b);
   }
 
   /**
-   * Tests that a missing gwt.typeArgs will not result in a failure. The set of
-   * types is not currently being checked.
+   * Tests that a raw List (missing gwt.typeArgs) will not result in a failure.
+   * The set of types is not currently being checked.
    */
   public void testMissingGwtTypeArgs() throws NotFoundException,
       UnableToCompleteException {
-    JClassType testServiceClass = typeOracle.getType(MissingGwtTypeArgs.class.getName());
+    TreeLogger logger = createLogger();
+
+    JClassType rawList = typeOracle.getType(List.class.getName());
     SerializableTypeOracleBuilder stob = new SerializableTypeOracleBuilder(
         logger, typeOracle);
-    stob.build(propertyOracle, testServiceClass);
+    stob.addRootType(logger, rawList);
+    SerializableTypeOracle sto = stob.build(logger);
+
+    // TODO: This test should should be updated to use a controlled type oracle
+    // then we can check the types.
+    assertNotInstantiable(sto, rawList);
   }
 
   /**
@@ -259,11 +739,14 @@
    */
   public void testNoSerializableTypes() throws NotFoundException,
       UnableToCompleteException {
-    JClassType testServiceClass = typeOracle.getType(NoSerializableTypes.class.getName());
+    TreeLogger logger = createLogger();
+
+    JClassType a = typeOracle.getType(NoSerializableTypes.A.class.getCanonicalName());
     SerializableTypeOracleBuilder stob = new SerializableTypeOracleBuilder(
         logger, typeOracle);
+    stob.addRootType(logger, a);
     try {
-      stob.build(propertyOracle, testServiceClass);
+      stob.build(logger);
       fail("Should have thrown an UnableToCompleteException");
     } catch (UnableToCompleteException ex) {
       // expected to get here
@@ -277,36 +760,36 @@
    */
   public void testNotAllSubtypesAreSerializable()
       throws UnableToCompleteException, NotFoundException {
+    TreeLogger logger = createLogger();
 
-    JClassType testServiceClass = typeOracle.getType(NotAllSubtypesAreSerializable.class.getName());
+    JClassType a = typeOracle.getType(NotAllSubtypesAreSerializable.A.class.getCanonicalName());
     SerializableTypeOracleBuilder stob = new SerializableTypeOracleBuilder(
         logger, typeOracle);
-    SerializableTypeOracle sto = stob.build(propertyOracle, testServiceClass);
+    stob.addRootType(logger, a);
+    SerializableTypeOracle sto = stob.build(logger);
 
     TypeInfo[] expected = new TypeInfo[] {
-        new TypeInfo(IncompatibleRemoteServiceException.class.getName(), true),
         new TypeInfo(
             makeSourceName(NotAllSubtypesAreSerializable.B.class.getName()),
             true),
         new TypeInfo(
             makeSourceName(NotAllSubtypesAreSerializable.D.class.getName()),
-            true), new TypeInfo(Exception.class.getName(), false),
-        new TypeInfo(RuntimeException.class.getName(), false),
-        new TypeInfo(String.class.getName(), true),
-        new TypeInfo(Throwable.class.getName(), false)};
+            true)};
     validateSTO(sto, expected);
   }
 
   /**
-   * Tests that a method signature which only has Object in its signature fails.
+   * Tests that Object[] is not instantiable.
    */
-  public void testObjectArrayInMethodSignature() throws NotFoundException,
-      UnableToCompleteException {
-    JClassType testServiceClass = typeOracle.getType(ObjectArrayInMethodSignature.class.getName());
+  public void testObjectArrayNotInstantiable() throws UnableToCompleteException {
+    TreeLogger logger = createLogger();
+
+    JArrayType objectArray = typeOracle.getArrayType(typeOracle.getJavaLangObject());
     SerializableTypeOracleBuilder stob = new SerializableTypeOracleBuilder(
         logger, typeOracle);
+    stob.addRootType(logger, objectArray);
     try {
-      stob.build(propertyOracle, testServiceClass);
+      stob.build(logger);
       fail("Expected UnableToCompleteException");
     } catch (UnableToCompleteException e) {
       // Should get here
@@ -314,15 +797,16 @@
   }
 
   /**
-   * Tests that a method signature which only has Object in its signature fails.
+   * Tests that Object is not considered instantiable.
    */
-  public void testObjectInMethodSignature() throws NotFoundException,
-      UnableToCompleteException {
-    JClassType testServiceClass = typeOracle.getType(ObjectInMethodSignature.class.getName());
+  public void testObjectNotInstantiable() throws UnableToCompleteException {
+    TreeLogger logger = createLogger();
+
     SerializableTypeOracleBuilder stob = new SerializableTypeOracleBuilder(
         logger, typeOracle);
+    stob.addRootType(logger, typeOracle.getJavaLangObject());
     try {
-      stob.build(propertyOracle, testServiceClass);
+      stob.build(logger);
       fail("Expected UnableToCompleteException");
     } catch (UnableToCompleteException e) {
       // Should get here
@@ -335,15 +819,354 @@
    */
   public void testOnlyAbstractSerializableTypes()
       throws UnableToCompleteException, NotFoundException {
+    TreeLogger logger = createLogger();
 
-    JClassType testServiceClass = typeOracle.getType(AbstractSerializableTypes.class.getName());
     SerializableTypeOracleBuilder stob = new SerializableTypeOracleBuilder(
         logger, typeOracle);
+    stob.addRootType(
+        logger,
+        typeOracle.getType(AbstractSerializableTypes.IFoo.class.getCanonicalName()));
+    stob.addRootType(
+        logger,
+        typeOracle.getType(AbstractSerializableTypes.AbstractClass.class.getCanonicalName()));
+
     try {
-      stob.build(propertyOracle, testServiceClass);
+      stob.build(logger);
       fail("Expected UnableToCompleteException");
     } catch (UnableToCompleteException e) {
       // Should get here
     }
   }
+
+  /**
+   * Tests that raw type with type parameters that are instantiable are
+   * themselves instantiable.
+   * 
+   * @throws UnableToCompleteException
+   * @throws NotFoundException
+   */
+  public void testRawTypes() throws UnableToCompleteException,
+      NotFoundException {
+    Set<CompilationUnit> units = new HashSet<CompilationUnit>();
+    addStandardClasses(units);
+
+    {
+      StringBuilder code = new StringBuilder();
+      code.append("import java.io.Serializable;\n");
+      code.append("public class A<T extends SerializableClass> implements Serializable {\n");
+      code.append("  T x;\n");
+      code.append("}\n");
+      units.add(createMockCompilationUnit("A", code));
+    }
+
+    {
+      StringBuilder code = new StringBuilder();
+      code.append("import java.io.Serializable;\n");
+      code.append("public class SerializableClass implements Serializable {\n");
+      code.append("}\n");
+      units.add(createMockCompilationUnit("SerializableClass", code));
+    }
+
+    TreeLogger logger = createLogger();
+    TypeOracle to = TypeOracleTestingUtils.buildTypeOracle(logger, units);
+
+    JGenericType a = to.getType("A").isGenericType();
+    JRawType rawA = a.getRawType();
+
+    JClassType serializableClass = to.getType("SerializableClass");
+
+    SerializableTypeOracleBuilder sob = new SerializableTypeOracleBuilder(
+        logger, to);
+    sob.addRootType(logger, rawA);
+    SerializableTypeOracle so = sob.build(logger);
+
+    assertInstantiable(so, a.getRawType());
+    assertSerializableTypes(so, rawA, serializableClass);
+  }
+
+  /*
+   * Tests the isAssignable test for deciding whether a subclass should be
+   * pulled in.
+   */
+  public void testSubclassIsAssignable() throws UnableToCompleteException,
+      NotFoundException {
+    Set<CompilationUnit> units = new HashSet<CompilationUnit>();
+    addStandardClasses(units);
+
+    {
+      StringBuilder code = new StringBuilder();
+      code.append("import java.io.Serializable;\n");
+      code.append("public class A<T> implements Serializable {\n");
+      code.append("}\n");
+      units.add(createMockCompilationUnit("A", code));
+    }
+
+    {
+      StringBuilder code = new StringBuilder();
+      code.append("import java.io.Serializable;\n");
+      code.append("public class B extends A<String> implements Serializable {\n");
+      code.append("  Object o;\n");
+      code.append("}\n");
+      units.add(createMockCompilationUnit("B", code));
+    }
+
+    {
+      StringBuilder code = new StringBuilder();
+      code.append("import java.io.Serializable;\n");
+      code.append("public class C extends A<Ser> implements Serializable {\n");
+      // TODO: rejecting Ser requires a better pruner in STOB
+      // code.append(" Ser ser;\n");
+      code.append("}\n");
+      units.add(createMockCompilationUnit("C", code));
+    }
+
+    {
+      StringBuilder code = new StringBuilder();
+      code.append("import java.io.Serializable;\n");
+      code.append("public class Ser implements Serializable {\n");
+      code.append("}\n");
+      units.add(createMockCompilationUnit("Ser", code));
+    }
+
+    TreeLogger logger = createLogger();
+    TypeOracle to = TypeOracleTestingUtils.buildTypeOracle(logger, units);
+
+    JGenericType a = to.getType("A").isGenericType();
+    JClassType b = to.getType("B");
+    JClassType c = to.getType("C");
+    JClassType ser = to.getType("Ser");
+
+    JClassType javaLangString = to.getType(String.class.getName());
+    JParameterizedType aOfString = to.getParameterizedType(a,
+        new JClassType[] {javaLangString});
+    SerializableTypeOracleBuilder stob = new SerializableTypeOracleBuilder(
+        logger, to);
+    stob.addRootType(logger, aOfString);
+
+    assertTrue(stob.getTypeParameterExposure(a, 0) < 0);
+
+    SerializableTypeOracle so = stob.build(logger);
+
+    assertSerializableTypes(so, a.getRawType());
+
+    assertInstantiable(so, a.getRawType());
+    assertNotInstantiableOrFieldSerializable(so, b);
+    assertNotInstantiableOrFieldSerializable(so, c);
+    assertNotInstantiableOrFieldSerializable(so, javaLangString);
+    assertNotInstantiableOrFieldSerializable(so, ser);
+  }
+
+  /**
+   * Tests subtypes that introduce new instantiable type parameters.
+   * 
+   * @throws UnableToCompleteException
+   * @throws NotFoundException
+   */
+  public void testSubclassWithNewInstantiableTypeParameters()
+      throws UnableToCompleteException, NotFoundException {
+    Set<CompilationUnit> units = new HashSet<CompilationUnit>();
+    addStandardClasses(units);
+
+    {
+      StringBuilder code = new StringBuilder();
+      code.append("import java.io.Serializable;\n");
+      code.append("public class A implements Serializable {\n");
+      code.append("}\n");
+      units.add(createMockCompilationUnit("A", code));
+    }
+
+    {
+      StringBuilder code = new StringBuilder();
+      code.append("import java.io.Serializable;\n");
+      code.append("public class B<T extends C> extends A {\n");
+      code.append("  T c;\n");
+      code.append("}\n");
+      units.add(createMockCompilationUnit("B", code));
+    }
+
+    {
+      StringBuilder code = new StringBuilder();
+      code.append("import java.io.Serializable;\n");
+      code.append("public class C implements Serializable {\n");
+      code.append("}\n");
+      units.add(createMockCompilationUnit("C", code));
+    }
+
+    TreeLogger logger = createLogger();
+    TypeOracle to = TypeOracleTestingUtils.buildTypeOracle(logger, units);
+
+    JClassType a = to.getType("A");
+    JRawType rawB = to.getType("B").isGenericType().getRawType();
+    JClassType c = to.getType("C");
+
+    SerializableTypeOracleBuilder sob = new SerializableTypeOracleBuilder(
+        logger, to);
+    sob.addRootType(logger, a);
+    SerializableTypeOracle so = sob.build(logger);
+
+    assertInstantiable(so, a);
+    assertSerializableTypes(so, a, rawB, c);
+  }
+
+  /**
+   * Tests subtypes that introduce new uninstantiable type parameters as
+   * compared to an implemented interface, where the root type is the interface.
+   * 
+   * @throws UnableToCompleteException
+   * @throws NotFoundException
+   */
+  public void testSubclassWithNewTypeParameterComparedToAnImplementedInterface()
+      throws UnableToCompleteException, NotFoundException {
+    Set<CompilationUnit> units = new HashSet<CompilationUnit>();
+    addStandardClasses(units);
+
+    {
+      StringBuilder code = new StringBuilder();
+      code.append("import java.io.Serializable;\n");
+      code.append("public interface Intf<T> extends Serializable {\n");
+      code.append("}\n");
+      units.add(createMockCompilationUnit("Intf", code));
+    }
+
+    {
+      StringBuilder code = new StringBuilder();
+      code.append("import java.io.Serializable;\n");
+      code.append("public class Bar<T> implements Serializable {\n");
+      code.append("}\n");
+      units.add(createMockCompilationUnit("Bar", code));
+    }
+
+    {
+      StringBuilder code = new StringBuilder();
+      code.append("import java.io.Serializable;\n");
+      code.append("public class Foo<T extends Ser> extends Bar<T> implements Intf<String> {\n");
+      code.append("  T x;\n");
+      code.append("}\n");
+      units.add(createMockCompilationUnit("Foo", code));
+    }
+
+    {
+      StringBuilder code = new StringBuilder();
+      code.append("import java.io.Serializable;\n");
+      code.append("public class Ser implements Serializable {\n");
+      code.append("}\n");
+      units.add(createMockCompilationUnit("Ser", code));
+    }
+
+    TreeLogger logger = createLogger();
+    TypeOracle to = TypeOracleTestingUtils.buildTypeOracle(logger, units);
+
+    JGenericType intf = to.getType("Intf").isGenericType();
+    JClassType foo = to.getType("Foo");
+    JClassType bar = to.getType("Bar");
+    JClassType intfOfString = to.getParameterizedType(intf,
+        new JClassType[] {to.getType(String.class.getName())});
+    JClassType ser = to.getType("Ser");
+
+    SerializableTypeOracleBuilder sob = new SerializableTypeOracleBuilder(
+        logger, to);
+    sob.addRootType(logger, intfOfString);
+    SerializableTypeOracle so = sob.build(logger);
+
+    /*
+     * TODO(spoon): should also check that Intf<String> has instantiable
+     * subclasses; currently the APIs for STOB and STO do not make this possible
+     * to test
+     */
+    assertInstantiable(so, ser);
+    assertSerializableTypes(so, foo.getErasedType(), bar.getErasedType(),
+        ser.getErasedType());
+  }
+
+  /**
+   * Tests subtypes that introduce new uninstantiable type parameters.
+   * 
+   * @throws UnableToCompleteException
+   * @throws NotFoundException
+   */
+  public void testSubclassWithNewUninstantiableTypeParameters()
+      throws UnableToCompleteException, NotFoundException {
+    Set<CompilationUnit> units = new HashSet<CompilationUnit>();
+    addStandardClasses(units);
+
+    {
+      StringBuilder code = new StringBuilder();
+      code.append("import java.io.Serializable;\n");
+      code.append("public class A implements Serializable {\n");
+      code.append("}\n");
+      units.add(createMockCompilationUnit("A", code));
+    }
+
+    {
+      StringBuilder code = new StringBuilder();
+      code.append("import java.io.Serializable;\n");
+      code.append("public class B<T> extends A {\n");
+      code.append("  T x;\n");
+      code.append("}\n");
+      units.add(createMockCompilationUnit("B", code));
+    }
+
+    TreeLogger logger = createLogger();
+    TypeOracle to = TypeOracleTestingUtils.buildTypeOracle(logger, units);
+
+    JClassType a = to.getType("A");
+
+    SerializableTypeOracleBuilder sob = new SerializableTypeOracleBuilder(
+        logger, to);
+    sob.addRootType(logger, a);
+    SerializableTypeOracle so = sob.build(logger);
+
+    assertInstantiable(so, a);
+    assertSerializableTypes(so, a);
+  }
+
+  /**
+   * Tests root types that have type parameters.
+   * 
+   * @throws UnableToCompleteException
+   * @throws NotFoundException
+   */
+  public void testTypeParametersInRootTypes() throws UnableToCompleteException,
+      NotFoundException {
+    Set<CompilationUnit> units = new HashSet<CompilationUnit>();
+    addStandardClasses(units);
+
+    {
+      StringBuilder code = new StringBuilder();
+      code.append("import java.io.Serializable;\n");
+      code.append("public class A<T> implements Serializable {\n");
+      code.append("  T t;\n");
+      code.append("}\n");
+      units.add(createMockCompilationUnit("A", code));
+    }
+
+    {
+      StringBuilder code = new StringBuilder();
+      code.append("import java.io.Serializable;\n");
+      code.append("public class B implements Serializable {\n");
+      code.append("}\n");
+      units.add(createMockCompilationUnit("B", code));
+    }
+
+    TreeLogger logger = createLogger();
+    TypeOracle to = TypeOracleTestingUtils.buildTypeOracle(logger, units);
+
+    JGenericType a = to.getType("A").isGenericType();
+    JRawType rawA = a.getRawType();
+    JClassType b = to.getType("B");
+
+    JTypeParameter syntheticTypeParam = new JTypeParameter("U", 0);
+    syntheticTypeParam.setBounds(new JClassType[] {b});
+
+    JParameterizedType parameterizedType = to.getParameterizedType(a,
+        new JClassType[] {syntheticTypeParam});
+    SerializableTypeOracleBuilder sob = new SerializableTypeOracleBuilder(
+        logger, to);
+    sob.addRootType(logger, parameterizedType);
+    SerializableTypeOracle so = sob.build(logger);
+
+    assertInstantiable(so, rawA);
+    assertInstantiable(so, b);
+    assertSerializableTypes(so, rawA, b);
+  }
 }