Merge "Warn about and remove invalid class signatures"
diff --git a/src/main/java/com/android/tools/r8/D8CommandParser.java b/src/main/java/com/android/tools/r8/D8CommandParser.java
index 9e5d36a..93c0126 100644
--- a/src/main/java/com/android/tools/r8/D8CommandParser.java
+++ b/src/main/java/com/android/tools/r8/D8CommandParser.java
@@ -3,16 +3,79 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import static com.android.tools.r8.utils.FileUtils.isArchive;
+
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.origin.PathOrigin;
+import com.android.tools.r8.utils.ExceptionDiagnostic;
 import com.android.tools.r8.utils.FlagFile;
 import com.android.tools.r8.utils.StringDiagnostic;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Sets;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
 
 public class D8CommandParser extends BaseCompilerCommandParser {
 
+  static class OrderedClassFileResourceProvider implements ClassFileResourceProvider {
+    static class Builder {
+      private final ImmutableList.Builder<ClassFileResourceProvider> builder =
+          ImmutableList.builder();
+      boolean empty = true;
+
+      OrderedClassFileResourceProvider build() {
+        return new OrderedClassFileResourceProvider(builder.build());
+      }
+
+      Builder addClassFileResourceProvider(ClassFileResourceProvider provider) {
+        builder.add(provider);
+        empty = false;
+        return this;
+      }
+
+      boolean isEmpty() {
+        return empty;
+      }
+    }
+
+    final List<ClassFileResourceProvider> providers;
+    final Set<String> descriptors = Sets.newHashSet();
+
+    private OrderedClassFileResourceProvider(ImmutableList<ClassFileResourceProvider> providers) {
+      this.providers = providers;
+      // Collect all descriptors that can be provided.
+      this.providers.forEach(provider -> this.descriptors.addAll(provider.getClassDescriptors()));
+    }
+
+    static Builder builder() {
+      return new Builder();
+    }
+
+    @Override
+    public Set<String> getClassDescriptors() {
+      return descriptors;
+    }
+
+    @Override
+    public ProgramResource getProgramResource(String descriptor) {
+      // Search the providers in order. Return the program resource from the first provider that
+      // can provide it.
+      for (ClassFileResourceProvider provider : providers) {
+        if (provider.getClassDescriptors().contains(descriptor)) {
+          return provider.getProgramResource(descriptor);
+        }
+      }
+      return null;
+    }
+  }
+
   public static void main(String[] args) throws CompilationFailedException {
     D8Command command = parse(args, Origin.root()).build();
     if (command.isPrintHelp()) {
@@ -76,6 +139,8 @@
     Path outputPath = null;
     OutputMode outputMode = null;
     boolean hasDefinedApiLevel = false;
+    OrderedClassFileResourceProvider.Builder classpathBuilder =
+        OrderedClassFileResourceProvider.builder();
     String[] expandedArgs = FlagFile.expandFlagFiles(args, builder);
     try {
       for (int i = 0; i < expandedArgs.length; i++) {
@@ -115,7 +180,22 @@
         } else if (arg.equals("--lib")) {
           builder.addLibraryFiles(Paths.get(expandedArgs[++i]));
         } else if (arg.equals("--classpath")) {
-          builder.addClasspathFiles(Paths.get(expandedArgs[++i]));
+          Path file = Paths.get(expandedArgs[++i]);
+          try {
+            if (!Files.exists(file)) {
+              throw new NoSuchFileException(file.toString());
+            }
+            if (isArchive(file)) {
+              classpathBuilder.addClassFileResourceProvider(new ArchiveClassFileProvider(file));
+            } else if (Files.isDirectory(file)) {
+              classpathBuilder.addClassFileResourceProvider(
+                  DirectoryClassFileProvider.fromDirectory(file));
+            } else {
+              throw new CompilationError("Unsupported classpath file type", new PathOrigin(file));
+            }
+          } catch (IOException e) {
+            builder.error(new ExceptionDiagnostic(e, new PathOrigin(file)));
+          }
         } else if (arg.equals("--main-dex-list")) {
           builder.addMainDexListFiles(Paths.get(expandedArgs[++i]));
         } else if (arg.equals("--optimize-multidex-for-linearalloc")) {
@@ -140,6 +220,9 @@
           builder.addProgramFiles(Paths.get(arg));
         }
       }
+      if (!classpathBuilder.isEmpty()) {
+        builder.addClasspathResourceProvider(classpathBuilder.build());
+      }
       if (compilationMode != null) {
         builder.setMode(compilationMode);
       }
diff --git a/src/main/java/com/android/tools/r8/PrintSeeds.java b/src/main/java/com/android/tools/r8/PrintSeeds.java
index b1bc7ca..782c91d 100644
--- a/src/main/java/com/android/tools/r8/PrintSeeds.java
+++ b/src/main/java/com/android/tools/r8/PrintSeeds.java
@@ -20,17 +20,32 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 
+/**
+ * PrintSeeds prints the classes, interfaces, methods and fields selected by a given ProGuard
+ * configuration &lt;pg-conf.txt&gt; when compiling a given program &lt;r8.jar&gt; alongside a given
+ * library &lt;rt.jar&gt;.
+ *
+ * <p>The output format is identical to what is printed when {@code -printseeds} is specified in
+ * &lt;pg-conf.txt&gt;, but running PrintSeeds can be faster than running R8 with {@code
+ * -printseeds}. See also the {@link PrintUses} program in R8.
+ */
 public class PrintSeeds {
 
   private static final String USAGE =
       "Arguments: <rt.jar> <r8.jar> <pg-conf.txt>\n"
           + "\n"
           + "PrintSeeds prints the classes, interfaces, methods and fields selected by\n"
-          + "<pg-conf.txt> when compiling <r8.jar> alongside <rt.jar>.";
+          + "<pg-conf.txt> when compiling <r8.jar> alongside <rt.jar>.\n"
+          + "\n"
+          + "The output format is identical to what is printed when -printseeds is specified in\n"
+          + "<pg-conf.txt>, but running PrintSeeds can be faster than running R8 with \n"
+          + "-printseeds. See also the "
+          + PrintUses.class.getSimpleName()
+          + " program in R8.";
 
   public static void main(String[] args) throws Exception {
     if (args.length != 3) {
-      System.out.println(USAGE);
+      System.out.println(USAGE.replace("\n", System.lineSeparator()));
       System.exit(1);
     }
     Path rtJar = Paths.get(args[0]);
diff --git a/src/main/java/com/android/tools/r8/PrintUses.java b/src/main/java/com/android/tools/r8/PrintUses.java
index 0e69de6..c762182 100644
--- a/src/main/java/com/android/tools/r8/PrintUses.java
+++ b/src/main/java/com/android/tools/r8/PrintUses.java
@@ -31,6 +31,17 @@
 import java.util.Map;
 import java.util.Set;
 
+/**
+ * PrintUses prints the classes, interfaces, methods and fields used by a given program
+ * &lt;sample.jar&gt;, restricted to classes and interfaces in a given library &lt;r8.jar&gt; that
+ * are not in &lt;sample.jar&gt;.
+ *
+ * <p>The output is in the same format as what is printed when specifying {@code -printseeds} in a
+ * ProGuard configuration file. See also the {@link PrintSeeds} program in R8.
+ *
+ * <p>Note that this tool is not related to the {@code -printusage} option of ProGuard configuration
+ * files.
+ */
 public class PrintUses {
 
   private static final String USAGE =
@@ -38,7 +49,12 @@
           + "\n"
           + "PrintUses prints the classes, interfaces, methods and fields used by <sample.jar>,\n"
           + "restricted to classes and interfaces in <r8.jar> that are not in <sample.jar>.\n"
-          + "<rt.jar> and <r8.jar> should point to libraries used by <sample.jar>.";
+          + "<rt.jar> and <r8.jar> should point to libraries used by <sample.jar>.\n"
+          + "\n"
+          + "The output is in the same format as what is printed when specifying -printseeds in\n"
+          + "a ProGuard configuration file. See also the "
+          + PrintSeeds.class.getSimpleName()
+          + " program in R8.";
 
   private final Set<String> descriptors;
   private final PrintStream out;
@@ -191,7 +207,7 @@
 
   public static void main(String[] args) throws Exception {
     if (args.length != 3) {
-      System.out.println(USAGE);
+      System.out.println(USAGE.replace("\n", System.lineSeparator()));
       return;
     }
     AndroidApp.Builder builder = AndroidApp.builder();
diff --git a/src/test/java/com/android/tools/r8/D8CommandTest.java b/src/test/java/com/android/tools/r8/D8CommandTest.java
index 1b846a5..0c278f7 100644
--- a/src/test/java/com/android/tools/r8/D8CommandTest.java
+++ b/src/test/java/com/android/tools/r8/D8CommandTest.java
@@ -6,13 +6,13 @@
 import static com.android.tools.r8.R8CommandTest.getOutputPath;
 import static com.android.tools.r8.ToolHelper.EXAMPLES_BUILD_DIR;
 import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
-import static java.nio.file.StandardOpenOption.CREATE_NEW;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 
 import com.android.sdklib.AndroidVersion;
+import com.android.tools.r8.D8CommandParser.OrderedClassFileResourceProvider;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.dex.Marker;
 import com.android.tools.r8.dex.Marker.Tool;
@@ -24,7 +24,6 @@
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
 import java.nio.file.Files;
-import java.nio.file.OpenOption;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.List;
@@ -263,13 +262,37 @@
         tmpClassesDir.toString());
     AndroidApp inputApp = ToolHelper.getApp(command);
     assertEquals(1, inputApp.getClasspathResourceProviders().size());
+    OrderedClassFileResourceProvider classpathProvider =
+        (OrderedClassFileResourceProvider) inputApp.getClasspathResourceProviders().get(0);
+    assertEquals(1, classpathProvider.providers.size());
     assertTrue(Files.isSameFile(tmpClassesDir,
-        ((DirectoryClassFileProvider) inputApp.getClasspathResourceProviders().get(0)).getRoot()));
+        ((DirectoryClassFileProvider) classpathProvider.providers.get(0)).getRoot()));
     assertEquals(1, inputApp.getLibraryResourceProviders().size());
     assertTrue(Files.isSameFile(tmpClassesDir,
         ((DirectoryClassFileProvider) inputApp.getLibraryResourceProviders().get(0)).getRoot()));
   }
 
+  @Test
+  public void folderClasspathMultiple() throws Throwable {
+    Path inputFile =
+        Paths.get(ToolHelper.EXAMPLES_ANDROID_N_BUILD_DIR, "interfacemethods" + JAR_EXTENSION);
+    Path tmpClassesDir1 = temp.newFolder().toPath();
+    Path tmpClassesDir2 = temp.newFolder().toPath();
+    ZipUtils.unzip(inputFile.toString(), tmpClassesDir1.toFile());
+    ZipUtils.unzip(inputFile.toString(), tmpClassesDir2.toFile());
+    D8Command command = parse("--classpath", tmpClassesDir1.toString(), "--classpath",
+        tmpClassesDir2.toString());
+    AndroidApp inputApp = ToolHelper.getApp(command);
+    assertEquals(1, inputApp.getClasspathResourceProviders().size());
+    OrderedClassFileResourceProvider classpathProvider =
+        (OrderedClassFileResourceProvider) inputApp.getClasspathResourceProviders().get(0);
+    assertEquals(2, classpathProvider.providers.size());
+    assertTrue(Files.isSameFile(tmpClassesDir1,
+        ((DirectoryClassFileProvider) classpathProvider.providers.get(0)).getRoot()));
+    assertTrue(Files.isSameFile(tmpClassesDir2,
+        ((DirectoryClassFileProvider) classpathProvider.providers.get(1)).getRoot()));
+  }
+
   @Test(expected = CompilationFailedException.class)
   public void classFolderProgram() throws Throwable {
     Path inputFile =
diff --git a/src/test/java/com/android/tools/r8/OrderedClassFileResourceProviderTest.java b/src/test/java/com/android/tools/r8/OrderedClassFileResourceProviderTest.java
new file mode 100644
index 0000000..dc497c4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/OrderedClassFileResourceProviderTest.java
@@ -0,0 +1,113 @@
+// Copyright (c) 2018, 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 org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.D8CommandParser.OrderedClassFileResourceProvider;
+import com.android.tools.r8.origin.Origin;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import java.io.InputStream;
+import java.util.Map;
+import java.util.Set;
+import org.junit.Test;
+
+public class OrderedClassFileResourceProviderTest extends TestBase {
+  class SimpleClassFileResourceProvider implements ClassFileResourceProvider {
+
+    private final Set<String> descriptors;
+    private final ProgramResource fixedProgramResource;
+
+    SimpleClassFileResourceProvider(int id, Set<String> descriptors) {
+      this.descriptors = descriptors;
+      this.fixedProgramResource = new SimpleProgramResource(id);
+    }
+
+    @Override
+    public Set<String> getClassDescriptors() {
+      return descriptors;
+    }
+
+    @Override
+    public ProgramResource getProgramResource(String descriptor) {
+      return fixedProgramResource;
+    }
+  }
+
+  class SimpleProgramResource implements ProgramResource {
+
+    private final Origin origin;
+
+    SimpleProgramResource(int id) {
+      origin = new SimpleOrigin(id);
+    }
+
+    @Override
+    public Kind getKind() {
+      return null;
+    }
+
+    @Override
+    public InputStream getByteStream() throws ResourceException {
+      return null;
+    }
+
+    @Override
+    public Set<String> getClassDescriptors() {
+      return null;
+    }
+
+    @Override
+    public Origin getOrigin() {
+      return origin;
+    }
+  }
+
+  public class SimpleOrigin extends Origin {
+
+    private final int id;
+
+    private SimpleOrigin(int index) {
+      super(root());
+      this.id = index;
+    }
+
+    int getId() {
+      return id;
+    }
+
+    @Override
+    public String part() {
+      return "Test";
+    }
+  }
+
+  @Test
+  public void test() {
+    OrderedClassFileResourceProvider.Builder builder = OrderedClassFileResourceProvider.builder();
+    builder.addClassFileResourceProvider(new SimpleClassFileResourceProvider(1, ImmutableSet.of(
+        "L/a/a/a", "L/a/a/b", "L/a/a/c"
+    )));
+    builder.addClassFileResourceProvider(new SimpleClassFileResourceProvider(2, ImmutableSet.of(
+        "L/a/a/b", "L/a/a/c", "L/a/a/d"
+        )));
+    ClassFileResourceProvider provider = builder.build();
+    assertEquals(
+        ImmutableSet.of("L/a/a/a", "L/a/a/b", "L/a/a/c", "L/a/a/d"),
+        provider.getClassDescriptors());
+
+    Map<String, Integer> expectations = ImmutableMap.of(
+        "L/a/a/a", 1,
+        "L/a/a/b", 1,
+        "L/a/a/c", 1,
+        "L/a/a/d", 2
+    );
+    expectations.forEach((descriptor, id) ->
+        assertEquals(
+            (int) id,
+            ((SimpleOrigin) provider.getProgramResource(descriptor).getOrigin()).getId()));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/GenericSignatureParserTest.java b/src/test/java/com/android/tools/r8/naming/GenericSignatureParserTest.java
index 751ac83..1d1604b 100644
--- a/src/test/java/com/android/tools/r8/naming/GenericSignatureParserTest.java
+++ b/src/test/java/com/android/tools/r8/naming/GenericSignatureParserTest.java
@@ -82,7 +82,7 @@
     parseSimpleError(
         GenericSignatureParser::parseClassSignature,
         e -> assertTrue(e.getMessage().startsWith("Expected L at position 1")));
-    // TODO(sgjesse): The position 2 reported here is onr off.
+    // TODO(sgjesse): The position 2 reported here is one off.
     parseSimpleError(
         GenericSignatureParser::parseFieldSignature,
         e -> assertTrue(e.getMessage().startsWith("Expected L, [ or T at position 2")));
@@ -104,7 +104,7 @@
           fail("Succesfully parsed " + signature.substring(0, i) + " (position " + i +")");
         }
       } catch (GenericSignatureFormatError e) {
-        assertTrue("" + i + " Was: " + e.getMessage(), e.getMessage().contains("at position " + (i + 1)));
+        assertTrue(e.getMessage().contains("at position " + (i + 1)));
       }
     }
   }