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