package org.smoothbuild.builtin.file.match; import static org.smoothbuild.builtin.file.match.Constants.SINGLE_STAR_CHAR; import static org.smoothbuild.builtin.file.match.NamePattern.namePattern; import static org.smoothbuild.io.fs.base.Path.path; import java.util.ArrayList; import java.util.List; import java.util.function.Predicate; import org.smoothbuild.io.fs.base.Path; public class PathMatcher implements Predicate<Path> { private static final Predicate<Path> DOUBLE_STAR_PREDICATE = null; private final List<Predicate<Path>> patternParts; public static Predicate<Path> pathMatcher(String patternString) { PathPattern pathPattern = PathPattern.pathPattern(patternString); if (patternString.indexOf(SINGLE_STAR_CHAR) == -1) { return Predicate.isEqual(path(patternString)); } if (patternString.equals("**")) { return (value) -> true; } if (patternString.equals("*")) { return hasOnlyOnePartPredicate(); } return new PathMatcher(pathPattern); } public PathMatcher(PathPattern pattern) { this.patternParts = toParts(pattern); } public boolean test(Path path) { List<Path> pathParts = path.parts(); // matching leftmost path parts to first "**" int patternLeft = 0; int pathLeft = 0; while (patternLeft < patternParts.size()) { Predicate<Path> patternPart = patternParts.get(patternLeft); if (patternPart == DOUBLE_STAR_PREDICATE) { break; } else if (pathLeft == pathParts.size()) { return false; } else if (patternPart.test(pathParts.get(pathLeft))) { pathLeft++; patternLeft++; } else { return false; } } if (patternLeft == patternParts.size()) { return pathLeft == pathParts.size(); } if (pathLeft == pathParts.size()) { return false; } // matching rightmost path parts to last "**" int patternRight = patternParts.size() - 1; int pathRight = pathParts.size() - 1; while (patternLeft < patternRight) { Predicate<Path> patternPart = patternParts.get(patternRight); if (patternPart == DOUBLE_STAR_PREDICATE) { break; } else if (pathRight < pathLeft) { return false; } else if (patternPart.test(pathParts.get(pathRight))) { patternRight--; pathRight--; } else { return false; } } /* * At this point pLeft and pRight points to parts that contain SINGLE_STAR. * If they both point to the same part then we've matched the whole pattern * - matching is successful if sRight and sLeft haven't overlapped. All * these cases are checked inside 'while' clause below. */ int stillToMatch = minimalPartsNeeded(patternLeft + 1, patternRight - 1); int stillAvailable = pathRight - pathLeft + 1; while (true) { if (stillAvailable < stillToMatch) { return false; } if (patternLeft == patternRight) { return true; } patternLeft++; int partsCount = patternPartsCountToNextDoubleStar(patternLeft); int steps = stillAvailable - stillToMatch + 1; boolean found = false; OUT: for (int i = 0; i < steps; i++) { for (int j = 0; j < partsCount; j++) { Predicate<Path> patternPart = patternParts.get(patternLeft + j); Path pathPart = pathParts.get(pathLeft + i + j); if (!patternPart.test(pathPart)) { continue OUT; } } found = true; patternLeft += partsCount; pathLeft += i + partsCount; break; } if (!found) { return false; } } } private int patternPartsCountToNextDoubleStar(int index) { int result = 0; while (patternParts.get(index) != DOUBLE_STAR_PREDICATE) { result++; index++; } return result; } private int minimalPartsNeeded(int fromIndex, int toIndex) { int result = 0; for (int i = fromIndex; i <= toIndex; i++) { Predicate<Path> part = patternParts.get(i); if (part != DOUBLE_STAR_PREDICATE) { result++; } } return result; } private static List<Predicate<Path>> toParts(PathPattern pattern) { List<Predicate<Path>> result = new ArrayList<>(); Predicate<Path> last = null; for (String part : pattern.parts()) { last = elementPatternToMatcher(namePattern(part)); result.add(last); } // If last part == "**" we have to add "*" at the end. // This way pattern "abc/**" won't match "abc" file. if (last == DOUBLE_STAR_PREDICATE) { result.add((value) -> true); } return result; } private static Predicate<Path> elementPatternToMatcher(NamePattern namePattern) { if (!namePattern.hasStars()) { return Predicate.isEqual(path(namePattern.value())); } if (namePattern.isDoubleStar()) { return DOUBLE_STAR_PREDICATE; } if (namePattern.isSingleStar()) { return (value) -> true; } return new NameMatcher(namePattern); } public static Predicate<Path> hasOnlyOnePartPredicate() { return (path) -> path.isRoot() || path.value().indexOf(Path.SEPARATOR) == -1; } }