package org.apache.lucene.search; /** * 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. */ import java.io.IOException; import org.apache.lucene.index.IndexReader; import org.apache.lucene.search.DocIdSet; import org.apache.lucene.search.DocIdSetIterator; import org.apache.lucene.search.Filter; import org.apache.lucene.util.OpenBitSet; import org.apache.lucene.util.OpenBitSetDISI; /** * <p> * Allows multiple {@link Filter}s to be chained. * Logical operations such as <b>NOT</b> and <b>XOR</b> * are applied between filters. One operation can be used * for all filters, or a specific operation can be declared * for each filter. * </p> * <p> * Order in which filters are called depends on * the position of the filter in the chain. It's probably * more efficient to place the most restrictive filters * /least computationally-intensive filters first. * </p> * */ public class ChainedFilter extends Filter { public static final int OR = 0; public static final int AND = 1; public static final int ANDNOT = 2; public static final int XOR = 3; /** * Logical operation when none is declared. Defaults to * OR. */ public static int DEFAULT = OR; /** The filter chain */ private Filter[] chain = null; private int[] logicArray; private int logic = -1; /** * Ctor. * @param chain The chain of filters */ public ChainedFilter(Filter[] chain) { this.chain = chain; } /** * Ctor. * @param chain The chain of filters * @param logicArray Logical operations to apply between filters */ public ChainedFilter(Filter[] chain, int[] logicArray) { this.chain = chain; this.logicArray = logicArray; } /** * Ctor. * @param chain The chain of filters * @param logic Logical operation to apply to ALL filters */ public ChainedFilter(Filter[] chain, int logic) { this.chain = chain; this.logic = logic; } /** * {@link Filter#getDocIdSet}. */ @Override public DocIdSet getDocIdSet(IndexReader reader) throws IOException { int[] index = new int[1]; // use array as reference to modifiable int; index[0] = 0; // an object attribute would not be thread safe. if (logic != -1) return getDocIdSet(reader, logic, index); else if (logicArray != null) return getDocIdSet(reader, logicArray, index); else return getDocIdSet(reader, DEFAULT, index); } private DocIdSetIterator getDISI(Filter filter, IndexReader reader) throws IOException { DocIdSet docIdSet = filter.getDocIdSet(reader); if (docIdSet == null) { return DocIdSet.EMPTY_DOCIDSET.iterator(); } else { DocIdSetIterator iter = docIdSet.iterator(); if (iter == null) { return DocIdSet.EMPTY_DOCIDSET.iterator(); } else { return iter; } } } private OpenBitSetDISI initialResult(IndexReader reader, int logic, int[] index) throws IOException { OpenBitSetDISI result; /** * First AND operation takes place against a completely false * bitset and will always return zero results. */ if (logic == AND) { result = new OpenBitSetDISI(getDISI(chain[index[0]], reader), reader.maxDoc()); ++index[0]; } else if (logic == ANDNOT) { result = new OpenBitSetDISI(getDISI(chain[index[0]], reader), reader.maxDoc()); result.flip(0,reader.maxDoc()); // NOTE: may set bits for deleted docs. ++index[0]; } else { result = new OpenBitSetDISI(reader.maxDoc()); } return result; } /** Provide a SortedVIntList when it is definitely * smaller than an OpenBitSet * @deprecated Either use CachingWrapperFilter, or * switch to a different DocIdSet implementation yourself. * This method will be removed in Lucene 4.0 **/ @Deprecated protected final DocIdSet finalResult(OpenBitSetDISI result, int maxDocs) { return result; } /** * Delegates to each filter in the chain. * @param reader IndexReader * @param logic Logical operation * @return DocIdSet */ private DocIdSet getDocIdSet(IndexReader reader, int logic, int[] index) throws IOException { OpenBitSetDISI result = initialResult(reader, logic, index); for (; index[0] < chain.length; index[0]++) { doChain(result, logic, chain[index[0]].getDocIdSet(reader)); } return finalResult(result, reader.maxDoc()); } /** * Delegates to each filter in the chain. * @param reader IndexReader * @param logic Logical operation * @return DocIdSet */ private DocIdSet getDocIdSet(IndexReader reader, int[] logic, int[] index) throws IOException { if (logic.length != chain.length) throw new IllegalArgumentException("Invalid number of elements in logic array"); OpenBitSetDISI result = initialResult(reader, logic[0], index); for (; index[0] < chain.length; index[0]++) { doChain(result, logic[index[0]], chain[index[0]].getDocIdSet(reader)); } return finalResult(result, reader.maxDoc()); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("ChainedFilter: ["); for (int i = 0; i < chain.length; i++) { sb.append(chain[i]); sb.append(' '); } sb.append(']'); return sb.toString(); } private void doChain(OpenBitSetDISI result, int logic, DocIdSet dis) throws IOException { if (dis instanceof OpenBitSet) { // optimized case for OpenBitSets switch (logic) { case OR: result.or((OpenBitSet) dis); break; case AND: result.and((OpenBitSet) dis); break; case ANDNOT: result.andNot((OpenBitSet) dis); break; case XOR: result.xor((OpenBitSet) dis); break; default: doChain(result, DEFAULT, dis); break; } } else { DocIdSetIterator disi; if (dis == null) { disi = DocIdSet.EMPTY_DOCIDSET.iterator(); } else { disi = dis.iterator(); if (disi == null) { disi = DocIdSet.EMPTY_DOCIDSET.iterator(); } } switch (logic) { case OR: result.inPlaceOr(disi); break; case AND: result.inPlaceAnd(disi); break; case ANDNOT: result.inPlaceNot(disi); break; case XOR: result.inPlaceXor(disi); break; default: doChain(result, DEFAULT, dis); break; } } } }