Index: src/test/org/apache/lucene/search/TestFieldCacheRangeFilterPerformance.java =================================================================== --- src/test/org/apache/lucene/search/TestFieldCacheRangeFilterPerformance.java (revision 0) +++ src/test/org/apache/lucene/search/TestFieldCacheRangeFilterPerformance.java (revision 0) @@ -0,0 +1,141 @@ +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.File; +import java.io.IOException; +import java.util.Date; +import java.util.Random; + +import junit.framework.TestCase; + +import org.apache.lucene.analysis.KeywordAnalyzer; +import org.apache.lucene.document.DateTools; +import org.apache.lucene.document.Document; +import org.apache.lucene.document.Field; +import org.apache.lucene.document.DateTools.Resolution; +import org.apache.lucene.document.Field.Index; +import org.apache.lucene.document.Field.Store; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.IndexWriter; +import org.apache.lucene.index.Term; +import org.apache.lucene.search.RangeFilter; +import org.apache.lucene.store.Directory; +import org.apache.lucene.store.FSDirectory; +import org.apache.lucene.store.RAMDirectory; + +/** + * Compares performance between FieldCacheRangeFilter and standard RangeFilter + */ +public class TestFieldCacheRangeFilterPerformance extends TestCase { + + public static final long INTERVAL = 5 * 365 * 24 * 60 * 60 * 1000L; + + Random r = new Random(1); + + public void testPerformance() throws IOException { + RAMDirectory ramDir = new RAMDirectory(); +// Directory ramDir = FSDirectory.getDirectory(new File("/tmp/dateindex")); + IndexWriter writer = new IndexWriter(ramDir, new KeywordAnalyzer(), true); + + Date endInterval = new Date(); + + Date startInterval = new Date(endInterval.getTime() - (INTERVAL)); + + System.out.println("Start interval: " + startInterval.toString()); + System.out.println("End interval: " + endInterval.toString()); + + System.out.println("Creating RAMDirectory index..."); + for (int i=0; i<100000; i++) { + long newInterval = Math.round(r.nextDouble() * INTERVAL); + + Date curDate = new Date(startInterval.getTime() + newInterval); + String dateStr = DateTools.dateToString(curDate, Resolution.MINUTE); + + Document document = new Document(); + + document.add(new Field("id", String.valueOf(i), Store.YES, Index.NO)); + document.add(new Field("date", dateStr, Store.YES, Index.UN_TOKENIZED)); + + writer.addDocument(document); + } + + writer.optimize(); + writer.close(); + + IndexReader reader = IndexReader.open(ramDir); + + System.out.println("Reader opened with " + reader.maxDoc() + " documents. Creating RangeFilters..."); + + IndexSearcher searcher = new IndexSearcher(reader); + + long s = System.currentTimeMillis(); + for (int i=0; i<1000; i++) { + // Generate random date interval + Date date1 = new Date(startInterval.getTime() + Math.round(r.nextDouble() * INTERVAL)); + Date date2 = new Date(startInterval.getTime() + Math.round(r.nextDouble() * INTERVAL)); + + String start, end; + if (date1.after(date2)) { + start = DateTools.dateToString(date2, Resolution.MINUTE); + end = DateTools.dateToString(date1, Resolution.MINUTE); + } else { + start = DateTools.dateToString(date1, Resolution.MINUTE); + end = DateTools.dateToString(date2, Resolution.MINUTE); + } + + RangeFilter filter = new RangeFilter("date", start, end, true, true); + Query q = new TermQuery(new Term("id", Integer.toString(i))); + searcher.search(q,filter); + } + long e = System.currentTimeMillis() - s; + System.out.println("Standard RangeFilter finished in " + e + "ms"); + searcher.close(); + + //By Running the query once we will prime the Field Cache and get it ready + FieldCacheRangeFilter warmfilter = new FieldCacheRangeFilter("date", 0L, 1L, true, true); + warmfilter.bits(reader); + searcher = new IndexSearcher(reader); + + s = System.currentTimeMillis(); + for (int i=0; i<1000; i++) { + // Generate random date interval + Date date1 = new Date(startInterval.getTime() + Math.round(r.nextDouble() * INTERVAL)); + Date date2 = new Date(startInterval.getTime() + Math.round(r.nextDouble() * INTERVAL)); + + String start, end; + if (date1.after(date2)) { + start = DateTools.dateToString(date2, Resolution.MINUTE); + end = DateTools.dateToString(date1, Resolution.MINUTE); + } else { + start = DateTools.dateToString(date1, Resolution.MINUTE); + end = DateTools.dateToString(date2, Resolution.MINUTE); + } + + Query q = new TermQuery(new Term("id", Integer.toString(i))); + FieldCacheRangeFilter filter = new FieldCacheRangeFilter("date", Long.parseLong(start), Long.parseLong(end), true, true); + searcher.search(q,filter); + } + + long e1 = System.currentTimeMillis() - s; + System.out.println("FieldCacheRangeFilter finished in " + e1 + "ms"); + searcher.close(); + assertEquals("New FieldCacheFilter is faster then RangeFilter ", true, e1 < e); + } + +} Index: src/test/org/apache/lucene/search/TestFieldCacheRangeFilter.java =================================================================== --- src/test/org/apache/lucene/search/TestFieldCacheRangeFilter.java (revision 0) +++ src/test/org/apache/lucene/search/TestFieldCacheRangeFilter.java (revision 0) @@ -0,0 +1,181 @@ +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 java.util.BitSet; + +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.Term; +import org.apache.lucene.search.BaseTestRangeFilter; +import org.apache.lucene.search.Hits; +import org.apache.lucene.search.IndexSearcher; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.RangeFilter; +import org.apache.lucene.search.TermQuery; + + +/** + * Bulk of the code lifted from TestRangeFilter + */ +public class TestFieldCacheRangeFilter extends BaseTestRangeFilter { + public TestFieldCacheRangeFilter(String name) { + super(name); + } + + public TestFieldCacheRangeFilter() { + super(); + } + + public void testRangeFilterId() throws IOException { + IndexReader reader = IndexReader.open(index); + IndexSearcher search = new IndexSearcher(reader); + + int medId = ((maxId - minId) / 2); + + long minIP = minId; + long maxIP = maxId; + long medIP = medId; + + int numDocs = reader.numDocs(); + + assertEquals("num of docs", numDocs, 1 + maxId - minId); + + Hits result; + Query q = new TermQuery(new Term("body", "body")); + + // test id, bounded on both ends + result = search.search(q, new FieldCacheRangeFilter("id", minIP, maxIP, T, T)); + assertEquals("find all", numDocs, result.length()); + + result = search.search(q, new FieldCacheRangeFilter("id", minIP, maxIP, T, F)); + assertEquals("all but last", numDocs - 1, result.length()); + + result = search.search(q, new FieldCacheRangeFilter("id", minIP, maxIP, F, T)); + assertEquals("all but first", numDocs - 1, result.length()); + + result = search.search(q, new FieldCacheRangeFilter("id", minIP, maxIP, F, F)); + assertEquals("all but ends", numDocs - 2, result.length()); + + result = search.search(q, new FieldCacheRangeFilter("id", medIP, maxIP, T, T)); + assertEquals("med and up", 1 + maxId - medId, result.length()); + + result = search.search(q, new FieldCacheRangeFilter("id", minIP, medIP, T, T)); + assertEquals("up to med", 1 + medId - minId, result.length()); + + // unbounded id + + result = search.search(q, new FieldCacheRangeFilter("id", minIP, Long.MAX_VALUE, T, F)); + assertEquals("min and up", numDocs, result.length()); + + result = search.search(q, new FieldCacheRangeFilter("id", Long.MIN_VALUE, maxIP, F, T)); + assertEquals("max and down", numDocs, result.length()); + + result = search.search(q, new FieldCacheRangeFilter("id", minIP, Long.MAX_VALUE, F, F)); + assertEquals("not min, but up", numDocs - 1, result.length()); + + result = search.search(q, new FieldCacheRangeFilter("id", Long.MIN_VALUE, maxIP, F, F)); + assertEquals("not max, but down", numDocs - 1, result.length()); + + result = search.search(q, new FieldCacheRangeFilter("id", medIP, maxIP, T, F)); + assertEquals("med and up, not max", maxId - medId, result.length()); + + result = search.search(q, new FieldCacheRangeFilter("id", minIP, medIP, F, T)); + assertEquals("not min, up to med", medId - minId, result.length()); + + // very small sets + + result = search.search(q, new FieldCacheRangeFilter("id", minIP, minIP, F, F)); + assertEquals("min,min,F,F", 0, result.length()); + result = search.search(q, new FieldCacheRangeFilter("id", medIP, medIP, F, F)); + assertEquals("med,med,F,F", 0, result.length()); + result = search.search(q, new FieldCacheRangeFilter("id", maxIP, maxIP, F, F)); + assertEquals("max,max,F,F", 0, result.length()); + + result = search.search(q, new FieldCacheRangeFilter("id", minIP, minIP, T, T)); + assertEquals("min,min,T,T", 1, result.length()); + result = search.search(q, new FieldCacheRangeFilter("id", Long.MIN_VALUE, minIP, F, T)); + assertEquals("nul,min,F,T", 1, result.length()); + + result = search.search(q, new FieldCacheRangeFilter("id", maxIP, maxIP, T, T)); + assertEquals("max,max,T,T", 1, result.length()); + result = search.search(q, new FieldCacheRangeFilter("id", maxIP, Long.MAX_VALUE, T, F)); + assertEquals("max,nul,T,T", 1, result.length()); + + result = search.search(q, new FieldCacheRangeFilter("id", medIP, medIP, T, T)); + assertEquals("med,med,T,T", 1, result.length()); + + + //DO IT again for ints + // test id, bounded on both ends + result = search.search(q, new FieldCacheRangeFilter("id", (int)minIP, (int)maxIP, T, T)); + assertEquals("find all", numDocs, result.length()); + + result = search.search(q, new FieldCacheRangeFilter("id", (int)minIP, (int)maxIP, T, F)); + assertEquals("all but last", numDocs - 1, result.length()); + + result = search.search(q, new FieldCacheRangeFilter("id", (int)minIP, (int)maxIP, F, T)); + assertEquals("all but first", numDocs - 1, result.length()); + + result = search.search(q, new FieldCacheRangeFilter("id", (int)minIP, (int)maxIP, F, F)); + assertEquals("all but ends", numDocs - 2, result.length()); + + result = search.search(q, new FieldCacheRangeFilter("id", (int)medIP, (int)maxIP, T, T)); + assertEquals("med and up", 1 + maxId - medId, result.length()); + + result = search.search(q, new FieldCacheRangeFilter("id", (int)minIP, (int)medIP, T, T)); + assertEquals("up to med", 1 + medId - minId, result.length()); + + //DO IT again for ints + // test id, bounded on both ends + result = search.search(q, new FieldCacheRangeFilter("id", (float)minIP, (float)maxIP, T, T)); + assertEquals("find all", numDocs, result.length()); + + result = search.search(q, new FieldCacheRangeFilter("id", (float)minIP, (float)maxIP, T, F)); + assertEquals("all but last", numDocs - 1, result.length()); + + result = search.search(q, new FieldCacheRangeFilter("id", (float)minIP, (float)maxIP, F, T)); + assertEquals("all but first", numDocs - 1, result.length()); + + result = search.search(q, new FieldCacheRangeFilter("id", (float)minIP, (float)maxIP, F, F)); + assertEquals("all but ends", numDocs - 2, result.length()); + + result = search.search(q, new FieldCacheRangeFilter("id", (float)medIP, (float)maxIP, T, T)); + assertEquals("med and up", 1 + maxId - medId, result.length()); + + result = search.search(q, new FieldCacheRangeFilter("id", (float)minIP, (float)medIP, T, T)); + assertEquals("up to med", 1 + medId - minId, result.length()); + + + //Test the Bit sets + FieldCacheRangeFilter filter = new FieldCacheRangeFilter("id", medIP, maxIP, T, T); + BitSet fcBits = filter.bits(reader); + BitSet bits = (BitSet)fcBits.clone(); + + assertEquals("They have the same size", bits.length(), fcBits.length()); + for (int i = 0; i < fcBits.length() ; i++) { + assertEquals("All Bits are they same ", bits.get(i), fcBits.get(i)); + assertEquals("Next clear bit ", bits.nextClearBit(i), fcBits.nextClearBit(i)); + assertEquals("Next Set bit ", bits.nextSetBit(i), fcBits.nextSetBit(i)); + + } + assertEquals("med and up", 1 + maxId - medId, result.length()); + + } + +} Index: src/java/org/apache/lucene/search/FieldCacheRangeFilter.java =================================================================== --- src/java/org/apache/lucene/search/FieldCacheRangeFilter.java (revision 0) +++ src/java/org/apache/lucene/search/FieldCacheRangeFilter.java (revision 0) @@ -0,0 +1,339 @@ +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 java.util.BitSet; + +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.search.FieldCache.FloatParser; +import org.apache.lucene.search.FieldCache.IntParser; +import org.apache.lucene.search.ExtendedFieldCache.LongParser; + +/** + * Uses the field cache to do Range Filters + * Lucene already has the Field cache to store term info on a given query + * This will use that same cache to do range filters by creating a pass through BitSet that + * will proxy to the field cache + * + * If you can convert any Field into a int, long or float and then you can use this filter to + * to do a range filter + * + * By default the filed will be converted from strings to values by using Long.parseLong() or + * Integer.parseInt() if your data is formatted differently then you will need to pass in a + * parser that will convert it correctly. + * + * Here is an example of a non standard parser that will turn a date string into a long + * This would convert a date of yyyy.MM.dd G 'at' HH:mm:ss z into a long and allow the + * user to do a range query on it + * + * FieldCacheRangeFilter filter = new FieldCacheRangeFilter("date",1L,500L,false,false); + * filter.setLongParser(new FieldCache.LongParser() { + * SimpleDateFormat dateFormater = new SimpleDateForamt("yyyy.MM.dd G 'at' HH:mm:ss z"); + * public long parseLong(String value) { + * return dateFormater.parse(value).getTime(); + * } + * }); + * + * + * @author Matt Ericson + * + */ +public class FieldCacheRangeFilter extends Filter { + + protected String fieldName; + protected long lowerTerm; + protected long upperTerm; + protected boolean includeLower; + protected boolean includeUpper; + protected int fcType; + + //If the user wants to use floats then the input should be floats + protected float floatLowerTerm; + protected float floatUpperTerm; + + + + protected static final IntParser INT_PARSER = new IntParser() { + public int parseInt(String value) { + return Integer.parseInt(value); + } + }; + + + protected static final FloatParser FLOAT_PARSER = new FloatParser() { + public float parseFloat(String value) { + return Float.parseFloat(value); + } + }; + + protected static final LongParser LONG_PARSER = new LongParser() { + public long parseLong(String value) { + return Long.parseLong(value); + } + }; + + protected FloatParser floatParser = FLOAT_PARSER; + protected IntParser intParser = INT_PARSER; + protected LongParser longParser = LONG_PARSER; + + + public static final int INT = 0; + public static final int LONG = 1; + public static final int FLOAT = 2; + + + /** + * Will make a new FieldCacheRangeFilter that will filter using Ints + * This will create a field cache entry for your field and save the values as ints + * @param fieldName The Field to query + * @param lowerTerm Lower value + * @param upperTerm Upper value + * @param includeLower boolean + * @param includeUpper boolean + */ + public FieldCacheRangeFilter(String fieldName, int lowerTerm, int upperTerm, + boolean includeLower, boolean includeUpper) { + this(fieldName,lowerTerm,upperTerm,includeLower,includeUpper,INT); + } + + /** + * Will make a new FieldCacheRangeFilter that will filter using Longs + * This will create a field cache entry for your field and save the values as Longs + * @param fieldName The Field to query + * @param lowerTerm Lower value + * @param upperTerm Upper value + * @param includeLower boolean + * @param includeUpper boolean + */ + public FieldCacheRangeFilter(String fieldName, long lowerTerm, long upperTerm, + boolean includeLower, boolean includeUpper) { + this(fieldName,lowerTerm,upperTerm,includeLower,includeUpper,LONG); + } + + /** + * Will make a new FieldCacheRangeFilter that will filter using Float + * This will create a field cache entry for your field and save the values as floats + * @param fieldName The Field to query + * @param lowerTerm Lower value + * @param upperTerm Upper value + * @param includeLower boolean + * @param includeUpper boolean + */ + public FieldCacheRangeFilter(String fieldName, float lowerTerm, float upperTerm, + boolean includeLower, boolean includeUpper) { + this(fieldName,lowerTerm,upperTerm,includeLower,includeUpper,FLOAT); + } + + protected FieldCacheRangeFilter(String fieldName, long lowerTerm, long upperTerm, + boolean includeLower, boolean includeUpper,int fcType) { + this.fieldName = fieldName; + this.lowerTerm = lowerTerm; + this.upperTerm = upperTerm; + this.includeLower = includeLower; + this.includeUpper = includeUpper; + this.fcType = fcType; + } + + protected FieldCacheRangeFilter(String fieldName, float lowerTerm, float upperTerm, + boolean includeLower, boolean includeUpper,int fcType) { + this.fieldName = fieldName; + this.floatLowerTerm = lowerTerm; + this.floatUpperTerm = upperTerm; + this.includeLower = includeLower; + this.includeUpper = includeUpper; + this.fcType = fcType; + } + /** + * Override the default Integer parser with this one + * @param intParser + */ + public void setIntParser(IntParser intParser) { + this.intParser = intParser; + } + + /** + * Override the default Float parser with this one + * @param intParser + */ + public void setFloatParser(FloatParser floatParser) { + this.floatParser = floatParser; + } + + /** + * Override the default Long parser with this one + * @param intParser + */ + public void setLongParser(LongParser longParser) { + this.longParser = longParser; + } + + /** + * Create a bit set that is just a wrapper to the filed cache + * Store all data as longs/ints or floats in the field cache + */ + public BitSet bits(IndexReader reader) throws IOException { + //This will allow the user to pick if they want ints ofr longs or floats + if (fcType == INT) { + final int[] values = ExtendedFieldCache.EXT_DEFAULT.getInts(reader, fieldName,intParser); + final long fLowerTerm = lowerTerm; + final long fUpperTerm = upperTerm; + + //Do they work now and create the correct BitSet so that it will run faster + //If we do the check outside of the BitSet it will not need to check on every request + if (includeLower && includeUpper) { + return new AbstractGetOnlyBitSet(values.length) { + public boolean get(int bitIndex) { + if (bitIndex < length && bitIndex >= 0 && values[bitIndex] >= fLowerTerm && values[bitIndex] <= fUpperTerm) { + return true; + } + return false; + } + + }; + } else if (includeLower) { + return new AbstractGetOnlyBitSet(values.length) { + public boolean get(int bitIndex) { + if (bitIndex < length && bitIndex >= 0 && values[bitIndex] >= fLowerTerm && values[bitIndex] < fUpperTerm) { + return true; + } + return false; + } + + }; + } else if (includeUpper) { + return new AbstractGetOnlyBitSet(values.length) { + public boolean get(int bitIndex) { + if (bitIndex < length && bitIndex >= 0 && values[bitIndex] > fLowerTerm && values[bitIndex] <= fUpperTerm) { + return true; + } + return false; + } + + }; + } else { + return new AbstractGetOnlyBitSet(values.length) { + public boolean get(int bitIndex) { + if (bitIndex < length && bitIndex >= 0 && values[bitIndex] > fLowerTerm && values[bitIndex] < fUpperTerm) { + return true; + } + return false; + } + }; + } + //Will create a different Bit set for LONGS + } else if (fcType == LONG) { + + //First we get the Array + final long[] values = ExtendedFieldCache.EXT_DEFAULT.getLongs(reader, fieldName,longParser); + final long fLowerTerm = lowerTerm; + final long fUpperTerm = upperTerm; + + //Do they work now and create the correct BitSet so that it will run faster + //If we do the check outside of the BitSet it will not need to check on every request + if (includeLower && includeUpper) { + return new AbstractGetOnlyBitSet(values.length) { + public boolean get(int bitIndex) { + if (bitIndex < length && bitIndex >= 0 && values[bitIndex] >= fLowerTerm && values[bitIndex] <= fUpperTerm) { + return true; + } + return false; + } + + }; + } else if (includeLower) { + return new AbstractGetOnlyBitSet(values.length) { + public boolean get(int bitIndex) { + if (bitIndex < length && bitIndex >= 0 && values[bitIndex] >= fLowerTerm && values[bitIndex] < fUpperTerm) { + return true; + } + return false; + } + + }; + } else if (includeUpper) { + return new AbstractGetOnlyBitSet(values.length) { + public boolean get(int bitIndex) { + if (bitIndex < length && bitIndex >= 0 && values[bitIndex] > fLowerTerm && values[bitIndex] <= fUpperTerm) { + return true; + } + return false; + } + + }; + + } else { + return new AbstractGetOnlyBitSet(values.length) { + public boolean get(int bitIndex) { + if (bitIndex < length && bitIndex >= 0 && values[bitIndex] > fLowerTerm && values[bitIndex] < fUpperTerm) { + return true; + } + return false; + } + }; + } + } else { + final float[] values = ExtendedFieldCache.EXT_DEFAULT.getFloats(reader, fieldName,floatParser); + final float fLowerTerm = floatLowerTerm; + final float fUpperTerm = floatUpperTerm; + + //Do they work now and create the correct BitSet so that it will run faster + //If we do the check outside of the BitSet it will not need to check on every request + if (includeLower && includeUpper) { + return new AbstractGetOnlyBitSet(values.length) { + public boolean get(int bitIndex) { + if (bitIndex < length && bitIndex >= 0 && values[bitIndex] >= fLowerTerm && values[bitIndex] <= fUpperTerm) { + return true; + } + return false; + } + }; + } else if (includeLower) { + return new AbstractGetOnlyBitSet(values.length) { + public boolean get(int bitIndex) { + if (bitIndex < length && bitIndex >= 0 && values[bitIndex] >= fLowerTerm && values[bitIndex] < fUpperTerm) { + return true; + } + return false; + } + }; + } else if (includeUpper) { + return new AbstractGetOnlyBitSet(values.length) { + public boolean get(int bitIndex) { + if (bitIndex < length && bitIndex >= 0 && values[bitIndex] > fLowerTerm && values[bitIndex] <= fUpperTerm) { + return true; + } + return false; + } + }; + + + } else { + return new AbstractGetOnlyBitSet(values.length) { + public boolean get(int bitIndex) { + if (bitIndex < length && bitIndex >= 0 && values[bitIndex] > fLowerTerm && values[bitIndex] < fUpperTerm) { + return true; + } + return false; + } + }; + } + } + } + +} Index: src/java/org/apache/lucene/search/AbstractGetOnlyBitSet.java =================================================================== --- src/java/org/apache/lucene/search/AbstractGetOnlyBitSet.java (revision 0) +++ src/java/org/apache/lucene/search/AbstractGetOnlyBitSet.java (revision 0) @@ -0,0 +1,193 @@ +package org.apache.lucene.search; + + +/* ==================================================================== + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2001-2003 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Apache" and "Apache Software Foundation" and + * "Apache Lucene" must not be used to endorse or promote products + * derived from this software without prior written permission. For + * written permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * "Apache Lucene", nor may "Apache" appear in their name, without + * prior written permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + */ + + +import java.util.BitSet; + + +/** + * Abstract Version of a GetOnlyBitSet + * This will allow users to create bit sets that are not real bit sets since + * this class will use get() for every operation + * + * It handles all of the functions like isEmpty() or nextClearBit() + * + * The constructor needs to take the logical length of the BitSet + * So it knows how big the array is to loop over with each function + * + * @author Matt Ericson + */ +public abstract class AbstractGetOnlyBitSet extends BitSet { + + protected int length; + + public AbstractGetOnlyBitSet(int length) { + super(0); + this.length = length; + } + + /** + * Will use get to decide if the two sets intersects + */ + public boolean intersects(BitSet set) { + for (int i =0; i < length; i++) { + if (get(i) && set.get(i)) { + return true; + } + } + return false; + } + + /** + * All sub classes must implement get + */ + public abstract boolean get(int bitIndex); + + /** + * Will make a real bit set and set all the fields to be the same they are in this + * BitSet. + */ + public Object clone() { + return convertToBitSet(); + } + + /** + * Turn this FC Bit Set and make a real bit set so all other functions can have access to it + * @return + */ + public BitSet convertToBitSet() { + BitSet returnSet = new BitSet(length); + for (int i =0; i < length; i++) { + returnSet.set(i,get(i)); + } + return returnSet; + } + + /* + * Will return the if the set is Empty + * (non-Javadoc) + * @see java.util.BitSet#isEmpty() + */ + public boolean isEmpty() { + for (int i =0; i < length; i++) { + if (get(i)) { + return false; + } + } + return true; + } + + /* + * Will return the next Not set Bit + * (non-Javadoc) + * @see java.util.BitSet#nextClearBit(int) + */ + public int nextClearBit(int fromIndex) { + int length = length(); + if (fromIndex < 0 ) { + throw new IndexOutOfBoundsException ("Indxex " + fromIndex + " is invalid"); + } + if (fromIndex > length()) { + return -1; + } + + int i = 0; + for (i = fromIndex; i < length; i ++) { + if (!get(i)) { + return i; + } + } + return i; + } + + /* + * Will return the next Set bit + * (non-Javadoc) + * @see java.util.BitSet#nextSetBit(int) + */ + public int nextSetBit(int fromIndex) { + int length = length(); + if (fromIndex < 0 ) { + throw new IndexOutOfBoundsException ("Indxex " + fromIndex + " is invalid"); + } + if (fromIndex > length()) { + return -1; + } + int i = 0; + for (i = fromIndex ; i < length; i ++) { + if (get(i)) { + return i; + } + } + return i; + } + + public int size() { return length;} + public int length() { return length;} + + //The Following are all Unsupported Operations as they cant be used with this type of filter + public void set(int bitIndex) { throw new UnsupportedOperationException("can not set"); } + public void set(int bitIndex,boolean value) { throw new UnsupportedOperationException("can not set"); } + public void set(int bitIndex,int toIndex, boolean value) { throw new UnsupportedOperationException("can not set"); } + public void set(int bitIndex,int toIndex) { throw new UnsupportedOperationException("can not set"); } + public void flip(int bit) { throw new UnsupportedOperationException("can not flip"); } + public void flip(int bit, int toindex) { throw new UnsupportedOperationException("can not flip"); } + public void or(BitSet set) { throw new UnsupportedOperationException("can not or"); } + public void and(BitSet set) { throw new UnsupportedOperationException("can not and"); } + public void andNot(BitSet set) { throw new UnsupportedOperationException("can not andNot"); } + public void xor(BitSet set) { throw new UnsupportedOperationException("can not xor"); } +} Index: contrib/miscellaneous/src/test/org/apache/lucene/misc/RuntimeChainedFilterTest.java =================================================================== --- contrib/miscellaneous/src/test/org/apache/lucene/misc/RuntimeChainedFilterTest.java (revision 0) +++ contrib/miscellaneous/src/test/org/apache/lucene/misc/RuntimeChainedFilterTest.java (revision 0) @@ -0,0 +1,161 @@ +package org.apache.lucene.misc; + +/** + * 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 junit.framework.TestCase; +import java.util.Calendar; +import java.util.Date; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import org.apache.lucene.index.IndexWriter; +import org.apache.lucene.index.Term; +import org.apache.lucene.store.RAMDirectory; +import org.apache.lucene.analysis.WhitespaceAnalyzer; +import org.apache.lucene.document.Document; +import org.apache.lucene.document.Field; +import org.apache.lucene.search.*; + +public class RuntimeChainedFilterTest extends TestCase { + public static final int MAX = 500; + + private RAMDirectory directory; + private IndexSearcher searcher; + private Query query; + // private DateFilter dateFilter; DateFilter was deprecated and removed + private RangeFilter dateFilter; + private QueryFilter bobFilter; + private QueryFilter sueFilter; + private FieldCacheRangeFilter idFilter; + + public void setUp() throws Exception { + directory = new RAMDirectory(); + IndexWriter writer = + new IndexWriter(directory, new WhitespaceAnalyzer(), true); + + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(1041397200000L); // 2003 January 01 + + for (int i = 0; i < MAX; i++) { + Document doc = new Document(); + doc.add(new Field("key", "" + (i + 1), Field.Store.YES, Field.Index.UN_TOKENIZED)); + doc.add(new Field("owner", (i < MAX / 2) ? "bob" : "sue", Field.Store.YES, Field.Index.UN_TOKENIZED)); + doc.add(new Field("date", cal.getTime().toString(), Field.Store.YES, Field.Index.UN_TOKENIZED)); + writer.addDocument(doc); + + cal.add(Calendar.DATE, 1); + } + + writer.close(); + + searcher = new IndexSearcher(directory); + + // query for everything to make life easier + BooleanQuery bq = new BooleanQuery(); + bq.add(new TermQuery(new Term("owner", "bob")), BooleanClause.Occur.SHOULD); + bq.add(new TermQuery(new Term("owner", "sue")), BooleanClause.Occur.SHOULD); + query = bq; + + // date filter matches everything too + Date pastTheEnd = parseDate("2099 Jan 1"); + // dateFilter = DateFilter.Before("date", pastTheEnd); + // just treat dates as strings and select the whole range for now... + dateFilter = new RangeFilter("date","","ZZZZ",true,true); + + bobFilter = new QueryFilter( + new TermQuery(new Term("owner", "bob"))); + sueFilter = new QueryFilter( + new TermQuery(new Term("owner", "sue"))); + idFilter = new FieldCacheRangeFilter("key",1, MAX / 2 , true , true); + } + + public void testSingleFilter() throws Exception { + RuntimeChainedFilter chain = new RuntimeChainedFilter( + new Filter[] {dateFilter}); + + Hits hits = searcher.search(query, chain); + assertEquals(MAX, hits.length()); + + chain = new RuntimeChainedFilter(new Filter[] {bobFilter}); + hits = searcher.search(query, chain); + assertEquals(MAX / 2, hits.length()); + + chain = new RuntimeChainedFilter( + new Filter[] {idFilter}); + + hits = searcher.search(query, chain); + assertEquals(MAX / 2, hits.length()); + } + + public void testOR() throws Exception { + RuntimeChainedFilter chain = new RuntimeChainedFilter( + new Filter[] {sueFilter, bobFilter}); + + Hits hits = searcher.search(query, chain); + assertEquals("OR matches all", MAX, hits.length()); + } + + public void testAND() throws Exception { + RuntimeChainedFilter chain = new RuntimeChainedFilter( + new Filter[] {dateFilter, bobFilter}, RuntimeChainedFilter.AND); + + Hits hits = searcher.search(query, chain); + assertEquals("AND matches just bob", MAX / 2, hits.length()); + assertEquals("bob", hits.doc(0).get("owner")); + } + + public void testXOR() throws Exception { + RuntimeChainedFilter chain = new RuntimeChainedFilter( + new Filter[]{dateFilter, bobFilter}, RuntimeChainedFilter.XOR); + + Hits hits = searcher.search(query, chain); + assertEquals("XOR matches sue", MAX / 2, hits.length()); + assertEquals("sue", hits.doc(0).get("owner")); + } + + public void testANDNOT() throws Exception { + RuntimeChainedFilter chain = new RuntimeChainedFilter( + new Filter[]{dateFilter, sueFilter}, + new int[] {RuntimeChainedFilter.AND, RuntimeChainedFilter.ANDNOT}); + + Hits hits = searcher.search(query, chain); + assertEquals("ANDNOT matches just bob", + MAX / 2, hits.length()); + assertEquals("bob", hits.doc(0).get("owner")); + } + + public void testFieldCacheRangeFilter() throws Exception + { + RuntimeChainedFilter chain = new RuntimeChainedFilter( + new Filter[] {idFilter, bobFilter}, RuntimeChainedFilter.AND); + + Hits hits = searcher.search(query, chain); + assertEquals("FieldCacheRangeFilter matches just bob", MAX / 2, hits.length()); + assertEquals("bob", hits.doc(0).get("owner")); + + chain = new RuntimeChainedFilter( + new Filter[] {idFilter, sueFilter}, RuntimeChainedFilter.AND); + hits = searcher.search(query, chain); + assertEquals("FieldCacheRangeFilter NONE matche sue", 0, hits.length()); + + } + + private Date parseDate(String s) throws ParseException { + return new SimpleDateFormat("yyyy MMM dd").parse(s); + } + +} Index: contrib/miscellaneous/src/java/org/apache/lucene/misc/ChainedFilter.java.orig =================================================================== --- contrib/miscellaneous/src/java/org/apache/lucene/misc/ChainedFilter.java.orig (revision 0) +++ contrib/miscellaneous/src/java/org/apache/lucene/misc/ChainedFilter.java.orig (revision 0) @@ -0,0 +1,274 @@ +package org.apache.lucene.misc; + +/* ==================================================================== + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2001-2003 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Apache" and "Apache Software Foundation" and + * "Apache Lucene" must not be used to endorse or promote products + * derived from this software without prior written permission. For + * written permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * "Apache Lucene", nor may "Apache" appear in their name, without + * prior written permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + */ + +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.search.Filter; + +import java.io.IOException; +import java.util.BitSet; + +/** + *

+ * Allows multiple {@link Filter}s to be chained. + * Logical operations such as NOT and XOR + * are applied between filters. One operation can be used + * for all filters, or a specific operation can be declared + * for each filter. + *

+ *

+ * 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. + *

+ * + * @author Kelvin Tan + */ +public class ChainedFilter extends Filter +{ + /** + * {@link BitSet#or}. + */ + public static final int OR = 0; + + /** + * {@link BitSet#and}. + */ + public static final int AND = 1; + + /** + * {@link BitSet#andNot}. + */ + public static final int ANDNOT = 2; + + /** + * {@link BitSet#xor}. + */ + public static final int XOR = 3; + + /** + * Logical operation when none is declared. Defaults to + * {@link BitSet#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 Logicial operation to apply to ALL filters + */ + public ChainedFilter(Filter[] chain, int logic) + { + this.chain = chain; + this.logic = logic; + } + + /** + * {@link Filter#bits}. + */ + public BitSet bits(IndexReader reader) throws IOException + { + if (logic != -1) + return bits(reader, logic); + else if (logicArray != null) + return bits(reader, logicArray); + else + return bits(reader, DEFAULT); + } + + /** + * Delegates to each filter in the chain. + * @param reader IndexReader + * @param logic Logical operation + * @return BitSet + */ + private BitSet bits(IndexReader reader, int logic) throws IOException + { + BitSet result; + int i = 0; + + /** + * First AND operation takes place against a completely false + * bitset and will always return zero results. Thanks to + * Daniel Armbrust for pointing this out and suggesting workaround. + */ + if (logic == AND) + { + result = (BitSet) chain[i].bits(reader).clone(); + ++i; + } + else if (logic == ANDNOT) + { + result = (BitSet) chain[i].bits(reader).clone(); + result.flip(0,reader.maxDoc()); + ++i; + } + else + { + result = new BitSet(reader.maxDoc()); + } + + for (; i < chain.length; i++) + { + doChain(result, reader, logic, chain[i]); + } + return result; + } + + /** + * Delegates to each filter in the chain. + * @param reader IndexReader + * @param logic Logical operation + * @return BitSet + */ + private BitSet bits(IndexReader reader, int[] logic) throws IOException + { + if (logic.length != chain.length) + throw new IllegalArgumentException("Invalid number of elements in logic array"); + BitSet result; + int i = 0; + + /** + * First AND operation takes place against a completely false + * bitset and will always return zero results. Thanks to + * Daniel Armbrust for pointing this out and suggesting workaround. + */ + if (logic[0] == AND) + { + result = (BitSet) chain[i].bits(reader).clone(); + ++i; + } + else if (logic[0] == ANDNOT) + { + result = (BitSet) chain[i].bits(reader).clone(); + result.flip(0,reader.maxDoc()); + ++i; + } + else + { + result = new BitSet(reader.maxDoc()); + } + + for (; i < chain.length; i++) + { + doChain(result, reader, logic[i], chain[i]); + } + return result; + } + + public String toString() + { + StringBuffer sb = new StringBuffer(); + 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(BitSet result, IndexReader reader, + int logic, Filter filter) throws IOException + { + switch (logic) + { + case OR: + result.or(filter.bits(reader)); + break; + case AND: + result.and(filter.bits(reader)); + break; + case ANDNOT: + result.andNot(filter.bits(reader)); + break; + case XOR: + result.xor(filter.bits(reader)); + break; + default: + doChain(result, reader, DEFAULT, filter); + break; + } + } +} Index: contrib/miscellaneous/src/java/org/apache/lucene/misc/ChainedFilter.java =================================================================== --- contrib/miscellaneous/src/java/org/apache/lucene/misc/ChainedFilter.java (revision 618173) +++ contrib/miscellaneous/src/java/org/apache/lucene/misc/ChainedFilter.java (working copy) @@ -106,11 +106,11 @@ public static int DEFAULT = OR; /** The filter chain */ - private Filter[] chain = null; + protected Filter[] chain = null; - private int[] logicArray; + protected int[] logicArray; - private int logic = -1; + protected int logic = -1; /** * Ctor. Index: contrib/miscellaneous/src/java/org/apache/lucene/misc/RuntimeChainedFilter.java =================================================================== --- contrib/miscellaneous/src/java/org/apache/lucene/misc/RuntimeChainedFilter.java (revision 0) +++ contrib/miscellaneous/src/java/org/apache/lucene/misc/RuntimeChainedFilter.java (revision 0) @@ -0,0 +1,223 @@ +package org.apache.lucene.misc; + + +/* ==================================================================== + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2001-2003 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Apache" and "Apache Software Foundation" and + * "Apache Lucene" must not be used to endorse or promote products + * derived from this software without prior written permission. For + * written permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * "Apache Lucene", nor may "Apache" appear in their name, without + * prior written permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + */ + + +import java.io.IOException; +import java.util.BitSet; + +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.search.AbstractGetOnlyBitSet; +import org.apache.lucene.search.Filter; + + +/** + * A chained filter that does not do its ANDing or ORing until you call + * BitSet.get() this will allow and BitSet implementing AbstractGetOnlyBitSet to work correctly with a + * Chained Filter + * + * It might be a bit slower then the Chained Filter as it has to do the AND or OR or XOR or AND NOT at runtime + * but if your # of matches are small then it might run faster as it only cacluates the bits that it needs to look at + * this will also not create new BitSets and save Memory + * + * @author Matt Ericson + * + */ + +public class RuntimeChainedFilter extends ChainedFilter { + + public RuntimeChainedFilter(Filter[] chain) { + super(chain); + } + + public RuntimeChainedFilter(Filter[] chain, int[] logicArray) { + super(chain, logicArray); + } + + public RuntimeChainedFilter(Filter[] chain, int logic) { + super(chain, logic); + } + + /** + * Create a new chained Bit Set that will know how to + */ + public BitSet bits(IndexReader reader) throws IOException + { + if (logic != -1) + return getChainedBitSet(reader,logic); + else if (logicArray != null) + return getChainedBitSet(reader, logicArray); + else + return getChainedBitSet(reader, DEFAULT); + } + + + protected BitSet[] getBits(IndexReader reader) throws IOException { + if (chain.length > 0) { + BitSet[] sets = new BitSet[chain.length]; + for(int i =0 ; i < chain.length; i ++) { + sets[i] = chain[i].bits(reader); + } + return sets; + } + return null; + } + + protected BitSet getChainedBitSet(IndexReader reader, int logic) throws IOException { + BitSet[] sets = getBits(reader); + return new ChainedBitSet(sets,logic); + } + + protected BitSet getChainedBitSet(IndexReader reader, int[] logicArray) throws IOException { + BitSet[] sets = getBits(reader); + if (logicArray.length != sets.length) { + throw new IllegalArgumentException("Invalid number of elements in logic array"); + } + return new ChainedBitSet(sets,logicArray); + } + + /** + * Will act as a normal bit set but have an array of bit sets that it can act on + * + * Does the chain with the bit sets and allows users to use non-standard bit sets + * @author Matt Ericson + * + */ + protected class ChainedBitSet extends AbstractGetOnlyBitSet { + + protected BitSet[] sets; + protected int logic = -1; + protected int[] logicArray; + + public ChainedBitSet(BitSet[] sets, int logic ) { + super(0); + this.sets = sets; + this.logic = logic; + setBitSetLength(); + } + + public ChainedBitSet(BitSet[] sets, int[] logicArray ) { + super(0); + this.sets = sets; + this.logicArray = logicArray; + this.logic = -1; + setBitSetLength(); + } + + + public boolean get(int bitIndex) { + + if (logic > -1 ) { + return getBooleanFromBitSet(bitIndex,logic); + } else if (logicArray != null) { + return getBooleanFromBitSet(bitIndex,logicArray); + } else { + return getBooleanFromBitSet(bitIndex,DEFAULT); + } + } + + + protected boolean getBooleanFromBitSet(int bitIndex , int logic) { + boolean result = false; + for (int i = 0 ; i < sets.length; i++) { + if (logic == AND && i == 0 ) { + result = sets[i].get(bitIndex); + } else { + result = doLogic(result,sets[i].get(bitIndex) , logic) ; + } + } + return result; + } + + protected boolean getBooleanFromBitSet(int bitIndex , int[] logicArray) { + boolean result = false; + for (int i = 0 ; i < sets.length; i++) { + if (logicArray[0] == AND && i == 0 ) { + result = sets[i].get(bitIndex); + } else { + result = doLogic(result,sets[i].get(bitIndex) , logicArray[i]) ; + } + } + return result; + } + + + protected boolean doLogic (boolean current, boolean fromBitSet, int logic) + { + switch (logic) + { + case OR: + return current | fromBitSet; + case AND: + return current & fromBitSet; + case ANDNOT: + return current & !fromBitSet; + case XOR: + return current ^ fromBitSet; + default: + return current | fromBitSet; + } + } + + protected void setBitSetLength () { + for (int i = 0; i < sets.length; i ++) { + if (sets[i].length() > length ) { + this.length = sets[i].length(); + } + } + } + } +}