blob: 5745daf86e05b7c90036f21ae1b022197c8d5412 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.jackrabbit.vault.fs.api;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* The item filter set holds a set of item filters each attributed as include
* or exclude filter. The evaluation of the set allows included items and
* rejects excluded items.
* <p>
* Additionally it contains a "root" path for which the filters are evaluated.
* if an item has not the node addressed by the root path as ancestor, it is
* always excluded.
*/
public abstract class FilterSet<E extends Filter> implements Dumpable {
/**
* root path of this definition
*/
@Nonnull
private String root;
/**
* root patten to check for inclusion
*/
@Nonnull
private String rootPattern;
/**
* filter entries
*/
@Nullable
private List<Entry<E>> entries;
/**
* flag that indicates if set is sealed
*/
private boolean sealed;
/**
* import mode. defaults to {@link ImportMode#REPLACE}.
*/
@Nonnull
private ImportMode mode = ImportMode.REPLACE;
/**
* Default constructor. initializes the root path to "/"
*/
public FilterSet() {
this("");
}
/**
* Creates a new item filter set and sets the respective root path
* @param root path
*/
public FilterSet(String root) {
setRoot(root);
}
/**
* Returns the root path
* @return root path
*/
@Nonnull
public String getRoot() {
return "".equals(root) ? "/" : root;
}
/**
* Sets the root path
* @param path root path
*/
public void setRoot(@Nonnull String path) {
if (sealed) {
throw new UnsupportedOperationException("FilterSet is sealed.");
}
if (path.endsWith("/")) {
rootPattern = path;
root = path.substring(0, path.length() - 1);
} else {
rootPattern = path + "/";
root = path;
}
}
/**
* Returns the import mode that is specified for this filter set. Defaults to
* {@link ImportMode#REPLACE}.
*
* @return the import mode.
*/
@Nonnull
public ImportMode getImportMode() {
return mode;
}
/**
* Sets the import mode.
* @param mode import mode
*/
public void setImportMode(@Nonnull ImportMode mode) {
if (sealed) {
throw new UnsupportedOperationException("FilterSet is sealed.");
}
this.mode = mode;
}
/**
* Seals this list, i.e. makes it unmodifiable.
* @return this list
*/
@Nonnull
public FilterSet seal() {
if (!sealed) {
if (entries == null) {
entries = Collections.emptyList();
} else {
entries = Collections.unmodifiableList(entries);
}
sealed = true;
}
return this;
}
/**
* Checks if this filter set is sealed.
* @return {@code true} if sealed.
*/
public boolean isSealed() {
return sealed;
}
/**
* Adds (replaces) all entries from the given set to this one.
* @param set the set of entries
* @return {@code this} suitable for chaining.
*/
@Nonnull
public FilterSet addAll(@Nonnull FilterSet<E> set) {
if (sealed) {
throw new UnsupportedOperationException("FilterSet is sealed.");
}
entries = null;
if (set.entries != null) {
entries = new LinkedList<>(set.entries);
}
return this;
}
/**
* Adds a new item filter as included entry.
* @param filter the filter
* @return {@code this} suitable for chaining.
*/
@Nonnull
public FilterSet addInclude(@Nonnull E filter) {
addEntry(new Entry<>(filter, true));
return this;
}
/**
* Adds a new item filter as excluded entry.
* @param filter the filter
* @return {@code this} suitable for chaining.
*/
@Nonnull
public FilterSet addExclude(@Nonnull E filter) {
addEntry(new Entry<>(filter, false));
return this;
}
/**
* Internally adds a new entry to the list
* @param e the entry
*/
private void addEntry(@Nonnull Entry<E> e) {
if (sealed) {
throw new UnsupportedOperationException("FilterSet is sealed.");
}
if (entries == null) {
entries = new LinkedList<>();
}
entries.add(e);
}
/**
* Returns the list of entries
* @return the list of entries
*/
@Nonnull
public List<Entry<E>> getEntries() {
seal();
//noinspection ConstantConditions
return entries;
}
/**
* Checks if this filter set has any entries defined.
* @return {@code true} if empty
*/
public boolean isEmpty() {
return entries == null || entries.isEmpty();
}
/**
* Checks if the given item is covered by this filter set. I.e. if the node
* addressed by the {@code root} path is an ancestor of the given item.
*
* @param path path of the item
* @return {@code true} if this set covers the given item
*/
public boolean covers(@Nonnull String path) {
return path.equals(root) || path.startsWith(rootPattern);
}
/**
* Checks if the given item is an ancestor of the root node.
* @param path path of the item to check
* @return {@code true} if the given item is an ancestor
*/
public boolean isAncestor(@Nonnull String path) {
return path.equals(root) || root.startsWith(path + "/") || "/".equals(path);
}
/**
* {@inheritDoc}
*/
@Override
public void dump(@Nonnull DumpContext ctx, boolean isLast) {
ctx.printf(false, "root: %s", getRoot());
if (entries != null) {
Iterator<Entry<E>> iter = entries.iterator();
while (iter.hasNext()) {
Entry e = iter.next();
e.dump(ctx, !iter.hasNext());
}
}
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
int result = root.hashCode();
result = 31 * result + (entries != null ? entries.hashCode() : 0);
return result;
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof FilterSet)) return false;
FilterSet filterSet = (FilterSet) o;
if (entries != null ? !entries.equals(filterSet.entries) : filterSet.entries != null) return false;
return root.equals(filterSet.root);
}
@Override
public String toString() {
StringWriter stringWriter = new StringWriter();
dump(new DumpContext(new PrintWriter(stringWriter)), true);
return stringWriter.toString();
}
/**
* Holds a filter entry
*/
public static class Entry<E extends Filter> implements Dumpable {
/**
* The item filter
*/
@Nonnull
protected final E filter;
/**
* indicates if this an include filter
*/
protected final boolean include;
/**
* Constructs a new entry
* @param filter the filter
* @param include the include flag
*/
public Entry(@Nonnull E filter, boolean include) {
this.filter = filter;
this.include = include;
}
/**
* Returns the filter of this entry
* @return the filter
*/
@Nonnull
public E getFilter() {
return filter;
}
/**
* Returns the 'include' flag of this entry
* @return the flag
*/
public boolean isInclude() {
return include;
}
/**
* {@inheritDoc}
*/
@Override
public void dump(@Nonnull DumpContext ctx, boolean isLast) {
if (include) {
ctx.println(isLast, "include");
} else {
ctx.println(isLast, "exclude");
}
ctx.indent(isLast);
filter.dump(ctx, true);
ctx.outdent();
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
int result = filter.hashCode();
result = 31 * result + (include ? 1 : 0);
return result;
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Entry)) return false;
Entry entry = (Entry) o;
return include == entry.include && filter.equals(entry.filter);
}
@Override
public String toString() {
StringWriter stringWriter = new StringWriter();
dump(new DumpContext(new PrintWriter(stringWriter)), true);
return stringWriter.toString();
}
}
}