Merge "Add a public ArchiveProgramResourceProvider utility to the API."
diff --git a/src/main/java/com/android/tools/r8/ArchiveProgramResourceProvider.java b/src/main/java/com/android/tools/r8/ArchiveProgramResourceProvider.java
new file mode 100644
index 0000000..b3a4bfb
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ArchiveProgramResourceProvider.java
@@ -0,0 +1,132 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8;
+
+import static com.android.tools.r8.utils.FileUtils.isClassFile;
+import static com.android.tools.r8.utils.FileUtils.isDexFile;
+
+import com.android.tools.r8.ProgramResource.Kind;
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.origin.ArchiveEntryOrigin;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.FileUtils;
+import com.google.common.io.ByteStreams;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.function.Predicate;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipException;
+import java.util.zip.ZipFile;
+
+/** Provider for archives of program resources. */
+public class ArchiveProgramResourceProvider implements ProgramResourceProvider {
+
+  public interface ZipFileSupplier {
+    ZipFile open() throws IOException;
+  }
+
+  public static boolean includeClassFileEntries(String entry) {
+    return isClassFile(Paths.get(entry));
+  }
+
+  public static boolean includeDexEntries(String entry) {
+    return isDexFile(Paths.get(entry));
+  }
+
+  public static boolean includeClassFileOrDexEntries(String entry) {
+    Path path = Paths.get(entry);
+    return isClassFile(path) || isDexFile(path);
+  }
+
+  private final Origin origin;
+  private final ZipFileSupplier supplier;
+  private final Predicate<String> include;
+
+  public static ArchiveProgramResourceProvider fromArchive(Path archive) {
+    return fromArchive(archive, ArchiveProgramResourceProvider::includeClassFileOrDexEntries);
+  }
+
+  public static ArchiveProgramResourceProvider fromArchive(
+      Path archive, Predicate<String> include) {
+    return fromSupplier(new PathOrigin(archive), () -> new ZipFile(archive.toFile()), include);
+  }
+
+  public static ArchiveProgramResourceProvider fromSupplier(
+      Origin origin, ZipFileSupplier supplier) {
+    return fromSupplier(
+        origin, supplier, ArchiveProgramResourceProvider::includeClassFileOrDexEntries);
+  }
+
+  public static ArchiveProgramResourceProvider fromSupplier(
+      Origin origin, ZipFileSupplier supplier, Predicate<String> include) {
+    return new ArchiveProgramResourceProvider(origin, supplier, include);
+  }
+
+  private ArchiveProgramResourceProvider(
+      Origin origin, ZipFileSupplier supplier, Predicate<String> include) {
+    assert origin != null;
+    assert supplier != null;
+    assert include != null;
+    this.origin = origin;
+    this.supplier = supplier;
+    this.include = include;
+  }
+
+  private List<ProgramResource> readArchive() throws IOException {
+    List<ProgramResource> dexResources = new ArrayList<>();
+    List<ProgramResource> classResources = new ArrayList<>();
+    try (ZipFile zipFile = supplier.open()) {
+      final Enumeration<? extends ZipEntry> entries = zipFile.entries();
+      while (entries.hasMoreElements()) {
+        ZipEntry entry = entries.nextElement();
+        try (InputStream stream = zipFile.getInputStream(entry)) {
+          String name = entry.getName();
+          Path path = Paths.get(name);
+          Origin entryOrigin = new ArchiveEntryOrigin(entry.getName(), origin);
+          if (include.test(name)) {
+            if (FileUtils.isDexFile(path)) {
+              dexResources.add(
+                  ProgramResource.fromBytes(
+                      entryOrigin, Kind.DEX, ByteStreams.toByteArray(stream), null));
+            } else if (isClassFile(path)) {
+              String descriptor = DescriptorUtils.guessTypeDescriptor(name);
+              classResources.add(
+                  ProgramResource.fromBytes(
+                      entryOrigin,
+                      Kind.CF,
+                      ByteStreams.toByteArray(stream),
+                      Collections.singleton(descriptor)));
+            }
+          }
+        }
+      }
+    } catch (ZipException e) {
+      throw new CompilationError("Zip error while reading archive" + e.getMessage(), e, origin);
+    }
+    if (!dexResources.isEmpty() && !classResources.isEmpty()) {
+      throw new CompilationError(
+          "Cannot create android app from an archive containing both DEX and Java-bytecode content",
+          origin);
+    }
+    return !dexResources.isEmpty() ? dexResources : classResources;
+  }
+
+  @Override
+  public Collection<ProgramResource> getProgramResources() throws ResourceException {
+    try {
+      return readArchive();
+    } catch (IOException e) {
+      throw new ResourceException(origin, e);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/BaseCommand.java b/src/main/java/com/android/tools/r8/BaseCommand.java
index 43c68af..10c92dc 100644
--- a/src/main/java/com/android/tools/r8/BaseCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCommand.java
@@ -136,6 +136,11 @@
       return self();
     }
 
+    public B addProgramResourceProvider(ProgramResourceProvider programProvider) {
+      app.addProgramResourceProvider(programProvider);
+      return self();
+    }
+
     /** Add library file resource provider. */
     public B addLibraryResourceProvider(ClassFileResourceProvider provider) {
       guard(() -> getAppBuilder().addLibraryResourceProvider(provider));
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index e18133b..9e6d79c 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -511,7 +511,9 @@
       } else if (isClassFile(file)) {
         addProgramResources(ProgramResource.fromFile(Kind.CF, file));
       } else if (isArchive(file)) {
-        addProgramResourceProvider(new ArchiveProgramResourceProvider(file, ignoreDexInArchive));
+        addProgramResourceProvider(
+            new FilteredArchiveProgramResourceProvider(
+                FilteredClassPath.unfiltered(file), ignoreDexInArchive));
       } else {
         throw new CompilationError("Unsupported source file type", new PathOrigin(file));
       }
diff --git a/src/main/java/com/android/tools/r8/utils/ArchiveProgramResourceProvider.java b/src/main/java/com/android/tools/r8/utils/ArchiveProgramResourceProvider.java
deleted file mode 100644
index 4485688..0000000
--- a/src/main/java/com/android/tools/r8/utils/ArchiveProgramResourceProvider.java
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.utils;
-
-import com.android.tools.r8.ProgramResourceProvider;
-import com.android.tools.r8.shaking.FilteredClassPath;
-import java.nio.file.Path;
-
-/** Provider for archives of program resources. */
-public class ArchiveProgramResourceProvider extends FilteredArchiveProgramResourceProvider
-    implements ProgramResourceProvider {
-
-  ArchiveProgramResourceProvider(Path archive, boolean ignoreDexInArchive) {
-    super(FilteredClassPath.unfiltered(archive), ignoreDexInArchive);
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/D8CommandTest.java b/src/test/java/com/android/tools/r8/D8CommandTest.java
index c2f0966..7eb73df 100644
--- a/src/test/java/com/android/tools/r8/D8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/D8CommandTest.java
@@ -13,6 +13,7 @@
 import com.android.sdklib.AndroidVersion;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.origin.EmbeddedOrigin;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DirectoryClassFileProvider;
 import com.android.tools.r8.utils.OutputMode;
@@ -22,6 +23,7 @@
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.List;
+import java.util.zip.ZipFile;
 import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
@@ -258,6 +260,40 @@
     D8Command.builder().addProgramFiles(vdexFile).build();
   }
 
+  @Test
+  public void addProgramResources() throws ResourceException, CompilationFailedException {
+
+    // Stub out a custom origin to identify our resources.
+    class MyOrigin extends Origin {
+
+      public MyOrigin() {
+        super(Origin.root());
+      }
+
+      @Override
+      public String part() {
+        return "MyOrigin";
+      }
+    }
+
+    Path input = Paths.get(EXAMPLES_BUILD_DIR, "arithmetic.jar");
+    ProgramResourceProvider myProvider =
+        ArchiveProgramResourceProvider.fromSupplier(
+            new MyOrigin(), () -> new ZipFile(input.toFile()));
+    D8Command command = D8Command.builder().addProgramResourceProvider(myProvider).build();
+
+    // Check that each resource was provided by our provider.
+    ProgramResourceProvider inAppProvider =
+        command.getInputApp().getProgramResourceProviders().get(0);
+    for (ProgramResource resource : inAppProvider.getProgramResources()) {
+      Origin outermost = resource.getOrigin();
+      while (outermost.parent() != null && outermost.parent() != Origin.root()) {
+        outermost = outermost.parent();
+      }
+      assertTrue(outermost instanceof MyOrigin);
+    }
+  }
+
   private D8Command parse(String... args) throws CompilationFailedException {
     return D8Command.parse(args, EmbeddedOrigin.INSTANCE).build();
   }