pylucene 3.5.0-3
[pylucene.git] / lucene-java-3.5.0 / lucene / src / java / org / apache / lucene / search / FieldCacheRangeFilter.java
1 package org.apache.lucene.search;
2 /**
3  * Licensed to the Apache Software Foundation (ASF) under one or more
4  * contributor license agreements.  See the NOTICE file distributed with
5  * this work for additional information regarding copyright ownership.
6  * The ASF licenses this file to You under the Apache License, Version 2.0
7  * (the "License"); you may not use this file except in compliance with
8  * the License.  You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18
19 import java.io.IOException;
20
21 import org.apache.lucene.index.IndexReader;
22 import org.apache.lucene.index.TermDocs;
23 import org.apache.lucene.util.NumericUtils;
24 import org.apache.lucene.document.NumericField; // for javadocs
25
26 /**
27  * A range filter built on top of a cached single term field (in {@link FieldCache}).
28  * 
29  * <p>{@code FieldCacheRangeFilter} builds a single cache for the field the first time it is used.
30  * Each subsequent {@code FieldCacheRangeFilter} on the same field then reuses this cache,
31  * even if the range itself changes. 
32  * 
33  * <p>This means that {@code FieldCacheRangeFilter} is much faster (sometimes more than 100x as fast) 
34  * as building a {@link TermRangeFilter}, if using a {@link #newStringRange}.
35  * However, if the range never changes it is slower (around 2x as slow) than building
36  * a CachingWrapperFilter on top of a single {@link TermRangeFilter}.
37  *
38  * For numeric data types, this filter may be significantly faster than {@link NumericRangeFilter}.
39  * Furthermore, it does not need the numeric values encoded by {@link NumericField}. But
40  * it has the problem that it only works with exact one value/document (see below).
41  *
42  * <p>As with all {@link FieldCache} based functionality, {@code FieldCacheRangeFilter} is only valid for 
43  * fields which exact one term for each document (except for {@link #newStringRange}
44  * where 0 terms are also allowed). Due to a restriction of {@link FieldCache}, for numeric ranges
45  * all terms that do not have a numeric value, 0 is assumed.
46  *
47  * <p>Thus it works on dates, prices and other single value fields but will not work on
48  * regular text fields. It is preferable to use a <code>NOT_ANALYZED</code> field to ensure that
49  * there is only a single term. 
50  *
51  * <p>This class does not have an constructor, use one of the static factory methods available,
52  * that create a correct instance for different data types supported by {@link FieldCache}.
53  */
54
55 public abstract class FieldCacheRangeFilter<T> extends Filter {
56   final String field;
57   final FieldCache.Parser parser;
58   final T lowerVal;
59   final T upperVal;
60   final boolean includeLower;
61   final boolean includeUpper;
62   
63   private FieldCacheRangeFilter(String field, FieldCache.Parser parser, T lowerVal, T upperVal, boolean includeLower, boolean includeUpper) {
64     this.field = field;
65     this.parser = parser;
66     this.lowerVal = lowerVal;
67     this.upperVal = upperVal;
68     this.includeLower = includeLower;
69     this.includeUpper = includeUpper;
70   }
71   
72   /** This method is implemented for each data type */
73   @Override
74   public abstract DocIdSet getDocIdSet(IndexReader reader) throws IOException;
75
76   /**
77    * Creates a string range filter using {@link FieldCache#getStringIndex}. This works with all
78    * fields containing zero or one term in the field. The range can be half-open by setting one
79    * of the values to <code>null</code>.
80    */
81   public static FieldCacheRangeFilter<String> newStringRange(String field, String lowerVal, String upperVal, boolean includeLower, boolean includeUpper) {
82     return new FieldCacheRangeFilter<String>(field, null, lowerVal, upperVal, includeLower, includeUpper) {
83       @Override
84       public DocIdSet getDocIdSet(IndexReader reader) throws IOException {
85         final FieldCache.StringIndex fcsi = FieldCache.DEFAULT.getStringIndex(reader, field);
86         final int lowerPoint = fcsi.binarySearchLookup(lowerVal);
87         final int upperPoint = fcsi.binarySearchLookup(upperVal);
88         
89         final int inclusiveLowerPoint, inclusiveUpperPoint;
90
91         // Hints:
92         // * binarySearchLookup returns 0, if value was null.
93         // * the value is <0 if no exact hit was found, the returned value
94         //   is (-(insertion point) - 1)
95         if (lowerPoint == 0) {
96           assert lowerVal == null;
97           inclusiveLowerPoint = 1;
98         } else if (includeLower && lowerPoint > 0) {
99           inclusiveLowerPoint = lowerPoint;
100         } else if (lowerPoint > 0) {
101           inclusiveLowerPoint = lowerPoint + 1;
102         } else {
103           inclusiveLowerPoint = Math.max(1, -lowerPoint - 1);
104         }
105         
106         if (upperPoint == 0) {
107           assert upperVal == null;
108           inclusiveUpperPoint = Integer.MAX_VALUE;  
109         } else if (includeUpper && upperPoint > 0) {
110           inclusiveUpperPoint = upperPoint;
111         } else if (upperPoint > 0) {
112           inclusiveUpperPoint = upperPoint - 1;
113         } else {
114           inclusiveUpperPoint = -upperPoint - 2;
115         }      
116
117         if (inclusiveUpperPoint <= 0 || inclusiveLowerPoint > inclusiveUpperPoint)
118           return DocIdSet.EMPTY_DOCIDSET;
119         
120         assert inclusiveLowerPoint > 0 && inclusiveUpperPoint > 0;
121         
122         // for this DocIdSet, we never need to use TermDocs,
123         // because deleted docs have an order of 0 (null entry in StringIndex)
124         return new FieldCacheDocIdSet(reader, false) {
125           @Override
126           final boolean matchDoc(int doc) {
127             return fcsi.order[doc] >= inclusiveLowerPoint && fcsi.order[doc] <= inclusiveUpperPoint;
128           }
129         };
130       }
131     };
132   }
133   
134   /**
135    * Creates a numeric range filter using {@link FieldCache#getBytes(IndexReader,String)}. This works with all
136    * byte fields containing exactly one numeric term in the field. The range can be half-open by setting one
137    * of the values to <code>null</code>.
138    */
139   public static FieldCacheRangeFilter<Byte> newByteRange(String field, Byte lowerVal, Byte upperVal, boolean includeLower, boolean includeUpper) {
140     return newByteRange(field, null, lowerVal, upperVal, includeLower, includeUpper);
141   }
142   
143   /**
144    * Creates a numeric range filter using {@link FieldCache#getBytes(IndexReader,String,FieldCache.ByteParser)}. This works with all
145    * byte fields containing exactly one numeric term in the field. The range can be half-open by setting one
146    * of the values to <code>null</code>.
147    */
148   public static FieldCacheRangeFilter<Byte> newByteRange(String field, FieldCache.ByteParser parser, Byte lowerVal, Byte upperVal, boolean includeLower, boolean includeUpper) {
149     return new FieldCacheRangeFilter<Byte>(field, parser, lowerVal, upperVal, includeLower, includeUpper) {
150       @Override
151       public DocIdSet getDocIdSet(IndexReader reader) throws IOException {
152         final byte inclusiveLowerPoint, inclusiveUpperPoint;
153         if (lowerVal != null) {
154           final byte i = lowerVal.byteValue();
155           if (!includeLower && i == Byte.MAX_VALUE)
156             return DocIdSet.EMPTY_DOCIDSET;
157           inclusiveLowerPoint = (byte) (includeLower ?  i : (i + 1));
158         } else {
159           inclusiveLowerPoint = Byte.MIN_VALUE;
160         }
161         if (upperVal != null) {
162           final byte i = upperVal.byteValue();
163           if (!includeUpper && i == Byte.MIN_VALUE)
164             return DocIdSet.EMPTY_DOCIDSET;
165           inclusiveUpperPoint = (byte) (includeUpper ? i : (i - 1));
166         } else {
167           inclusiveUpperPoint = Byte.MAX_VALUE;
168         }
169         
170         if (inclusiveLowerPoint > inclusiveUpperPoint)
171           return DocIdSet.EMPTY_DOCIDSET;
172         
173         final byte[] values = FieldCache.DEFAULT.getBytes(reader, field, (FieldCache.ByteParser) parser);
174         // we only request the usage of termDocs, if the range contains 0
175         return new FieldCacheDocIdSet(reader, (inclusiveLowerPoint <= 0 && inclusiveUpperPoint >= 0)) {
176           @Override
177           boolean matchDoc(int doc) {
178             return values[doc] >= inclusiveLowerPoint && values[doc] <= inclusiveUpperPoint;
179           }
180         };
181       }
182     };
183   }
184   
185   /**
186    * Creates a numeric range filter using {@link FieldCache#getShorts(IndexReader,String)}. This works with all
187    * short fields containing exactly one numeric term in the field. The range can be half-open by setting one
188    * of the values to <code>null</code>.
189    */
190   public static FieldCacheRangeFilter<Short> newShortRange(String field, Short lowerVal, Short upperVal, boolean includeLower, boolean includeUpper) {
191     return newShortRange(field, null, lowerVal, upperVal, includeLower, includeUpper);
192   }
193   
194   /**
195    * Creates a numeric range filter using {@link FieldCache#getShorts(IndexReader,String,FieldCache.ShortParser)}. This works with all
196    * short fields containing exactly one numeric term in the field. The range can be half-open by setting one
197    * of the values to <code>null</code>.
198    */
199   public static FieldCacheRangeFilter<Short> newShortRange(String field, FieldCache.ShortParser parser, Short lowerVal, Short upperVal, boolean includeLower, boolean includeUpper) {
200     return new FieldCacheRangeFilter<Short>(field, parser, lowerVal, upperVal, includeLower, includeUpper) {
201       @Override
202       public DocIdSet getDocIdSet(IndexReader reader) throws IOException {
203         final short inclusiveLowerPoint, inclusiveUpperPoint;
204         if (lowerVal != null) {
205           short i = lowerVal.shortValue();
206           if (!includeLower && i == Short.MAX_VALUE)
207             return DocIdSet.EMPTY_DOCIDSET;
208           inclusiveLowerPoint = (short) (includeLower ? i : (i + 1));
209         } else {
210           inclusiveLowerPoint = Short.MIN_VALUE;
211         }
212         if (upperVal != null) {
213           short i = upperVal.shortValue();
214           if (!includeUpper && i == Short.MIN_VALUE)
215             return DocIdSet.EMPTY_DOCIDSET;
216           inclusiveUpperPoint = (short) (includeUpper ? i : (i - 1));
217         } else {
218           inclusiveUpperPoint = Short.MAX_VALUE;
219         }
220         
221         if (inclusiveLowerPoint > inclusiveUpperPoint)
222           return DocIdSet.EMPTY_DOCIDSET;
223         
224         final short[] values = FieldCache.DEFAULT.getShorts(reader, field, (FieldCache.ShortParser) parser);
225         // we only request the usage of termDocs, if the range contains 0
226         return new FieldCacheDocIdSet(reader, (inclusiveLowerPoint <= 0 && inclusiveUpperPoint >= 0)) {
227           @Override
228           boolean matchDoc(int doc) {
229             return values[doc] >= inclusiveLowerPoint && values[doc] <= inclusiveUpperPoint;
230           }
231         };
232       }
233     };
234   }
235   
236   /**
237    * Creates a numeric range filter using {@link FieldCache#getInts(IndexReader,String)}. This works with all
238    * int fields containing exactly one numeric term in the field. The range can be half-open by setting one
239    * of the values to <code>null</code>.
240    */
241   public static FieldCacheRangeFilter<Integer> newIntRange(String field, Integer lowerVal, Integer upperVal, boolean includeLower, boolean includeUpper) {
242     return newIntRange(field, null, lowerVal, upperVal, includeLower, includeUpper);
243   }
244   
245   /**
246    * Creates a numeric range filter using {@link FieldCache#getInts(IndexReader,String,FieldCache.IntParser)}. This works with all
247    * int fields containing exactly one numeric term in the field. The range can be half-open by setting one
248    * of the values to <code>null</code>.
249    */
250   public static FieldCacheRangeFilter<Integer> newIntRange(String field, FieldCache.IntParser parser, Integer lowerVal, Integer upperVal, boolean includeLower, boolean includeUpper) {
251     return new FieldCacheRangeFilter<Integer>(field, parser, lowerVal, upperVal, includeLower, includeUpper) {
252       @Override
253       public DocIdSet getDocIdSet(IndexReader reader) throws IOException {
254         final int inclusiveLowerPoint, inclusiveUpperPoint;
255         if (lowerVal != null) {
256           int i = lowerVal.intValue();
257           if (!includeLower && i == Integer.MAX_VALUE)
258             return DocIdSet.EMPTY_DOCIDSET;
259           inclusiveLowerPoint = includeLower ? i : (i + 1);
260         } else {
261           inclusiveLowerPoint = Integer.MIN_VALUE;
262         }
263         if (upperVal != null) {
264           int i = upperVal.intValue();
265           if (!includeUpper && i == Integer.MIN_VALUE)
266             return DocIdSet.EMPTY_DOCIDSET;
267           inclusiveUpperPoint = includeUpper ? i : (i - 1);
268         } else {
269           inclusiveUpperPoint = Integer.MAX_VALUE;
270         }
271         
272         if (inclusiveLowerPoint > inclusiveUpperPoint)
273           return DocIdSet.EMPTY_DOCIDSET;
274         
275         final int[] values = FieldCache.DEFAULT.getInts(reader, field, (FieldCache.IntParser) parser);
276         // we only request the usage of termDocs, if the range contains 0
277         return new FieldCacheDocIdSet(reader, (inclusiveLowerPoint <= 0 && inclusiveUpperPoint >= 0)) {
278           @Override
279           boolean matchDoc(int doc) {
280             return values[doc] >= inclusiveLowerPoint && values[doc] <= inclusiveUpperPoint;
281           }
282         };
283       }
284     };
285   }
286   
287   /**
288    * Creates a numeric range filter using {@link FieldCache#getLongs(IndexReader,String)}. This works with all
289    * long fields containing exactly one numeric term in the field. The range can be half-open by setting one
290    * of the values to <code>null</code>.
291    */
292   public static FieldCacheRangeFilter<Long> newLongRange(String field, Long lowerVal, Long upperVal, boolean includeLower, boolean includeUpper) {
293     return newLongRange(field, null, lowerVal, upperVal, includeLower, includeUpper);
294   }
295   
296   /**
297    * Creates a numeric range filter using {@link FieldCache#getLongs(IndexReader,String,FieldCache.LongParser)}. This works with all
298    * long fields containing exactly one numeric term in the field. The range can be half-open by setting one
299    * of the values to <code>null</code>.
300    */
301   public static FieldCacheRangeFilter<Long> newLongRange(String field, FieldCache.LongParser parser, Long lowerVal, Long upperVal, boolean includeLower, boolean includeUpper) {
302     return new FieldCacheRangeFilter<Long>(field, parser, lowerVal, upperVal, includeLower, includeUpper) {
303       @Override
304       public DocIdSet getDocIdSet(IndexReader reader) throws IOException {
305         final long inclusiveLowerPoint, inclusiveUpperPoint;
306         if (lowerVal != null) {
307           long i = lowerVal.longValue();
308           if (!includeLower && i == Long.MAX_VALUE)
309             return DocIdSet.EMPTY_DOCIDSET;
310           inclusiveLowerPoint = includeLower ? i : (i + 1L);
311         } else {
312           inclusiveLowerPoint = Long.MIN_VALUE;
313         }
314         if (upperVal != null) {
315           long i = upperVal.longValue();
316           if (!includeUpper && i == Long.MIN_VALUE)
317             return DocIdSet.EMPTY_DOCIDSET;
318           inclusiveUpperPoint = includeUpper ? i : (i - 1L);
319         } else {
320           inclusiveUpperPoint = Long.MAX_VALUE;
321         }
322         
323         if (inclusiveLowerPoint > inclusiveUpperPoint)
324           return DocIdSet.EMPTY_DOCIDSET;
325         
326         final long[] values = FieldCache.DEFAULT.getLongs(reader, field, (FieldCache.LongParser) parser);
327         // we only request the usage of termDocs, if the range contains 0
328         return new FieldCacheDocIdSet(reader, (inclusiveLowerPoint <= 0L && inclusiveUpperPoint >= 0L)) {
329           @Override
330           boolean matchDoc(int doc) {
331             return values[doc] >= inclusiveLowerPoint && values[doc] <= inclusiveUpperPoint;
332           }
333         };
334       }
335     };
336   }
337   
338   /**
339    * Creates a numeric range filter using {@link FieldCache#getFloats(IndexReader,String)}. This works with all
340    * float fields containing exactly one numeric term in the field. The range can be half-open by setting one
341    * of the values to <code>null</code>.
342    */
343   public static FieldCacheRangeFilter<Float> newFloatRange(String field, Float lowerVal, Float upperVal, boolean includeLower, boolean includeUpper) {
344     return newFloatRange(field, null, lowerVal, upperVal, includeLower, includeUpper);
345   }
346   
347   /**
348    * Creates a numeric range filter using {@link FieldCache#getFloats(IndexReader,String,FieldCache.FloatParser)}. This works with all
349    * float fields containing exactly one numeric term in the field. The range can be half-open by setting one
350    * of the values to <code>null</code>.
351    */
352   public static FieldCacheRangeFilter<Float> newFloatRange(String field, FieldCache.FloatParser parser, Float lowerVal, Float upperVal, boolean includeLower, boolean includeUpper) {
353     return new FieldCacheRangeFilter<Float>(field, parser, lowerVal, upperVal, includeLower, includeUpper) {
354       @Override
355       public DocIdSet getDocIdSet(IndexReader reader) throws IOException {
356         // we transform the floating point numbers to sortable integers
357         // using NumericUtils to easier find the next bigger/lower value
358         final float inclusiveLowerPoint, inclusiveUpperPoint;
359         if (lowerVal != null) {
360           float f = lowerVal.floatValue();
361           if (!includeUpper && f > 0.0f && Float.isInfinite(f))
362             return DocIdSet.EMPTY_DOCIDSET;
363           int i = NumericUtils.floatToSortableInt(f);
364           inclusiveLowerPoint = NumericUtils.sortableIntToFloat( includeLower ?  i : (i + 1) );
365         } else {
366           inclusiveLowerPoint = Float.NEGATIVE_INFINITY;
367         }
368         if (upperVal != null) {
369           float f = upperVal.floatValue();
370           if (!includeUpper && f < 0.0f && Float.isInfinite(f))
371             return DocIdSet.EMPTY_DOCIDSET;
372           int i = NumericUtils.floatToSortableInt(f);
373           inclusiveUpperPoint = NumericUtils.sortableIntToFloat( includeUpper ? i : (i - 1) );
374         } else {
375           inclusiveUpperPoint = Float.POSITIVE_INFINITY;
376         }
377         
378         if (inclusiveLowerPoint > inclusiveUpperPoint)
379           return DocIdSet.EMPTY_DOCIDSET;
380         
381         final float[] values = FieldCache.DEFAULT.getFloats(reader, field, (FieldCache.FloatParser) parser);
382         // we only request the usage of termDocs, if the range contains 0
383         return new FieldCacheDocIdSet(reader, (inclusiveLowerPoint <= 0.0f && inclusiveUpperPoint >= 0.0f)) {
384           @Override
385           boolean matchDoc(int doc) {
386             return values[doc] >= inclusiveLowerPoint && values[doc] <= inclusiveUpperPoint;
387           }
388         };
389       }
390     };
391   }
392   
393   /**
394    * Creates a numeric range filter using {@link FieldCache#getDoubles(IndexReader,String)}. This works with all
395    * double fields containing exactly one numeric term in the field. The range can be half-open by setting one
396    * of the values to <code>null</code>.
397    */
398   public static FieldCacheRangeFilter<Double> newDoubleRange(String field, Double lowerVal, Double upperVal, boolean includeLower, boolean includeUpper) {
399     return newDoubleRange(field, null, lowerVal, upperVal, includeLower, includeUpper);
400   }
401   
402   /**
403    * Creates a numeric range filter using {@link FieldCache#getDoubles(IndexReader,String,FieldCache.DoubleParser)}. This works with all
404    * double fields containing exactly one numeric term in the field. The range can be half-open by setting one
405    * of the values to <code>null</code>.
406    */
407   public static FieldCacheRangeFilter<Double> newDoubleRange(String field, FieldCache.DoubleParser parser, Double lowerVal, Double upperVal, boolean includeLower, boolean includeUpper) {
408     return new FieldCacheRangeFilter<Double>(field, parser, lowerVal, upperVal, includeLower, includeUpper) {
409       @Override
410       public DocIdSet getDocIdSet(IndexReader reader) throws IOException {
411         // we transform the floating point numbers to sortable integers
412         // using NumericUtils to easier find the next bigger/lower value
413         final double inclusiveLowerPoint, inclusiveUpperPoint;
414         if (lowerVal != null) {
415           double f = lowerVal.doubleValue();
416           if (!includeUpper && f > 0.0 && Double.isInfinite(f))
417             return DocIdSet.EMPTY_DOCIDSET;
418           long i = NumericUtils.doubleToSortableLong(f);
419           inclusiveLowerPoint = NumericUtils.sortableLongToDouble( includeLower ?  i : (i + 1L) );
420         } else {
421           inclusiveLowerPoint = Double.NEGATIVE_INFINITY;
422         }
423         if (upperVal != null) {
424           double f = upperVal.doubleValue();
425           if (!includeUpper && f < 0.0 && Double.isInfinite(f))
426             return DocIdSet.EMPTY_DOCIDSET;
427           long i = NumericUtils.doubleToSortableLong(f);
428           inclusiveUpperPoint = NumericUtils.sortableLongToDouble( includeUpper ? i : (i - 1L) );
429         } else {
430           inclusiveUpperPoint = Double.POSITIVE_INFINITY;
431         }
432         
433         if (inclusiveLowerPoint > inclusiveUpperPoint)
434           return DocIdSet.EMPTY_DOCIDSET;
435         
436         final double[] values = FieldCache.DEFAULT.getDoubles(reader, field, (FieldCache.DoubleParser) parser);
437         // we only request the usage of termDocs, if the range contains 0
438         return new FieldCacheDocIdSet(reader, (inclusiveLowerPoint <= 0.0 && inclusiveUpperPoint >= 0.0)) {
439           @Override
440           boolean matchDoc(int doc) {
441             return values[doc] >= inclusiveLowerPoint && values[doc] <= inclusiveUpperPoint;
442           }
443         };
444       }
445     };
446   }
447   
448   @Override
449   public final String toString() {
450     final StringBuilder sb = new StringBuilder(field).append(":");
451     return sb.append(includeLower ? '[' : '{')
452       .append((lowerVal == null) ? "*" : lowerVal.toString())
453       .append(" TO ")
454       .append((upperVal == null) ? "*" : upperVal.toString())
455       .append(includeUpper ? ']' : '}')
456       .toString();
457   }
458
459   @Override
460   public final boolean equals(Object o) {
461     if (this == o) return true;
462     if (!(o instanceof FieldCacheRangeFilter)) return false;
463     FieldCacheRangeFilter other = (FieldCacheRangeFilter) o;
464
465     if (!this.field.equals(other.field)
466         || this.includeLower != other.includeLower
467         || this.includeUpper != other.includeUpper
468     ) { return false; }
469     if (this.lowerVal != null ? !this.lowerVal.equals(other.lowerVal) : other.lowerVal != null) return false;
470     if (this.upperVal != null ? !this.upperVal.equals(other.upperVal) : other.upperVal != null) return false;
471     if (this.parser != null ? !this.parser.equals(other.parser) : other.parser != null) return false;
472     return true;
473   }
474   
475   @Override
476   public final int hashCode() {
477     int h = field.hashCode();
478     h ^= (lowerVal != null) ? lowerVal.hashCode() : 550356204;
479     h = (h << 1) | (h >>> 31);  // rotate to distinguish lower from upper
480     h ^= (upperVal != null) ? upperVal.hashCode() : -1674416163;
481     h ^= (parser != null) ? parser.hashCode() : -1572457324;
482     h ^= (includeLower ? 1549299360 : -365038026) ^ (includeUpper ? 1721088258 : 1948649653);
483     return h;
484   }
485
486   /** Returns the field name for this filter */
487   public String getField() { return field; }
488
489   /** Returns <code>true</code> if the lower endpoint is inclusive */
490   public boolean includesLower() { return includeLower; }
491   
492   /** Returns <code>true</code> if the upper endpoint is inclusive */
493   public boolean includesUpper() { return includeUpper; }
494
495   /** Returns the lower value of this range filter */
496   public T getLowerVal() { return lowerVal; }
497
498   /** Returns the upper value of this range filter */
499   public T getUpperVal() { return upperVal; }
500   
501   /** Returns the current numeric parser ({@code null} for {@code T} is {@code String}} */
502   public FieldCache.Parser getParser() { return parser; }
503   
504   static abstract class FieldCacheDocIdSet extends DocIdSet {
505     private final IndexReader reader;
506     private boolean mayUseTermDocs;
507   
508     FieldCacheDocIdSet(IndexReader reader, boolean mayUseTermDocs) {
509       this.reader = reader;
510       this.mayUseTermDocs = mayUseTermDocs;
511     }
512   
513     /** this method checks, if a doc is a hit, should throw AIOBE, when position invalid */
514     abstract boolean matchDoc(int doc) throws ArrayIndexOutOfBoundsException;
515     
516     /** this DocIdSet is cacheable, if it works solely with FieldCache and no TermDocs */
517     @Override
518     public boolean isCacheable() {
519       return !(mayUseTermDocs && reader.hasDeletions());
520     }
521
522     @Override
523     public DocIdSetIterator iterator() throws IOException {
524       // Synchronization needed because deleted docs BitVector
525       // can change after call to hasDeletions until TermDocs creation.
526       // We only use an iterator with termDocs, when this was requested (e.g. range contains 0)
527       // and the index has deletions
528       final TermDocs termDocs;
529       synchronized(reader) {
530         termDocs = isCacheable() ? null : reader.termDocs(null);
531       }
532       if (termDocs != null) {
533         // a DocIdSetIterator using TermDocs to iterate valid docIds
534         return new DocIdSetIterator() {
535           private int doc = -1;
536           
537           @Override
538           public int docID() {
539             return doc;
540           }
541           
542           @Override
543           public int nextDoc() throws IOException {
544             do {
545               if (!termDocs.next())
546                 return doc = NO_MORE_DOCS;
547             } while (!matchDoc(doc = termDocs.doc()));
548             return doc;
549           }
550           
551           @Override
552           public int advance(int target) throws IOException {
553             if (!termDocs.skipTo(target))
554               return doc = NO_MORE_DOCS;
555             while (!matchDoc(doc = termDocs.doc())) { 
556               if (!termDocs.next())
557                 return doc = NO_MORE_DOCS;
558             }
559             return doc;
560           }
561         };
562       } else {
563         // a DocIdSetIterator generating docIds by incrementing a variable -
564         // this one can be used if there are no deletions are on the index
565         return new DocIdSetIterator() {
566           private int doc = -1;
567           
568           @Override
569           public int docID() {
570             return doc;
571           }
572           
573           @Override
574           public int nextDoc() {
575             try {
576               do {
577                 doc++;
578               } while (!matchDoc(doc));
579               return doc;
580             } catch (ArrayIndexOutOfBoundsException e) {
581               return doc = NO_MORE_DOCS;
582             }
583           }
584           
585           @Override
586           public int advance(int target) {
587             try {
588               doc = target;
589               while (!matchDoc(doc)) { 
590                 doc++;
591               }
592               return doc;
593             } catch (ArrayIndexOutOfBoundsException e) {
594               return doc = NO_MORE_DOCS;
595             }
596           }
597         };
598       }
599     }
600   }
601
602 }