Make the GWT compiler pluggable through an experimental "x.compiler.class" configuration property.

This allows experimenting with the GWT compile chain.

Review by: spoon

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@6052 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/CompilePerms.java b/dev/core/src/com/google/gwt/dev/CompilePerms.java
index 8fef668..d033f5d 100644
--- a/dev/core/src/com/google/gwt/dev/CompilePerms.java
+++ b/dev/core/src/com/google/gwt/dev/CompilePerms.java
@@ -18,7 +18,6 @@
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.dev.CompileTaskRunner.CompileTask;
-import com.google.gwt.dev.jjs.JavaToJavaScriptCompiler;
 import com.google.gwt.dev.jjs.PermutationResult;
 import com.google.gwt.dev.jjs.UnifiedAst;
 import com.google.gwt.dev.util.FileBackedObject;
@@ -185,7 +184,7 @@
   public static PermutationResult compile(TreeLogger logger,
       Permutation permutation, UnifiedAst unifiedAst)
       throws UnableToCompleteException {
-    return JavaToJavaScriptCompiler.compilePermutation(logger, unifiedAst,
+    return unifiedAst.compilePermutation(logger,
         permutation.getRebindAnswers(), permutation.getPropertyOracles(),
         permutation.getId());
   }
diff --git a/dev/core/src/com/google/gwt/dev/Permutation.java b/dev/core/src/com/google/gwt/dev/Permutation.java
index 28bacf7..c0319b1 100644
--- a/dev/core/src/com/google/gwt/dev/Permutation.java
+++ b/dev/core/src/com/google/gwt/dev/Permutation.java
@@ -19,9 +19,10 @@
 
 import java.io.Serializable;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
-import java.util.Set;
 import java.util.SortedMap;
+import java.util.SortedSet;
 import java.util.TreeMap;
 
 /**
@@ -58,11 +59,17 @@
   }
 
   public SortedMap<String, String> getRebindAnswers() {
-    return rebindAnswers;
+    return Collections.unmodifiableSortedMap(rebindAnswers);
   }
 
-  public void mergeFrom(Permutation other) {
-    assert rebindAnswers.equals(other.rebindAnswers);
+  public void mergeFrom(Permutation other, SortedSet<String> liveRebindRequests) {
+    if (getClass().desiredAssertionStatus()) {
+      for (String rebindRequest : liveRebindRequests) {
+        String myAnswer = rebindAnswers.get(rebindRequest);
+        String otherAnswer = other.rebindAnswers.get(rebindRequest);
+        assert myAnswer.equals(otherAnswer);
+      }
+    }
     assert !propertyOracles.isEmpty();
     assert !other.propertyOracles.isEmpty();
     propertyOracles.addAll(other.propertyOracles);
@@ -72,8 +79,4 @@
   public void putRebindAnswer(String requestType, String resultType) {
     rebindAnswers.put(requestType, resultType);
   }
-
-  public void reduceRebindAnswers(Set<String> liveRebindRequests) {
-    rebindAnswers.keySet().retainAll(liveRebindRequests);
-  }
 }
diff --git a/dev/core/src/com/google/gwt/dev/Precompile.java b/dev/core/src/com/google/gwt/dev/Precompile.java
index a529f16..811892d 100644
--- a/dev/core/src/com/google/gwt/dev/Precompile.java
+++ b/dev/core/src/com/google/gwt/dev/Precompile.java
@@ -31,9 +31,10 @@
 import com.google.gwt.dev.javac.StandardGeneratorContext;
 import com.google.gwt.dev.jdt.RebindOracle;
 import com.google.gwt.dev.jdt.RebindPermutationOracle;
+import com.google.gwt.dev.jjs.AbstractCompiler;
 import com.google.gwt.dev.jjs.JJSOptions;
 import com.google.gwt.dev.jjs.JJSOptionsImpl;
-import com.google.gwt.dev.jjs.JavaToJavaScriptCompiler;
+import com.google.gwt.dev.jjs.JavaScriptCompiler;
 import com.google.gwt.dev.jjs.JsOutputOption;
 import com.google.gwt.dev.jjs.UnifiedAst;
 import com.google.gwt.dev.shell.CheckForUpdates;
@@ -448,7 +449,7 @@
           generatorResourcesDir);
       // Never optimize on a validation run.
       jjsOptions.setOptimizePrecompile(false);
-      JavaToJavaScriptCompiler.precompile(logger, module, rpo, declEntryPts,
+      getCompiler(module).precompile(logger, module, rpo, declEntryPts,
           additionalRootTypes, jjsOptions, true);
       return true;
     } catch (UnableToCompleteException e) {
@@ -457,6 +458,28 @@
     }
   }
 
+  private static AbstractCompiler getCompiler(ModuleDef module) {
+    ConfigurationProperty compilerClassProp = module.getProperties().createConfiguration(
+        "x.compiler.class", false);
+    String compilerClassName = compilerClassProp.getValue();
+    if (compilerClassName == null || compilerClassName.length() == 0) {
+      return new JavaScriptCompiler();
+    }
+    Throwable caught;
+    try {
+      Class<?> compilerClass = Class.forName(compilerClassName);
+      return (AbstractCompiler) compilerClass.newInstance();
+    } catch (ClassNotFoundException e) {
+      caught = e;
+    } catch (InstantiationException e) {
+      caught = e;
+    } catch (IllegalAccessException e) {
+      caught = e;
+    }
+    throw new RuntimeException("Unable to instantiate compiler class '"
+        + compilerClassName + "'", caught);
+  }
+
   private static Precompilation precompile(TreeLogger logger,
       JJSOptions jjsOptions, ModuleDef module, int permutationBase,
       PropertyPermutations allPermutations, File genDir,
@@ -481,7 +504,7 @@
           module, compilationState, generatedArtifacts, allPermutations,
           genDir, generatorResourcesDir);
       PerfLogger.start("Precompile");
-      UnifiedAst unifiedAst = JavaToJavaScriptCompiler.precompile(logger,
+      UnifiedAst unifiedAst = getCompiler(module).precompile(logger,
           module, rpo, declEntryPts, null, jjsOptions,
           rpo.getPermuationCount() == 1);
       PerfLogger.end();
@@ -490,15 +513,18 @@
       Permutation[] permutations = rpo.getPermutations();
       // Sort the permutations by an ordered key to ensure determinism.
       SortedMap<String, Permutation> merged = new TreeMap<String, Permutation>();
+      SortedSet<String> liveRebindRequests = unifiedAst.getRebindRequests();
       for (Permutation permutation : permutations) {
-        permutation.reduceRebindAnswers(unifiedAst.getRebindRequests());
-        // Arbitrarily choose as a key the stringified map of rebind answers.
-        String rebindResultsString = permutation.getRebindAnswers().toString();
-        if (merged.containsKey(rebindResultsString)) {
-          Permutation existing = merged.get(rebindResultsString);
-          existing.mergeFrom(permutation);
+        // Construct a key from the stringified map of live rebind answers.
+        SortedMap<String, String> rebindAnswers = new TreeMap<String, String>(
+            permutation.getRebindAnswers());
+        rebindAnswers.keySet().retainAll(liveRebindRequests);
+        String key = rebindAnswers.toString();
+        if (merged.containsKey(key)) {
+          Permutation existing = merged.get(key);
+          existing.mergeFrom(permutation, liveRebindRequests);
         } else {
-          merged.put(rebindResultsString, permutation);
+          merged.put(key, permutation);
         }
       }
       return new Precompilation(unifiedAst, merged.values(), permutationBase,
diff --git a/dev/core/src/com/google/gwt/dev/jjs/AbstractCompiler.java b/dev/core/src/com/google/gwt/dev/jjs/AbstractCompiler.java
new file mode 100644
index 0000000..f1d94bb
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/AbstractCompiler.java
@@ -0,0 +1,49 @@
+/*

+ * Copyright 2009 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;

+

+import com.google.gwt.core.ext.TreeLogger;

+import com.google.gwt.core.ext.UnableToCompleteException;

+import com.google.gwt.dev.cfg.ModuleDef;

+import com.google.gwt.dev.jdt.RebindPermutationOracle;

+

+/**

+ * A Compiler used to compile a GWT project into artifacts.

+ */

+public interface AbstractCompiler {

+  /**

+   * Performs a precompilation, returning an object that can then be used to

+   * compile individual permutations..

+   * 

+   * @param logger the logger to use

+   * @param module the module to compile

+   * @param rpo the RebindPermutationOracle

+   * @param declEntryPts the set of entry classes declared in a GWT module;

+   *          these will be automatically rebound

+   * @param additionalRootTypes additional classes that should serve as code

+   *          roots; will not be rebound; may be <code>null</code>

+   * @param options the compiler options

+   * @param singlePermutation if true, do not pre-optimize the resulting AST or

+   *          allow serialization of the result

+   * @return the unified AST used to drive permutation compiles

+   * @throws UnableToCompleteException if an error other than

+   *           {@link OutOfMemoryError} occurs

+   */

+  UnifiedAst precompile(TreeLogger logger, ModuleDef module,

+      RebindPermutationOracle rpo, String[] declEntryPts,

+      String[] additionalRootTypes, JJSOptions options,

+      boolean singlePermutation) throws UnableToCompleteException;

+}

diff --git a/dev/core/src/com/google/gwt/dev/jjs/JavaScriptCompiler.java b/dev/core/src/com/google/gwt/dev/jjs/JavaScriptCompiler.java
new file mode 100644
index 0000000..a3b9976
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/jjs/JavaScriptCompiler.java
@@ -0,0 +1,36 @@
+/*

+ * Copyright 2009 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;

+

+import com.google.gwt.core.ext.TreeLogger;

+import com.google.gwt.core.ext.UnableToCompleteException;

+import com.google.gwt.dev.cfg.ModuleDef;

+import com.google.gwt.dev.jdt.RebindPermutationOracle;

+

+/**

+ * Uses the default compiler {@link JavaToJavaScriptCompiler}.

+ */

+public class JavaScriptCompiler implements AbstractCompiler {

+

+  public UnifiedAst precompile(TreeLogger logger, ModuleDef module,

+      RebindPermutationOracle rpo, String[] declEntryPts,

+      String[] additionalRootTypes, JJSOptions options,

+      boolean singlePermutation) throws UnableToCompleteException {

+    return JavaToJavaScriptCompiler.precompile(logger, module, rpo,

+        declEntryPts, additionalRootTypes, options, singlePermutation);

+  }

+

+}

diff --git a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
index da19855..a689889 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -191,9 +191,10 @@
    * @param unifiedAst the result of a
    *          {@link #precompile(TreeLogger, WebModeCompilerFrontEnd, String[], JJSOptions, boolean)}
    * @param rebindAnswers the set of rebind answers to resolve all outstanding
-   *          rebind decisions
-   * @param propertyOracles All property oracles corresponding to this
-   *          permutation.
+   *          rebind decisions for this permutation
+   * @param propertyOracles all property oracles corresponding to this
+   *          permutation
+   * @param permutationId the unique id of this permutation
    * @return the output JavaScript
    * @throws UnableToCompleteException if an error other than
    *           {@link OutOfMemoryError} occurs
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 d0d2e6d..a33b8e8 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/UnifiedAst.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/UnifiedAst.java
@@ -15,6 +15,9 @@
  */
 package com.google.gwt.dev.jjs;
 
+import com.google.gwt.core.ext.PropertyOracle;
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.dev.jjs.ast.JProgram;
 import com.google.gwt.dev.js.ast.JsProgram;
 import com.google.gwt.dev.util.PerfLogger;
@@ -26,6 +29,7 @@
 import java.io.ObjectOutputStream;
 import java.io.Serializable;
 import java.util.Collections;
+import java.util.Map;
 import java.util.Set;
 import java.util.SortedSet;
 import java.util.TreeSet;
@@ -34,7 +38,7 @@
  * Represents a unified, non-permutation specific AST. This AST is used to drive
  * per-permutation compiles.
  */
-public final class UnifiedAst implements Serializable {
+public class UnifiedAst implements Serializable {
 
   /**
    * Encapsulates the combined programs.
@@ -130,6 +134,37 @@
   }
 
   /**
+   * Copy constructor, invalidates the original.
+   */
+  UnifiedAst(UnifiedAst other) {
+    this.options = other.options;
+    this.initialAst = other.initialAst;
+    other.initialAst = null; // steal its copy
+    this.rebindRequests = other.rebindRequests;
+    this.serializedAst = other.serializedAst;
+  }
+
+  /**
+   * Compiles a particular permutation.
+   * 
+   * @param logger the logger to use
+   * @param rebindAnswers the set of rebind answers to resolve all outstanding
+   *          rebind decisions for this permutation
+   * @param propertyOracles all property oracles corresponding to this
+   *          permutation
+   * @param permutationId the unique id of this permutation
+   * @return the permutation result
+   * @throws UnableToCompleteException if an error other than
+   *           {@link OutOfMemoryError} occurs
+   */
+  public PermutationResult compilePermutation(TreeLogger logger,
+      Map<String, String> rebindAnswers, PropertyOracle[] propertyOracles,
+      int permutationId) throws UnableToCompleteException {
+    return JavaToJavaScriptCompiler.compilePermutation(logger, this,
+        rebindAnswers, propertyOracles, permutationId);
+  }
+
+  /**
    * Returns the active set of JJS options associated with this compile.
    */
   public JJSOptions getOptions() {