Avoid unnecessary path scanning during JSNI resolution.

This patch:
- caches the set of unresolvable types to avoid file scanning, however the
  cache is cleared between generator invocations (generators may generate
  one of the missing files).
- resolves inner classes by binary name, avoiding looking for files in
  subdirectories.

Change-Id: Ic848d63f81400effdc92fa50f60e1e63200eed19
Bug-Link: https://github.com/gwtproject/gwt/issues/8960
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 ce6e733..b3fbfa3 100644
--- a/dev/core/src/com/google/gwt/dev/javac/JdtCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/javac/JdtCompiler.java
@@ -30,6 +30,7 @@
 import com.google.gwt.thirdparty.guava.common.collect.ImmutableMap;
 import com.google.gwt.thirdparty.guava.common.collect.ListMultimap;
 import com.google.gwt.thirdparty.guava.common.collect.Maps;
+import com.google.gwt.thirdparty.guava.common.collect.Sets;
 import com.google.gwt.thirdparty.guava.common.io.BaseEncoding;
 
 import org.eclipse.jdt.core.compiler.CharOperation;
@@ -104,7 +105,7 @@
    * types are encountered during compilation. Currently used for allowing
    * external tools to provide source lazily when undefined references appear.
    */
-  public static interface AdditionalTypeProviderDelegate {
+  public interface AdditionalTypeProviderDelegate {
     /**
      * Checks for additional packages which may contain additional compilation
      * units.
@@ -761,6 +762,11 @@
       Maps.newHashMap();
 
   /**
+   * Remembers types that where not found during resolution to avoid unnecessary file scanning.
+   */
+  private final Set<String> unresolvableReferences = Sets.newHashSet();
+
+  /**
    * Only active during a compile.
    */
   private transient CompilerImpl compilerImpl;
@@ -1035,7 +1041,15 @@
   }
 
   public ReferenceBinding resolveType(String sourceOrBinaryName) {
-    return resolveType(compilerImpl.lookupEnvironment, sourceOrBinaryName);
+    if (unresolvableReferences.contains(sourceOrBinaryName)) {
+      return null;
+    }
+    ReferenceBinding typeBinding =
+        resolveType(compilerImpl.lookupEnvironment, sourceOrBinaryName);
+    if (typeBinding == null) {
+      unresolvableReferences.add(sourceOrBinaryName);
+    }
+    return typeBinding;
   }
 
   public void setAdditionalTypeProviderDelegate(AdditionalTypeProviderDelegate newDelegate) {
diff --git a/dev/core/src/com/google/gwt/dev/javac/JdtUtil.java b/dev/core/src/com/google/gwt/dev/javac/JdtUtil.java
index e1513b2..88983ea 100644
--- a/dev/core/src/com/google/gwt/dev/javac/JdtUtil.java
+++ b/dev/core/src/com/google/gwt/dev/javac/JdtUtil.java
@@ -108,9 +108,25 @@
   }
 
   public static String getSourceName(TypeBinding classBinding) {
+    return getSourceName(CharOperation.charToString(classBinding.qualifiedPackageName()),
+        CharOperation.charToString(classBinding.qualifiedSourceName()));
+  }
+
+  public static String getSourceName(String qualifiedPackageName, String qualifiedSourceName) {
     return Joiner.on(".").skipNulls().join(new String[] {
-        Strings.emptyToNull(CharOperation.charToString(classBinding.qualifiedPackageName())),
-        CharOperation.charToString(classBinding.qualifiedSourceName())});
+        Strings.emptyToNull(qualifiedPackageName),
+        qualifiedSourceName});
+  }
+
+  public static String getBinaryName(TypeBinding classBinding) {
+    return getBinaryName(CharOperation.charToString(classBinding.qualifiedPackageName()),
+        CharOperation.charToString(classBinding.qualifiedSourceName()));
+  }
+
+  public static String getBinaryName(String qualifiedPackageName, String qualifiedSourceName) {
+    return Joiner.on(".").skipNulls().join(new String[] {
+        Strings.emptyToNull(qualifiedPackageName),
+        qualifiedSourceName.replace('.','$')});
   }
 
   public static boolean isInnerClass(ReferenceBinding binding) {
diff --git a/dev/core/src/com/google/gwt/dev/javac/JsniReferenceResolver.java b/dev/core/src/com/google/gwt/dev/javac/JsniReferenceResolver.java
index 6ea9488..ae1b897 100644
--- a/dev/core/src/com/google/gwt/dev/javac/JsniReferenceResolver.java
+++ b/dev/core/src/com/google/gwt/dev/javac/JsniReferenceResolver.java
@@ -295,9 +295,10 @@
               originalName.substring(importedClassName.length()));
           return;
         }
-        String fullClassName = declaringClassName + "." + originalName;
+        String fullClassName =
+            JdtUtil.getBinaryName(declaringClass) + "$" + originalName.replace('.', '$');
         if (typeResolver.resolveType(fullClassName) != null) {
-          jsniRef.setResolvedClassName(fullClassName);
+          jsniRef.setResolvedClassName(JdtUtil.getSourceName(declaringClass) + "." + originalName);
           return;
         }
         declaringClass = declaringClass.enclosingTypeAt(1);
@@ -315,12 +316,16 @@
       }
 
       // 4. Check to see if this name is resolvable from the current package.
-      String currentPackageClassName =
-          String.valueOf(method.binding.declaringClass.qualifiedPackageName());
-      currentPackageClassName += (currentPackageClassName.isEmpty() ? "" : ".") +  originalName;
+      String currentPackageBinaryClassName =
+          JdtUtil.getBinaryName(
+              CharOperation.charToString(method.binding.declaringClass.qualifiedPackageName()),
+              originalName);
 
-      if (typeResolver.resolveType(currentPackageClassName) != null) {
-        jsniRef.setResolvedClassName(currentPackageClassName);
+      if (typeResolver.resolveType(currentPackageBinaryClassName) != null) {
+        jsniRef.setResolvedClassName(
+            JdtUtil.getSourceName(
+                CharOperation.charToString(method.binding.declaringClass.qualifiedPackageName()),
+                originalName));
         return;
       }
 
@@ -333,9 +338,9 @@
         importPackages.add(JdtUtil.asDottedString(importReference.getImportName()));
       }
       for (String importPackage : importPackages) {
-        String fullClassName = importPackage + "." + originalName;
+        String fullClassName = importPackage + "." + originalName.replace('.', '$');
         if (typeResolver.resolveType(fullClassName) != null) {
-          jsniRef.setResolvedClassName(fullClassName);
+          jsniRef.setResolvedClassName(importPackage + "." + originalName);
           return;
         }
       }