//
// 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.files.Utils.isUnix;
import static com.github.protobufel.common.verifications.Verifications.*;
import java.io.File;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import com.github.protobufel.common.files.ContextPathMatchers.ContextHierarchicalMatcher;
import com.github.protobufel.common.files.ContextPathMatchers.HierarchicalMatcher;
import com.github.protobufel.common.files.PathContexts.PathContext;
import com.github.protobufel.common.files.HistoryCaches.SimpleHistoryCache;
public final class ContextResourcePathMatchers {
private ContextResourcePathMatchers() {
}
public static class CompositePathMatcher<T, E extends ContextHierarchicalMatcher<T>>
implements ContextHierarchicalMatcher<T> {
private static final CompositePathMatcher<?, ?> EMPTY =
new CompositePathMatcher<Object, ContextHierarchicalMatcher<Object>>();
private final List<E> matchers;
private final boolean allowDirs;
private final boolean allowFiles;
//private final transient SimpleHistoryCache<MatcherCacheData> cache;
private final transient SimpleHistoryCache<IterableFilter> cache;
private CompositePathMatcher() {
matchers = assertNonNull(Collections.<E>emptyList());
this.cache = SimpleHistoryCache.emptyCache();
this.allowDirs = false;
this.allowFiles = false;
}
public CompositePathMatcher(final Iterable<? extends E> matchers) {
this(matchers, Integer.MAX_VALUE);
}
public CompositePathMatcher(final Iterable<? extends E> matchers, final int cacheSize) {
final @NonNull Set<E> matcherSet = new HashSet<E>();
final @NonNull List<E> matcherList = new ArrayList<E>();
boolean allowDirs = false;
boolean allowFiles = false;
for (E matcher : verifyNonNull(matchers)) {
if (!verifyNonNull(matcher).isEmpty()) {
matcherSet.add(matcher); // there should be no equal matchers!
matcherList.add(matcher);
if (!allowDirs && matcher.isAllowDirs()) {
allowDirs = true;
}
if (!allowFiles && matcher.isAllowFiles()) {
allowFiles = true;
}
}
}
this.allowDirs = allowDirs;
this.allowFiles = allowFiles;
@SuppressWarnings("null")
final @NonNull List<E> unmodifiableList = Collections.unmodifiableList(matcherList);
this.matchers = unmodifiableList;
this.cache = new SimpleHistoryCache<IterableFilter>(verifyArgument(cacheSize > 0, cacheSize));
}
@SuppressWarnings("unchecked")
public static <T, E extends FileSetPathMatcher<T>> CompositePathMatcher<T, E> emptyInstance() {
return (CompositePathMatcher<T, E>) EMPTY;
}
@Override
public boolean isAllowDirs() {
return allowDirs;
}
@Override
public boolean isAllowFiles() {
return allowFiles;
}
@Override
public String getPattern() {
if (matchers.isEmpty()) {
return "[]";
}
final StringBuilder sb = new StringBuilder("[");
for (E matcher : matchers) {
sb.append(matcher.getPattern()).append(",");
}
sb.setCharAt(sb.length() - 1, ']');
@SuppressWarnings("null")
final @NonNull String result = sb.toString();
return result;
}
@Override
public boolean matches(final T path, final PathContext<T> context) {
if (!allowFiles) {
return false;
}
cache.adjustCache(context.currentDepth());
final IterableFilter cacheData = getCacheData();
for (int index : cacheData) {
if (matchers.get(index).matches(path, context)) {
return true;
}
}
return false;
}
@Override
public DirectoryMatchResult matchesDirectory(final T path, final PathContext<T> context) {
if ((this == EMPTY) || matchers.isEmpty()) {
return DirectoryMatchResult.NO_MATCH;
}
cache.adjustCache(context.currentDepth());
final IterableFilter cacheData = getCacheData();
if (cacheData.isEmpty()) {
return DirectoryMatchResult.NO_MATCH;
}
final IterableFilter.Builder cacheBuilder = cacheData.toBuilder();
boolean skip = true;
boolean matched = false;
for (int index : cacheData) {
final DirectoryMatchResult matchResult = matchers.get(index)
.matchesDirectory(path, context);
if (matchResult.isMatched()) {
matched = true;
if (!matchResult.isSkip()) {
// MATCH_CONTINUE
skip = false;
break;
}
cacheBuilder.clear(index);
if (!skip) {
// MATCH_CONTINUE
break;
}
} else if (!matchResult.isSkip()) {
skip = false;
if (matched) {
// MATCH_CONTINUE
break;
} else if (!allowDirs) {
// exact match is not required, so we lazily stop searching further
// NO_MATCH_CONTINUE
break;
}
} else {
// this matcher didn't match anyhow, so remove it, and continue searching for match
cacheBuilder.clear(index);
}
}
final DirectoryMatchResult result = DirectoryMatchResult.valueOf(matched, skip);
if (!skip) {
cache.push(cacheBuilder.build());
}
return result;
}
private IterableFilter getCacheData() {
if (cache.isEmpty()) {
return IterableFilter.builder().resetAll(matchers).build();
} else {
return assertNonNull(cache.peek());
}
}
@Override
public boolean isEmpty() {
return (this == EMPTY) || matchers.isEmpty();
}
@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 + ((matchers == null) ? 0 : matchers.hashCode());
return result;
}
@NonNullByDefault(false)
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof CompositePathMatcher)) {
return false;
}
final CompositePathMatcher<?, ?> other = (CompositePathMatcher<?, ?>) obj;
if (allowDirs != other.allowDirs) {
return false;
}
if (allowFiles != other.allowFiles) {
return false;
}
if (!matchers.equals(other.matchers)) {
return false;
}
return true;
}
@Override
public String toString() {
@SuppressWarnings("null")
@NonNull String result = new StringBuilder()
.append("CompositePathMatcher [matchers=").append(matchers)
.append(", allowDirs=").append(allowDirs)
.append(", allowFiles=").append(allowFiles)
.append("]").toString();
return result;
}
}
public static class FileSetPathMatcher<T> implements HierarchicalMatcher<T> {
private static final FileSetPathMatcher<?> EMPTY = new FileSetPathMatcher<Object>();
private final List<HierarchicalMatcher<T>> includes;
private final List<HierarchicalMatcher<T>> excludes;
private final T dir;
private final boolean allowDirs;
private final boolean allowFiles;
private final transient SimpleHistoryCache<FileSetCacheData> cache;
@SuppressWarnings("unchecked")
public static <T> FileSetPathMatcher<T> emptyInstance() {
return (FileSetPathMatcher<T>) EMPTY;
}
private FileSetPathMatcher() {
@SuppressWarnings("null")
final @NonNull List<HierarchicalMatcher<T>> emptyList = Collections.emptyList();
this.includes = emptyList;
this.excludes = emptyList;
@SuppressWarnings("unchecked")
final T t = (T) new Object(); // okay because only used for the EMPTY singleton
this.dir = t;
this.allowDirs = false;
this.allowFiles = false;
this.cache = SimpleHistoryCache.emptyCache();
}
public FileSetPathMatcher(final Collection<String> includes, final Collection<String> excludes,
final T dir, final boolean allowDirs, final boolean allowFiles) {
this(includes, excludes, dir, allowDirs, allowFiles, Integer.MAX_VALUE);
}
public FileSetPathMatcher(final Collection<String> includes, final Collection<String> excludes,
final T dir, final boolean allowDirs, final boolean allowFiles, final int cacheSize) {
verifyCondition((allowDirs || allowFiles), "allowDirs and allowFiles cannot be both false");
this.cache = new SimpleHistoryCache<FileSetCacheData>(verifyArgument(cacheSize > 0, cacheSize));
this.allowDirs = allowDirs;
this.allowFiles = allowFiles;
this.dir = verifyNonNull(dir);
@SuppressWarnings({ "unchecked", "null" })
final @NonNull Class<T> pathType = (Class<T>) dir.getClass();
@SuppressWarnings("null")
final @NonNull List<HierarchicalMatcher<T>> emptyList = Collections.emptyList();
if (verifyNonNull(includes).isEmpty()) {
this.includes = emptyList;
} else {
final List<HierarchicalMatcher<T>> includesList =
new ArrayList<HierarchicalMatcher<T>>(includes.size());
for (String include : includes) {
includesList.add(ContextPathMatchers.getHierarchicalMatcher(verifyNonNull(include),
isUnix(), allowDirs, allowFiles, pathType));
}
this.includes = assertNonNull(Collections.unmodifiableList(includesList));
}
if (verifyNonNull(excludes).isEmpty()) {
this.excludes = emptyList;
} else {
final List<HierarchicalMatcher<T>> excludesList =
new ArrayList<HierarchicalMatcher<T>>(excludes.size());
for (String exclude : excludes) {
excludesList.add(ContextPathMatchers.getHierarchicalMatcher(verifyNonNull(exclude),
isUnix(), allowDirs, allowFiles, pathType));
}
this.excludes = assertNonNull(Collections.unmodifiableList(excludesList));
}
}
private List<HierarchicalMatcher<T>> getIncludes() {
return includes;
}
private List<HierarchicalMatcher<T>> getExcludes() {
return excludes;
}
public static <T, BType extends Builder<T, BType>> BType builder(final T dir) {
return new Builder<T, BType>(dir).self();
}
public <BType extends Builder<T, BType>> BType newBuilder() {
return new Builder<T, BType>(dir).self();
}
public <BType extends Builder<T, BType>> BType toBuilder() {
return new Builder<T, BType>(this).self();
}
@Override
public String getPattern() {
final StringBuilder sb = new StringBuilder("[filter:[");
if (includes.isEmpty()) {
sb.append("]");
} else {
for (HierarchicalMatcher<T> include : includes) {
sb.append(include.getPattern()).append(",");
}
sb.setCharAt(sb.length() - 1, ']');
}
sb.append(", excludes:[");
if (excludes.isEmpty()) {
sb.append("]");
} else {
for (HierarchicalMatcher<T> exclude : excludes) {
sb.append(exclude.getPattern()).append(",");
}
sb.setCharAt(sb.length() - 1, ']');
}
@SuppressWarnings("null")
final @NonNull String result = sb.append("]").toString();
return result;
}
@Override
public String toString() {
return "FileSetPathMatcher [filter=" + includes + ", excludes=" + excludes + ", dir=" + dir
+ ", allowDirs=" + allowDirs + ", allowFiles=" + allowFiles + "]";
}
@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 + ((dir == null) ? 0 : dir.hashCode());
result = prime * result + ((excludes == null) ? 0 : excludes.hashCode());
result = prime * result + ((includes == null) ? 0 : includes.hashCode());
return result;
}
@Override
@NonNullByDefault(false)
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof FileSetPathMatcher)) {
return false;
}
try {
final FileSetPathMatcher<?> other = (FileSetPathMatcher<?>) obj;
if (allowDirs != other.allowDirs) {
return false;
}
if (allowFiles != other.allowFiles) {
return false;
}
if (!dir.equals(other.dir)) {
return false;
}
if (!excludes.equals(other.excludes)) {
return false;
}
if (!includes.equals(other.includes)) {
return false;
}
return true;
} catch (Exception e) {
return false;
}
}
@Override
public boolean isEmpty() {
return (this == EMPTY) || includes.isEmpty();
}
public T getDir() {
return dir;
}
public boolean isAllowDirs() {
return allowDirs;
}
public boolean isAllowFiles() {
return allowFiles;
}
protected SimpleHistoryCache<FileSetCacheData> getCache() {
return cache;
}
@Override
public boolean matches(final T path, final PathContext<T> context) {
if (!allowFiles) {
return false;
}
return matchesResolved(context.resolvePath(dir, path), context);
}
@Override
public boolean matchesResolved(final @Nullable String sanitizedPath, final PathContext<T> context) {
if (!allowFiles) {
return false;
}
cache.adjustCache(context.currentDepth());
boolean matched = false;
final FileSetCacheData cacheData = getCacheData();
if (cacheData.getIncludes().isEmpty()) {
return false;
}
for (int index : cacheData.getIncludes()) {
final HierarchicalMatcher<T> include = includes.get(index);
if (include.matchesResolved(sanitizedPath, context)) {
matched = true;
break;
}
}
if (!matched) {
return false;
}
if (cacheData.getExcludes().isEmpty()) {
return true;
}
for (int index : cacheData.getExcludes()) {
final HierarchicalMatcher<T> exclude = includes.get(index);
if (exclude.matchesResolved(sanitizedPath, context)) {
return false;
}
}
return true;
}
@Override
public DirectoryMatchResult matchesDirectory(final T path, final PathContext<T> context) {
if (this == EMPTY) {
return DirectoryMatchResult.NO_MATCH;
}
if (context.resolvePath(path, dir) != null) {
//this a parent of dir
return DirectoryMatchResult.NO_MATCH_CONTINUE;
}
return matchesResolvedDirectory(context.resolvePath(dir, path), context.getSeparator(path),
context);
}
@Override
public DirectoryMatchResult matchesResolvedDirectory(final @Nullable String sanitizedPath,
final @Nullable String separator, final PathContext<T> context) {
if ((this == EMPTY) || (sanitizedPath == null) || (separator == null)) {
return DirectoryMatchResult.NO_MATCH;
}
cache.adjustCache(context.currentDepth());
/* if (sanitizedPath.isEmpty()) {
return DirectoryMatchResult.NO_MATCH_CONTINUE;
}
*/
final FileSetCacheData cacheData = getCacheData();
final FileSetCacheData.Builder cacheBuilder = cacheData.newBuilder();
if (!cacheData.getExcludes().isEmpty()) {
for (int index : cacheData.getExcludes()) {
final HierarchicalMatcher<T> exclude = excludes.get(index);
final DirectoryMatchResult matchResult = exclude.matchesResolvedDirectory(sanitizedPath,
separator, context);
if (matchResult.isMatched()) {
return DirectoryMatchResult.NO_MATCH;
} else if (!matchResult.isSkip()) {
cacheBuilder.setExclude(index);
}
}
}
boolean skip = true;
boolean matched = false;
cacheBuilder.includesFrom(cacheData);
if (!cacheData.getIncludes().isEmpty()) {
for (int index : cacheData.getIncludes()) {
final HierarchicalMatcher<T> include = includes.get(index);
final DirectoryMatchResult matchResult = include.matchesResolvedDirectory(sanitizedPath,
separator, context);
if (matchResult.isMatched()) {
matched = true;
if (!matchResult.isSkip()) {
// MATCH_CONTINUE
skip = false;
break;
}
cacheBuilder.clearInclude(index);
if (!skip) {
// MATCH_CONTINUE
break;
}
} else if (!matchResult.isSkip()) {
skip = false;
if (matched) {
// MATCH_CONTINUE
break;
} else if (!allowDirs) {
// exact match is not required, so we lazily stop searching further
// NO_MATCH_CONTINUE
break;
}
} else {
// this include didn't match anyhow, so remove it, and continue searching for match
cacheBuilder.clearInclude(index);
}
}
}
final DirectoryMatchResult result = DirectoryMatchResult.valueOf(matched, skip);
// if (result != DirectoryMatchResult.NO_MATCH) {
if (!skip) {
cache.push(cacheBuilder.build());
}
return result;
}
private FileSetCacheData getCacheData() {
if (cache.isEmpty()) {
return FileSetCacheData.builder().setIncludes(includes).setExcludes(excludes).build();
} else {
return assertNonNull(cache.peek());
}
}
public static class Builder<T, BType extends Builder<T, BType>> {
private final LinkedHashSet<String> includes;
private final LinkedHashSet<String> excludes;
private T dir;
private boolean allowDirs;
private boolean allowFiles;
public Builder(final T dir) {
this.includes = new LinkedHashSet<String>();
this.excludes = new LinkedHashSet<String>();
this.dir = dir;
this.allowDirs = false;
this.allowFiles = true;
}
public Builder(final FileSetPathMatcher<T> original) {
this(original.getDir());
from(original);
}
public Builder(final BType original) {
this(original.dir());
from(original);
}
@SuppressWarnings("unchecked")
protected BType self() {
return (BType) this;
}
public BType from(final FileSetPathMatcher<T> original) {
verifyNonNull(original);
this.includes.clear();
this.excludes.clear();
addAllMatchers(this.includes, original.getIncludes());
addAllMatchers(this.excludes, original.getExcludes());
this.dir = original.getDir();
this.allowDirs = original.isAllowDirs();
this.allowFiles = original.isAllowFiles();
return self();
}
public BType from(final BType original) {
verifyNonNull(original);
this.includes.clear();
this.excludes.clear();
this.includes.addAll(original.includes());
this.excludes.addAll(original.excludes());
this.dir = original.dir();
this.allowDirs = original.isAllowDirs();
this.allowFiles = original.isAllowFiles();
return self();
}
private LinkedHashSet<String> addAllMatchers(final LinkedHashSet<String> target,
final Iterable<? extends HierarchicalMatcher<? extends T>> source) {
for (HierarchicalMatcher<? extends T> include : verifyNonNull(source)) {
target.add(verifyNonNull(include).getPattern());
}
return target;
}
//TODO should this be replaced by addAllIncludesAndExcludes, as the rest is murky!
@SuppressWarnings("unused")
@Deprecated
private BType combine(final FileSetPathMatcher<T> other) {
addAllMatchers(this.includes, other.getIncludes());
addAllMatchers(this.excludes, other.getExcludes());
verifyCondition(Objects.equals(this.dir, other.getDir()), "directories must be equal");
//this.dir = other.dir;
this.allowDirs = this.allowDirs || other.isAllowDirs();
this.allowFiles = this.allowFiles || other.isAllowFiles();
return self();
}
public BType addIncludesExcludesFrom(final FileSetPathMatcher<T> other) {
addAllMatchers(this.includes, other.getIncludes());
addAllMatchers(this.excludes, other.getExcludes());
return self();
}
@SuppressWarnings("null")
public Set<String> includes() {
return Collections.unmodifiableSet(includes);
}
@SuppressWarnings("null")
public Set<String> excludes() {
return Collections.unmodifiableSet(excludes);
}
public BType clearExcludes() {
excludes.clear();
return self();
}
public BType removeExclude(final String exclude) {
excludes.remove(exclude);
return self();
}
public BType removeExcludes(final Collection<String> excludes) {
this.excludes.removeAll(excludes);
return self();
}
public BType clearIncludes() {
includes.clear();
return self();
}
public BType removeInclude(final String include) {
includes.remove(include);
return self();
}
public BType removeIncludes(final Collection<String> includes) {
this.includes.removeAll(includes);
return self();
}
public BType addExcludes(final Iterable<String> excludes) {
return addAll(this.excludes, excludes);
}
public BType addExclude(final String exclude) {
this.excludes.add(verifyNonNull(exclude));
return self();
}
public BType addIncludes(final Iterable<String> includes) {
return addAll(this.includes, includes);
}
public BType addInclude(final String include) {
this.includes.add(verifyNonNull(include));
return self();
}
private BType addAll(final Collection<String> target, final Iterable<String> source) {
for (String include : verifyNonNull(source)) {
target.add(verifyNonNull(include));
}
return self();
}
public T dir() {
return dir;
}
@SuppressWarnings({ "unchecked", "null" })
protected T convertPath(String dir) {
if (this.dir instanceof Path) {
return (T) ((Path) this.dir).getFileSystem().getPath(dir);
} else if (this.dir instanceof File) {
return (T) new File(dir);
} else if (this.dir instanceof String) {
return (T) dir;
} else {
throw new UnsupportedOperationException();
}
}
public BType dir(final String dir) {
this.dir = convertPath(verifyNonNull(dir));
return self();
}
public BType dir(final T dir) {
this.dir = verifyNonNull(dir);
return self();
}
public boolean isAllowDirs() {
return allowDirs;
}
public BType allowDirs(boolean allowDirs) {
this.allowDirs = allowDirs;
return self();
}
public boolean isAllowFiles() {
return allowFiles;
}
public BType allowFiles(boolean allowFiles) {
this.allowFiles = allowFiles;
return self();
}
public FileSetPathMatcher<T> build() {
if (includes.isEmpty()) {
@SuppressWarnings("unchecked")
final FileSetPathMatcher<T> empty = (FileSetPathMatcher<T>) EMPTY;
return empty;
}
final FileSetPathMatcher<T> result = new FileSetPathMatcher<T>(includes, excludes,
verifyNonNull(dir), allowDirs, allowFiles);
return result;
}
}
}
protected static final class FileSetCacheData {
private final IterableFilter includes;
private final IterableFilter excludes;
private FileSetCacheData(IterableFilter includes, IterableFilter excludes) {
this.includes = new IterableFilter(includes);
this.excludes = new IterableFilter(excludes);
}
private FileSetCacheData(BitSet includes, BitSet excludes) {
this.includes = new IterableFilter(includes);
this.excludes = new IterableFilter(excludes);
}
public IterableFilter getIncludes() {
return includes;
}
public IterableFilter getExcludes() {
return excludes;
}
public static Builder builder() {
return new Builder();
}
public Builder newBuilder() {
return new Builder();
}
public Builder toBuilder() {
return new Builder().from(this);
}
@Override
public String toString() {
@SuppressWarnings("null")
@NonNull String result = new StringBuilder()
.append("FileSetCacheData [includes=").append(includes)
.append(", excludes=").append(excludes)
.append("]").toString();
return result;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((excludes == null) ? 0 : excludes.hashCode());
result = prime * result + ((includes == null) ? 0 : includes.hashCode());
return result;
}
@NonNullByDefault(false)
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof FileSetCacheData)) {
return false;
}
FileSetCacheData other = (FileSetCacheData) obj;
if (!excludes.equals(other.excludes)) {
return false;
}
if (!includes.equals(other.includes)) {
return false;
}
return true;
}
public static class Builder {
private IterableFilter.Builder includes;
private IterableFilter.Builder excludes;
private Builder() {
this.includes = IterableFilter.builder();
this.excludes = IterableFilter.builder();
}
public Builder from(final Builder other) {
this.includes = IterableFilter.builder().from(other.includes);
this.excludes = IterableFilter.builder().from(other.excludes);
return this;
}
public Builder from(final FileSetCacheData other) {
this.includes = IterableFilter.builder().from(other.includes);
this.excludes = IterableFilter.builder().from(other.excludes);
return this;
}
public Builder includesFrom(final FileSetCacheData other) {
this.includes = IterableFilter.builder().from(other.includes);
return this;
}
public Builder excludesFrom(final FileSetCacheData other) {
this.excludes = IterableFilter.builder().from(other.excludes);
return this;
}
public Builder setExclude(final int index) {
excludes.set(index);
return this;
}
public Builder setInclude(final int index) {
includes.set(index);
return this;
}
public Builder setAllIncludes(final Iterable<?> values) {
includes.resetAll(values);
return this;
}
public Builder setAllExcludes(final Iterable<?> values) {
excludes.resetAll(values);
return this;
}
public Builder setIncludes(final Iterable<?> values) {
includes.reset(values);
return this;
}
public Builder setExcludes(final Iterable<?> values) {
excludes.reset(values);
return this;
}
public Builder clearIncludes() {
includes.clear();
return this;
}
public Builder clearExcludes() {
excludes.clear();
return this;
}
public Builder clearExclude(final int index) {
excludes.clear(index);
return this;
}
public Builder clearInclude(final int index) {
includes.clear(index);
return this;
}
public FileSetCacheData build() {
return new FileSetCacheData(includes.build(), excludes.build());
}
}
}
protected static final class IterableFilter implements Iterable<Integer> {
private final BitSet filter;
private IterableFilter() {
this.filter = new BitSet();
}
private IterableFilter(final BitSet filter) {
this.filter = compactClone(verifyNonNull(filter));
}
private IterableFilter(final IterableFilter other) {
this.filter = compactClone(verifyNonNull(other.filter));
}
@SuppressWarnings("null")
protected static final BitSet compactClone(final BitSet original) {
return BitSet.valueOf(original.toLongArray());
}
public static Builder builder() {
return new Builder();
}
public Builder newBuilder() {
return new Builder();
}
public Builder toBuilder() {
return new Builder().from(this);
}
public int size() {
return filter.length();
}
public boolean isEmpty() {
return filter.isEmpty();
}
@Override
public Iterator<Integer> iterator() {
return new FilteredIterator(filter);
}
@Override
public String toString() {
@SuppressWarnings("null")
final @NonNull String result = new StringBuilder()
.append("IterableFilter [filter=").append(filter)
.append("]")
.toString();
return result;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((filter == null) ? 0 : filter.hashCode());
return result;
}
@NonNullByDefault(false)
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof IterableFilter)) {
return false;
}
IterableFilter other = (IterableFilter) obj;
if (!filter.equals(other.filter)) {
return false;
}
return true;
}
public static class Builder {
private BitSet filter;
private Builder() {
this.filter = new BitSet();
}
public Builder from(final Builder other) {
this.filter = compactClone(other.filter);
return this;
}
public Builder from(final IterableFilter other) {
this.filter = compactClone(other.filter);
return this;
}
public Builder set(final int index) {
filter.set(index);
return this;
}
public Builder reset(final int size, boolean value) {
filter.set(0, size, value);
return this;
}
public Builder resetAll(final Iterable<?> values) {
int size = 0;
if (values instanceof Collection) {
size = ((Collection<?>) values).size();
} else {
for (@SuppressWarnings("unused") Object value : values) {
size++;
}
}
return reset(size, true);
}
public Builder reset(final Iterable<?> values) {
if (values instanceof Collection) {
final Collection<?> col = (Collection<?>) values;
filter = new BitSet(col.size());
if (col.isEmpty()) {
return this;
}
}
int i = 0;
for (Object value : values) {
filter.set(i++, (value != null));
}
return this;
}
public Builder clear() {
filter.clear();
return this;
}
public Builder clear(final int index) {
filter.clear(index);
return this;
}
public boolean isEmpty() {
return filter.isEmpty();
}
public IterableFilter build() {
return new IterableFilter(filter);
}
}
private static final class FilteredIterator implements Iterator<Integer> {
private final BitSet filter;
private int current;
public FilteredIterator(final BitSet filter) {
this.filter = verifyNonNull(filter);
this.current = filter.nextSetBit(0);
}
@Override
public boolean hasNext() {
return (current >= 0);
}
@Override
public Integer next() {
if (current < 0) {
throw new NoSuchElementException();
}
@SuppressWarnings("null")
final @NonNull Integer result = Integer.valueOf(current);
current = filter.nextSetBit(current + 1);
return result;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
}
}