Add useful hooks into GWT to allow other tools to parse and analyze Java code.
Review at http://gwt-code-reviews.appspot.com/631801


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@8273 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/javac/CompilationStateBuilder.java b/dev/core/src/com/google/gwt/dev/javac/CompilationStateBuilder.java
index eb4f340..da0035b 100644
--- a/dev/core/src/com/google/gwt/dev/javac/CompilationStateBuilder.java
+++ b/dev/core/src/com/google/gwt/dev/javac/CompilationStateBuilder.java
@@ -18,6 +18,7 @@
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.dev.javac.CompilationUnitBuilder.GeneratedCompilationUnitBuilder;
 import com.google.gwt.dev.javac.CompilationUnitBuilder.ResourceCompilationUnitBuilder;
+import com.google.gwt.dev.javac.JdtCompiler.AdditionalTypeProviderDelegate;
 import com.google.gwt.dev.javac.JdtCompiler.UnitProcessor;
 import com.google.gwt.dev.js.ast.JsProgram;
 import com.google.gwt.dev.resource.Resource;
@@ -122,6 +123,16 @@
     private transient Map<String, CompilationUnit> resultUnits;
     private final Set<ContentId> validDependencies = new HashSet<ContentId>();
 
+    /**
+     * Provides compilation unit for unknown classes encountered by the
+     * compiler if desired.
+     */
+    private AdditionalTypeProviderDelegate delegate;
+
+    public CompileMoreLater(AdditionalTypeProviderDelegate delegate) {
+      compiler.setAdditionalTypeProviderDelegate(delegate);
+    }
+    
     public Collection<CompilationUnit> addGeneratedTypes(TreeLogger logger,
         Collection<GeneratedUnit> generatedUnits) {
       return doBuildGeneratedTypes(logger, generatedUnits, this);
@@ -179,7 +190,12 @@
 
   public static CompilationState buildFrom(TreeLogger logger,
       Set<Resource> resources) {
-    return instance.doBuildFrom(logger, resources);
+    return instance.doBuildFrom(logger, resources, null);
+  }
+
+  public static CompilationState buildFrom(TreeLogger logger,
+      Set<Resource> resources, AdditionalTypeProviderDelegate delegate) {
+    return instance.doBuildFrom(logger, resources, delegate);
   }
 
   public static CompilationStateBuilder get() {
@@ -244,6 +260,15 @@
    */
   public synchronized CompilationState doBuildFrom(TreeLogger logger,
       Set<Resource> resources) {
+    return doBuildFrom(logger, resources, null);
+  }
+
+  /**
+   * Build a new compilation state from a source oracle.  Allow the caller to specify 
+   * a compiler delegate that will handle undefined names. 
+   */
+  public synchronized CompilationState doBuildFrom(TreeLogger logger,
+      Set<Resource> resources, AdditionalTypeProviderDelegate compilerDelegate) {
     Map<String, CompilationUnit> resultUnits = new HashMap<String, CompilationUnit>();
 
     // For each incoming Java source file...
@@ -265,7 +290,7 @@
         resultUnits.values());
 
     // Compile everything else.
-    CompileMoreLater compileMoreLater = new CompileMoreLater();
+    CompileMoreLater compileMoreLater = new CompileMoreLater(compilerDelegate);
     List<CompilationUnitBuilder> builders = new ArrayList<CompilationUnitBuilder>();
     for (Resource resource : resources) {
       String typeName = Shared.toTypeName(resource.getPath());
diff --git a/dev/core/src/com/google/gwt/dev/javac/JdtCompiler.java b/dev/core/src/com/google/gwt/dev/javac/JdtCompiler.java
index 8116586..40110f4 100644
--- a/dev/core/src/com/google/gwt/dev/javac/JdtCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/javac/JdtCompiler.java
@@ -65,6 +65,32 @@
  * Manages the process of compiling {@link CompilationUnit}s.
  */
 public class JdtCompiler {
+  /**
+   * Provides hooks for changing the behavior of the JdtCompiler when unknown
+   * types are encountered during compilation.  Currently
+   * used for allowing external tools to provide source lazily when
+   * undefined references appear.
+   */
+  public static interface AdditionalTypeProviderDelegate {
+    /**
+     * Checks for additional packages which may contain additional compilation
+     * units.
+     *
+     * @param slashedPackageName the '/' separated name of the package to find
+     * @return <code>true</code> if such a package exists
+     */
+    public boolean doFindAdditionalPackage(String slashedPackageName);
+
+    /**
+     * Finds a new compilation unit on-the-fly for the requested type, if there
+     * is an alternate mechanism for doing so.
+     *
+     * @param binaryName the binary name of the requested type
+     * @return a unit answering the name, or <code>null</code> if no such unit
+     *         can be created
+     */
+    public GeneratedUnit doFindAdditionalType(String binaryName);
+  }
 
   /**
    * A default processor that simply collects build units.
@@ -235,6 +261,14 @@
       if (isPackage(binaryName)) {
         return null;
       }
+      if (additionalTypeProviderDelegate != null) {
+        GeneratedUnit unit = additionalTypeProviderDelegate.doFindAdditionalType(binaryName);
+        if (unit != null) {
+          CompilationUnitBuilder b = CompilationUnitBuilder.create(unit);
+          Adapter a = new Adapter(b);
+          return new NameEnvironmentAnswer(a, null);
+        }
+      }
       try {
         URL resource = getClassLoader().getResource(binaryName + ".class");
         if (resource != null) {
@@ -272,6 +306,11 @@
         return false;
       }
       String resourceName = slashedPackageName + '/';
+      if ((additionalTypeProviderDelegate != null
+          && additionalTypeProviderDelegate.doFindAdditionalPackage(slashedPackageName))) {
+        addPackages(slashedPackageName);
+        return true;
+      }
       if (getClassLoader().getResource(resourceName) != null) {
         addPackages(slashedPackageName);
         return true;
@@ -385,6 +424,8 @@
 
   private final UnitProcessor processor;
 
+  private AdditionalTypeProviderDelegate additionalTypeProviderDelegate;
+
   public JdtCompiler(UnitProcessor processor) {
     this.processor = processor;
   }
@@ -474,6 +515,10 @@
     return resolveType(compilerImpl.lookupEnvironment, typeName);
   }
 
+  public void setAdditionalTypeProviderDelegate(AdditionalTypeProviderDelegate newDelegate) {
+    additionalTypeProviderDelegate = newDelegate;
+  }
+
   private void addBinaryTypes(Collection<CompiledClass> compiledClasses) {
     for (CompiledClass cc : compiledClasses) {
       binaryTypes.put(cc.getInternalName(), cc);
diff --git a/dev/core/src/com/google/gwt/dev/jjs/UnifiedAst.java b/dev/core/src/com/google/gwt/dev/jjs/UnifiedAst.java
index d0c521c7..2a91521 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/UnifiedAst.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/UnifiedAst.java
@@ -41,7 +41,7 @@
   /**
    * Encapsulates the combined programs.
    */
-  static final class AST implements Serializable {
+  public static final class AST implements Serializable {
     private final JProgram jProgram;
     private final JsProgram jsProgram;
 
@@ -50,7 +50,7 @@
       this.jsProgram = jsProgram;
     }
 
-    JProgram getJProgram() {
+    public JProgram getJProgram() {
       return jProgram;
     }
 
@@ -124,6 +124,28 @@
   }
 
   /**
+   * Return the current AST so that clients can explicitly walk the
+   * Java or JavaScript parse trees.
+   *
+   * @return the current AST object holding the Java and JavaScript trees.
+   */
+  public AST getFreshAst() {
+    synchronized (myLockObject) {
+      if (initialAst != null) {
+        AST result = initialAst;
+        initialAst = null;
+        return result;
+      } else {
+        if (serializedAstToken < 0) {
+          throw new IllegalStateException(
+              "No serialized AST was cached and AST was already consumed.");
+        }
+        return diskCache.readObject(serializedAstToken, AST.class);
+      }
+    }
+  }
+
+  /**
    * Returns the active set of JJS options associated with this compile.
    */
   public JJSOptions getOptions() {
@@ -149,22 +171,6 @@
     }
   }
 
-  AST getFreshAst() {
-    synchronized (myLockObject) {
-      if (initialAst != null) {
-        AST result = initialAst;
-        initialAst = null;
-        return result;
-      } else {
-        if (serializedAstToken < 0) {
-          throw new IllegalStateException(
-              "No serialized AST was cached and AST was already consumed.");
-        }
-        return diskCache.readObject(serializedAstToken, AST.class);
-      }
-    }
-  }
-
   /**
    * Re-initialize lock object; copy serialized AST straight to cache.
    */
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/AdditionalTypeProviderDelegateTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/AdditionalTypeProviderDelegateTest.java
new file mode 100644
index 0000000..7597cac
--- /dev/null
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/AdditionalTypeProviderDelegateTest.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright 2010 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.dev.jjs.impl;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.dev.javac.GeneratedUnit;
+import com.google.gwt.dev.javac.JdtCompiler.AdditionalTypeProviderDelegate;
+import com.google.gwt.dev.javac.impl.MockJavaResource;
+import com.google.gwt.dev.jjs.ast.JAnnotation;
+import com.google.gwt.dev.jjs.ast.JClassLiteral;
+import com.google.gwt.dev.jjs.ast.JDeclaredType;
+import com.google.gwt.dev.jjs.ast.JField;
+import com.google.gwt.dev.jjs.ast.JLocal;
+import com.google.gwt.dev.jjs.ast.JMethod;
+import com.google.gwt.dev.jjs.ast.JParameter;
+import com.google.gwt.dev.jjs.ast.JProgram;
+import com.google.gwt.dev.jjs.ast.JAnnotation.Property;
+import com.google.gwt.dev.jjs.ast.JAnnotation.SourceOnlyClassException;
+
+/**
+ * Tests that a AdditionalTypeProviderDelegate correctly gets control when an unknown
+ * class is found, and that source for an unknown class gets correctly parsed.
+ */
+public class AdditionalTypeProviderDelegateTest extends OptimizerTestBase {
+
+  /**
+   * Compilation unit for a class generated at runtime when an unknown
+   * reference appears.
+   */
+  private class JavaWrapperCompilationUnit implements GeneratedUnit {
+    private final long createTime = System.currentTimeMillis();
+
+    public String optionalFileLocation() {
+      // The named file location requires a non-Java extension,
+      // or else the file won't get compiled correctly.
+      return "myPackage/InsertedClass.notjava";
+    }
+
+    public String getStrongHash() {
+      return "InsertedClass";
+    }
+
+    public long creationTime() {
+      return createTime;
+    }
+
+   public String getSource() {
+      String classSource = 
+          "package myPackage;\n" +
+          "public class InsertedClass {\n" +
+          "  public static int getSmallNumber() {\n" +
+          "    return 5;\n" +
+          "  }\n" +
+          "}";
+      return classSource;
+    }
+   
+    public String getTypeName() {
+      return "myPackage.InsertedClass";
+    }
+  }
+
+   public boolean insertInsertedClass = false;
+
+   public void setUp() {
+     // Create a source class that passes fine (just to test infrastructure.)
+     sourceOracle.addOrReplace(new MockJavaResource("test.A") {
+       @Override
+       protected CharSequence getContent() {
+         StringBuffer code = new StringBuffer();
+         code.append("package test;\n");
+         code.append("class A {\n");
+         code.append("  void myFunc() {\n");
+         code.append("  }\n");
+         code.append("}\n");
+         return code;
+       }
+     });
+     
+     // Create a source file containing a reference to a class in another
+     // package that we don't yet know about.  That code will be inserted
+     // by the AdditionalTypeProviderDelegate.
+     sourceOracle.addOrReplace(new MockJavaResource("test.B") {
+       @Override
+       protected CharSequence getContent() {
+         StringBuffer code = new StringBuffer();
+         code.append("package test;\n");
+         code.append("import myPackage.InsertedClass;");
+         code.append("class B {\n");
+         code.append("  int func() {\n");
+         // Reference an unknown class that will be substituted on the fly.
+         code.append("    return myPackage.InsertedClass.getSmallNumber();\n");
+         code.append("  }\n");
+         code.append("}\n");
+         return code;
+       }
+     });
+     
+     // Create a source file containing a reference to a class in another
+     // package, but that lacks an import directive.  Are we creating the
+     // class anyway?
+     sourceOracle.addOrReplace(new MockJavaResource("test.B1") {
+       @Override
+       protected CharSequence getContent() {
+         StringBuffer code = new StringBuffer();
+         code.append("package test;\n");
+         code.append("class B1 {\n");
+         code.append("  int func() {\n");
+         // Reference an unknown class that will be substituted on the fly.
+         code.append("    return myPackage.InsertedClass.getSmallNumber();\n");
+         code.append("  }\n");
+         code.append("}\n");
+         return code;
+       }
+     });
+    
+     // Create a source file containing a reference to an unknown class.
+     sourceOracle.addOrReplace(new MockJavaResource("test.C") {
+       @Override
+       protected CharSequence getContent() {
+         StringBuffer code = new StringBuffer();
+         code.append("package test;\n");
+         code.append("import myPackage.UnknownClass;");
+         code.append("class C {\n");
+         code.append("  int func() {\n");
+         // Reference an unknown class.
+         code.append("    return myPackage.UnknownClass.getSmallNumber();\n");
+         code.append("  }\n");
+         code.append("}\n");
+         return code;
+       }
+     });
+     
+     // Create a final example with a reference to an unknown class and no
+     // import statement.
+     sourceOracle.addOrReplace(new MockJavaResource("test.D") {
+       @Override
+       protected CharSequence getContent() {
+         StringBuffer code = new StringBuffer();
+         code.append("package test;\n");
+         code.append("class D {\n");
+         code.append("  int func() {\n");
+         // Reference an unknown class.
+         code.append("    return myPackage.UnknownClass.getSmallNumber();\n");
+         code.append("  }\n");
+         code.append("}\n");
+         return code;
+       }
+     });
+  }
+
+  public void testInsertedClass() throws UnableToCompleteException {
+    JProgram program = compileSnippet("void", "new test.B().func();");
+    
+    // Make sure the compiled classes appeared.
+    JDeclaredType bType = findType(program, "test.B");
+    assertNotNull("Unknown type B", bType);
+    JDeclaredType insertedClassType = findType(program, "myPackage.InsertedClass");
+    assertNotNull("Unknown type InsertedClass", insertedClassType);  
+  }
+  
+  public void testInsertedClass2() throws UnableToCompleteException {
+    JProgram program = compileSnippet("void", "new test.B1().func();");
+    
+    // Make sure the compiled classes appeared.
+    JDeclaredType bType = findType(program, "test.B1");
+    assertNotNull("Unknown type B1", bType);
+    JDeclaredType insertedClassType = findType(program, "myPackage.InsertedClass");
+    assertNotNull("Unknown type InsertedClass", insertedClassType);  
+  }
+
+  // Make sure regular code not using the AdditionalTypeProviderDelegate still works.
+  public void testSimpleParse() throws UnableToCompleteException {
+    JProgram program = compileSnippet("void", "new test.A();");
+    JDeclaredType goodClassType = findType(program, "test.A");
+    assertNotNull("Unknown class A", goodClassType);
+  }
+
+  public void testUnknownClass() throws UnableToCompleteException {
+    try {
+      JProgram program = compileSnippet("void", "new test.C();");
+      fail("Shouldn't have compiled");
+    } catch (UnableToCompleteException expected) {
+    }
+  }
+ 
+  public void testUnknownClassNoImport() throws UnableToCompleteException {
+    try {
+      JProgram program = compileSnippet("void", "new test.D();");
+      fail("Shouldn't have compiled");
+    } catch (UnableToCompleteException expected) {
+    }
+  }
+
+  @Override
+  protected AdditionalTypeProviderDelegate getAdditionalTypeProviderDelegate() {
+    // We'll provide a simple compiler delegate that will provide source
+    // for a class called myPackage.InsertedClass.
+    return new AdditionalTypeProviderDelegate() {
+      public boolean doFindAdditionalPackage(String slashedPackageName) {
+        if (slashedPackageName.compareTo("myPackage") == 0) {
+            return true;
+        }
+        return false;
+      }
+
+      public GeneratedUnit doFindAdditionalType(String binaryName) {
+        if (binaryName.compareTo("myPackage/InsertedClass") == 0) {
+          return new JavaWrapperCompilationUnit();
+      }
+      return null;
+      }
+    };
+  }
+
+  protected boolean optimizeMethod(JProgram program, JMethod method) {
+    return false;
+  }
+}
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/JJSTestBase.java b/dev/core/test/com/google/gwt/dev/jjs/impl/JJSTestBase.java
index 50a4cf2..ce1328b 100644
--- a/dev/core/test/com/google/gwt/dev/jjs/impl/JJSTestBase.java
+++ b/dev/core/test/com/google/gwt/dev/jjs/impl/JJSTestBase.java
@@ -19,6 +19,7 @@
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.dev.javac.CompilationState;
 import com.google.gwt.dev.javac.CompilationStateBuilder;
+import com.google.gwt.dev.javac.JdtCompiler.AdditionalTypeProviderDelegate;
 import com.google.gwt.dev.javac.impl.MockJavaResource;
 import com.google.gwt.dev.javac.impl.MockResourceOracle;
 import com.google.gwt.dev.jjs.JavaAstConstructor;
@@ -226,7 +227,7 @@
     });
     addBuiltinClasses(sourceOracle);
     CompilationState state = CompilationStateBuilder.buildFrom(logger,
-        sourceOracle.getResources());
+        sourceOracle.getResources(), getAdditionalTypeProviderDelegate());
     JProgram program = JavaAstConstructor.construct(logger, state,
         "test.EntryPoint", "com.google.gwt.lang.Exceptions");
     return program;
@@ -260,4 +261,12 @@
       }
     });
   }
+  
+  /**
+   * Return an AdditionalTypeProviderDelegate that will be able to provide
+   * new sources for unknown classnames. 
+   */
+  protected AdditionalTypeProviderDelegate getAdditionalTypeProviderDelegate() {
+    return null;
+  }
 }