Merge "Refactor the virtual file distribution"
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index bba049c..414af25 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -114,7 +114,7 @@
       SortAnnotations sortAnnotations = new SortAnnotations();
       application.classes().forEach((clazz) -> clazz.addDependencies(sortAnnotations));
       Map<Integer, VirtualFile> newFiles =
-          VirtualFile.fileSetFrom(this, packageDistribution, executorService);
+          new VirtualFile.Distributor(this, packageDistribution, executorService).run();
 
       // Write the dex files and the Proguard mapping file in parallel.
       LinkedHashMap<VirtualFile, Future<byte[]>> dexDataFutures = new LinkedHashMap<>();
diff --git a/src/main/java/com/android/tools/r8/dex/VirtualFile.java b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
index 4415ea7..a0ce2e2 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -81,119 +81,6 @@
     return classDescriptors;
   }
 
-  public static Map<Integer, VirtualFile> fileSetFrom(
-      ApplicationWriter writer,
-      PackageDistribution packageDistribution,
-      ExecutorService executorService)
-      throws ExecutionException, IOException {
-    // Strategy for distributing classes for write out:
-    // 1. Place all files in the package distribution file in the proposed files (if any).
-    // 2. Place the remaining files based on their packages in sorted order.
-    DexApplication application = writer.application;
-    InternalOptions options = writer.options;
-    Map<Integer, VirtualFile> nameToFileMap = new LinkedHashMap<>();
-
-    if (options.outputMode == OutputMode.FilePerClass) {
-      assert packageDistribution == null :
-          "Cannot combine package distribution definition with file-per-class option.";
-      // Assign dedicated virtual files for all program classes.
-      for (DexProgramClass clazz : application.classes()) {
-        VirtualFile file = new VirtualFile(nameToFileMap.size(), writer.namingLens);
-        nameToFileMap.put(nameToFileMap.size(), file);
-        file.addClass(clazz);
-        file.commitTransaction();
-      }
-      return nameToFileMap;
-    }
-
-    if (packageDistribution != null) {
-      int maxReferencedIndex = packageDistribution.maxReferencedIndex();
-      for (int index = 0; index <= maxReferencedIndex; index++) {
-        VirtualFile file = new VirtualFile(index, writer.namingLens);
-        nameToFileMap.put(index, file);
-      }
-    } else {
-      // If we had no map we default to 1 file, the package populator will add more if needed.
-      nameToFileMap.put(0, new VirtualFile(0, writer.namingLens));
-    }
-    Set<DexProgramClass> classes = Sets.newHashSet(application.classes());
-
-    // Compute the original names.
-    Map<DexProgramClass, String> originalNames = computeOriginalNameMapping(classes,
-        application.getProguardMap());
-
-    if (application.mainDexList != null) {
-      VirtualFile mainDexFile = nameToFileMap.get(0);
-      for (DexType type : application.mainDexList) {
-        DexClass clazz = application.definitionFor(type);
-        if (clazz != null && clazz.isProgramClass()) {
-          DexProgramClass programClass = (DexProgramClass) clazz;
-          mainDexFile.addClass(programClass);
-          if (mainDexFile.isFull()) {
-            throw new CompilationError("Cannot fit requested classes in main-dex file.");
-          }
-          classes.remove(programClass);
-        } else {
-          System.out.println(
-              "WARNING: Application does not contain `"
-                  + type.toSourceString()
-                  + "` as referenced in main-dex-list.");
-        }
-        mainDexFile.commitTransaction();
-      }
-    }
-
-    // Sort the classes based on the original names.
-    // This with make classes from the same package be adjacent.
-    classes = sortClassesByPackage(classes, originalNames);
-
-    Set<String> usedPrefixes = null;
-    if (packageDistribution != null) {
-      ArrayList<Future<List<DexProgramClass>>> futures = new ArrayList<>(nameToFileMap.size());
-      usedPrefixes = packageDistribution.getFiles();
-      for (VirtualFile file : nameToFileMap.values()) {
-        PackageMapPopulator populator =
-            new PackageMapPopulator(file, classes, packageDistribution, originalNames);
-        futures.add(executorService.submit(populator));
-      }
-      ThreadUtils.awaitFutures(futures).forEach(classes::removeAll);
-    }
-
-    // TODO(zerny): Add the package map to AndroidApp and refactor its generation.
-    Path newPackageMap = Paths.get("package.map");
-    Map<String, Integer> newAssignments;
-    if (classes.isEmpty()) {
-      newAssignments = Collections.emptyMap();
-    } else {
-      newAssignments =
-          new PackageSplitPopulator(
-              nameToFileMap, classes, originalNames, usedPrefixes, application.dexItemFactory,
-              options, writer.namingLens)
-              .call();
-      if (!newAssignments.isEmpty() && nameToFileMap.size() > 1) {
-        if (packageDistribution == null) {
-          System.out.println(" * Consider using a package map to improve patch sizes.");
-        } else {
-          System.err.println(" * The used package map is missing entries. The following default "
-              + "mappings have been used:");
-          Writer output = new OutputStreamWriter(System.err);
-          for (Entry<String, Integer> entry : newAssignments.entrySet()) {
-            output.write("    ");
-            PackageDistribution.formatEntry(entry, output);
-            output.write("\n");
-          }
-          output.flush();
-          System.err.println(" * Consider updating the map.");
-        }
-      }
-    }
-    if (packageDistribution != null || nameToFileMap.size() > 1) {
-      System.out.println(" - " + newPackageMap.toString());
-      PackageDistribution.writePackageToFileMap(newPackageMap, newAssignments, packageDistribution);
-    }
-    return nameToFileMap;
-  }
-
   public static String deriveCommonPrefixAndSanityCheck(List<String> fileNames) {
     Iterator<String> nameIterator = fileNames.iterator();
     String first = nameIterator.next();
@@ -326,6 +213,132 @@
     return indexedItems.classes;
   }
 
+  public static class Distributor {
+    private final ApplicationWriter writer;
+    private final PackageDistribution packageDistribution;
+    private final ExecutorService executorService;
+
+    private final Map<Integer, VirtualFile> nameToFileMap = new LinkedHashMap<>();
+    public Distributor(
+        ApplicationWriter writer,
+        PackageDistribution packageDistribution,
+        ExecutorService executorService) {
+      this.writer = writer;
+      this.packageDistribution = packageDistribution;
+      this.executorService = executorService;
+    }
+
+    public Map<Integer, VirtualFile> run() throws ExecutionException, IOException {
+      // Strategy for distributing classes for write out:
+      // 1. Place all files in the package distribution file in the proposed files (if any).
+      // 2. Place the remaining files based on their packages in sorted order.
+      DexApplication application = writer.application;
+      InternalOptions options = writer.options;
+      Map<Integer, VirtualFile> nameToFileMap = new LinkedHashMap<>();
+
+      if (options.outputMode == OutputMode.FilePerClass) {
+        assert packageDistribution == null :
+            "Cannot combine package distribution definition with file-per-class option.";
+        // Assign dedicated virtual files for all program classes.
+        for (DexProgramClass clazz : application.classes()) {
+          VirtualFile file = new VirtualFile(nameToFileMap.size(), writer.namingLens);
+          nameToFileMap.put(nameToFileMap.size(), file);
+          file.addClass(clazz);
+          file.commitTransaction();
+        }
+        return nameToFileMap;
+      }
+
+      if (packageDistribution != null) {
+        int maxReferencedIndex = packageDistribution.maxReferencedIndex();
+        for (int index = 0; index <= maxReferencedIndex; index++) {
+          VirtualFile file = new VirtualFile(index, writer.namingLens);
+          nameToFileMap.put(index, file);
+        }
+      } else {
+        // If we had no map we default to 1 file, the package populator will add more if needed.
+        nameToFileMap.put(0, new VirtualFile(0, writer.namingLens));
+      }
+      Set<DexProgramClass> classes = Sets.newHashSet(application.classes());
+
+      // Compute the original names.
+      Map<DexProgramClass, String> originalNames = computeOriginalNameMapping(classes,
+          application.getProguardMap());
+
+      if (application.mainDexList != null) {
+        VirtualFile mainDexFile = nameToFileMap.get(0);
+        for (DexType type : application.mainDexList) {
+          DexClass clazz = application.definitionFor(type);
+          if (clazz != null && clazz.isProgramClass()) {
+            DexProgramClass programClass = (DexProgramClass) clazz;
+            mainDexFile.addClass(programClass);
+            if (mainDexFile.isFull()) {
+              throw new CompilationError("Cannot fit requested classes in main-dex file.");
+            }
+            classes.remove(programClass);
+          } else {
+            System.out.println(
+                "WARNING: Application does not contain `"
+                    + type.toSourceString()
+                    + "` as referenced in main-dex-list.");
+          }
+          mainDexFile.commitTransaction();
+        }
+      }
+
+      // Sort the classes based on the original names.
+      // This with make classes from the same package be adjacent.
+      classes = sortClassesByPackage(classes, originalNames);
+
+      Set<String> usedPrefixes = null;
+      if (packageDistribution != null) {
+        ArrayList<Future<List<DexProgramClass>>> futures = new ArrayList<>(nameToFileMap.size());
+        usedPrefixes = packageDistribution.getFiles();
+        for (VirtualFile file : nameToFileMap.values()) {
+          PackageMapPopulator populator =
+              new PackageMapPopulator(file, classes, packageDistribution, originalNames);
+          futures.add(executorService.submit(populator));
+        }
+        ThreadUtils.awaitFutures(futures).forEach(classes::removeAll);
+      }
+
+      // TODO(zerny): Add the package map to AndroidApp and refactor its generation.
+      Path newPackageMap = Paths.get("package.map");
+      Map<String, Integer> newAssignments;
+      if (classes.isEmpty()) {
+        newAssignments = Collections.emptyMap();
+      } else {
+        newAssignments =
+            new PackageSplitPopulator(
+                nameToFileMap, classes, originalNames, usedPrefixes, application.dexItemFactory,
+                options, writer.namingLens)
+                .call();
+        if (!newAssignments.isEmpty() && nameToFileMap.size() > 1) {
+          if (packageDistribution == null) {
+            System.out.println(" * Consider using a package map to improve patch sizes.");
+          } else {
+            System.err.println(" * The used package map is missing entries. The following default "
+                + "mappings have been used:");
+            Writer output = new OutputStreamWriter(System.err);
+            for (Entry<String, Integer> entry : newAssignments.entrySet()) {
+              output.write("    ");
+              PackageDistribution.formatEntry(entry, output);
+              output.write("\n");
+            }
+            output.flush();
+            System.err.println(" * Consider updating the map.");
+          }
+        }
+      }
+      if (packageDistribution != null || nameToFileMap.size() > 1) {
+        System.out.println(" - " + newPackageMap.toString());
+        PackageDistribution.writePackageToFileMap(
+            newPackageMap, newAssignments, packageDistribution);
+      }
+      return nameToFileMap;
+    }
+  }
+
   private static class VirtualFileIndexedItemCollection implements IndexedItemCollection {
 
     final int id;