TypeOracle.getParameterizedType would erroneously throw an IllegalArgumentException for a nested generic type if if its parameterized form was requested before the nested generic type was fully resolved.

Found by: ispeters
Review by: spoon

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@2119 8db76d5a-ed1c-0410-87a9-c151d255dfc7
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 a4a53c3..8105085 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
@@ -339,8 +339,16 @@
       if (genericType.getEnclosingType().isGenericType() != null
           && enclosingType.isParameterized() == null
           && enclosingType.isRawType() == null) {
-        throw new IllegalArgumentException(
-            "enclosingType needs to be a parameterized type or a raw type");
+        /*
+         * If the generic type is a non-static member type enclosed by a generic
+         * type then the enclosing type for this parameterized type should be
+         * raw or parameterized.
+         */
+        throw new IllegalArgumentException("Generic type '"
+            + genericType.getParameterizedQualifiedSourceName()
+            + "' is a non-static member type, but the enclosing type '"
+            + enclosingType.getQualifiedSourceName() 
+            + "' is not a parameterized or raw type");
       }
     }
 
diff --git a/dev/core/src/com/google/gwt/dev/jdt/TypeOracleBuilder.java b/dev/core/src/com/google/gwt/dev/jdt/TypeOracleBuilder.java
index cfc615c..77592a6 100644
--- a/dev/core/src/com/google/gwt/dev/jdt/TypeOracleBuilder.java
+++ b/dev/core/src/com/google/gwt/dev/jdt/TypeOracleBuilder.java
@@ -101,6 +101,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.TreeMap;
 import java.util.regex.Pattern;
 
 /**
@@ -526,7 +527,7 @@
 
     // Build a list that makes it easy to remove problems.
     //
-    final Map<String, CompilationUnitDeclaration> cudsByFileName = new HashMap<String, CompilationUnitDeclaration>();
+    final Map<String, CompilationUnitDeclaration> cudsByFileName = new TreeMap<String, CompilationUnitDeclaration>();
     for (int i = 0; i < cuds.length; i++) {
       CompilationUnitDeclaration cud = cuds[i];
       char[] location = cud.getFileName();
@@ -1042,6 +1043,12 @@
           jclassIsIntf);
     }
 
+    /*
+     * Add modifiers since these are needed for
+     * TypeOracle.getParameterizedType's error checking code.
+     */
+    jrealClassType.addModifierBits(Shared.bindingToModifierBits(binding));
+
     cacheManager.setTypeForBinding(binding, jrealClassType);
   }
 
@@ -1598,9 +1605,10 @@
       return false;
     }
 
-    // Add modifiers.
-    //
-    jtype.addModifierBits(Shared.bindingToModifierBits(clazz.binding));
+    /*
+     * Modifiers were added during processType since getParameterizedType
+     * depends on them being set.
+     */
 
     // Try to resolve annotations, ignore any that fail.
     Map<Class<? extends java.lang.annotation.Annotation>, java.lang.annotation.Annotation> declaredAnnotations = newAnnotationMap();
diff --git a/dev/core/test/com/google/gwt/dev/typeinfo/test/TypeOracleBuilderTest.java b/dev/core/test/com/google/gwt/dev/typeinfo/test/TypeOracleBuilderTest.java
index 65d709e..608c74d 100644
--- a/dev/core/test/com/google/gwt/dev/typeinfo/test/TypeOracleBuilderTest.java
+++ b/dev/core/test/com/google/gwt/dev/typeinfo/test/TypeOracleBuilderTest.java
@@ -44,13 +44,13 @@
     private final String[] typeNames;
 
     /**
-     * Creates a new {@code TestCup} with several types. The first type in 
+     * Creates a new {@code TestCup} with several types. The first type in
      * {@code typeNames} is considered to be the main type.
-     *
+     * 
      * @param packageName the package for the types in this {@code TestCup}
-     * @param typeNames the types for this {@code TestCup}. Must have
-     * at least one type. The first type is considered to be the main type
-     * for this {@code TestCup}. 
+     * @param typeNames the types for this {@code TestCup}. Must have at least
+     *          one type. The first type is considered to be the main type for
+     *          this {@code TestCup}.
      */
     public TestCup(String packageName, String... typeNames) {
       this.packageName = packageName;
@@ -84,6 +84,7 @@
     public String[] getTypeNames() {
       return typeNames;
     }
+
     public boolean isTransient() {
       return true;
     }
@@ -247,6 +248,51 @@
     }
   };
 
+  protected TestCup CU_ReferencesParameterizedTypeBeforeItsGenericFormHasBeenProcessed = new TestCup(
+      "parameterized.type.build.dependency", "Class0") {
+    @Override
+    public void check(JClassType type) throws NotFoundException {
+    }
+
+    public char[] getSource() throws UnableToCompleteException {
+      StringBuilder sb = new StringBuilder();
+      sb.append("package parameterized.type.build.dependency;\n");
+      sb.append("public class Class0 implements Class2.Inner<Object> {\n");
+      sb.append("}\n");
+      return sb.toString().toCharArray();
+    }
+  };
+
+  protected TestCup CU_DeclaresInnerGenericType = new TestCup(
+      "parameterized.type.build.dependency", "Class1") {
+    @Override
+    public void check(JClassType type) throws NotFoundException {
+    }
+
+    public char[] getSource() throws UnableToCompleteException {
+      StringBuilder sb = new StringBuilder();
+      sb.append("package parameterized.type.build.dependency;\n");
+      sb.append("public class Class1<T> {\n");
+      sb.append("  public interface Inner<T> {}\n");
+      sb.append("}\n");
+      return sb.toString().toCharArray();
+    }
+  };
+
+  protected TestCup CU_ExtendsParameterizedType = new TestCup(
+      "parameterized.type.build.dependency", "Class2") {
+    @Override
+    public void check(JClassType type) throws NotFoundException {
+    }
+
+    public char[] getSource() throws UnableToCompleteException {
+      StringBuilder sb = new StringBuilder();
+      sb.append("package parameterized.type.build.dependency;\n");
+      sb.append("public class Class2 extends Class1<Object> {}\n");
+      return sb.toString().toCharArray();
+    }
+  };
+
   protected TestCup CU_FieldsAndTypes = new TestCup("test", "Fields",
       "SomeType") {
     public void check(JClassType type) throws NotFoundException {
@@ -557,8 +603,7 @@
     }
   };
 
-  protected TestCup CU_OuterInner = new TestCup("test", "Outer",
-      "Outer.Inner") {
+  protected TestCup CU_OuterInner = new TestCup("test", "Outer", "Outer.Inner") {
 
     public void check(JClassType type) {
       final String name = type.getSimpleSourceName();
@@ -728,6 +773,27 @@
     checkTypes(types);
   }
 
+  /**
+   * Tests that we can build nested parameterized types even if that happens
+   * while the type oracle is being built. This test assumes that
+   * CU_ReferencesParameterizedTypeBeforeItsGenericFormHasBeenProcessed will
+   * cause a parameterized form of CU_DeclaresInnerGenericType to be created
+   * before the type oracle has had a chance to resolve
+   * CU_DeclaresInnerGenericType.
+   */
+  public void testParameterizedTypeBuildDependencies()
+      throws UnableToCompleteException, NotFoundException {
+    TypeOracleBuilder tiob = createTypeInfoOracleBuilder();
+
+    tiob.addCompilationUnit(CU_ReferencesParameterizedTypeBeforeItsGenericFormHasBeenProcessed);
+    tiob.addCompilationUnit(CU_ExtendsParameterizedType);
+    tiob.addCompilationUnit(CU_DeclaresInnerGenericType);
+    tiob.addCompilationUnit(CU_Object);
+
+    TypeOracle tio = tiob.build(createTreeLogger());
+    assertNull(tio.findType("test.parameterizedtype.build.dependencies.Class2"));
+  }
+
   public void testSyntaxErrors() throws TypeOracleException,
       UnableToCompleteException {
     TypeOracleBuilder tiob = createTypeInfoOracleBuilder();