001/*
002 * (C) Copyright 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Georges Racinet
018 *     Florent Guillaume
019 */
020
021package org.nuxeo.ecm.core.api.impl;
022
023import java.util.Collection;
024import java.util.Collections;
025import java.util.HashSet;
026import java.util.List;
027import java.util.Set;
028
029import org.nuxeo.ecm.core.api.DocumentModel;
030import org.nuxeo.ecm.core.api.Filter;
031
032/**
033 * A filter based on facets.
034 *
035 * @author Georges Racinet
036 * @author Florent Guillaume
037 */
038public class FacetFilter implements Filter {
039
040    public static final FacetFilter ALLOW = new FacetFilter((List<String>) null, (List<String>) null);
041
042    /** Set of required facets. Never {@code null}. */
043    public final Set<String> required;
044
045    /** Set of excluded facets. Never {@code null}. */
046    public final Set<String> excluded;
047
048    public final Boolean shortcut;
049
050    /**
051     * Generic constructor.
052     *
053     * @param required list of facets the models must have to pass the filter
054     * @param excluded list of facets the models must not have to pass the filter
055     */
056    public FacetFilter(List<String> required, List<String> excluded) {
057        if (required == null) {
058            this.required = Collections.emptySet();
059        } else {
060            this.required = new HashSet<>(required);
061        }
062        if (excluded == null) {
063            this.excluded = Collections.emptySet();
064        } else {
065            this.excluded = new HashSet<>(excluded);
066        }
067        shortcut = findShortcut();
068    }
069
070    /**
071     * Simpler constructor to filter on a single facet.
072     *
073     * @param facet the facet to filter on
074     * @param isRequired if true, accepted models must have the facet; if false, accepted models must not have the facet
075     */
076    public FacetFilter(String facet, boolean isRequired) {
077        if (isRequired) {
078            required = Collections.singleton(facet);
079            excluded = Collections.emptySet();
080        } else {
081            required = Collections.emptySet();
082            excluded = Collections.singleton(facet);
083        }
084        shortcut = null;
085    }
086
087    /**
088     * Constructor that ANDs two filters.
089     *
090     * @param filter1 the first filter
091     * @param filter2 the second filter
092     */
093    public FacetFilter(FacetFilter filter1, FacetFilter filter2) {
094        if (filter1.required.isEmpty() && filter2.required.isEmpty()) {
095            required = Collections.emptySet();
096        } else {
097            required = new HashSet<>(filter1.required);
098            required.addAll(filter2.required);
099        }
100        if (filter1.excluded.isEmpty() && filter2.excluded.isEmpty()) {
101            excluded = Collections.emptySet();
102        } else {
103            excluded = new HashSet<>(filter1.excluded);
104            excluded.addAll(filter2.excluded);
105        }
106        shortcut = findShortcut();
107    }
108
109    protected Boolean findShortcut() {
110        if (required.isEmpty() && excluded.isEmpty()) {
111            // no condition, always matches
112            return Boolean.TRUE;
113        }
114        Collection<String> intersection = new HashSet<>(required);
115        intersection.retainAll(excluded);
116        if (!intersection.isEmpty()) {
117            // non-empty intersection, filter can never match
118            return Boolean.FALSE;
119        }
120        return null;
121    }
122
123    @Override
124    public boolean accept(DocumentModel docModel) {
125        if (shortcut != null) {
126            return shortcut;
127        }
128        for (String exc : excluded) {
129            if (docModel.hasFacet(exc)) {
130                return false;
131            }
132        }
133        for (String req : required) {
134            if (!docModel.hasFacet(req)) {
135                return false;
136            }
137        }
138        return true;
139    }
140
141}