// // Copyright © 2014, David Tesler (https://github.com/protobufel) // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of the <organization> nor the // names of its contributors may be used to endorse or promote products // derived from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // package com.github.protobufel.common.files; import static com.github.protobufel.common.verifications.Verifications.*; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import com.github.protobufel.common.files.PathContexts.PathContext; public final class ContextPathMatchers { private ContextPathMatchers() {} interface HierarchicalMatcherCommon { boolean isEmpty(); boolean isAllowDirs(); boolean isAllowFiles(); String getPattern(); } public interface ContextHierarchicalMatcher<T> extends HierarchicalMatcherCommon { boolean matches(T path, PathContext<T> pathContext); DirectoryMatchResult matchesDirectory(T path, PathContext<T> pathContext); } public interface BasicHierarchicalMatcher<T> extends HierarchicalMatcherCommon { boolean matchesResolved(@Nullable String path, PathContext<T> pathContext); DirectoryMatchResult matchesResolvedDirectory(@Nullable String path, @Nullable String separator, PathContext<T> pathContext); } public interface HierarchicalMatcher<T> extends ContextHierarchicalMatcher<T>, BasicHierarchicalMatcher<T> { } public static <T> HierarchicalMatcher<T> getHierarchicalMatcher(final String syntaxAndPattern, final boolean isUnix, final boolean allowDirs, final boolean allowFiles, final Class<? extends T> pathType) { final String[] parts = verifyNonNull(syntaxAndPattern).split(":", 2); final @NonNull String regex = verifyArgument(parts.length == 2, parts[1]); if (parts[0].equals("regex")) { return new SimpleHierarchicalMatcher<T>(isUnix, regex, allowDirs, allowFiles); } else if (parts[0].equals("glob")) { return new SimpleHierarchicalMatcher<T>(regex , isUnix, allowDirs, allowFiles); } else { throw new UnsupportedOperationException(String.format("%s syntax", parts[0])); } } public static final class SimpleHierarchicalMatcher<T> implements HierarchicalMatcher<T> { private final Pattern pattern; private final boolean allowDirs; private final boolean allowFiles; private final boolean isUnix; public SimpleHierarchicalMatcher(final SimpleHierarchicalMatcher<T> other) { this.pattern = other.pattern; this.allowDirs = other.allowDirs; this.allowFiles = other.allowFiles; this.isUnix = other.isUnix; } public SimpleHierarchicalMatcher(final String glob, final boolean isUnix, final boolean allowDirs, final boolean allowFiles) { this(isUnix, convertGlobToRegex(glob, isUnix), true, allowDirs, allowFiles); } public SimpleHierarchicalMatcher(final boolean isUnix, final String regex, final boolean allowDirs, final boolean allowFiles) { this(isUnix, regex, false, allowDirs, allowFiles); } public SimpleHierarchicalMatcher(final boolean isUnix, final String regex, final boolean isRegexSystemSpecific, final boolean allowDirs, final boolean allowFiles) { verifyCondition((allowDirs || allowFiles), "allowDirs and allowFiles cannot be both false"); this.allowDirs = allowDirs; this.allowFiles = allowFiles; final String regexFlags = isUnix ? "" : "(?iu)"; final String fsRegex = isRegexSystemSpecific ? regex : convertRegexToSystemSpecific(regex, isUnix); @SuppressWarnings("null") @NonNull Pattern compiled = Pattern.compile(regexFlags + verifyNonNull(fsRegex)); this.pattern = compiled; this.isUnix = isUnix; } @SuppressWarnings("null") @Override public String getPattern() { return pattern.pattern(); } @Override public String toString() { return "SimpleHierarchicalMatcher [pattern=" + pattern + ", flags=" + pattern.flags() + ", allowDirs=" + allowDirs + ", allowFiles=" + allowFiles + ", isUnix=" + isUnix + "]"; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + (allowDirs ? 1231 : 1237); result = prime * result + (allowFiles ? 1231 : 1237); result = prime * result + (isUnix ? 1231 : 1237); result = prime * result + pattern.pattern().hashCode(); return result; } @Override @NonNullByDefault(false) public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (!(obj instanceof SimpleHierarchicalMatcher)) { return false; } final SimpleHierarchicalMatcher<?> other = (SimpleHierarchicalMatcher<?>) obj; if (allowDirs != other.allowDirs) { return false; } if (allowFiles != other.allowFiles) { return false; } if (isUnix != other.isUnix) { return false; } if (!pattern.pattern().equals(other.pattern.pattern())) { return false; } return true; } @Override public boolean isEmpty() { return "".equals(pattern.pattern()); } public boolean isAllowDirs() { return allowDirs; } public boolean isAllowFiles() { return allowFiles; } @Override public boolean matches(final T path, final PathContext<T> pathContext) { return matchesResolved(pathContext.resolvePath(path), pathContext); } @Override public DirectoryMatchResult matchesDirectory(final T path, final PathContext<T> pathContext) { return matchesResolvedDirectory(pathContext.resolvePath(path), pathContext.getSeparator(path), pathContext); } @Override public boolean matchesResolved(final @Nullable String path, final PathContext<T> pathContext) { return (allowFiles && (path != null)) ? pattern.matcher(path).matches() : false; } @Override public DirectoryMatchResult matchesResolvedDirectory(final @Nullable String path, final @Nullable String separator, final PathContext<T> pathContext) { if ((path == null) || (separator == null)) { return DirectoryMatchResult.NO_MATCH; } final String pathWithSlash = path + separator; final Matcher matcher; final boolean isMatched; if (allowDirs) { matcher = pattern.matcher(path); isMatched = matcher.matches(); matcher.reset(pathWithSlash); } else { matcher = pattern.matcher(pathWithSlash); isMatched = false; } final boolean skip = matcher.matches() ? matcher.requireEnd() : (!matcher.hitEnd()); return DirectoryMatchResult.valueOf(isMatched, skip); } protected final static String convertGlobToRegex(final String glob, final boolean isUnix) { if (isUnix) { return Globs.toUnixRegexPattern(glob); } else { return Globs.toWindowsRegexPattern(glob); } } protected final String convertRegexToSystemSpecific(final String normalizedRelativeRegex, final boolean isUnix) { if (isUnix) { return normalizedRelativeRegex; } else { return convertNormalizedRelativeRegexToWindows(normalizedRelativeRegex); } } @SuppressWarnings("null") private static final Pattern UNIX_TO_WINDOWS_SLASH_FIND_REGEX = Pattern .compile("/|(?:(\\\\*+)(?:(\\[)|(\\])|(Q)|(E)))"); protected final String convertNormalizedRelativeRegexToWindows( final String normalizedRelativeRegex) { @SuppressWarnings("null") final @NonNull Matcher matcher = UNIX_TO_WINDOWS_SLASH_FIND_REGEX.matcher( normalizedRelativeRegex); final StringBuffer sb = new StringBuffer(); boolean foundCharGroup = false; boolean foundQuote = false; while (matcher.find()) { if (foundQuote) { if (matcher.group(5) != null) { if ((matcher.group(1).length() % 2) == 1) { foundQuote = false; } } } else if (matcher.group(4) != null) { if ((matcher.group(1).length() % 2) == 1) { foundQuote = true; } } else if (matcher.group(2) != null) { if ((matcher.group(1).length() % 2) == 0) { foundCharGroup = true; } } else if (matcher.group(3) != null) { if ((matcher.group(1).length() % 2) == 0) { foundCharGroup = false; } } else if (!foundCharGroup) { matcher.appendReplacement(sb, "\\\\\\\\"); } } @SuppressWarnings("null") final @NonNull String result = matcher.appendTail(sb).toString(); return result; } } }