| // 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; |
| |
| public 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(String name) { |
| if (isUnfiltered()) { |
| return true; |
| } |
| boolean isNegated = false; |
| for (String pattern : pattern) { |
| isNegated = pattern.charAt(0) == '!'; |
| boolean matches = matchAgainstFileName(name, 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('/') != -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) == '/') { |
| 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(); |
| } |
| } |