| /* |
| * 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(); |
| } |
| |
| |
| } |
| } |