This patch addresses the following problems with the TypeOracle:

JParamterizedTypes we reporting too many subtypes if the JParameterizedType was constructed before its corresponding generic type was completely contructed.  Changed to lazy initialization of the substitution map.  Also updated the isAssignableFrom and getSubtype logic.

TypeOracle was throwning an NPE if gwt.typeArgs were applied to a type that was not the raw version of a generic type.  This has been changed to an error and logged accordingly.

TypeOracle used to report all gwt.typeArgs parsing errors as generic could-not-find-type error messages even though more specific failure information was avaiable.  The more detailed error messages are now logged to the shell.

Patch by: mmendez
Review by: scottb (desk check)

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@1543 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/core/ext/typeinfo/JParameterizedType.java b/dev/core/src/com/google/gwt/core/ext/typeinfo/JParameterizedType.java
index ad0a18f..2e1d342 100644
--- a/dev/core/src/com/google/gwt/core/ext/typeinfo/JParameterizedType.java
+++ b/dev/core/src/com/google/gwt/core/ext/typeinfo/JParameterizedType.java
@@ -27,16 +27,40 @@
  * Represents a parameterized type in a declaration.
  */
 public class JParameterizedType extends JDelegatingClassType {
+  private static boolean areTypeArgsAssignableFrom(JClassType[] myTypeArgs,
+      JClassType[] otherTypeArgs) {
+    assert (myTypeArgs.length <= otherTypeArgs.length);
+
+    for (int i = 0; i < myTypeArgs.length; ++i) {
+      JClassType myTypeArg = myTypeArgs[i];
+      JClassType otherTypeArg = otherTypeArgs[i];
+
+      if (myTypeArg.isWildcard() == null) {
+        // myTypeArg needs to be a wildcard or we cannot be assignable.
+        return false;
+      }
+
+      if (!myTypeArg.isAssignableFrom(otherTypeArg)) {
+        return false;
+      }
+    }
+
+    return true;
+  }
+
   /**
    * Create a parameterized type along with any necessary enclosing
    * parameterized types. Enclosing parameterized types are necessary when the
    * base type is a non-static member and the enclosing type is also generic.
    */
   private static JParameterizedType createParameterizedTypeRecursive(
-      JGenericType baseType, Map<JClassType, JClassType> substitutionMap) {
-    JClassType enclosingType = null;
+      JGenericType baseType, Map<JTypeParameter, JClassType> substitutionMap) {
+    JClassType enclosingType = baseType.getEnclosingType();
     if (baseType.isMemberType() && !baseType.isStatic()) {
-      JGenericType isGenericEnclosingType = baseType.getEnclosingType().isGenericType();
+      // This base type is a non-static generic type so we build the necessary
+      // enclosing parameterized type and update the enclosing type to be 
+      // a parameterized type.
+      JGenericType isGenericEnclosingType = enclosingType.isGenericType();
       if (isGenericEnclosingType != null) {
         enclosingType = createParameterizedTypeRecursive(
             isGenericEnclosingType, substitutionMap);
@@ -58,7 +82,6 @@
       newTypeArgs[i] = newTypeArg;
     }
 
-    // TODO: this is wrong if the generic type is a non-static inner class.
     JParameterizedType parameterizedType = oracle.getParameterizedType(
         baseType, enclosingType, newTypeArgs);
     return parameterizedType;
@@ -78,7 +101,7 @@
    * This map records the JClassType that should be used in place of a given
    * {@link JTypeParameter}.
    */
-  private final Map<JTypeParameter, JClassType> substitutionMap = new IdentityHashMap<JTypeParameter, JClassType>();
+  private Map<JTypeParameter, JClassType> lazySubstitutionMap;
 
   public JParameterizedType(JGenericType baseType, JClassType enclosingType,
       JClassType[] typeArgs) {
@@ -99,8 +122,6 @@
     this.typeArgs.addAll(typeArgsList);
     assert (typeArgsList.indexOf(null) == -1);
 
-    initializeTypeParameterSubstitutionMap();
-
     // NOTE: Can't perform substitutions until we are done building
   }
 
@@ -280,14 +301,16 @@
     // type
     JClassType[] genericSubtypes = getBaseType().getSubtypes();
     for (JClassType subtype : genericSubtypes) {
-      Set<JClassType> typeHierarchy = getFlattenedTypeHierarchy(subtype);
-
       // Could be a subtype depending on how it is substituted
-      Map<JClassType, JClassType> substitutions = new IdentityHashMap<JClassType, JClassType>();
-      if (isSubtype(subtype, typeHierarchy, substitutions, true)) {
+      Map<JTypeParameter, JClassType> substitutions = findSubtypeSubstitution(subtype);
+      if (substitutions != null) {
         JGenericType genericType = subtype.isGenericType();
         if (genericType != null) {
           subtype = createParameterizedTypeRecursive(genericType, substitutions);
+        } else {
+          // If this is not a generic type then there should not be any
+          // substitution.
+          assert (substitutions.isEmpty());
         }
 
         subtypeList.add(subtype);
@@ -307,7 +330,6 @@
       JGenericType baseType = getBaseType();
       JClassType superclass = baseType.getSuperclass();
       assert (superclass != null);
-
       lazySuperclass = superclass.getSubstitutedType(this);
     }
 
@@ -319,23 +341,54 @@
   }
 
   @Override
-  public boolean isAssignableFrom(JClassType possibleSubtype) {
-    if (possibleSubtype == this) {
+  public boolean isAssignableFrom(JClassType otherType) {
+    Set<JClassType> typeHierarchy = getFlattenedSuperTypeHierarchy(otherType);
+    if (typeHierarchy.contains(this)) {
+      // Done, appear explicitly in the supertype hierarchy
       return true;
     }
 
-    JRawType possibleRawSubtype = possibleSubtype.isRawType();
-    if (possibleRawSubtype != null) {
-      return getBaseType().isAssignableFrom(possibleRawSubtype.getBaseType());
+    // Check for implicit subtype
+    for (JClassType type : typeHierarchy) {
+      JGenericType isGeneric = type.isGenericType();
+      if (isGeneric != null) {
+        // TODO: Do we want to treat a generic type as a raw type?
+        type = isGeneric.getRawType();
+      }
+
+      JParameterizedType isParameterized = type.isParameterized();
+      if (isParameterized != null
+          && isParameterized.getBaseType() == getBaseType()) {
+        // parameters are not exact, need to test type arguments
+        assert (isParameterized != this);
+
+        return areTypeArgsAssignableFrom(this.getTypeArgs(),
+            isParameterized.getTypeArgs());
+      }
+
+      JRawType isRaw = type.isRawType();
+      if (isRaw != null && isRaw.getBaseType() == getBaseType()) {
+        // Parameterized types are assignable from their raw types
+        return true;
+      }
+
+      JWildcardType isWildcard = type.isWildcard();
+      if (isWildcard != null && isWildcard.isAssignableTo(this)) {
+        return true;
+      }
     }
 
-    Set<JClassType> typeHierarchy = getFlattenedTypeHierarchy(possibleSubtype);
-    return isSubtype(possibleSubtype, typeHierarchy,
-        new IdentityHashMap<JClassType, JClassType>(), false);
+    return false;
   }
 
   @Override
   public boolean isAssignableTo(JClassType possibleSupertype) {
+    JGenericType genericPossibleSupertype = possibleSupertype.isGenericType();
+    if (genericPossibleSupertype != null) {
+      // Checks for assignability to the generic type's raw type
+      return genericPossibleSupertype.getRawType().isAssignableFrom(this);
+    }
+
     return possibleSupertype.isAssignableFrom(this);
   }
 
@@ -401,6 +454,8 @@
 
   @Override
   JClassType getSubstitutedType(JParameterizedType parameterizedType) {
+    maybeInitializeTypeParameterSubstitutionMap();
+
     if (this == parameterizedType) {
       return this;
     }
@@ -420,7 +475,9 @@
    * {@link JTypeParameter} is returned.
    */
   JClassType getTypeParameterSubstitution(JTypeParameter typeParameter) {
-    JClassType substitute = substitutionMap.get(typeParameter);
+    maybeInitializeTypeParameterSubstitutionMap();
+
+    JClassType substitute = lazySubstitutionMap.get(typeParameter);
     if (substitute != null) {
       return substitute;
     }
@@ -446,9 +503,16 @@
 
   /**
    * Initialize a map of substitutions for {@link JTypeParameter}s to
-   * corresponding {@link JClassType}s.
+   * corresponding {@link JClassType}s. This can only be initialized after the
+   * {@link com.google.gwt.dev.jdt.TypeOracleBuilder TypeOracleBuilder} has
+   * fully resolved all of the {@link JClassType}s.
    */
-  void initializeTypeParameterSubstitutionMap() {
+  void maybeInitializeTypeParameterSubstitutionMap() {
+    if (lazySubstitutionMap != null) {
+      return;
+    }
+    lazySubstitutionMap = new IdentityHashMap<JTypeParameter, JClassType>();
+
     JParameterizedType currentParameterizedType = this;
 
     while (currentParameterizedType != null) {
@@ -457,7 +521,7 @@
       JClassType[] typeArguments = currentParameterizedType.getTypeArgs();
 
       for (JTypeParameter typeParameter : typeParameters) {
-        substitutionMap.put(typeParameter,
+        lazySubstitutionMap.put(typeParameter,
             typeArguments[typeParameter.getOrdinal()]);
       }
 
@@ -475,15 +539,85 @@
   }
 
   /**
-   * Returns the flattened view of the type hierarchy.
+   * Returns a map of substitutions that will make the subtype a proper subtype
+   * of this parameterized type. The map maybe empty in the case that it is
+   * already an exact subtype.
    */
-  private Set<JClassType> getFlattenedTypeHierarchy(JClassType type) {
+  private Map<JTypeParameter, JClassType> findSubtypeSubstitution(
+      JClassType subtype) {
+    Map<JTypeParameter, JClassType> substitutions = new IdentityHashMap<JTypeParameter, JClassType>();
+
+    // Get the supertype hierarchy. If this JParameterizedType exists
+    // exactly in this set we are done.
+    Set<JClassType> supertypeHierarchy = getFlattenedSuperTypeHierarchy(subtype);
+    if (supertypeHierarchy.contains(this)) {
+      return substitutions;
+    }
+
+    /*
+     * Try to find a parameterized supertype whose base type is the same as our
+     * own. Because that parameterized supertype might be made into ourself via
+     * substitution.
+     */
+    for (JClassType candidate : supertypeHierarchy) {
+      JParameterizedType parameterizedCandidate = candidate.isParameterized();
+      if (parameterizedCandidate == null) {
+        // If not parameterized then there is no substitution possible.
+        continue;
+      }
+
+      if (parameterizedCandidate.getBaseType() != getBaseType()) {
+        // This candidate be parameterized to us.
+        continue;
+      }
+
+      /*
+       * We have a parameterization of our base type. Now we need to see if it
+       * is possible to parameterize subtype such that candidate becomes
+       * equivalent to us.
+       */
+      JClassType[] candidateTypeArgs = parameterizedCandidate.getTypeArgs();
+      JClassType[] myTypeArgs = getTypeArgs();
+      for (int i = 0; i < myTypeArgs.length; ++i) {
+        JClassType otherTypeArg = candidateTypeArgs[i];
+        JClassType myTypeArg = myTypeArgs[i];
+
+        if (myTypeArg == otherTypeArg) {
+          // There are identical so there is no substitution that is needed.
+          continue;
+        }
+
+        JTypeParameter otherTypeParameter = otherTypeArg.isTypeParameter();
+        if (otherTypeParameter == null) {
+          // Not a type parameter and not equal so no substitution can make it
+          // equal.
+          return null;
+        }
+
+        if (!otherTypeParameter.isAssignableFrom(myTypeArg)) {
+          // Make sure that my type argument can be substituted for this type
+          // parameter.
+          return null;
+        }
+
+        substitutions.put(otherTypeParameter, myTypeArg);
+      }
+    }
+
+    // Legal substitution can be made and is record in substitutions.
+    return substitutions;
+  }
+
+  /**
+   * Returns the flattened view of the supertype hierarchy.
+   */
+  private Set<JClassType> getFlattenedSuperTypeHierarchy(JClassType type) {
     Set<JClassType> typesSeen = new HashSet<JClassType>();
-    getFlattenedTypeHierarchyRecursive(type, typesSeen);
+    getFlattenedSuperTypeHierarchyRecursive(type, typesSeen);
     return typesSeen;
   }
 
-  private void getFlattenedTypeHierarchyRecursive(JClassType type,
+  private void getFlattenedSuperTypeHierarchyRecursive(JClassType type,
       Set<JClassType> typesSeen) {
     if (typesSeen.contains(type)) {
       return;
@@ -493,79 +627,13 @@
     // Superclass
     JClassType superclass = type.getSuperclass();
     if (superclass != null) {
-      getFlattenedTypeHierarchyRecursive(superclass, typesSeen);
+      getFlattenedSuperTypeHierarchyRecursive(superclass, typesSeen);
     }
 
     // Check the interfaces
     JClassType[] intfs = type.getImplementedInterfaces();
     for (JClassType intf : intfs) {
-      getFlattenedTypeHierarchyRecursive(intf, typesSeen);
+      getFlattenedSuperTypeHierarchyRecursive(intf, typesSeen);
     }
   }
-
-  /**
-   * Look at the type hierarchy and see if we can find a parameterized type that
-   * has the same base type as this instance. If we find one then we check to
-   * see if the type arguments are compatible. If they are, then we record what
-   * the typeArgument needs to be replaced with in order to make it a proper
-   * subtype of this parameterized type.
-   */
-  private boolean isSubtype(JClassType subtype, Set<JClassType> typeHierarchy,
-      Map<JClassType, JClassType> substitutions, boolean lookForSubstitutions) {
-    if (typeHierarchy.contains(this)) {
-      return true;
-    }
-
-    for (JClassType type : typeHierarchy) {
-      JParameterizedType parameterizedType = type.isParameterized();
-      if (parameterizedType == null) {
-        continue;
-      }
-
-      if (parameterizedType.getBaseType() != getBaseType()) {
-        continue;
-      }
-
-      // Check the type arguments to see if they are compatible.
-      JClassType[] otherTypeArgs = parameterizedType.getTypeArgs();
-      JClassType[] myTypeArgs = getTypeArgs();
-      boolean validSubstitution = true;
-      for (int i = 0; i < myTypeArgs.length; ++i) {
-        JClassType otherTypeArg = otherTypeArgs[i];
-        JClassType myTypeArg = myTypeArgs[i];
-
-        validSubstitution = myTypeArg == otherTypeArg;
-        if (!validSubstitution) {
-          if (lookForSubstitutions) {
-            // Make sure that the other type argument is assignable from mine
-            validSubstitution = otherTypeArg.isAssignableFrom(myTypeArg);
-          } else {
-            // Looking for strict subtypes; only wildcards allow a non-exact
-            // match
-            JWildcardType isWildcard = myTypeArg.isWildcard();
-            if (isWildcard != null) {
-              validSubstitution = myTypeArg.isAssignableFrom(otherTypeArg);
-            }
-          }
-        }
-
-        if (!validSubstitution) {
-          break;
-        }
-
-        substitutions.put(otherTypeArg, myTypeArg);
-      }
-
-      if (validSubstitution) {
-        /*
-         * At this point we know that the type can be a subtype and we know the
-         * substitution to apply.
-         */
-        return true;
-      }
-    }
-
-    // Can't be a subtype regardless of substitution.
-    return false;
-  }
 }
\ No newline at end of file
diff --git a/dev/core/src/com/google/gwt/core/ext/typeinfo/JPrimitiveType.java b/dev/core/src/com/google/gwt/core/ext/typeinfo/JPrimitiveType.java
index 2d1e9ce..f07be8d 100644
--- a/dev/core/src/com/google/gwt/core/ext/typeinfo/JPrimitiveType.java
+++ b/dev/core/src/com/google/gwt/core/ext/typeinfo/JPrimitiveType.java
@@ -100,6 +100,11 @@
   }
 
   @Override
+  public JGenericType isGenericType() {
+    return null;
+  }
+
+  @Override
   public JClassType isInterface() {
     // intentional null
     return null;
diff --git a/dev/core/src/com/google/gwt/core/ext/typeinfo/JType.java b/dev/core/src/com/google/gwt/core/ext/typeinfo/JType.java
index c16d62c..f5d3cea 100644
--- a/dev/core/src/com/google/gwt/core/ext/typeinfo/JType.java
+++ b/dev/core/src/com/google/gwt/core/ext/typeinfo/JType.java
@@ -68,12 +68,16 @@
    */
   public abstract JEnumType isEnum();
 
+  // TODO: Rename this to isGeneric
+  public abstract JGenericType isGenericType();
+  
   public abstract JClassType isInterface();
 
   public abstract JParameterizedType isParameterized();
 
   public abstract JPrimitiveType isPrimitive();
 
+  // TODO: Rename this to isRaw
   public abstract JRawType isRawType();
 
   public JTypeParameter isTypeParameter() {
diff --git a/dev/core/src/com/google/gwt/core/ext/typeinfo/JWildcardType.java b/dev/core/src/com/google/gwt/core/ext/typeinfo/JWildcardType.java
index 580e966..e76ac2e 100644
--- a/dev/core/src/com/google/gwt/core/ext/typeinfo/JWildcardType.java
+++ b/dev/core/src/com/google/gwt/core/ext/typeinfo/JWildcardType.java
@@ -91,6 +91,13 @@
   }
 
   @Override
+  public boolean isAssignableTo(JClassType otherType) {
+    // TODO: This need to handle all possible subtypes of JClassType that could
+    // reach here...
+    return super.isAssignableTo(otherType);
+  }
+
+  @Override
   public JGenericType isGenericType() {
     return null;
   }
diff --git a/dev/core/src/com/google/gwt/core/ext/typeinfo/TypeOracle.java b/dev/core/src/com/google/gwt/core/ext/typeinfo/TypeOracle.java
index dab0a9e..5352f8c 100644
--- a/dev/core/src/com/google/gwt/core/ext/typeinfo/TypeOracle.java
+++ b/dev/core/src/com/google/gwt/core/ext/typeinfo/TypeOracle.java
@@ -324,11 +324,18 @@
    * @param typeArgs the type arguments bound to the specified generic type
    * @return a type object representing this particular binding of type
    *         arguments to the specified generic
+   * @throws IllegalArgumentException if the parameterization of a non-static
+   *           member type does not specify an enclosing type or if not enough
+   *           arguments were specified to parameterize the generic type
+   * @throws NullPointerException if genericType is <code>null</code>
    */
   public JParameterizedType getParameterizedType(JGenericType genericType,
       JClassType enclosingType, JClassType[] typeArgs) {
+    if (genericType == null) {
+      throw new NullPointerException("genericType");
+    }
 
-    if (genericType.isMemberType()) {
+    if (genericType.isMemberType() && !genericType.isStatic()) {
       if (genericType.getEnclosingType().isGenericType() != null
           && enclosingType.isParameterized() == null
           && enclosingType.isRawType() == null) {
@@ -337,6 +344,18 @@
       }
     }
 
+    JTypeParameter[] typeParameters = genericType.getTypeParameters();
+    if (typeArgs.length < typeParameters.length) {
+      throw new IllegalArgumentException(
+          "Not enough type arguments were specified to parameterize '"
+              + genericType.getParameterizedQualifiedSourceName() + "'");
+    } else {
+      /*
+       * TODO: Should WARN if we specify too many type arguments but we have no
+       * logger.
+       */
+    }
+
     // TODO: validate that the type arguments satisfy the generic type parameter
     // bounds if any were specified
 
@@ -375,6 +394,10 @@
    * @param typeArgs the type arguments bound to the specified generic type
    * @return a type object representing this particular binding of type
    *         arguments to the specified generic
+   * @throws IllegalArgumentException if the generic type is a non-static member
+   *           type or if not enough arguments were specified to parameterize
+   *           the generic type
+   * @throws NullPointerException if genericType is <code>null</code>
    */
   public JParameterizedType getParameterizedType(JGenericType genericType,
       JClassType[] typeArgs) {
@@ -823,6 +846,11 @@
             "Only classes and interface can be parameterized, so "
                 + rawType.getQualifiedSourceName()
                 + " cannot be used in this context");
+      } else if (rawType.isGenericType() == null) {
+        throw new BadTypeArgsException(
+            "'"
+                + rawType.getQualifiedSourceName()
+                + "' is not a generic type; only generic types can be parameterized");
       }
 
       // Resolve each type argument.
@@ -832,8 +860,7 @@
 
       // Intern this type.
       //
-      return getParameterizedType(rawType.isClassOrInterface().isGenericType(),
-          typeArgs);
+      return getParameterizedType(rawType.isGenericType(), typeArgs);
     }
 
     JType result = JPrimitiveType.valueOf(type);
@@ -846,7 +873,8 @@
       return result;
     }
 
-    throw new NotFoundException(type);
+    throw new NotFoundException("Unable to recognize '" + type
+        + "' as a type name (is it fully qualified?)");
   }
 
   private void parseTypeArgComponent(List<JClassType> typeArgList,
@@ -856,9 +884,10 @@
     if (typeArg.isPrimitive() != null) {
       // Cannot be primitive.
       //
-      throw new BadTypeArgsException("Type arguments cannot be primitive, so "
-          + typeArg.getQualifiedSourceName()
-          + " cannot be used in this context");
+      throw new BadTypeArgsException(
+          "Type arguments cannot be primitives, so '"
+              + typeArg.getQualifiedSourceName()
+              + "' cannot be used in this context");
     }
 
     typeArgList.add((JClassType) typeArg);
@@ -918,10 +947,11 @@
     JType parameterizedType;
     try {
       parameterizedType = parse(toParse);
+    } catch (IllegalArgumentException e) {
+      logger.log(TreeLogger.WARN, e.getMessage(), e);
+      throw new UnableToCompleteException();
     } catch (TypeOracleException e) {
-      String msg = "Unable to recognize '" + toParse
-          + "' as a type name (is it fully qualified?)";
-      logger.log(TreeLogger.WARN, msg, null);
+      logger.log(TreeLogger.WARN, e.getMessage(), e);
       throw new UnableToCompleteException();
     }
     return parameterizedType;