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;
+ }
}