Adds ability to query the generator context whether a rebind rule exists for a given type

Review at http://gwt-code-reviews.appspot.com/1450806


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@10265 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/core/ext/GeneratorContext.java b/dev/core/src/com/google/gwt/core/ext/GeneratorContext.java
index 9c744e1..6e70447 100644
--- a/dev/core/src/com/google/gwt/core/ext/GeneratorContext.java
+++ b/dev/core/src/com/google/gwt/core/ext/GeneratorContext.java
@@ -29,6 +29,16 @@
 public interface GeneratorContext {
 
   /**
+   * Checks whether a rebind rule is available for a given sourceTypeName, such
+   * as can appear in a replace-with or generate-with rule.
+   * 
+   * @param sourceTypeName the name of a type to check for rebind rule
+   *          availability.
+   * @return true if a rebind rule is available
+   */
+  boolean checkRebindRuleAvailable(String sourceTypeName);
+
+  /**
    * Commits source generation begun with
    * {@link #tryCreate(TreeLogger, String, String)}.
    */
diff --git a/dev/core/src/com/google/gwt/core/ext/GeneratorContextExtWrapper.java b/dev/core/src/com/google/gwt/core/ext/GeneratorContextExtWrapper.java
index a0c79d3..3970057 100644
--- a/dev/core/src/com/google/gwt/core/ext/GeneratorContextExtWrapper.java
+++ b/dev/core/src/com/google/gwt/core/ext/GeneratorContextExtWrapper.java
@@ -50,6 +50,10 @@
     this.baseContext = baseContext;
   }
 
+  public boolean checkRebindRuleAvailable(String sourceTypeName) {
+    return baseContext.checkRebindRuleAvailable(sourceTypeName);
+  }
+
   public void commit(TreeLogger logger, PrintWriter pw) {
     baseContext.commit(logger, pw);
   }
diff --git a/dev/core/src/com/google/gwt/dev/javac/StandardGeneratorContext.java b/dev/core/src/com/google/gwt/dev/javac/StandardGeneratorContext.java
index 78c560f..ca3f0f5 100644
--- a/dev/core/src/com/google/gwt/dev/javac/StandardGeneratorContext.java
+++ b/dev/core/src/com/google/gwt/dev/javac/StandardGeneratorContext.java
@@ -33,6 +33,7 @@
 import com.google.gwt.dev.cfg.ModuleDef;
 import com.google.gwt.dev.javac.rebind.CachedRebindResult;
 import com.google.gwt.dev.javac.rebind.RebindResult;
+import com.google.gwt.dev.javac.rebind.RebindRuleResolver;
 import com.google.gwt.dev.javac.rebind.RebindStatus;
 import com.google.gwt.dev.resource.Resource;
 import com.google.gwt.dev.resource.ResourceOracle;
@@ -291,6 +292,8 @@
 
   private transient PropertyOracle propOracle;
 
+  private RebindRuleResolver rebindRuleResolver;
+
   private final Map<PrintWriter, Generated> uncommittedGeneratedCupsByPrintWriter =
       new IdentityHashMap<PrintWriter, Generated>();
 
@@ -361,6 +364,17 @@
   }
 
   /**
+   * Checks whether a rebind rule is available for a given sourceTypeName.
+   */
+  public boolean checkRebindRuleAvailable(String sourceTypeName) {
+    if (rebindRuleResolver != null) {
+      return rebindRuleResolver.checkRebindRuleResolvable(sourceTypeName);
+    } else {
+      return false;
+    }
+  }
+
+  /**
    * Frees memory used up by compilation state.
    */
   public void clear() {
@@ -715,6 +729,10 @@
     this.propOracle = propOracle;
   }
 
+  public void setRebindRuleResolver(RebindRuleResolver resolver) {
+    this.rebindRuleResolver = resolver;
+  }
+
   public final PrintWriter tryCreate(TreeLogger logger, String packageName, String simpleTypeName) {
     String typeName;
     if (packageName.length() == 0) {
diff --git a/dev/core/src/com/google/gwt/dev/javac/rebind/RebindRuleResolver.java b/dev/core/src/com/google/gwt/dev/javac/rebind/RebindRuleResolver.java
new file mode 100644
index 0000000..5cc5dcb
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/javac/rebind/RebindRuleResolver.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2011 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.javac.rebind;
+
+/**
+ * An interface for encapsulating rebind rule resolution.
+ */
+public interface RebindRuleResolver {
+  boolean checkRebindRuleResolvable(String typeName);
+}
diff --git a/dev/core/src/com/google/gwt/dev/shell/StandardRebindOracle.java b/dev/core/src/com/google/gwt/dev/shell/StandardRebindOracle.java
index 4eafb72..8449fee 100644
--- a/dev/core/src/com/google/gwt/dev/shell/StandardRebindOracle.java
+++ b/dev/core/src/com/google/gwt/dev/shell/StandardRebindOracle.java
@@ -25,20 +25,16 @@
 import com.google.gwt.dev.javac.rebind.CachedRebindResult;
 import com.google.gwt.dev.javac.rebind.RebindCache;
 import com.google.gwt.dev.javac.rebind.RebindResult;
+import com.google.gwt.dev.javac.rebind.RebindRuleResolver;
 import com.google.gwt.dev.javac.rebind.RebindStatus;
 import com.google.gwt.dev.jdt.RebindOracle;
-import com.google.gwt.dev.util.Util;
 import com.google.gwt.dev.util.log.speedtracer.DevModeEventType;
 import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger;
 import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event;
 
-import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Iterator;
-import java.util.List;
 import java.util.Map;
-import java.util.Set;
 
 /**
  * Implements rebind logic in terms of a variety of other well-known oracles.
@@ -48,17 +44,25 @@
   /**
    * Makes the actual deferred binding decision by examining rules.
    */
-  private final class Rebinder {
+  private final class Rebinder implements RebindRuleResolver {
 
-    private final Set<Rule> usedRules = new HashSet<Rule>();
-
-    private final List<String> usedTypeNames = new ArrayList<String>();
+    @Override
+    public boolean checkRebindRuleResolvable(String typeName) {
+      try {
+        if (getRebindRule(TreeLogger.NULL, typeName) != null) {
+          return true;
+        }
+      } catch (UnableToCompleteException utcEx) {
+      }
+      return false;
+    }
 
     public String rebind(TreeLogger logger, String typeName, ArtifactAcceptor artifactAcceptor)
         throws UnableToCompleteException {
       Event rebindEvent = SpeedTracerLogger.start(DevModeEventType.REBIND, "Type Name", typeName);
       try {
         genCtx.setPropertyOracle(propOracle);
+        genCtx.setRebindRuleResolver(this);
         Rule rule = getRebindRule(logger, typeName);
 
         if (rule == null) {
@@ -96,17 +100,6 @@
     }
 
     private Rule getRebindRule(TreeLogger logger, String typeName) throws UnableToCompleteException {
-      if (usedTypeNames.contains(typeName)) {
-        // Found a cycle.
-        //
-        String[] cycle = Util.toArray(String.class, usedTypeNames);
-        Messages.UNABLE_TO_REBIND_DUE_TO_CYCLE_IN_RULES.log(logger, cycle, null);
-        throw new UnableToCompleteException();
-      }
-
-      // Remember that we've seen this one.
-      //
-      usedTypeNames.add(typeName);
 
       // Make the rebind decision.
       //
@@ -125,19 +118,8 @@
         TreeLogger branch = Messages.TRACE_CHECKING_RULE.branch(logger, rule, null);
 
         if (rule.isApplicable(branch, genCtx, typeName)) {
-          // See if this rule has already been used. This is needed to prevent
-          // infinite loops with 'when-assignable' conditions.
-          //
-          if (!usedRules.contains(rule)) {
-            usedRules.add(rule);
-            Messages.TRACE_RULE_MATCHED.log(logger, null);
-
-            return rule;
-          } else {
-            // We are skipping this rule because it has already been used
-            // in a previous iteration.
-            //
-          }
+          Messages.TRACE_RULE_MATCHED.log(logger, null);
+          return rule;
         } else {
           Messages.TRACE_RULE_DID_NOT_MATCH.log(logger, null);
 
@@ -166,14 +148,11 @@
               + " based on fall back values. You may need to implement a specific "
               + "binding in case the fall back behavior does not replace the missing binding");
         }
-        if (!usedRules.contains(minCostRuleSoFar)) {
-          usedRules.add(minCostRuleSoFar);
-          if (logger.isLoggable(TreeLogger.DEBUG)) {
-            logger.log(TreeLogger.DEBUG, "No exact match was found, using closest match rule "
-                + minCostRuleSoFar);
-          }
-          return minCostRuleSoFar;
+        if (logger.isLoggable(TreeLogger.DEBUG)) {
+          logger.log(TreeLogger.DEBUG, "No exact match was found, using closest match rule "
+              + minCostRuleSoFar);
         }
+        return minCostRuleSoFar;
       }
 
       // No matching rule for this type.
diff --git a/user/src/com/google/gwt/i18n/rebind/CachedGeneratorContext.java b/user/src/com/google/gwt/i18n/rebind/CachedGeneratorContext.java
index 95793b6..e786bec 100644
--- a/user/src/com/google/gwt/i18n/rebind/CachedGeneratorContext.java
+++ b/user/src/com/google/gwt/i18n/rebind/CachedGeneratorContext.java
@@ -44,6 +44,10 @@
     this.context = context;
   }
 
+  public boolean checkRebindRuleAvailable(String sourceTypeName) {
+    return context.checkRebindRuleAvailable(sourceTypeName);
+  }
+
   public void commit(TreeLogger logger, PrintWriter pw) {
     context.commit(logger, pw);
   }
diff --git a/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java b/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java
index f19ba7c..4a4227a 100644
--- a/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java
+++ b/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java
@@ -79,6 +79,10 @@
       this.typeOracle = typeOracle;
     }
 
+    public boolean checkRebindRuleAvailable(String sourceTypeName) {
+      return true;
+    }
+
     public void commit(TreeLogger logger, PrintWriter pw) {
     }