Added multi-threaded support to processing of class files in dx.

Change-Id: Ideb54c414073a9651b21ce0697e6444fa80f146d
diff --git a/dx/src/com/android/dx/command/dexer/Main.java b/dx/src/com/android/dx/command/dexer/Main.java
index 363741a..204caa7 100644
--- a/dx/src/com/android/dx/command/dexer/Main.java
+++ b/dx/src/com/android/dx/command/dexer/Main.java
@@ -45,6 +45,9 @@
 import java.util.ArrayList;
 import java.util.Map;
 import java.util.TreeMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
 import java.util.jar.Attributes;
 import java.util.jar.JarEntry;
 import java.util.jar.JarOutputStream;
@@ -139,6 +142,12 @@
      */
     private static TreeMap<String, byte[]> outputResources;
 
+    /** thread pool object used for multi-threaded file processing */
+    private static ExecutorService threadPool;
+
+    /** true if any files are successfully processed */
+    private static boolean anyFilesProcessed;
+
     /**
      * This class is uninstantiable.
      */
@@ -212,12 +221,18 @@
             outputDex.setDumpWidth(args.dumpWidth);
         }
 
-        boolean any = false;
+        anyFilesProcessed = false;
         String[] fileNames = args.fileNames;
 
+        if (args.numThreads > 1) {
+            threadPool = Executors.newFixedThreadPool(args.numThreads);
+        }
+
         try {
             for (int i = 0; i < fileNames.length; i++) {
-                any |= processOne(fileNames[i]);
+                if (processOne(fileNames[i])) {
+                    anyFilesProcessed = true;
+                }
             }
         } catch (StopProcessing ex) {
             /*
@@ -226,6 +241,15 @@
              */
         }
 
+        if (args.numThreads > 1) {
+            try {
+                threadPool.shutdown();
+                threadPool.awaitTermination(600L, TimeUnit.SECONDS);
+            } catch (InterruptedException ex) {
+                throw new RuntimeException("Timed out waiting for threads.");
+            }
+        }
+
         if (warnings != 0) {
             DxConsole.err.println(warnings + " warning" +
                                ((warnings == 1) ? "" : "s"));
@@ -237,7 +261,7 @@
             return false;
         }
 
-        if (!(any || args.emptyOk)) {
+        if (!(anyFilesProcessed || args.emptyOk)) {
             DxConsole.err.println("no classfiles specified");
             return false;
         }
@@ -263,7 +287,12 @@
         opener = new ClassPathOpener(pathname, false,
                 new ClassPathOpener.Consumer() {
             public boolean processFileBytes(String name, byte[] bytes) {
-                return Main.processFileBytes(name, bytes);
+                if (args.numThreads > 1) {
+                    threadPool.execute(new ParallelProcessor(name, bytes));
+                    return false;
+                } else {
+                    return Main.processFileBytes(name, bytes);
+                }
             }
             public void onException(Exception ex) {
                 if (ex instanceof StopProcessing) {
@@ -310,11 +339,15 @@
 
         if (isClass) {
             if (keepResources && args.keepClassesInJar) {
-                outputResources.put(fixedName, bytes);
+                synchronized (outputResources) {
+                    outputResources.put(fixedName, bytes);
+                }
             }
             return processClass(fixedName, bytes);
         } else {
-            outputResources.put(fixedName, bytes);
+            synchronized (outputResources) {
+                outputResources.put(fixedName, bytes);
+            }
             return true;
         }
     }
@@ -335,7 +368,9 @@
         try {
             ClassDefItem clazz =
                 CfTranslator.translate(name, bytes, args.cfOptions);
-            outputDex.add(clazz);
+            synchronized (outputDex) {
+                outputDex.add(clazz);
+            }
             return true;
         } catch (ParseException ex) {
             DxConsole.err.println("\ntrouble processing:");
@@ -799,6 +834,9 @@
         /** Options for dex.cf.* */
         public CfOptions cfOptions;
 
+        /** number of threads to run with */
+        public int numThreads = 1;
+
         /**
          * Parses the given command-line arguments.
          *
@@ -882,6 +920,9 @@
                     }
                 } else if (arg.equals("--no-locals")) {
                     localInfo = false;
+                } else if (arg.startsWith("--num-threads=")) {
+                    arg = arg.substring(arg.indexOf('=') + 1);
+                    numThreads = Integer.parseInt(arg);
                 } else {
                     System.err.println("unknown option: " + arg);
                     throw new UsageException();
@@ -927,4 +968,33 @@
             cfOptions.warn = DxConsole.err;
         }
     }
+
+    /** Runnable helper class to process files in multiple threads */
+    private static class ParallelProcessor implements Runnable {
+
+        String path;
+        byte[] bytes;
+
+        /**
+         * Constructs an instance.
+         *
+         * @param path {@code non-null;} filename of element. May not be a valid
+         * filesystem path.
+         * @param bytes {@code non-null;} file data
+         */
+        private ParallelProcessor(String path, byte bytes[]) {
+            this.path = path;
+            this.bytes = bytes;
+        }
+
+        /**
+         * Task run by each thread in the thread pool. Runs processFileBytes
+         * with the given path and bytes.
+         */
+        public void run() {
+            if (Main.processFileBytes(path, bytes)) {
+                anyFilesProcessed = true;
+            }
+        }
+    }
 }
diff --git a/dx/src/com/android/dx/rop/code/RegisterSpec.java b/dx/src/com/android/dx/rop/code/RegisterSpec.java
index f1ac563..df5f338 100644
--- a/dx/src/com/android/dx/rop/code/RegisterSpec.java
+++ b/dx/src/com/android/dx/rop/code/RegisterSpec.java
@@ -60,16 +60,18 @@
      */
     private static RegisterSpec intern(int reg, TypeBearer type,
             LocalItem local) {
-        theInterningItem.set(reg, type, local);
-        RegisterSpec found = theInterns.get(theInterningItem);
+        synchronized (theInterns) {
+            theInterningItem.set(reg, type, local);
+            RegisterSpec found = theInterns.get(theInterningItem);
 
-        if (found != null) {
+            if (found != null) {
+                return found;
+            }
+
+            found = theInterningItem.toRegisterSpec();
+            theInterns.put(found, found);
             return found;
         }
-
-        found = theInterningItem.toRegisterSpec();
-        theInterns.put(found, found);
-        return found;
     }
 
     /**
diff --git a/dx/src/com/android/dx/rop/cst/CstType.java b/dx/src/com/android/dx/rop/cst/CstType.java
index 593adf8..077ad06 100644
--- a/dx/src/com/android/dx/rop/cst/CstType.java
+++ b/dx/src/com/android/dx/rop/cst/CstType.java
@@ -123,14 +123,16 @@
      * @return {@code non-null;} an appropriately-constructed instance
      */
     public static CstType intern(Type type) {
-        CstType cst = interns.get(type);
+        synchronized (interns) {
+            CstType cst = interns.get(type);
 
-        if (cst == null) {
-            cst = new CstType(type);
-            interns.put(type, cst);
+            if (cst == null) {
+                cst = new CstType(type);
+                interns.put(type, cst);
+            }
+
+            return cst;
         }
-
-        return cst;
     }
 
     /**
diff --git a/dx/src/com/android/dx/rop/type/Prototype.java b/dx/src/com/android/dx/rop/type/Prototype.java
index cbd5328..ec46ff9 100644
--- a/dx/src/com/android/dx/rop/type/Prototype.java
+++ b/dx/src/com/android/dx/rop/type/Prototype.java
@@ -55,7 +55,10 @@
             throw new NullPointerException("descriptor == null");
         }
 
-        Prototype result = internTable.get(descriptor);
+        Prototype result;
+        synchronized (internTable) {
+            result = internTable.get(descriptor);
+        }
         if (result != null) {
             return result;
         }
diff --git a/dx/src/com/android/dx/rop/type/Type.java b/dx/src/com/android/dx/rop/type/Type.java
index 244584d..eefd55b 100644
--- a/dx/src/com/android/dx/rop/type/Type.java
+++ b/dx/src/com/android/dx/rop/type/Type.java
@@ -292,7 +292,10 @@
      * invalid syntax
      */
     public static Type intern(String descriptor) {
-        Type result = internTable.get(descriptor);
+        Type result;
+        synchronized (internTable) {
+            result = internTable.get(descriptor);
+        }
         if (result != null) {
             return result;
         }