Expand the amount of data exported by the symbol maps.

Patch by: bobv
Review by: jat


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@5010 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 e194f59..b62f20c 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 JSNI-style
-   * identifiers. This data can allow for on-the-fly deobfuscation of stack
+   * 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.
    */
-  public abstract SortedMap<String, String> getSymbolMap();
+  public abstract SortedMap<SymbolData, String> 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
new file mode 100644
index 0000000..3b1b747
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/linker/SymbolData.java
@@ -0,0 +1,94 @@
+/*
+ * 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.core.ext.linker;
+
+import java.io.Serializable;
+import java.net.URI;
+import java.util.Comparator;
+
+/**
+ * Provides basic information about symbols in the generated JavaScript.
+ * 
+ * @see CompilationResult#getSymbolMap()
+ */
+public interface SymbolData extends Serializable {
+  /*
+   * NB: This class is intended to provide enough data to synthesize
+   * StackTraceElements, however we don't want to use STE in our API in the case
+   * that we want to provide additional data in the future.
+   * 
+   * Note also that this class does not provide the name of the symbol it is
+   * describing, mainly because the JS compilation process results in multiple
+   * symbols that are mapped onto the same SymbolData (e.g. MakeCallsStatic).
+   */
+
+  /**
+   * A Comparator for use when presenting the data to humans. This Comparator
+   * orders SymbolData objects by their class names or JSNI idents.
+   */
+  class ClassIdentComparator implements Comparator<SymbolData>, Serializable {
+    public int compare(SymbolData o1, SymbolData o2) {
+      String s1 = o1.isClass() ? o1.getClassName() : o1.getJsniIdent();
+      String s2 = o2.isClass() ? o2.getClassName() : o2.getJsniIdent();
+      return s1.compareTo(s2);
+    }
+  }
+
+  /**
+   * Returns the name of the type or enclosing type if the symbol is a method or
+   * field.
+   */
+  String getClassName();
+
+  /**
+   * Returns a JSNI-like identifier for the symbol if it a method or field,
+   * otherwise <code>null</code>.
+   */
+  String getJsniIdent();
+
+  /**
+   * Returns the name of the member if the symbol is a method or field.
+   */
+  String getMemberName();
+
+  /**
+   * Returns the line number on which the symbol was originally declared or
+   * <code>-1</code> if the line number is unknown.
+   */
+  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.
+   */
+  URI getSourceUri();
+
+  /**
+   * Returns <code>true</code> if the symbol represents a class.
+   */
+  boolean isClass();
+
+  /**
+   * Returns <code>true</code> if the symbol represents a field.
+   */
+  boolean isField();
+
+  /**
+   * Returns <code>true</code> if the symbol represents a method.
+   */
+  boolean isMethod();
+}
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 62be9ce..670b312 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
@@ -19,6 +19,7 @@
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.linker.CompilationResult;
 import com.google.gwt.core.ext.linker.SelectionProperty;
+import com.google.gwt.core.ext.linker.SymbolData;
 import com.google.gwt.dev.PermutationResult;
 import com.google.gwt.dev.util.FileBackedObject;
 
@@ -74,7 +75,7 @@
 
   private final FileBackedObject<PermutationResult> resultFile;
 
-  private transient SoftReference<SortedMap<String, String>> symbolMap;
+  private transient SoftReference<SortedMap<SymbolData, String>> symbolMap;
 
   private final SortedSet<SortedMap<SelectionProperty, String>> propertyValues = new TreeSet<SortedMap<SelectionProperty, String>>(
       MAP_COMPARATOR);
@@ -126,8 +127,8 @@
   }
 
   @Override
-  public SortedMap<String, String> getSymbolMap() {
-    SortedMap<String, String> toReturn = null;
+  public SortedMap<SymbolData, String> getSymbolMap() {
+    SortedMap<SymbolData, String> toReturn = null;
     if (symbolMap != null) {
       toReturn = symbolMap.get();
     }
@@ -135,7 +136,7 @@
     if (toReturn == null) {
       PermutationResult result = loadPermutationResult();
       toReturn = result.getSymbolMap();
-      symbolMap = new SoftReference<SortedMap<String, String>>(toReturn);
+      symbolMap = new SoftReference<SortedMap<SymbolData, String>>(toReturn);
     }
 
     return toReturn;
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
new file mode 100644
index 0000000..7ecc18d
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardSymbolData.java
@@ -0,0 +1,110 @@
+/*
+ * 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.core.ext.linker.impl;
+
+import com.google.gwt.core.ext.linker.SymbolData;
+
+import java.io.File;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+/**
+ * An immutable implementation of SymbolData.
+ */
+public class StandardSymbolData implements SymbolData {
+
+  public static SymbolData forClass(String className, String fileName,
+      int lineNumber) {
+    URI uri = inferUriFromFileName(fileName);
+    return new StandardSymbolData(className, null, null, uri, 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,
+        lineNumber);
+  }
+
+  private static URI inferUriFromFileName(String fileName) {
+    File f = new File(fileName);
+    if (f.exists()) {
+      return f.toURI();
+    } else {
+      try {
+        return new URI(fileName);
+      } 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 StandardSymbolData(String className, String jsniIdent,
+      String memberName, URI sourceUri, int sourceLine) {
+    assert className != null && className.length() > 0 : "className";
+    assert !(jsniIdent == null ^ memberName == null) : "jsniIdent ^ memberName";
+    assert sourceLine >= -1 : "sourceLine: " + sourceLine;
+
+    this.className = className;
+    this.jsniIdent = jsniIdent;
+    this.memberName = memberName;
+    this.sourceUri = sourceUri;
+    this.sourceLine = sourceLine;
+  }
+
+  public String getClassName() {
+    return className;
+  }
+
+  public String getJsniIdent() {
+    return jsniIdent;
+  }
+
+  public String getMemberName() {
+    return memberName;
+  }
+
+  public int getSourceLine() {
+    return sourceLine;
+  }
+
+  public URI getSourceUri() {
+    return sourceUri;
+  }
+
+  public boolean isClass() {
+    return memberName == null;
+  }
+
+  public boolean isField() {
+    return jsniIdent != null && !jsniIdent.contains("(");
+  }
+
+  public boolean isMethod() {
+    return jsniIdent != null && jsniIdent.contains("(");
+  }
+
+  @Override
+  public String toString() {
+    return jsniIdent != null ? jsniIdent : className;
+  }
+}
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 4f4afde..6124019 100644
--- a/dev/core/src/com/google/gwt/core/linker/SymbolMapsLinker.java
+++ b/dev/core/src/com/google/gwt/core/linker/SymbolMapsLinker.java
@@ -24,10 +24,12 @@
 import com.google.gwt.core.ext.linker.EmittedArtifact;
 import com.google.gwt.core.ext.linker.LinkerOrder;
 import com.google.gwt.core.ext.linker.SelectionProperty;
+import com.google.gwt.core.ext.linker.SymbolData;
 import com.google.gwt.core.ext.linker.LinkerOrder.Order;
 
 import java.io.ByteArrayOutputStream;
 import java.io.PrintWriter;
+import java.net.URI;
 import java.util.Map;
 import java.util.SortedMap;
 
@@ -44,7 +46,7 @@
    * This value is appended to the strong name of the CompilationResult to form
    * the symbol map's filename.
    */
-  public static final String STRONG_NAME_SUFFIX = "_symbolMap.properties";
+  public static final String STRONG_NAME_SUFFIX = ".symbolMap";
 
   @Override
   public String getDescription() {
@@ -107,12 +109,32 @@
       pw.println(" }");
     }
 
-    for (Map.Entry<String, String> entry : result.getSymbolMap().entrySet()) {
-      // Don't use an actual Properties object because it emits a timestamp
-      pw.print(entry.getKey());
-      pw.print(" = ");
+    pw.println("# jsName, jsniIdent, className, memberName, sourceUri, sourceLine");
+    for (Map.Entry<SymbolData, String> entry : result.getSymbolMap().entrySet()) {
+      SymbolData symbol = entry.getKey();
+
       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());
+    }
+  }
 }
diff --git a/dev/core/src/com/google/gwt/dev/PermutationResult.java b/dev/core/src/com/google/gwt/dev/PermutationResult.java
index 67e7da8..4f9aa6f 100644
--- a/dev/core/src/com/google/gwt/dev/PermutationResult.java
+++ b/dev/core/src/com/google/gwt/dev/PermutationResult.java
@@ -16,6 +16,7 @@
 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;
@@ -38,5 +39,5 @@
   /**
    * The symbol map for the permutation.
    */
-  SortedMap<String, String> getSymbolMap();
+  SortedMap<SymbolData, String> getSymbolMap();
 }
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 edadbb8..345c8b5 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -18,6 +18,7 @@
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
 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.soyc.Range;
 import com.google.gwt.core.ext.soyc.SplitPointRecorder;
@@ -120,10 +121,10 @@
   private static class PermutationResultImpl implements PermutationResult {
     private final ArtifactSet artifacts = new ArtifactSet();
     private final String[] js;
-    private final SortedMap<String, String> symbolMap;
+    private final SortedMap<SymbolData, String> symbolMap;
 
     public PermutationResultImpl(String[] js,
-        SortedMap<String, String> symbolMap) {
+        SortedMap<SymbolData, String> symbolMap) {
       this.js = js;
       this.symbolMap = symbolMap;
     }
@@ -136,7 +137,7 @@
       return js;
     }
 
-    public SortedMap<String, String> getSymbolMap() {
+    public SortedMap<SymbolData, String> getSymbolMap() {
       return symbolMap;
     }
   }
@@ -167,7 +168,8 @@
       JProgram jprogram = ast.getJProgram();
       JsProgram jsProgram = ast.getJsProgram();
       JJSOptions options = unifiedAst.getOptions();
-      Map<String, JsName> symbolTable = new HashMap<String, JsName>();
+      Map<SymbolData, JsName> symbolTable = new TreeMap<SymbolData, JsName>(
+          new SymbolData.ClassIdentComparator());
 
       ResolveRebinds.exec(jprogram, rebindAnswers);
 
@@ -278,7 +280,7 @@
         }
       }
 
-      SortedMap<String, String> symbolMap = makeSymbolMap(symbolTable);
+      SortedMap<SymbolData, String> symbolMap = makeSymbolMap(symbolTable);
 
       PermutationResult toReturn = new PermutationResultImpl(js, symbolMap);
       if (sourceInfoMaps != null) {
@@ -788,12 +790,15 @@
     return amp.makeStatement();
   }
 
-  private static SortedMap<String, String> makeSymbolMap(
-      Map<String, JsName> symbolTable) {
+  private static SortedMap<SymbolData, String> makeSymbolMap(
+      Map<SymbolData, JsName> symbolTable) {
 
-    SortedMap<String, String> toReturn = new TreeMap<String, String>();
+    SortedMap<SymbolData, String> toReturn = new TreeMap<SymbolData, String>(
+        new SymbolData.ClassIdentComparator());
 
-    for (Map.Entry<String, JsName> entry : symbolTable.entrySet()) {
+    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());
     }
 
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 b9c5310..5ee7364 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,10 @@
  */
 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.Correlation;
+import com.google.gwt.dev.jjs.HasSourceInfo;
 import com.google.gwt.dev.jjs.InternalCompilerException;
 import com.google.gwt.dev.jjs.JsOutputOption;
 import com.google.gwt.dev.jjs.SourceInfo;
@@ -373,12 +376,14 @@
     }
 
     private void recordSymbol(JReferenceType x, JsName jsName) {
-      assert !symbolTable.containsKey(x.getName());
-      symbolTable.put(x.getName(), jsName);
+      SymbolData symbolData = StandardSymbolData.forClass(x.getName(),
+          x.getSourceInfo().getFileName(), x.getSourceInfo().getStartLine());
+      assert !symbolTable.containsKey(symbolData);
+      symbolTable.put(symbolData, jsName);
     }
 
-    private <T extends HasEnclosingType & HasName> void recordSymbol(T x,
-        JsName jsName) {
+    private <T extends HasEnclosingType & HasName & HasSourceInfo> void recordSymbol(
+        T x, JsName jsName) {
       StringBuilder sb = new StringBuilder();
       sb.append(x.getEnclosingType().getName());
       sb.append("::");
@@ -405,10 +410,13 @@
         sb.append(')');
       }
 
-      assert !symbolTable.containsKey(sb.toString()) : "Duplicate symbol "
+      SymbolData symbolData = StandardSymbolData.forMember(
+          x.getEnclosingType().getName(), x.getName(), sb.toString(),
+          x.getSourceInfo().getFileName(), x.getSourceInfo().getStartLine());
+      assert !symbolTable.containsKey(symbolData) : "Duplicate symbol "
           + "recorded " + jsName.getIdent() + " for " + x.getName()
           + " and key " + sb.toString();
-      symbolTable.put(sb.toString(), jsName);
+      symbolTable.put(symbolData, jsName);
     }
   }
 
@@ -1796,7 +1804,7 @@
   }
 
   public static JavaToJavaScriptMap exec(JProgram program, JsProgram jsProgram,
-      JsOutputOption output, Map<String, JsName> symbolTable) {
+      JsOutputOption output, Map<SymbolData, JsName> symbolTable) {
     GenerateJavaScriptAST generateJavaScriptAST = new GenerateJavaScriptAST(
         program, jsProgram, output, symbolTable);
     return generateJavaScriptAST.execImpl();
@@ -1870,10 +1878,10 @@
   /**
    * Maps JsNames to machine-usable identifiers.
    */
-  private final Map<String, JsName> symbolTable;
+  private final Map<SymbolData, JsName> symbolTable;
 
   private GenerateJavaScriptAST(JProgram program, JsProgram jsProgram,
-      JsOutputOption output, Map<String, JsName> symbolTable) {
+      JsOutputOption output, Map<SymbolData, JsName> symbolTable) {
     this.program = program;
     typeOracle = program.typeOracle;
     this.jsProgram = jsProgram;