Merge "Implement support for filtering class paths."
diff --git a/src/main/java/com/android/tools/r8/BaseCommand.java b/src/main/java/com/android/tools/r8/BaseCommand.java
index cbd8dd6..1e5fea9 100644
--- a/src/main/java/com/android/tools/r8/BaseCommand.java
+++ b/src/main/java/com/android/tools/r8/BaseCommand.java
@@ -3,10 +3,13 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import com.android.tools.r8.shaking.FilteredClassPath;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ListUtils;
 import java.io.IOException;
 import java.nio.file.Path;
+import java.util.Arrays;
 import java.util.Collection;
 
 /**
@@ -86,13 +89,13 @@
 
     /** Add program file resources. */
     public B addProgramFiles(Path... files) throws IOException {
-      app.addProgramFiles(files);
+      addProgramFiles(Arrays.asList(files));
       return self();
     }
 
     /** Add program file resources. */
     public B addProgramFiles(Collection<Path> files) throws IOException {
-      app.addProgramFiles(files);
+      app.addProgramFiles(ListUtils.map(files, FilteredClassPath::unfiltered));
       return self();
     }
 
@@ -104,13 +107,13 @@
 
     /** Add library file resources. */
     public B addLibraryFiles(Path... files) throws IOException {
-      app.addLibraryFiles(files);
+      addLibraryFiles(Arrays.asList(files));
       return self();
     }
 
     /** Add library file resources. */
     public B addLibraryFiles(Collection<Path> files) throws IOException {
-      app.addLibraryFiles(files);
+      app.addLibraryFiles(ListUtils.map(files, FilteredClassPath::unfiltered));
       return self();
     }
 
diff --git a/src/main/java/com/android/tools/r8/PrintClassList.java b/src/main/java/com/android/tools/r8/PrintClassList.java
index bc274a8..896a66a 100644
--- a/src/main/java/com/android/tools/r8/PrintClassList.java
+++ b/src/main/java/com/android/tools/r8/PrintClassList.java
@@ -14,12 +14,12 @@
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.shaking.FilteredClassPath;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.AndroidApp.Builder;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.Timing;
-
 import java.io.IOException;
 import java.nio.file.Paths;
 import java.util.Arrays;
@@ -37,7 +37,7 @@
       builder.setProguardMapFile(Paths.get(args[0]));
       dexFiles = dexFiles.subList(1, dexFiles.size());
     }
-    builder.addProgramFiles(ListUtils.map(dexFiles, Paths::get));
+    builder.addProgramFiles(ListUtils.map(dexFiles, FilteredClassPath::unfiltered));
 
     ExecutorService executorService = Executors.newCachedThreadPool();
     DexApplication application =
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 890d570..ab9155b 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -178,7 +178,8 @@
 
     protected void validate() throws CompilationException {
       super.validate();
-      if (mainDexListOutput != null && mainDexRules.isEmpty() && !getAppBuilder().hasMainDexList()) {
+      if (mainDexListOutput != null && mainDexRules.isEmpty() && !getAppBuilder()
+          .hasMainDexList()) {
         throw new CompilationException(
             "Option --main-dex-list-output require --main-dex-rules and/or --main-dex-list");
       }
@@ -224,8 +225,8 @@
           proguardConfigurationConsumer.accept(configurationBuilder);
         }
         configuration = configurationBuilder.build();
-        addProgramFiles(configuration.getInjars());
-        addLibraryFiles(configuration.getLibraryjars());
+        getAppBuilder().addProgramFiles(configuration.getInjars());
+        getAppBuilder().addLibraryFiles(configuration.getLibraryjars());
       }
 
       // TODO(b/64802420): setProguardMapFile if configuration.hasApplyMappingFile
diff --git a/src/main/java/com/android/tools/r8/shaking/FilteredClassPath.java b/src/main/java/com/android/tools/r8/shaking/FilteredClassPath.java
new file mode 100644
index 0000000..755ac6c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/FilteredClassPath.java
@@ -0,0 +1,140 @@
+// 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.shaking;
+
+import com.google.common.collect.ImmutableList;
+import java.io.File;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+/**
+ * Implements class path filtering as per
+ * <a href="https://www.guardsquare.com/en/proguard/manual/usage#classpath">ProGuards classpath</a>
+ * documentation.
+ * <p>
+ * Some of the implementation details are derived from the examples. For example the implict
+ * catch-all positive filter after a trailing negative filter.
+ */
+public class FilteredClassPath {
+
+  private final Path path;
+  private final ImmutableList<String> pattern;
+
+  FilteredClassPath(Path path, ImmutableList<String> pattern) {
+    this.path = path;
+    this.pattern = pattern;
+  }
+
+  private FilteredClassPath(Path path) {
+    this(path, ImmutableList.of());
+  }
+
+  public static FilteredClassPath unfiltered(File file) {
+    return new FilteredClassPath(file.toPath());
+  }
+
+  public static FilteredClassPath unfiltered(Path path) {
+    return new FilteredClassPath(path);
+  }
+
+  public static FilteredClassPath unfiltered(String path) {
+    return new FilteredClassPath(Paths.get(path));
+  }
+
+  public Path getPath() {
+    return path;
+  }
+
+  public boolean matchesFile(Path file) {
+    if (isUnfiltered()) {
+      return true;
+    }
+    boolean isNegated = false;
+    for (String pattern : pattern) {
+      isNegated = pattern.charAt(0) == '!';
+      boolean matches = matchAgainstFileName(file.toString(), 0, pattern, isNegated ? 1 : 0);
+      if (matches) {
+        return !isNegated;
+      }
+    }
+    // If the last filter was a negated one, we do catch all positive thereafter.
+    return isNegated;
+  }
+
+  private boolean containsFileSeparator(String string) {
+    return string.indexOf(File.separatorChar) != -1;
+  }
+
+  private boolean matchAgainstFileName(String fileName, int namePos, String pattern,
+      int patternPos) {
+    if (patternPos >= pattern.length()) {
+      // We have exhausted the pattern before the filename.
+      return namePos == fileName.length();
+    }
+    char currentPattern = pattern.charAt(patternPos);
+    if (currentPattern == '*') {
+      boolean includeFileSeparators =
+          pattern.length() > patternPos + 1 && pattern.charAt(patternPos + 1) == '*';
+      if (includeFileSeparators) {
+        patternPos++;
+      }
+      // Common case where the end is a file name suffix without further wildcards.
+      String remainingPattern = pattern.substring(patternPos + 1);
+      if (remainingPattern.indexOf('*') == -1) {
+        // The pattern contains no multi-char wildcards, so only the postfix has to match.
+        int remaining = remainingPattern.length();
+        if (namePos + remaining > fileName.length()) {
+          // Exhausted the name too early.
+          return false;
+        }
+        if (includeFileSeparators
+            || !containsFileSeparator(fileName.substring(namePos, fileName.length() - remaining))) {
+          return matchAgainstFileName(fileName, fileName.length() - remaining, pattern,
+              patternPos + 1);
+        }
+      } else {
+        for (int i = namePos; i < fileName.length(); i++) {
+          if (!includeFileSeparators && fileName.charAt(i) == File.separatorChar) {
+            return false;
+          }
+          if (matchAgainstFileName(fileName, i, pattern, patternPos + 1)) {
+            return true;
+          }
+        }
+      }
+    } else {
+      if (namePos >= fileName.length()) {
+        return false;
+      }
+      if (currentPattern == '?' || currentPattern == fileName.charAt(namePos)) {
+        return matchAgainstFileName(fileName, namePos + 1, pattern, patternPos + 1);
+      }
+    }
+    return false;
+  }
+
+  public boolean isUnfiltered() {
+    return pattern.isEmpty();
+  }
+
+  @Override
+  public String toString() {
+    if (isUnfiltered()) {
+      return path.toString();
+    }
+    StringBuilder builder = new StringBuilder();
+    builder.append(path);
+    builder.append('(');
+    boolean first = true;
+    for (String pattern : pattern) {
+      if (!first) {
+        builder.append(',');
+      }
+      builder.append(pattern);
+      first = false;
+    }
+    builder.append(')');
+    return builder.toString();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
index f47adbd..05fb09c 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfiguration.java
@@ -18,8 +18,8 @@
 
   public static class Builder {
 
-    private final List<Path> injars = new ArrayList<>();
-    private final List<Path> libraryjars = new ArrayList<>();
+    private final List<FilteredClassPath> injars = new ArrayList<>();
+    private final List<FilteredClassPath> libraryjars = new ArrayList<>();
     private PackageObfuscationMode packageObfuscationMode = PackageObfuscationMode.NONE;
     private String packagePrefix = "";
     private boolean allowAccessModification = false;
@@ -47,11 +47,11 @@
       this.dexItemFactory = dexItemFactory;
     }
 
-    public void addInjars(List<Path> injars) {
+    public void addInjars(List<FilteredClassPath> injars) {
       this.injars.addAll(injars);
     }
 
-    public void addLibraryJars(List<Path> libraryJars) {
+    public void addLibraryJars(List<FilteredClassPath> libraryJars) {
       this.libraryjars.addAll(libraryJars);
     }
 
@@ -174,8 +174,8 @@
   }
 
   private final DexItemFactory dexItemFactory;
-  private final ImmutableList<Path> injars;
-  private final ImmutableList<Path> libraryjars;
+  private final ImmutableList<FilteredClassPath> injars;
+  private final ImmutableList<FilteredClassPath> libraryjars;
   private final PackageObfuscationMode packageObfuscationMode;
   private final String packagePrefix;
   private final boolean allowAccessModification;
@@ -199,8 +199,8 @@
 
   private ProguardConfiguration(
       DexItemFactory factory,
-      List<Path> injars,
-      List<Path> libraryjars,
+      List<FilteredClassPath> injars,
+      List<FilteredClassPath> libraryjars,
       PackageObfuscationMode packageObfuscationMode,
       String packagePrefix,
       boolean allowAccessModification,
@@ -261,11 +261,11 @@
     return false;
   }
 
-  public ImmutableList<Path> getInjars() {
+  public ImmutableList<FilteredClassPath> getInjars() {
     return injars;
   }
 
-  public ImmutableList<Path> getLibraryjars() {
+  public ImmutableList<FilteredClassPath> getLibraryjars() {
     return libraryjars;
   }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index 1c3051d..a61017c 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -772,7 +772,9 @@
       int end = position;
       while (!eof(end)) {
         char current = contents.charAt(end);
-        if (current != File.pathSeparatorChar && !Character.isWhitespace(current)) {
+        if (current != File.pathSeparatorChar
+            && !Character.isWhitespace(current)
+            && current != '(') {
           end++;
         } else {
           break;
@@ -785,18 +787,60 @@
       return baseDirectory.resolve(contents.substring(start, end));
     }
 
-    private List<Path> parseClassPath() throws ProguardRuleParserException {
-      List<Path> classPath = new ArrayList<>();
+    private List<FilteredClassPath> parseClassPath() throws ProguardRuleParserException {
+      List<FilteredClassPath> classPath = new ArrayList<>();
       skipWhitespace();
       Path file = parseFileName();
-      classPath.add(file);
+      ImmutableList<String> filters = parseClassPathFilters();
+      classPath.add(new FilteredClassPath(file, filters));
       while (acceptChar(File.pathSeparatorChar)) {
         file = parseFileName();
-        classPath.add(file);
+        filters = parseClassPathFilters();
+        classPath.add(new FilteredClassPath(file, filters));
       }
       return classPath;
     }
 
+    private ImmutableList<String> parseClassPathFilters() throws ProguardRuleParserException {
+      skipWhitespace();
+      if (acceptChar('(')) {
+        ImmutableList.Builder<String> filters = new ImmutableList.Builder<>();
+        filters.add(parseFileFilter());
+        skipWhitespace();
+        while (acceptChar(',')) {
+          filters.add(parseFileFilter());
+          skipWhitespace();
+        }
+        if (acceptChar(';')) {
+          throw parseError("Only class file filters are supported in classpath");
+        }
+        expectChar(')');
+        return filters.build();
+      } else {
+        return ImmutableList.of();
+      }
+    }
+
+    private String parseFileFilter() throws ProguardRuleParserException {
+      skipWhitespace();
+      int start = position;
+      int end = position;
+      while (!eof(end)) {
+        char current = contents.charAt(end);
+        if (current != ',' && current != ';' && current != ')' && !Character
+            .isWhitespace(current)) {
+          end++;
+        } else {
+          break;
+        }
+      }
+      if (start == end) {
+        throw parseError("file filter expected");
+      }
+      position = end;
+      return contents.substring(start, end);
+    }
+
     private ProguardAssumeNoSideEffectRule parseAssumeNoSideEffectsRule()
         throws ProguardRuleParserException {
       ProguardAssumeNoSideEffectRule.Builder builder = ProguardAssumeNoSideEffectRule.builder();
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 ec53553..4a190ac 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.dex.VDexFile;
 import com.android.tools.r8.dex.VDexFileReader;
 import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.shaking.FilteredClassPath;
 import com.google.common.collect.ImmutableList;
 import com.google.common.io.ByteStreams;
 import com.google.common.io.Closer;
@@ -52,6 +53,7 @@
   private final ImmutableList<Resource> programResources;
   private final ImmutableList<ClassFileResourceProvider> classpathResourceProviders;
   private final ImmutableList<ClassFileResourceProvider> libraryResourceProviders;
+
   private final ImmutableList<ProgramFileArchiveReader> programFileArchiveReaders;
   private final Resource deadCode;
   private final Resource proguardMap;
@@ -112,7 +114,7 @@
    * Create an app from program files @code{files}. See also Builder::addProgramFiles.
    */
   public static AndroidApp fromProgramFiles(List<Path> files) throws IOException {
-    return builder().addProgramFiles(files).build();
+    return builder().addProgramFiles(ListUtils.map(files, FilteredClassPath::unfiltered)).build();
   }
 
   /**
@@ -189,6 +191,10 @@
     return libraryResourceProviders;
   }
 
+  public List<ProgramFileArchiveReader> getProgramFileArchiveReaders() {
+    return programFileArchiveReaders;
+  }
+
   private List<Resource> filter(List<Resource> resources, Resource.Kind kind) {
     List<Resource> out = new ArrayList<>(resources.size());
     for (Resource resource : resources) {
@@ -469,9 +475,9 @@
      * @param directory Directory containing dex program files and optional proguard-map file.
      */
     public Builder addProgramDirectory(Path directory) throws IOException {
-      List<Path> resources =
+      List<FilteredClassPath> resources =
           Arrays.asList(directory.toFile().listFiles(file -> isDexFile(file.toPath()))).stream()
-          .map(file -> file.toPath()).collect(Collectors.toList());
+              .map(file -> FilteredClassPath.unfiltered(file)).collect(Collectors.toList());
       addProgramFiles(resources);
       File mapFile = new File(directory.toFile(), DEFAULT_PROGUARD_MAP_FILE);
       if (mapFile.exists()) {
@@ -483,15 +489,15 @@
     /**
      * Add program file resources.
      */
-    public Builder addProgramFiles(Path... files) throws IOException {
+    public Builder addProgramFiles(FilteredClassPath... files) throws IOException {
       return addProgramFiles(Arrays.asList(files));
     }
 
     /**
      * Add program file resources.
      */
-    public Builder addProgramFiles(Collection<Path> files) throws IOException {
-      for (Path file : files) {
+    public Builder addProgramFiles(Collection<FilteredClassPath> files) throws IOException {
+      for (FilteredClassPath file : files) {
         addProgramFile(file);
       }
       return this;
@@ -509,7 +515,7 @@
      */
     public Builder addClasspathFiles(Collection<Path> files) throws IOException {
       for (Path file : files) {
-        addClassProvider(file, classpathResourceProviders);
+        addClassProvider(FilteredClassPath.unfiltered(file), classpathResourceProviders);
       }
       return this;
     }
@@ -525,15 +531,15 @@
     /**
      * Add library file resources.
      */
-    public Builder addLibraryFiles(Path... files) throws IOException {
+    public Builder addLibraryFiles(FilteredClassPath... files) throws IOException {
       return addLibraryFiles(Arrays.asList(files));
     }
 
     /**
      * Add library file resources.
      */
-    public Builder addLibraryFiles(Collection<Path> files) throws IOException {
-      for (Path file : files) {
+    public Builder addLibraryFiles(Collection<FilteredClassPath> files) throws IOException {
+      for (FilteredClassPath file : files) {
         addClassProvider(file, libraryResourceProviders);
       }
       return this;
@@ -722,7 +728,8 @@
           mainDexListOutput);
     }
 
-    private void addProgramFile(Path file) throws IOException {
+    private void addProgramFile(FilteredClassPath filteredClassPath) throws IOException {
+      Path file = filteredClassPath.getPath();
       if (!Files.exists(file)) {
         throw new FileNotFoundException("Non-existent input file: " + file);
       }
@@ -733,20 +740,25 @@
       } else if (isClassFile(file)) {
         programResources.add(Resource.fromFile(Resource.Kind.CLASSFILE, file));
       } else if (isArchive(file)) {
-        programFileArchiveReaders.add(new ProgramFileArchiveReader(file, ignoreDexInArchive));
+        programFileArchiveReaders
+            .add(new ProgramFileArchiveReader(filteredClassPath, ignoreDexInArchive));
       } else {
         throw new CompilationError("Unsupported source file type for file: " + file);
       }
     }
 
-    private void addClassProvider(Path file, List<ClassFileResourceProvider> providerList)
+    private void addClassProvider(FilteredClassPath classPath,
+        List<ClassFileResourceProvider> providerList)
         throws IOException {
+      Path file = classPath.getPath();
       if (!Files.exists(file)) {
         throw new FileNotFoundException("Non-existent input file: " + file);
       }
       if (isArchive(file)) {
-        providerList.add(PreloadedClassFileProvider.fromArchive(file));
+        providerList.add(PreloadedClassFileProvider.fromArchive(classPath));
       } else if (Files.isDirectory(file) ) {
+        // This is only used for D8 incremental compilation.
+        assert classPath.isUnfiltered();
         providerList.add(DirectoryClassFileProvider.fromDirectory(file));
       } else {
         throw new CompilationError("Unsupported source file type for file: " + file);
diff --git a/src/main/java/com/android/tools/r8/utils/PreloadedClassFileProvider.java b/src/main/java/com/android/tools/r8/utils/PreloadedClassFileProvider.java
index 5ea2a30..8e3c638 100644
--- a/src/main/java/com/android/tools/r8/utils/PreloadedClassFileProvider.java
+++ b/src/main/java/com/android/tools/r8/utils/PreloadedClassFileProvider.java
@@ -10,11 +10,10 @@
 import com.android.tools.r8.ClassFileResourceProvider;
 import com.android.tools.r8.Resource;
 import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.shaking.FilteredClassPath;
 import com.google.common.collect.Sets;
 import com.google.common.io.ByteStreams;
-
 import java.io.File;
-import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.file.Path;
@@ -27,8 +26,11 @@
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
 
-/** Lazy Java class file resource provider based on preloaded/prebuilt context. */
+/**
+ * Lazy Java class file resource provider based on preloaded/prebuilt context.
+ */
 public final class PreloadedClassFileProvider implements ClassFileResourceProvider {
+
   private final Map<String, byte[]> content;
 
   private PreloadedClassFileProvider(Map<String, byte[]> content) {
@@ -49,16 +51,20 @@
     return Resource.fromBytes(Resource.Kind.CLASSFILE, bytes, Collections.singleton(descriptor));
   }
 
-  /** Create preloaded content resource provider from archive file. */
-  public static ClassFileResourceProvider fromArchive(Path archive) throws IOException {
-    assert isArchive(archive);
+  /**
+   * Create preloaded content resource provider from archive file.
+   */
+  public static ClassFileResourceProvider fromArchive(FilteredClassPath archive)
+      throws IOException {
+    assert isArchive(archive.getPath());
     Builder builder = builder();
-    try (ZipFile zipFile = new ZipFile(archive.toFile())) {
+    try (ZipFile zipFile = new ZipFile(archive.getPath().toFile())) {
       final Enumeration<? extends ZipEntry> entries = zipFile.entries();
       while (entries.hasMoreElements()) {
         ZipEntry entry = entries.nextElement();
         String name = entry.getName();
-        if (isClassFile(Paths.get(name))) {
+        Path entryPath = Paths.get(name);
+        if (isClassFile(entryPath) && archive.matchesFile(entryPath)) {
           try (InputStream entryStream = zipFile.getInputStream(entry)) {
             builder.addResource(guessTypeDescriptor(name), ByteStreams.toByteArray(entryStream));
           }
@@ -80,8 +86,8 @@
     assert name.endsWith(CLASS_EXTENSION) :
         "Name " + name + " must have " + CLASS_EXTENSION + " suffix";
     String fileName =
-            File.separatorChar == '/' ? name.toString() :
-                    name.toString().replace(File.separatorChar, '/');
+        File.separatorChar == '/' ? name.toString() :
+            name.toString().replace(File.separatorChar, '/');
     String descriptor = fileName.substring(0, fileName.length() - CLASS_EXTENSION.length());
     if (descriptor.contains(".")) {
       throw new CompilationError("Unexpected file name in the archive: " + fileName);
@@ -94,12 +100,15 @@
     return content.size() + " preloaded resources";
   }
 
-  /** Create a new empty builder. */
+  /**
+   * Create a new empty builder.
+   */
   public static Builder builder() {
     return new Builder();
   }
 
   public static final class Builder {
+
     private Map<String, byte[]> content = new HashMap<>();
 
     private Builder() {
diff --git a/src/main/java/com/android/tools/r8/utils/ProgramFileArchiveReader.java b/src/main/java/com/android/tools/r8/utils/ProgramFileArchiveReader.java
index 79ab853..57bcd68 100644
--- a/src/main/java/com/android/tools/r8/utils/ProgramFileArchiveReader.java
+++ b/src/main/java/com/android/tools/r8/utils/ProgramFileArchiveReader.java
@@ -9,6 +9,7 @@
 
 import com.android.tools.r8.Resource;
 import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.shaking.FilteredClassPath;
 import com.google.common.io.ByteStreams;
 import java.io.IOException;
 import java.io.InputStream;
@@ -23,39 +24,42 @@
 import java.util.zip.ZipException;
 import java.util.zip.ZipFile;
 
-class ProgramFileArchiveReader {
+public class ProgramFileArchiveReader {
 
-  private final Path archive;
+  private final FilteredClassPath archive;
   private boolean ignoreDexInArchive;
   private List<Resource> dexResources = null;
   private List<Resource> classResources = null;
 
-  ProgramFileArchiveReader(Path archive, boolean ignoreDexInArchive) {
+  ProgramFileArchiveReader(FilteredClassPath archive, boolean ignoreDexInArchive) {
     this.archive = archive;
     this.ignoreDexInArchive = ignoreDexInArchive;
   }
 
   private void readArchive() throws IOException {
-    assert isArchive(archive);
+    assert isArchive(archive.getPath());
     dexResources = new ArrayList<>();
     classResources = new ArrayList<>();
-    try (ZipFile zipFile = new ZipFile(archive.toFile())) {
+    try (ZipFile zipFile = new ZipFile(archive.getPath().toFile())) {
       final Enumeration<? extends ZipEntry> entries = zipFile.entries();
       while (entries.hasMoreElements()) {
         ZipEntry entry = entries.nextElement();
         try (InputStream stream = zipFile.getInputStream(entry)) {
           Path name = Paths.get(entry.getName());
-          if (isDexFile(name)) {
-            if (!ignoreDexInArchive) {
-              Resource resource =
-                  new OneShotByteResource(Resource.Kind.DEX, ByteStreams.toByteArray(stream), null);
-              dexResources.add(resource);
+          if (archive.matchesFile(name)) {
+            if (isDexFile(name)) {
+              if (!ignoreDexInArchive) {
+                Resource resource =
+                    new OneShotByteResource(Resource.Kind.DEX, ByteStreams.toByteArray(stream),
+                        null);
+                dexResources.add(resource);
+              }
+            } else if (isClassFile(name)) {
+              String descriptor = PreloadedClassFileProvider.guessTypeDescriptor(name);
+              Resource resource = new OneShotByteResource(Resource.Kind.CLASSFILE,
+                  ByteStreams.toByteArray(stream), Collections.singleton(descriptor));
+              classResources.add(resource);
             }
-          } else if (isClassFile(name)) {
-            String descriptor = PreloadedClassFileProvider.guessTypeDescriptor(name);
-            Resource resource = new OneShotByteResource(Resource.Kind.CLASSFILE,
-                ByteStreams.toByteArray(stream), Collections.singleton(descriptor));
-            classResources.add(resource);
           }
         }
       }
diff --git a/src/main/java/com/android/tools/r8/utils/ThrowingBiConsumer.java b/src/main/java/com/android/tools/r8/utils/ThrowingBiConsumer.java
index d7da332..e922562 100644
--- a/src/main/java/com/android/tools/r8/utils/ThrowingBiConsumer.java
+++ b/src/main/java/com/android/tools/r8/utils/ThrowingBiConsumer.java
@@ -1,3 +1,6 @@
+// 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 java.util.function.BiConsumer;
diff --git a/src/main/java/com/android/tools/r8/utils/ThrowingBiFunction.java b/src/main/java/com/android/tools/r8/utils/ThrowingBiFunction.java
new file mode 100644
index 0000000..01d00d2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/ThrowingBiFunction.java
@@ -0,0 +1,20 @@
+// 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 java.util.function.BiFunction;
+
+/**
+ * Similar to a {@link BiFunction} but throws a single {@link Throwable}.
+ *
+ * @param <T> the type of the first argument
+ * @param <U> the type of the second argument
+ * @param <R> the return type
+ * @param <E> the type of the {@link Throwable}
+ */
+@FunctionalInterface
+public interface ThrowingBiFunction<T, U, R, E extends Throwable> {
+
+  R apply(T t, U u) throws E;
+}
diff --git a/src/main/java/com/android/tools/r8/utils/ThrowingConsumer.java b/src/main/java/com/android/tools/r8/utils/ThrowingConsumer.java
index 8fb8c84..61557e5 100644
--- a/src/main/java/com/android/tools/r8/utils/ThrowingConsumer.java
+++ b/src/main/java/com/android/tools/r8/utils/ThrowingConsumer.java
@@ -1,3 +1,6 @@
+// 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 java.util.function.Consumer;
diff --git a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
index 2159d6b..6461a57 100644
--- a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
@@ -7,6 +7,7 @@
 import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
 import static org.junit.Assert.assertEquals;
 
+import com.android.tools.r8.D8Command.Builder;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.InternalCompilerError;
 import com.android.tools.r8.errors.Unimplemented;
@@ -195,8 +196,7 @@
     abstract void addClasspathReference(
         Path testJarFile, D8Command.Builder builder) throws IOException;
 
-    abstract void addLibraryReference(
-        D8Command.Builder builder, Path location) throws IOException;
+    abstract void addLibraryReference(Builder builder, Path location) throws IOException;
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/D8LazyRunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/D8LazyRunExamplesAndroidOTest.java
index c2ac969..c39dc31 100644
--- a/src/test/java/com/android/tools/r8/D8LazyRunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/D8LazyRunExamplesAndroidOTest.java
@@ -8,6 +8,8 @@
 import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
 import static org.junit.Assert.assertTrue;
 
+import com.android.tools.r8.D8Command.Builder;
+import com.android.tools.r8.shaking.FilteredClassPath;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DirectoryClassFileProvider;
 import com.android.tools.r8.utils.FileUtils;
@@ -48,9 +50,9 @@
     }
 
     @Override
-    void addLibraryReference(D8Command.Builder builder, Path location) throws IOException {
+    void addLibraryReference(Builder builder, Path location) throws IOException {
       builder.addLibraryResourceProvider(
-          PreloadedClassFileProvider.fromArchive(location));
+          PreloadedClassFileProvider.fromArchive(FilteredClassPath.unfiltered(location)));
     }
 
     @Override
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 925ba5b..f2c4d15 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -7,6 +7,7 @@
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.shaking.FilteredClassPath;
 import com.android.tools.r8.shaking.ProguardRuleParserException;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.FileUtils;
@@ -48,7 +49,8 @@
   protected static AndroidApp readClasses(Class... classes) throws IOException {
     AndroidApp.Builder builder = AndroidApp.builder();
     for (Class clazz : classes) {
-      builder.addProgramFiles(ToolHelper.getClassFileForTestClass(clazz));
+      builder.addProgramFiles(
+          FilteredClassPath.unfiltered(ToolHelper.getClassFileForTestClass(clazz)));
     }
     return builder.build();
   }
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index e14de46..e8fcc4c 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.shaking.FilteredClassPath;
 import com.android.tools.r8.shaking.ProguardConfiguration;
 import com.android.tools.r8.shaking.ProguardConfigurationParser;
 import com.android.tools.r8.shaking.ProguardRuleParserException;
@@ -315,10 +316,13 @@
   }
 
   static class RetainedTemporaryFolder extends TemporaryFolder {
+
     RetainedTemporaryFolder(java.io.File parentFolder) {
       super(parentFolder);
     }
-    protected void after() {} // instead of remove, do nothing
+
+    protected void after() {
+    } // instead of remove, do nothing
   }
 
   // For non-Linux platforms create the temporary directory in the repository root to simplify
@@ -435,9 +439,9 @@
   public static DexApplication buildApplication(List<String> fileNames)
       throws IOException, ExecutionException {
     return new ApplicationReader(
-            AndroidApp.fromProgramFiles(ListUtils.map(fileNames, Paths::get)),
-            new InternalOptions(),
-            new Timing("ToolHelper buildApplication"))
+        AndroidApp.fromProgramFiles(ListUtils.map(fileNames, Paths::get)),
+        new InternalOptions(),
+        new Timing("ToolHelper buildApplication"))
         .read();
   }
 
@@ -485,14 +489,15 @@
   }
 
   public static CompilationResult runR8WithFullResult(
-        R8Command command, Consumer<InternalOptions> optionsConsumer)
-        throws ProguardRuleParserException, ExecutionException, IOException, CompilationException {
-   // TODO(zerny): Should we really be adding the android library in ToolHelper?
+      R8Command command, Consumer<InternalOptions> optionsConsumer)
+      throws ProguardRuleParserException, ExecutionException, IOException, CompilationException {
+    // TODO(zerny): Should we really be adding the android library in ToolHelper?
     AndroidApp app = command.getInputApp();
     if (app.getLibraryResourceProviders().isEmpty()) {
       app =
           AndroidApp.builder(app)
-              .addLibraryFiles(Paths.get(getAndroidJar(command.getMinApiLevel())))
+              .addLibraryFiles(
+                  FilteredClassPath.unfiltered(getAndroidJar(command.getMinApiLevel())))
               .build();
     }
     InternalOptions options = command.getInternalOptions();
diff --git a/src/test/java/com/android/tools/r8/rewrite/switchmaps/RewriteSwitchMapsTest.java b/src/test/java/com/android/tools/r8/rewrite/switchmaps/RewriteSwitchMapsTest.java
index 4e8881a..104ba43 100644
--- a/src/test/java/com/android/tools/r8/rewrite/switchmaps/RewriteSwitchMapsTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/switchmaps/RewriteSwitchMapsTest.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.CompilationException;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.shaking.FilteredClassPath;
 import com.android.tools.r8.shaking.ProguardRuleParserException;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DexInspector;
@@ -27,8 +28,10 @@
   public void checkSwitchMapsRemoved()
       throws IOException, ProguardRuleParserException, ExecutionException, CompilationException {
     AndroidApp.Builder builder = AndroidApp.builder();
-    builder.addLibraryFiles(Paths.get(ToolHelper.getDefaultAndroidJar()));
-    builder.addProgramFiles(Paths.get(ToolHelper.EXAMPLES_BUILD_DIR).resolve(JAR_FILE));
+    builder.addLibraryFiles(
+        FilteredClassPath.unfiltered(ToolHelper.getDefaultAndroidJar()));
+    builder.addProgramFiles(
+        FilteredClassPath.unfiltered(Paths.get(ToolHelper.EXAMPLES_BUILD_DIR).resolve(JAR_FILE)));
     AndroidApp result = compileWithR8(builder.build(), writeTextToTempFile(PG_CONFIG));
     DexInspector inspector = new DexInspector(result);
     Assert.assertFalse(inspector.clazz(SWITCHMAP_CLASS_NAME).isPresent());
diff --git a/src/test/java/com/android/tools/r8/shaking/FilteredClassPathTest.java b/src/test/java/com/android/tools/r8/shaking/FilteredClassPathTest.java
new file mode 100644
index 0000000..48ac44a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/FilteredClassPathTest.java
@@ -0,0 +1,116 @@
+// 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.shaking;
+
+import com.android.tools.r8.ClassFileResourceProvider;
+import com.android.tools.r8.Resource;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.AndroidApp.Builder;
+import com.android.tools.r8.utils.ListUtils;
+import com.android.tools.r8.utils.ProgramFileArchiveReader;
+import com.android.tools.r8.utils.ThrowingBiFunction;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class FilteredClassPathTest {
+
+  private void testPath(List<String> filters, List<String> positives, List<String> negatives) {
+    FilteredClassPath path = makeFilteredClassPath(filters);
+    Assert.assertTrue(
+        positives.stream().map(FilteredClassPathTest::adaptFileSeparator).map(Paths::get)
+            .allMatch(path::matchesFile));
+    Assert.assertFalse(
+        negatives.stream().map(FilteredClassPathTest::adaptFileSeparator).map(Paths::get)
+            .allMatch(path::matchesFile));
+  }
+
+  private static String adaptFileSeparator(String s) {
+    return s.replace('/', File.separatorChar);
+  }
+
+  private static FilteredClassPath makeFilteredClassPath(List<String> filters) {
+    // TODO(herhut): Move to stream API once updated to guava 23.
+    return new FilteredClassPath(Paths.get("foo"),
+        ImmutableList.copyOf(ListUtils.map(filters, FilteredClassPathTest::adaptFileSeparator)));
+  }
+
+  @Test
+  public void testFilterMatching() {
+    testPath(ImmutableList.of("!boo"),
+        ImmutableList.of("bool", "bo"),
+        ImmutableList.of("boo"));
+    testPath(ImmutableList.of("!*.boo"),
+        ImmutableList.of("zoo.bar", "Fish.bool", "Cat.bo", "boo", "path/zoo.boo",
+            "zoo.bool", "zoo.bo"),
+        ImmutableList.of("mi.boo", ".boo"));
+    testPath(ImmutableList.of("!*s/*.boo"),
+        ImmutableList.of("zoo.bar", "Fish.bool", "Cat.bo", "boo", "path/zoo.boo", "sx/a.boo",
+            "ass/boo", "s/x/.boo", "s/s/s.boo", "s/zoo.bool"),
+        ImmutableList.of("mi.boo", ".boo", "ass/a.boo", "s/.boo", "asis/sos.boo"));
+    testPath(ImmutableList.of("!**s/*.boo"),
+        ImmutableList.of("zoo.bar", "Fish.bool", "Cat.bo", "boo", "path/zoo.boo", "sx/a.boo",
+            "ass/boo", "s/x/.boo", "s/zoo.bool"),
+        ImmutableList.of("mi.boo", ".boo", "ass/a.boo", "s/.boo", "asis/sos.boo", "s/s/s.boo",
+            "xyz/abz/ass/.boo"));
+    testPath(ImmutableList.of("!*.boo", "*.bar", "*.baz"),
+        ImmutableList.of("sinz.bar", "song.baz", ".baz", ".bar"),
+        ImmutableList.of("sinz.boo", ".boo", ".bang", "goo.bang", "s/foo.baz"));
+  }
+
+  private void testApplicationFiltered(
+      ThrowingBiFunction<Builder, FilteredClassPath, Builder, IOException> setter,
+      Function<AndroidApp, List<String>> getter)
+      throws IOException {
+    Path androidJar = Paths.get(ToolHelper.getDefaultAndroidJar());
+    AndroidApp app = setter.apply(AndroidApp.builder(), new FilteredClassPath(androidJar,
+        ImmutableList.of("!java/lang/**.class", "java/util/**.class"))).build();
+    List<String> descriptors = getter.apply(app);
+    Assert.assertTrue(descriptors.stream().noneMatch(s -> s.startsWith("Ljava/lang")));
+    Assert.assertTrue(descriptors.stream().anyMatch(s -> s.startsWith("Ljava/util")));
+    Assert.assertTrue(descriptors.stream().noneMatch(s -> s.startsWith("Lcom/android")));
+  }
+
+  @Test
+  public void testLibraryFiltered() throws IOException {
+    testApplicationFiltered(Builder::addLibraryFiles, app -> {
+      ClassFileResourceProvider provider = Iterables
+          .getOnlyElement(app.getLibraryResourceProviders());
+      List<Resource> resources = ListUtils
+          .map(provider.getClassDescriptors(), provider::getResource);
+      return resources.stream().flatMap(r -> r.getClassDescriptors().stream())
+          .collect(Collectors.toList());
+    });
+  }
+
+  private static Stream<Resource> getClassProgramResources(ProgramFileArchiveReader reader) {
+    try {
+      return reader.getClassProgramResources().stream();
+    } catch (IOException e) {
+      return Stream.empty();
+    }
+  }
+
+  @Test
+  public void testProgramFiltered() throws IOException {
+    testApplicationFiltered(Builder::addProgramFiles,
+        app -> app.getProgramFileArchiveReaders().stream()
+            .flatMap(FilteredClassPathTest::getClassProgramResources)
+            .flatMap(r -> r.getClassDescriptors().stream()).collect(
+                Collectors.toList()));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
index 48f8835..c96d603 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -19,6 +19,7 @@
 import java.io.IOException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.Collections;
 import java.util.List;
 import org.junit.Test;
 
@@ -414,6 +415,18 @@
   }
 
   @Test
+  public void parseInvalidFilePattern() throws IOException {
+    try {
+      ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
+      parser.parse(new ProguardConfigurationSourceStrings(
+          Collections.singletonList("-injars abc.jar(*.zip;*.class)")));
+    } catch (ProguardRuleParserException e) {
+      return;
+    }
+    fail();
+  }
+
+  @Test
   public void parseSeeds() throws IOException, ProguardRuleParserException {
     ProguardConfigurationParser parser = new ProguardConfigurationParser(new DexItemFactory());
     parser.parse(Paths.get(SEEDS));
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliBuildTest.java b/src/test/java/com/android/tools/r8/smali/SmaliBuildTest.java
index edcd454..470f16e 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliBuildTest.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliBuildTest.java
@@ -8,12 +8,12 @@
 
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.shaking.FilteredClassPath;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DexInspector;
 import com.android.tools.r8.utils.DexInspector.ClassSubject;
 import com.android.tools.r8.utils.InternalOptions;
 import java.io.IOException;
-import java.nio.file.Paths;
 import org.antlr.runtime.RecognitionException;
 import org.junit.Test;
 
@@ -61,7 +61,7 @@
     InternalOptions options = new InternalOptions();
     AndroidApp app = AndroidApp.builder()
         .addDexProgramData(builder.compile())
-        .addLibraryFiles(Paths.get(ToolHelper.getDefaultAndroidJar()))
+        .addLibraryFiles(FilteredClassPath.unfiltered(ToolHelper.getDefaultAndroidJar()))
         .build();
 
     // Java standard library added - java.lang.String is present.
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
index 56059f6..cecc10e 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
@@ -24,6 +24,7 @@
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.FilteredClassPath;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.DexInspector;
@@ -37,7 +38,6 @@
 import com.google.common.collect.Iterables;
 import java.io.IOException;
 import java.nio.file.Path;
-import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
@@ -435,7 +435,7 @@
     try {
       AndroidApp input = AndroidApp.builder()
           .addDexProgramData(builder.compile())
-          .addLibraryFiles(Paths.get(ToolHelper.getDefaultAndroidJar()))
+          .addLibraryFiles(FilteredClassPath.unfiltered(ToolHelper.getDefaultAndroidJar()))
           .build();
       return buildApplication(input, options);
     } catch (IOException | RecognitionException e) {
diff --git a/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java b/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java
index 21d3b9e..77366fb 100644
--- a/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java
@@ -21,6 +21,7 @@
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.jasmin.JasminBuilder;
+import com.android.tools.r8.shaking.FilteredClassPath;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DexInspector;
 import com.android.tools.r8.utils.DexInspector.MethodSubject;
@@ -502,7 +503,8 @@
     // Add the Jasmin class and a class from Java source with the main method.
     AndroidApp.Builder appBuilder = AndroidApp.builder();
     appBuilder.addClassProgramData(builder.buildClasses());
-    appBuilder.addProgramFiles(ToolHelper.getClassFileForTestClass(CheckSwitchInTestClass.class));
+    appBuilder.addProgramFiles(FilteredClassPath
+        .unfiltered(ToolHelper.getClassFileForTestClass(CheckSwitchInTestClass.class)));
     AndroidApp app = compileWithR8(appBuilder.build());
 
     DexInspector inspector = new DexInspector(app);
diff --git a/src/test/proguard/valid/library-jars-win.flags b/src/test/proguard/valid/library-jars-win.flags
index c6ffbb4..f580366 100644
--- a/src/test/proguard/valid/library-jars-win.flags
+++ b/src/test/proguard/valid/library-jars-win.flags
@@ -1,2 +1,2 @@
--libraryjars some/library.jar
--libraryjars some/library.jar;some/other/library.jar;yet/another/library.jar
\ No newline at end of file
+-libraryjars some\library.jar
+-libraryjars some\library.jar( **\*foo.class , **.bar );some\other\library.jar;yet\another\library.jar(**.class)
diff --git a/src/test/proguard/valid/library-jars.flags b/src/test/proguard/valid/library-jars.flags
index 9cb77d4..8a48828 100644
--- a/src/test/proguard/valid/library-jars.flags
+++ b/src/test/proguard/valid/library-jars.flags
@@ -1,2 +1,2 @@
 -libraryjars some/library.jar
--libraryjars some/library.jar:some/other/library.jar:yet/another/library.jar
\ No newline at end of file
+-libraryjars some/library.jar( **/*foo.class , **.bar ):some/other/library.jar:yet/another/library.jar(**.class)