Optimize SymbolData serialization to speed up Link.

- SymbolData is now stored in an array instead of a TreeMap.
- StandardSymbolData has explicit readObject/writeObject methods to avoid the built-in reflective calls and optimize instance sharing/non-sharing.
- Symbol data is now pre-serialized to bytes in CompilePerms to avoid multiple serialization/deserialization work in Link phase.

Review by: spoon

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@5342 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/CompilationResult.java b/dev/core/src/com/google/gwt/core/ext/linker/CompilationResult.java
index 9f6c0d2..e042f5a 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/CompilationResult.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/CompilationResult.java
@@ -56,12 +56,12 @@
   public abstract String getStrongName();
 
   /**
-   * Returns a map of obfuscated symbol names in the compilation to metadata
-   * about the symbol. This data can allow for on-the-fly deobfuscation of stack
-   * trace information or to allow server components to have in-depth knowledge
-   * of the runtime structure of compiled objects.
+   * Returns a sorted array of obfuscated symbol names in the compilation to
+   * metadata about the symbol. This data can allow for on-the-fly deobfuscation
+   * of stack trace information or to allow server components to have in-depth
+   * knowledge of the runtime structure of compiled objects.
    */
-  public abstract SortedMap<SymbolData, String> getSymbolMap();
+  public abstract SymbolData[] getSymbolMap();
 
   @Override
   public final int hashCode() {
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/SymbolData.java b/dev/core/src/com/google/gwt/core/ext/linker/SymbolData.java
index 3b1b747..d969d0b 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/SymbolData.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/SymbolData.java
@@ -16,7 +16,6 @@
 package com.google.gwt.core.ext.linker;
 
 import java.io.Serializable;
-import java.net.URI;
 import java.util.Comparator;
 
 /**
@@ -71,11 +70,16 @@
   int getSourceLine();
 
   /**
-   * Returns a URI representing the location of the source. This method will
-   * return <code>null</code> if the symbol was derived from a transient or
-   * unknown source.
+   * Returns a URI string representing the location of the source. This method
+   * will return <code>null</code> if the symbol was derived from a transient
+   * or unknown source.
    */
-  URI getSourceUri();
+  String getSourceUri();
+
+  /**
+   * Returns the JavaScript symbol this data maps to.
+   */
+  String getSymbolName();
 
   /**
    * Returns <code>true</code> if the symbol represents a class.
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardCompilationResult.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardCompilationResult.java
index f213f40..af9e753 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardCompilationResult.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardCompilationResult.java
@@ -76,14 +76,14 @@
 
   private final String strongName;
 
-  private final long symbolMapToken;
+  private final long symbolToken;
 
   public StandardCompilationResult(String strongName, String[] js,
-      SortedMap<SymbolData, String> symbolMap) {
+      byte[] serializedSymbolMap) {
     super(StandardLinkerContext.class);
     this.strongName = strongName;
     jsToken = diskCache.writeObject(js);
-    symbolMapToken = diskCache.writeObject(symbolMap);
+    symbolToken = diskCache.writeByteArray(serializedSymbolMap);
   }
 
   /**
@@ -113,8 +113,7 @@
   }
 
   @Override
-  @SuppressWarnings("unchecked")
-  public SortedMap<SymbolData, String> getSymbolMap() {
-    return diskCache.readObject(symbolMapToken, SortedMap.class);
+  public SymbolData[] getSymbolMap() {
+    return diskCache.readObject(symbolToken, SymbolData[].class);
   }
 }
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java
index 2b94b8e..a625bb3 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardLinkerContext.java
@@ -275,7 +275,7 @@
     StandardCompilationResult result = resultsByStrongName.get(strongName);
     if (result == null) {
       result = new StandardCompilationResult(strongName, js,
-          permutationResult.getSymbolMap());
+          permutationResult.getSerializedSymbolMap());
       resultsByStrongName.put(result.getStrongName(), result);
       artifacts.add(result);
 
@@ -439,26 +439,26 @@
         + outputPath.getPath(), null);
 
     for (EmittedArtifact artifact : artifacts.find(EmittedArtifact.class)) {
-      TreeLogger artifactLogger = logger.branch(TreeLogger.DEBUG,
-          "Emitting resource " + artifact.getPartialPath(), null);
+              TreeLogger artifactLogger = logger.branch(TreeLogger.DEBUG,
+                  "Emitting resource " + artifact.getPartialPath(), null);
 
-      File outFile;
-      if (artifact.isPrivate()) {
-        if (extraPath == null) {
-          continue;
-        }
-        outFile = new File(getExtraPathForLinker(extraPath,
-            artifact.getLinker()), artifact.getPartialPath());
-      } else {
-        outFile = new File(outputPath, artifact.getPartialPath());
-      }
+              File outFile;
+              if (artifact.isPrivate()) {
+                if (extraPath == null) {
+                  continue;
+                }
+                outFile = new File(getExtraPathForLinker(extraPath,
+                    artifact.getLinker()), artifact.getPartialPath());
+              } else {
+                outFile = new File(outputPath, artifact.getPartialPath());
+              }
 
-      if (!outFile.exists()
-          || (outFile.lastModified() <= artifact.getLastModified())) {
+              if (!outFile.exists()
+                  || (outFile.lastModified() <= artifact.getLastModified())) {
         Util.copy(artifactLogger, artifact.getContents(artifactLogger), outFile);
-        outFile.setLastModified(artifact.getLastModified());
-      }
-    }
+                outFile.setLastModified(artifact.getLastModified());
+              }
+            }
     for (StandardCompilationAnalysis soycFiles : artifacts.find(StandardCompilationAnalysis.class)) {
       TreeLogger artifactLogger = logger.branch(TreeLogger.DEBUG,
           "Emitting soyc resources.", null);
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardSymbolData.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardSymbolData.java
index 7ecc18d..3c096e2 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardSymbolData.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardSymbolData.java
@@ -18,6 +18,10 @@
 import com.google.gwt.core.ext.linker.SymbolData;
 
 import java.io.File;
+import java.io.IOException;
+import java.io.InvalidObjectException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
 import java.net.URI;
 import java.net.URISyntaxException;
 
@@ -26,47 +30,46 @@
  */
 public class StandardSymbolData implements SymbolData {
 
-  public static SymbolData forClass(String className, String fileName,
+  public static StandardSymbolData forClass(String className, String uriString,
       int lineNumber) {
-    URI uri = inferUriFromFileName(fileName);
-    return new StandardSymbolData(className, null, null, uri, lineNumber);
+    return new StandardSymbolData(className, null, null, uriString, lineNumber);
   }
 
-  public static SymbolData forMember(String className, String memberName,
-      String jsniIdent, String fileName, int lineNumber) {
-    URI uri = inferUriFromFileName(fileName);
-    return new StandardSymbolData(className, jsniIdent, memberName, uri,
+  public static StandardSymbolData forMember(String className,
+      String memberName, String methodSig, String uriString, int lineNumber) {
+    return new StandardSymbolData(className, memberName, methodSig, uriString,
         lineNumber);
   }
 
-  private static URI inferUriFromFileName(String fileName) {
+  public static String toUriString(String fileName) {
     File f = new File(fileName);
     if (f.exists()) {
-      return f.toURI();
+      return f.toURI().toASCIIString();
     } else {
       try {
-        return new URI(fileName);
+        return new URI(fileName).toASCIIString();
       } catch (URISyntaxException e) {
         return null;
       }
     }
   }
 
-  private final String className;
-  private final String jsniIdent;
-  private final String memberName;
-  private final int sourceLine;
-  private final URI sourceUri;
+  private String className;
+  private String memberName;
+  private String methodSig;
+  private int sourceLine;
+  private String sourceUri;
+  private String symbolName;
 
-  private StandardSymbolData(String className, String jsniIdent,
-      String memberName, URI sourceUri, int sourceLine) {
+  private StandardSymbolData(String className, String memberName,
+      String methodSig, String sourceUri, int sourceLine) {
     assert className != null && className.length() > 0 : "className";
-    assert !(jsniIdent == null ^ memberName == null) : "jsniIdent ^ memberName";
+    assert memberName != null || methodSig == null : "methodSig without memberName";
     assert sourceLine >= -1 : "sourceLine: " + sourceLine;
 
     this.className = className;
-    this.jsniIdent = jsniIdent;
     this.memberName = memberName;
+    this.methodSig = methodSig;
     this.sourceUri = sourceUri;
     this.sourceLine = sourceLine;
   }
@@ -76,7 +79,13 @@
   }
 
   public String getJsniIdent() {
-    return jsniIdent;
+    if (memberName == null) {
+      return null;
+    }
+    if (methodSig == null) {
+      return className + "::" + memberName;
+    }
+    return className + "::" + memberName + methodSig;
   }
 
   public String getMemberName() {
@@ -87,24 +96,79 @@
     return sourceLine;
   }
 
-  public URI getSourceUri() {
+  public String getSourceUri() {
     return sourceUri;
   }
 
+  public String getSymbolName() {
+    return symbolName;
+  }
+
   public boolean isClass() {
     return memberName == null;
   }
 
   public boolean isField() {
-    return jsniIdent != null && !jsniIdent.contains("(");
+    return memberName != null && methodSig == null;
   }
 
   public boolean isMethod() {
-    return jsniIdent != null && jsniIdent.contains("(");
+    return methodSig != null;
+  }
+
+  public void setSymbolName(String symbolName) {
+    this.symbolName = symbolName;
   }
 
   @Override
   public String toString() {
-    return jsniIdent != null ? jsniIdent : className;
+    return methodSig != null ? methodSig : className;
+  }
+
+  private void readObject(ObjectInputStream in) throws IOException,
+      ClassNotFoundException {
+    className = (String) in.readObject();
+    switch (in.read()) {
+      case 0:
+        break;
+      case 1:
+        memberName = in.readUTF();
+        break;
+      case 2:
+        memberName = in.readUTF();
+        methodSig = in.readUTF();
+        break;
+      default:
+        throw new InvalidObjectException("Unexpected input");
+    }
+    sourceLine = in.readInt();
+    sourceUri = (String) in.readObject();
+    symbolName = in.readUTF();
+  }
+
+  /**
+   * Implemented by hand for speed (over using reflection) because there are so
+   * many of these. Note that {@link #className} and {@link #sourceUri} are done
+   * as writeObject because the actual String instances are very likely to be
+   * shared among many instances of this class, so the same objects can be
+   * reused in the stream. The other String fields are done as UTF because it's
+   * slightly faster and the String objects are unlikely to be shared among
+   * instances.
+   */
+  private void writeObject(ObjectOutputStream out) throws IOException {
+    out.writeObject(className);
+    if (isClass()) {
+      out.write(0);
+    } else if (isField()) {
+      out.write(1);
+      out.writeUTF(memberName);
+    } else {
+      out.write(2);
+      out.writeUTF(memberName);
+      out.writeUTF(methodSig);
+    }
+    out.writeInt(sourceLine);
+    out.writeObject(sourceUri);
+    out.writeUTF(symbolName);
   }
 }
diff --git a/dev/core/src/com/google/gwt/core/linker/SymbolMapsLinker.java b/dev/core/src/com/google/gwt/core/linker/SymbolMapsLinker.java
index 6124019..fb260a5 100644
--- a/dev/core/src/com/google/gwt/core/linker/SymbolMapsLinker.java
+++ b/dev/core/src/com/google/gwt/core/linker/SymbolMapsLinker.java
@@ -29,7 +29,6 @@
 
 import java.io.ByteArrayOutputStream;
 import java.io.PrintWriter;
-import java.net.URI;
 import java.util.Map;
 import java.util.SortedMap;
 
@@ -110,31 +109,28 @@
     }
 
     pw.println("# jsName, jsniIdent, className, memberName, sourceUri, sourceLine");
-    for (Map.Entry<SymbolData, String> entry : result.getSymbolMap().entrySet()) {
-      SymbolData symbol = entry.getKey();
+    for (SymbolData symbol : result.getSymbolMap()) {
+      StringBuilder sb = new StringBuilder(1024);
+      sb.append(symbol.getSymbolName());
 
-      pw.print(entry.getValue());
-
-      print(pw, symbol.getJsniIdent());
-      print(pw, symbol.getClassName());
-      print(pw, symbol.getMemberName());
-      print(pw, symbol.getSourceUri());
-      print(pw, String.valueOf(symbol.getSourceLine()));
-      pw.println();
-    }
-  }
-
-  private void print(PrintWriter pw, String value) {
-    pw.print(",");
-    if (value != null) {
-      pw.print(value);
-    }
-  }
-
-  private void print(PrintWriter pw, URI uri) {
-    pw.print(",");
-    if (uri != null) {
-      pw.print(uri.toASCIIString());
+      sb.append(',');
+      sb.append(symbol.getJsniIdent());
+      sb.append(',');
+      sb.append(symbol.getClassName());
+      sb.append(',');
+      String memberName = symbol.getMemberName();
+      if (memberName != null) {
+        sb.append(memberName);
+      }
+      sb.append(',');
+      String sourceUri = symbol.getSourceUri();
+      if (sourceUri != null) {
+        sb.append(sourceUri);
+      }
+      sb.append(',');
+      sb.append(symbol.getSourceLine());
+      sb.append('\n');
+      pw.write(sb.toString());
     }
   }
 }
diff --git a/dev/core/src/com/google/gwt/dev/PermutationResult.java b/dev/core/src/com/google/gwt/dev/PermutationResult.java
index 4f9aa6f..f180acd 100644
--- a/dev/core/src/com/google/gwt/dev/PermutationResult.java
+++ b/dev/core/src/com/google/gwt/dev/PermutationResult.java
@@ -16,10 +16,8 @@
 package com.google.gwt.dev;
 
 import com.google.gwt.core.ext.linker.ArtifactSet;
-import com.google.gwt.core.ext.linker.SymbolData;
 
 import java.io.Serializable;
-import java.util.SortedMap;
 
 /**
  * An extensible return type for the results of compiling a single permutation.
@@ -39,5 +37,5 @@
   /**
    * The symbol map for the permutation.
    */
-  SortedMap<SymbolData, String> getSymbolMap();
+  byte[] getSerializedSymbolMap();
 }
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 e8347c4..0c61aac 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -20,6 +20,7 @@
 import com.google.gwt.core.ext.linker.ArtifactSet;
 import com.google.gwt.core.ext.linker.SymbolData;
 import com.google.gwt.core.ext.linker.impl.StandardCompilationAnalysis;
+import com.google.gwt.core.ext.linker.impl.StandardSymbolData;
 import com.google.gwt.core.ext.soyc.Range;
 import com.google.gwt.core.ext.soyc.SplitPointRecorder;
 import com.google.gwt.core.ext.soyc.StoryRecorder;
@@ -103,12 +104,14 @@
 import com.google.gwt.dev.util.Memory;
 import com.google.gwt.dev.util.PerfLogger;
 import com.google.gwt.dev.util.TextOutput;
+import com.google.gwt.dev.util.Util;
 
 import org.eclipse.jdt.core.compiler.IProblem;
 import org.eclipse.jdt.internal.compiler.CompilationResult;
 import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
 import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
 
+import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
@@ -121,7 +124,6 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.SortedMap;
 import java.util.TreeMap;
 import java.util.TreeSet;
 
@@ -134,12 +136,18 @@
   private static class PermutationResultImpl implements PermutationResult {
     private final ArtifactSet artifacts = new ArtifactSet();
     private final String[] js;
-    private final SortedMap<SymbolData, String> symbolMap;
+    private final byte[] serializedSymbolMap;
 
-    public PermutationResultImpl(String[] js,
-        SortedMap<SymbolData, String> symbolMap) {
+    public PermutationResultImpl(String[] js, SymbolData[] symbolMap) {
       this.js = js;
-      this.symbolMap = symbolMap;
+      try {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        Util.writeObjectToStream(baos, (Object) symbolMap);
+        this.serializedSymbolMap = baos.toByteArray();
+      } catch (IOException e) {
+        throw new RuntimeException("Should never happen with in-memory stream",
+            e);
+      }
     }
 
     public ArtifactSet getArtifacts() {
@@ -150,8 +158,8 @@
       return js;
     }
 
-    public SortedMap<SymbolData, String> getSymbolMap() {
-      return symbolMap;
+    public byte[] getSerializedSymbolMap() {
+      return serializedSymbolMap;
     }
   }
 
@@ -181,7 +189,7 @@
       JProgram jprogram = ast.getJProgram();
       JsProgram jsProgram = ast.getJsProgram();
       JJSOptions options = unifiedAst.getOptions();
-      Map<SymbolData, JsName> symbolTable = new TreeMap<SymbolData, JsName>(
+      Map<StandardSymbolData, JsName> symbolTable = new TreeMap<StandardSymbolData, JsName>(
           new SymbolData.ClassIdentComparator());
 
       ResolveRebinds.exec(jprogram, rebindAnswers);
@@ -305,9 +313,8 @@
         }
       }
 
-      SortedMap<SymbolData, String> symbolMap = makeSymbolMap(symbolTable);
-
-      PermutationResult toReturn = new PermutationResultImpl(js, symbolMap);
+      PermutationResult toReturn = new PermutationResultImpl(js,
+          makeSymbolMap(symbolTable));
       if (sourceInfoMaps != null) {
 
         // get method dependencies
@@ -859,19 +866,17 @@
     return amp.makeStatement();
   }
 
-  private static SortedMap<SymbolData, String> makeSymbolMap(
-      Map<SymbolData, JsName> symbolTable) {
+  private static SymbolData[] makeSymbolMap(
+      Map<StandardSymbolData, JsName> symbolTable) {
 
-    SortedMap<SymbolData, String> toReturn = new TreeMap<SymbolData, String>(
-        new SymbolData.ClassIdentComparator());
-
-    for (Map.Entry<SymbolData, JsName> entry : symbolTable.entrySet()) {
-      assert !toReturn.containsKey(entry.getKey()) : "Duplicate key for "
-          + entry.getKey().toString();
-      toReturn.put(entry.getKey(), entry.getValue().getShortIdent());
+    SymbolData[] result = new SymbolData[symbolTable.size()];
+    int i = 0;
+    for (Map.Entry<StandardSymbolData, JsName> entry : symbolTable.entrySet()) {
+      StandardSymbolData symbolData = entry.getKey();
+      symbolData.setSymbolName(entry.getValue().getShortIdent());
+      result[i++] = symbolData;
     }
-
-    return toReturn;
+    return result;
   }
 
   private static void maybeDumpAST(JProgram jprogram) {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
index 914fcc0..52e8186 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
@@ -15,7 +15,6 @@
  */
 package com.google.gwt.dev.jjs.impl;
 
-import com.google.gwt.core.ext.linker.SymbolData;
 import com.google.gwt.core.ext.linker.impl.StandardSymbolData;
 import com.google.gwt.dev.jjs.HasSourceInfo;
 import com.google.gwt.dev.jjs.InternalCompilerException;
@@ -153,6 +152,14 @@
   private class CreateNamesAndScopesVisitor extends JVisitor {
 
     private final JField arrayLengthField = program.getIndexedField("Array.length");
+
+    /**
+     * Cache of computed Java source file names to URI strings for symbol
+     * export. By using a cache we also ensure the miminum number of String
+     * instances are serialized.
+     */
+    private final Map<String, String> fileNameToUriString = new HashMap<String, String>();
+
     private final Stack<JsScope> scopeStack = new Stack<JsScope>();
 
     @Override
@@ -365,6 +372,23 @@
       return false;
     }
 
+    /**
+     * Generate a file name URI string for a source info, for symbol data
+     * export.
+     */
+    private String makeUriString(HasSourceInfo x) {
+      String fileName = x.getSourceInfo().getFileName();
+      if (fileName == null) {
+        return null;
+      }
+      String uriString = fileNameToUriString.get(fileName);
+      if (uriString == null) {
+        uriString = StandardSymbolData.toUriString(fileName);
+        fileNameToUriString.put(fileName, uriString);
+      }
+      return uriString;
+    }
+
     private JsScope peek() {
       return scopeStack.peek();
     }
@@ -378,18 +402,14 @@
     }
 
     private void recordSymbol(JReferenceType x, JsName jsName) {
-      SymbolData symbolData = StandardSymbolData.forClass(x.getName(),
-          x.getSourceInfo().getFileName(), x.getSourceInfo().getStartLine());
+      StandardSymbolData symbolData = StandardSymbolData.forClass(x.getName(),
+          makeUriString(x), x.getSourceInfo().getStartLine());
       assert !symbolTable.containsKey(symbolData);
       symbolTable.put(symbolData, jsName);
     }
 
     private <T extends HasEnclosingType & HasName & HasSourceInfo> void recordSymbol(
         T x, JsName jsName) {
-      StringBuilder sb = new StringBuilder();
-      sb.append(x.getEnclosingType().getName());
-      sb.append("::");
-
       /*
        * NB: The use of x.getName() can produce confusion in cases where a type
        * has both polymorphic and static dispatch for a method, because you
@@ -402,9 +422,10 @@
        * begin with a dollar-sign and whose first parameter is the enclosing
        * type.
        */
-      sb.append(x.getName());
 
+      String methodSig;
       if (x instanceof JMethod) {
+        StringBuilder sb = new StringBuilder();
         sb.append('(');
         JMethod method = ((JMethod) x);
         for (JType t : method.getOriginalParamTypes()) {
@@ -412,14 +433,17 @@
         }
         sb.append(')');
         sb.append(method.getOriginalReturnType().getJsniSignatureName());
+        methodSig = sb.toString();
+      } else {
+        methodSig = null;
       }
 
-      SymbolData symbolData = StandardSymbolData.forMember(
-          x.getEnclosingType().getName(), x.getName(), sb.toString(),
-          x.getSourceInfo().getFileName(), x.getSourceInfo().getStartLine());
+      StandardSymbolData symbolData = StandardSymbolData.forMember(
+          x.getEnclosingType().getName(), x.getName(), methodSig,
+          makeUriString(x), x.getSourceInfo().getStartLine());
       assert !symbolTable.containsKey(symbolData) : "Duplicate symbol "
           + "recorded " + jsName.getIdent() + " for " + x.getName()
-          + " and key " + sb.toString();
+          + " and key " + symbolData.getJsniIdent();
       symbolTable.put(symbolData, jsName);
     }
   }
@@ -1800,7 +1824,7 @@
   }
 
   public static JavaToJavaScriptMap exec(JProgram program, JsProgram jsProgram,
-      JsOutputOption output, Map<SymbolData, JsName> symbolTable) {
+      JsOutputOption output, Map<StandardSymbolData, JsName> symbolTable) {
     GenerateJavaScriptAST generateJavaScriptAST = new GenerateJavaScriptAST(
         program, jsProgram, output, symbolTable);
     return generateJavaScriptAST.execImpl();
@@ -1861,6 +1885,11 @@
   private final Set<JReferenceType> specialObfuscatedTypes = new HashSet<JReferenceType>();
 
   /**
+   * Maps JsNames to machine-usable identifiers.
+   */
+  private final Map<StandardSymbolData, JsName> symbolTable;
+
+  /**
    * Contains JsNames for all globals, such as static fields and methods.
    */
   private final JsScope topScope;
@@ -1871,13 +1900,8 @@
 
   private final Map<JsStatement, JMethod> vtableInitForMethodMap = new HashMap<JsStatement, JMethod>();
 
-  /**
-   * Maps JsNames to machine-usable identifiers.
-   */
-  private final Map<SymbolData, JsName> symbolTable;
-
   private GenerateJavaScriptAST(JProgram program, JsProgram jsProgram,
-      JsOutputOption output, Map<SymbolData, JsName> symbolTable) {
+      JsOutputOption output, Map<StandardSymbolData, JsName> symbolTable) {
     this.program = program;
     typeOracle = program.typeOracle;
     this.jsProgram = jsProgram;