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