Various improvements to dexdeps.

This includes:
* Adding an option to only produce lists of dependent classes (and not
  also fields and methods).
* Adding the ability to process multiple files on a single invocation.
* Adding a label at the head of the section for each file.
* Neatening up Output a bit by factoring "System.out" into a static field.

Change-Id: Id9a691d23afd18f82ab3790cb760cfd12e14bc86
diff --git a/tools/dexdeps/README.txt b/tools/dexdeps/README.txt
index 060aecb..22380ce 100644
--- a/tools/dexdeps/README.txt
+++ b/tools/dexdeps/README.txt
@@ -9,7 +9,7 @@
 
 Basic usage:
 
-  dexdeps [options] <file.{dex,apk,jar}>
+  dexdeps [options] <file.{dex,apk,jar}> ...
 
 For zip archives (including .jar and .apk), dexdeps will look for a
 "classes.dex" entry.
@@ -25,3 +25,8 @@
 
     "xml" produces a larger output file, readable with an XML browser.  Types
     are shown in a more human-readable form (e.g. "[I" becomes "int[]").
+
+  --just-classes
+
+    Indicates that output should only include a list of classes, as
+    opposed to also listing fields and methods.
diff --git a/tools/dexdeps/src/com/android/dexdeps/Main.java b/tools/dexdeps/src/com/android/dexdeps/Main.java
index d48889d..410694a 100644
--- a/tools/dexdeps/src/com/android/dexdeps/Main.java
+++ b/tools/dexdeps/src/com/android/dexdeps/Main.java
@@ -29,10 +29,16 @@
 public class Main {
     private static final String CLASSES_DEX = "classes.dex";
 
-    private String mInputFileName;
+    private String[] mInputFileNames;
     private String mOutputFormat = "xml";
 
     /**
+     * whether to only emit info about classes used; when {@code false},
+     * info about fields and methods is also emitted
+     */
+    private boolean mJustClasses = false;
+
+    /**
      * Entry point.
      */
     public static void main(String[] args) {
@@ -46,17 +52,31 @@
     void run(String[] args) {
         try {
             parseArgs(args);
-            RandomAccessFile raf = openInputFile();
-            DexData dexData = new DexData(raf);
-            dexData.load();
+            boolean first = true;
 
-            Output.generate(dexData, mOutputFormat);
+            for (String fileName : mInputFileNames) {
+                RandomAccessFile raf = openInputFile(fileName);
+                DexData dexData = new DexData(raf);
+                dexData.load();
+
+                if (first) {
+                    first = false;
+                    Output.generateFirstHeader(fileName, mOutputFormat);
+                } else {
+                    Output.generateHeader(fileName, mOutputFormat);
+                }
+
+                Output.generate(dexData, mOutputFormat, mJustClasses);
+                Output.generateFooter(mOutputFormat);
+                raf.close();
+            }
         } catch (UsageException ue) {
             usage();
             System.exit(2);
         } catch (IOException ioe) {
-            if (ioe.getMessage() != null)
+            if (ioe.getMessage() != null) {
                 System.err.println("Failed: " + ioe);
+            }
             System.exit(1);
         } catch (DexDataException dde) {
             /* a message was already reported, just bail quietly */
@@ -65,16 +85,18 @@
     }
 
     /**
-     * Opens the input file, which could be a .dex or a .jar/.apk with a
+     * Opens an input file, which could be a .dex or a .jar/.apk with a
      * classes.dex inside.  If the latter, we extract the contents to a
      * temporary file.
+     *
+     * @param fileName the name of the file to open
      */
-    RandomAccessFile openInputFile() throws IOException {
+    RandomAccessFile openInputFile(String fileName) throws IOException {
         RandomAccessFile raf;
 
-        raf = openInputFileAsZip();
+        raf = openInputFileAsZip(fileName);
         if (raf == null) {
-            File inputFile = new File(mInputFileName);
+            File inputFile = new File(fileName);
             raf = new RandomAccessFile(inputFile, "r");
         }
 
@@ -82,25 +104,26 @@
     }
 
     /**
-     * Tries to open the input file as a Zip archive (jar/apk) with a
+     * Tries to open an input file as a Zip archive (jar/apk) with a
      * "classes.dex" inside.
      *
+     * @param fileName the name of the file to open
      * @return a RandomAccessFile for classes.dex, or null if the input file
      *         is not a zip archive
      * @throws IOException if the file isn't found, or it's a zip and
      *         classes.dex isn't found inside
      */
-    RandomAccessFile openInputFileAsZip() throws IOException {
+    RandomAccessFile openInputFileAsZip(String fileName) throws IOException {
         ZipFile zipFile;
 
         /*
          * Try it as a zip file.
          */
         try {
-            zipFile = new ZipFile(mInputFileName);
+            zipFile = new ZipFile(fileName);
         } catch (FileNotFoundException fnfe) {
             /* not found, no point in retrying as non-zip */
-            System.err.println("Unable to open '" + mInputFileName + "': " +
+            System.err.println("Unable to open '" + fileName + "': " +
                 fnfe.getMessage());
             throw fnfe;
         } catch (ZipException ze) {
@@ -116,7 +139,7 @@
         ZipEntry entry = zipFile.getEntry(CLASSES_DEX);
         if (entry == null) {
             System.err.println("Unable to find '" + CLASSES_DEX +
-                "' in '" + mInputFileName + "'");
+                "' in '" + fileName + "'");
             zipFile.close();
             throw new ZipException();
         }
@@ -175,28 +198,34 @@
                     throw new UsageException();
                 }
                 //System.out.println("+++ using format " + mOutputFormat);
+            } else if (arg.equals("--just-classes")) {
+                mJustClasses = true;
             } else {
                 System.err.println("Unknown option '" + arg + "'");
                 throw new UsageException();
             }
         }
 
-        // expecting one argument left
-        if (idx != args.length - 1) {
+        // We expect at least one more argument (file name).
+        int fileCount = args.length - idx;
+        if (fileCount == 0) {
             throw new UsageException();
         }
 
-        mInputFileName = args[idx];
+        mInputFileNames = new String[fileCount];
+        System.arraycopy(args, idx, mInputFileNames, 0, fileCount);
     }
 
     /**
      * Prints command-line usage info.
      */
     void usage() {
-        System.err.println("DEX dependency scanner v1.1");
-        System.err.println("Copyright (C) 2009 The Android Open Source Project\n");
-        System.err.println("Usage: dexdeps [options] <file.{dex,apk,jar}>");
-        System.err.println("Options:");
-        System.err.println("  --format={xml,brief}");
+        System.err.print(
+                "DEX dependency scanner v1.2\n" +
+                "Copyright (C) 2009 The Android Open Source Project\n\n" +
+                "Usage: dexdeps [options] <file.{dex,apk,jar}> ...\n" +
+                "Options:\n" +
+                "  --format={xml,brief}\n" +
+                "  --just-classes\n");
     }
 }
diff --git a/tools/dexdeps/src/com/android/dexdeps/Output.java b/tools/dexdeps/src/com/android/dexdeps/Output.java
index b786825..dbe3bc2 100644
--- a/tools/dexdeps/src/com/android/dexdeps/Output.java
+++ b/tools/dexdeps/src/com/android/dexdeps/Output.java
@@ -16,6 +16,8 @@
 
 package com.android.dexdeps;
 
+import java.io.PrintStream;
+
 /**
  * Generate fancy output.
  */
@@ -26,11 +28,51 @@
     private static final String IN3 = "      ";
     private static final String IN4 = "        ";
 
-    public static void generate(DexData dexData, String format) {
+    private static final PrintStream out = System.out;
+
+    private static void generateHeader0(String fileName, String format) {
         if (format.equals("brief")) {
-            printBrief(dexData);
+            if (fileName != null) {
+                out.println("File: " + fileName);
+            }
         } else if (format.equals("xml")) {
-            printXml(dexData);
+            if (fileName != null) {
+                out.println(IN0 + "<external file=\"" + fileName + "\">");
+            } else {
+                out.println(IN0 + "<external>");
+            }
+        } else {
+            /* should've been trapped in arg handler */
+            throw new RuntimeException("unknown output format");
+        }
+    }
+
+    public static void generateFirstHeader(String fileName, String format) {
+        generateHeader0(fileName, format);
+    }
+
+    public static void generateHeader(String fileName, String format) {
+        out.println();
+        generateHeader0(fileName, format);
+    }
+
+    public static void generateFooter(String format) {
+        if (format.equals("brief")) {
+            // Nothing to do.
+        } else if (format.equals("xml")) {
+            out.println("</external>");
+        } else {
+            /* should've been trapped in arg handler */
+            throw new RuntimeException("unknown output format");
+        }
+    }
+
+    public static void generate(DexData dexData, String format,
+            boolean justClasses) {
+        if (format.equals("brief")) {
+            printBrief(dexData, justClasses);
+        } else if (format.equals("xml")) {
+            printXml(dexData, justClasses);
         } else {
             /* should've been trapped in arg handler */
             throw new RuntimeException("unknown output format");
@@ -40,23 +82,29 @@
     /**
      * Prints the data in a simple human-readable format.
      */
-    static void printBrief(DexData dexData) {
+    static void printBrief(DexData dexData, boolean justClasses) {
         ClassRef[] externClassRefs = dexData.getExternalReferences();
 
-        printClassRefs(externClassRefs);
-        printFieldRefs(externClassRefs);
-        printMethodRefs(externClassRefs);
+        printClassRefs(externClassRefs, justClasses);
+
+        if (!justClasses) {
+            printFieldRefs(externClassRefs);
+            printMethodRefs(externClassRefs);
+        }
     }
 
     /**
      * Prints the list of classes in a simple human-readable format.
      */
-    static void printClassRefs(ClassRef[] classes) {
-        System.out.println("Classes:");
+    static void printClassRefs(ClassRef[] classes, boolean justClasses) {
+        if (!justClasses) {
+            out.println("Classes:");
+        }
+
         for (int i = 0; i < classes.length; i++) {
             ClassRef ref = classes[i];
 
-            System.out.println(descriptorToDot(ref.getName()));
+            out.println(descriptorToDot(ref.getName()));
         }
     }
 
@@ -64,14 +112,14 @@
      * Prints the list of fields in a simple human-readable format.
      */
     static void printFieldRefs(ClassRef[] classes) {
-        System.out.println("\nFields:");
+        out.println("\nFields:");
         for (int i = 0; i < classes.length; i++) {
             FieldRef[] fields = classes[i].getFieldArray();
 
             for (int j = 0; j < fields.length; j++) {
                 FieldRef ref = fields[j];
 
-                System.out.println(descriptorToDot(ref.getDeclClassName()) +
+                out.println(descriptorToDot(ref.getDeclClassName()) +
                     "." + ref.getName() + " : " + ref.getTypeName());
             }
         }
@@ -81,30 +129,27 @@
      * Prints the list of methods in a simple human-readable format.
      */
     static void printMethodRefs(ClassRef[] classes) {
-        System.out.println("\nMethods:");
+        out.println("\nMethods:");
         for (int i = 0; i < classes.length; i++) {
             MethodRef[] methods = classes[i].getMethodArray();
 
             for (int j = 0; j < methods.length; j++) {
                 MethodRef ref = methods[j];
 
-                System.out.println(descriptorToDot(ref.getDeclClassName()) +
+                out.println(descriptorToDot(ref.getDeclClassName()) +
                     "." + ref.getName() + " : " + ref.getDescriptor());
             }
         }
     }
 
-
     /**
      * Prints the output in XML format.
      *
      * We shouldn't need to XML-escape the field/method info.
      */
-    static void printXml(DexData dexData) {
+    static void printXml(DexData dexData, boolean justClasses) {
         ClassRef[] externClassRefs = dexData.getExternalReferences();
 
-        System.out.println(IN0 + "<external>");
-
         /*
          * Iterate through externClassRefs.  For each class, dump all of
          * the matching fields and methods.
@@ -121,24 +166,25 @@
              */
             if (!packageName.equals(prevPackage)) {
                 if (prevPackage != null) {
-                    System.out.println(IN1 + "</package>");
+                    out.println(IN1 + "</package>");
                 }
 
-                System.out.println(IN1 +
+                out.println(IN1 +
                     "<package name=\"" + packageName + "\">");
 
                 prevPackage = packageName;
             }
 
-            System.out.println(IN2 + "<class name=\"" + className + "\">");
-            printXmlFields(cref);
-            printXmlMethods(cref);
-            System.out.println(IN2 + "</class>");
+            out.println(IN2 + "<class name=\"" + className + "\">");
+            if (!justClasses) {
+                printXmlFields(cref);
+                printXmlMethods(cref);
+            }
+            out.println(IN2 + "</class>");
         }
 
         if (prevPackage != null)
-            System.out.println(IN1 + "</package>");
-        System.out.println(IN0 + "</external>");
+            out.println(IN1 + "</package>");
     }
 
     /**
@@ -149,7 +195,7 @@
         for (int i = 0; i < fields.length; i++) {
             FieldRef fref = fields[i];
 
-            System.out.println(IN3 + "<field name=\"" + fref.getName() +
+            out.println(IN3 + "<field name=\"" + fref.getName() +
                 "\" type=\"" + descriptorToDot(fref.getTypeName()) + "\"/>");
         }
     }
@@ -167,22 +213,22 @@
             constructor = mref.getName().equals("<init>");
             if (constructor) {
                 // use class name instead of method name
-                System.out.println(IN3 + "<constructor name=\"" +
+                out.println(IN3 + "<constructor name=\"" +
                     classNameOnly(declClassName) + "\">");
             } else {
-                System.out.println(IN3 + "<method name=\"" + mref.getName() +
+                out.println(IN3 + "<method name=\"" + mref.getName() +
                     "\" return=\"" + descriptorToDot(mref.getReturnTypeName()) +
                     "\">");
             }
             String[] args = mref.getArgumentTypeNames();
             for (int j = 0; j < args.length; j++) {
-                System.out.println(IN4 + "<parameter type=\"" +
+                out.println(IN4 + "<parameter type=\"" +
                     descriptorToDot(args[j]) + "\"/>");
             }
             if (constructor) {
-                System.out.println(IN3 + "</constructor>");
+                out.println(IN3 + "</constructor>");
             } else {
-                System.out.println(IN3 + "</method>");
+                out.println(IN3 + "</method>");
             }
         }
     }