001/*
002 * Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the Eclipse Public License v1.0
006 * which accompanies this distribution, and is available at
007 * http://www.eclipse.org/legal/epl-v10.html
008 *
009 * Contributors:
010 *     Georges Racinet
011 *     Florent Guillaume
012 */
013
014package org.nuxeo.ecm.core.api.impl;
015
016import java.util.Collection;
017import java.util.Collections;
018import java.util.HashSet;
019import java.util.List;
020import java.util.Set;
021
022import org.nuxeo.ecm.core.api.DocumentModel;
023import org.nuxeo.ecm.core.api.Filter;
024
025/**
026 * A filter based on facets.
027 *
028 * @author Georges Racinet
029 * @author Florent Guillaume
030 */
031public class FacetFilter implements Filter {
032
033    private static final long serialVersionUID = 1L;
034
035    public static final FacetFilter ALLOW = new FacetFilter((List<String>) null, (List<String>) null);
036
037    /** Set of required facets. Never {@code null}. */
038    public final Set<String> required;
039
040    /** Set of excluded facets. Never {@code null}. */
041    public final Set<String> excluded;
042
043    public final Boolean shortcut;
044
045    /**
046     * Generic constructor.
047     *
048     * @param required list of facets the models must have to pass the filter
049     * @param excluded list of facets the models must not have to pass the filter
050     */
051    public FacetFilter(List<String> required, List<String> excluded) {
052        if (required == null) {
053            this.required = Collections.emptySet();
054        } else {
055            this.required = new HashSet<String>(required);
056        }
057        if (excluded == null) {
058            this.excluded = Collections.emptySet();
059        } else {
060            this.excluded = new HashSet<String>(excluded);
061        }
062        shortcut = findShortcut();
063    }
064
065    /**
066     * Simpler constructor to filter on a single facet.
067     *
068     * @param facet the facet to filter on
069     * @param isRequired if true, accepted models must have the facet; if false, accepted models must not have the facet
070     */
071    public FacetFilter(String facet, boolean isRequired) {
072        if (isRequired) {
073            required = Collections.singleton(facet);
074            excluded = Collections.emptySet();
075        } else {
076            required = Collections.emptySet();
077            excluded = Collections.singleton(facet);
078        }
079        shortcut = null;
080    }
081
082    /**
083     * Constructor that ANDs two filters.
084     *
085     * @param filter1 the first filter
086     * @param filter2 the second filter
087     */
088    public FacetFilter(FacetFilter filter1, FacetFilter filter2) {
089        if (filter1.required.isEmpty() && filter2.required.isEmpty()) {
090            required = Collections.emptySet();
091        } else {
092            required = new HashSet<String>(filter1.required);
093            required.addAll(filter2.required);
094        }
095        if (filter1.excluded.isEmpty() && filter2.excluded.isEmpty()) {
096            excluded = Collections.emptySet();
097        } else {
098            excluded = new HashSet<String>(filter1.excluded);
099            excluded.addAll(filter2.excluded);
100        }
101        shortcut = findShortcut();
102    }
103
104    protected Boolean findShortcut() {
105        if (required.isEmpty() && excluded.isEmpty()) {
106            // no condition, always matches
107            return Boolean.TRUE;
108        }
109        Collection<String> intersection = new HashSet<String>(required);
110        intersection.retainAll(excluded);
111        if (!intersection.isEmpty()) {
112            // non-empty intersection, filter can never match
113            return Boolean.FALSE;
114        }
115        return null;
116    }
117
118    @Override
119    public boolean accept(DocumentModel docModel) {
120        if (shortcut != null) {
121            return shortcut;
122        }
123        for (String exc : excluded) {
124            if (docModel.hasFacet(exc)) {
125                return false;
126            }
127        }
128        for (String req : required) {
129            if (!docModel.hasFacet(req)) {
130                return false;
131            }
132        }
133        return true;
134    }
135
136}