Merge "Support for dex signature -> source position map."
diff --git a/src/com/google/doclava/Doclava.java b/src/com/google/doclava/Doclava.java
index 9aaf577..57a4b5a 100644
--- a/src/com/google/doclava/Doclava.java
+++ b/src/com/google/doclava/Doclava.java
@@ -189,6 +189,7 @@
     String privateApiFile = null;
     String privateDexApiFile = null;
     String debugStubsFile = "";
+    String apiMappingFile = null;
     HashSet<String> stubPackages = null;
     HashSet<String> stubImportPackages = null;
     boolean stubSourceOnly = false;
@@ -316,6 +317,8 @@
         privateApiFile = a[1];
       } else if (a[0].equals("-privateDexApi")) {
         privateDexApiFile = a[1];
+      } else if (a[0].equals("-apiMapping")) {
+        apiMappingFile = a[1];
       } else if (a[0].equals("-nodocs")) {
         generateDocs = false;
       } else if (a[0].equals("-noassets")) {
@@ -554,10 +557,10 @@
     // Stubs
     if (stubsDir != null || apiFile != null || dexApiFile != null || proguardFile != null
         || removedApiFile != null || removedDexApiFile != null || exactApiFile != null
-        || privateApiFile != null || privateDexApiFile != null) {
+        || privateApiFile != null || privateDexApiFile != null || apiMappingFile != null) {
       Stubs.writeStubsAndApi(stubsDir, apiFile, dexApiFile, proguardFile, removedApiFile,
-          removedDexApiFile, exactApiFile, privateApiFile, privateDexApiFile, stubPackages,
-          stubImportPackages, stubSourceOnly);
+          removedDexApiFile, exactApiFile, privateApiFile, privateDexApiFile, apiMappingFile,
+          stubPackages, stubImportPackages, stubSourceOnly);
     }
 
     Errors.printErrors();
@@ -864,6 +867,9 @@
     if (option.equals("-privateDexApi")) {
       return 2;
     }
+    if (option.equals("-apiMapping")) {
+      return 2;
+    }
     if (option.equals("-nodocs")) {
       return 1;
     }
diff --git a/src/com/google/doclava/Stubs.java b/src/com/google/doclava/Stubs.java
index 343a020..3c9138a 100644
--- a/src/com/google/doclava/Stubs.java
+++ b/src/com/google/doclava/Stubs.java
@@ -48,8 +48,8 @@
 public class Stubs {
   public static void writeStubsAndApi(String stubsDir, String apiFile, String dexApiFile,
       String keepListFile, String removedApiFile, String removedDexApiFile, String exactApiFile,
-      String privateApiFile, String privateDexApiFile, HashSet<String> stubPackages,
-      HashSet<String> stubImportPackages, boolean stubSourceOnly) {
+      String privateApiFile, String privateDexApiFile, String apiMappingFile,
+      HashSet<String> stubPackages, HashSet<String> stubImportPackages, boolean stubSourceOnly) {
     // figure out which classes we need
     final HashSet<ClassInfo> notStrippable = new HashSet<ClassInfo>();
     Collection<ClassInfo> all = Converter.allClasses();
@@ -62,6 +62,7 @@
     PrintStream exactApiWriter = null;
     PrintStream privateApiWriter = null;
     PrintStream privateDexApiWriter = null;
+    PrintStream apiMappingWriter = null;
 
     if (apiFile != null) {
       try {
@@ -148,6 +149,17 @@
             "Cannot open file for write");
       }
     }
+    if (apiMappingFile != null) {
+      try {
+        File apiMapping = new File(apiMappingFile);
+        apiMapping.getParentFile().mkdirs();
+        apiMappingWriter = new PrintStream(
+            new BufferedOutputStream(new FileOutputStream(apiMapping)));
+      } catch (FileNotFoundException e) {
+        Errors.error(Errors.IO_ERROR, new SourcePositionInfo(apiMappingFile, 0, 0),
+            "Cannot open file for write");
+      }
+    }
     // If a class is public or protected, not hidden, not imported and marked as included,
     // then we can't strip it
     for (ClassInfo cl : all) {
@@ -264,7 +276,7 @@
     }
 
     if (privateApiWriter != null || privateDexApiWriter != null || removedApiWriter != null
-            || removedDexApiWriter != null) {
+            || removedDexApiWriter != null || apiMappingWriter != null) {
       allClassesByPackage = Converter.allClasses().stream()
           // Make sure that the files only contains information from the required packages.
           .filter(ci -> stubPackages == null
@@ -320,6 +332,12 @@
       privateDexApiWriter.close();
     }
 
+    // Write out the API mapping
+    if (apiMappingWriter != null) {
+      writeApiMapping(apiMappingWriter, allClassesByPackage);
+      apiMappingWriter.close();
+    }
+
     // Write out the removed API
     if (removedApiWriter != null) {
       writeApi(removedApiWriter, allClassesByPackage, removedEmit, removedReference);
@@ -1459,6 +1477,34 @@
     }
   }
 
+  static void writeApiMapping(PrintStream mappingWriter,
+      Map<PackageInfo, List<ClassInfo>> classesByPackage) {
+
+    for (PackageInfo pkg : classesByPackage.keySet().stream().sorted(PackageInfo.comparator)
+            .collect(Collectors.toList())) {
+      if (pkg.name().equals(PackageInfo.DEFAULT_PACKAGE)) continue;
+      for (ClassInfo cl : classesByPackage.get(pkg).stream().sorted(ClassInfo.comparator)
+              .collect(Collectors.toList())) {
+        cl.getExhaustiveConstructors().stream().sorted(MethodInfo.comparator).forEach(method -> {
+          writeMethodDexApi(mappingWriter, cl, method);
+          writeSourcePositionInfo(mappingWriter, method);
+        });
+        cl.getExhaustiveMethods().stream().sorted(MethodInfo.comparator).forEach(method -> {
+          writeMethodDexApi(mappingWriter, cl, method);
+          writeSourcePositionInfo(mappingWriter, method);
+        });
+        cl.getExhaustiveEnumConstants().stream().sorted(FieldInfo.comparator).forEach(enumInfo -> {
+          writeFieldDexApi(mappingWriter, cl, enumInfo);
+          writeSourcePositionInfo(mappingWriter, enumInfo);
+        });
+        cl.getExhaustiveFields().stream().sorted(FieldInfo.comparator).forEach(field -> {
+          writeFieldDexApi(mappingWriter, cl, field);
+          writeSourcePositionInfo(mappingWriter, field);
+        });
+      }
+    }
+  }
+
   /**
    * Write the removed members of the class to removed.txt
    */
@@ -1765,6 +1811,11 @@
     apiWriter.print(";\n");
   }
 
+  static void writeSourcePositionInfo(PrintStream writer, DocInfo docInfo) {
+    SourcePositionInfo pos = docInfo.position();
+    writer.println(pos);
+  }
+
   static void writeMethodDexApi(PrintStream apiWriter, ClassInfo cl, MethodInfo mi) {
     apiWriter.print(toSlashFormat(cl.qualifiedName()));
     apiWriter.print("->");