add --shared
[pylucene.git] / lucene-java-3.4.0 / lucene / src / java / org / apache / lucene / search / NumericRangeQuery.java
1 package org.apache.lucene.search;
2
3 /**
4  * Licensed to the Apache Software Foundation (ASF) under one or more
5  * contributor license agreements.  See the NOTICE file distributed with
6  * this work for additional information regarding copyright ownership.
7  * The ASF licenses this file to You under the Apache License, Version 2.0
8  * (the "License"); you may not use this file except in compliance with
9  * the License.  You may obtain a copy of the License at
10  *
11  *     http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19
20 import java.io.IOException;
21 import java.util.LinkedList;
22
23 import org.apache.lucene.analysis.NumericTokenStream; // for javadocs
24 import org.apache.lucene.document.NumericField; // for javadocs
25 import org.apache.lucene.util.NumericUtils;
26 import org.apache.lucene.util.ToStringUtils;
27 import org.apache.lucene.util.StringHelper;
28 import org.apache.lucene.index.IndexReader;
29 import org.apache.lucene.index.Term;
30 import org.apache.lucene.index.TermEnum;
31
32 /**
33  * <p>A {@link Query} that matches numeric values within a
34  * specified range.  To use this, you must first index the
35  * numeric values using {@link NumericField} (expert: {@link
36  * NumericTokenStream}).  If your terms are instead textual,
37  * you should use {@link TermRangeQuery}.  {@link
38  * NumericRangeFilter} is the filter equivalent of this
39  * query.</p>
40  *
41  * <p>You create a new NumericRangeQuery with the static
42  * factory methods, eg:
43  *
44  * <pre>
45  * Query q = NumericRangeQuery.newFloatRange("weight", 0.03f, 0.10f, true, true);
46  * </pre>
47  *
48  * matches all documents whose float valued "weight" field
49  * ranges from 0.03 to 0.10, inclusive.
50  *
51  * <p>The performance of NumericRangeQuery is much better
52  * than the corresponding {@link TermRangeQuery} because the
53  * number of terms that must be searched is usually far
54  * fewer, thanks to trie indexing, described below.</p>
55  *
56  * <p>You can optionally specify a <a
57  * href="#precisionStepDesc"><code>precisionStep</code></a>
58  * when creating this query.  This is necessary if you've
59  * changed this configuration from its default (4) during
60  * indexing.  Lower values consume more disk space but speed
61  * up searching.  Suitable values are between <b>1</b> and
62  * <b>8</b>. A good starting point to test is <b>4</b>,
63  * which is the default value for all <code>Numeric*</code>
64  * classes.  See <a href="#precisionStepDesc">below</a> for
65  * details.
66  *
67  * <p>This query defaults to {@linkplain
68  * MultiTermQuery#CONSTANT_SCORE_AUTO_REWRITE_DEFAULT} for
69  * 32 bit (int/float) ranges with precisionStep &le;8 and 64
70  * bit (long/double) ranges with precisionStep &le;6.
71  * Otherwise it uses {@linkplain
72  * MultiTermQuery#CONSTANT_SCORE_FILTER_REWRITE} as the
73  * number of terms is likely to be high.  With precision
74  * steps of &le;4, this query can be run with one of the
75  * BooleanQuery rewrite methods without changing
76  * BooleanQuery's default max clause count.
77  *
78  * <br><h3>How it works</h3>
79  *
80  * <p>See the publication about <a target="_blank" href="http://www.panfmp.org">panFMP</a>,
81  * where this algorithm was described (referred to as <code>TrieRangeQuery</code>):
82  *
83  * <blockquote><strong>Schindler, U, Diepenbroek, M</strong>, 2008.
84  * <em>Generic XML-based Framework for Metadata Portals.</em>
85  * Computers &amp; Geosciences 34 (12), 1947-1955.
86  * <a href="http://dx.doi.org/10.1016/j.cageo.2008.02.023"
87  * target="_blank">doi:10.1016/j.cageo.2008.02.023</a></blockquote>
88  *
89  * <p><em>A quote from this paper:</em> Because Apache Lucene is a full-text
90  * search engine and not a conventional database, it cannot handle numerical ranges
91  * (e.g., field value is inside user defined bounds, even dates are numerical values).
92  * We have developed an extension to Apache Lucene that stores
93  * the numerical values in a special string-encoded format with variable precision
94  * (all numerical values like doubles, longs, floats, and ints are converted to
95  * lexicographic sortable string representations and stored with different precisions
96  * (for a more detailed description of how the values are stored,
97  * see {@link NumericUtils}). A range is then divided recursively into multiple intervals for searching:
98  * The center of the range is searched only with the lowest possible precision in the <em>trie</em>,
99  * while the boundaries are matched more exactly. This reduces the number of terms dramatically.</p>
100  *
101  * <p>For the variant that stores long values in 8 different precisions (each reduced by 8 bits) that
102  * uses a lowest precision of 1 byte, the index contains only a maximum of 256 distinct values in the
103  * lowest precision. Overall, a range could consist of a theoretical maximum of
104  * <code>7*255*2 + 255 = 3825</code> distinct terms (when there is a term for every distinct value of an
105  * 8-byte-number in the index and the range covers almost all of them; a maximum of 255 distinct values is used
106  * because it would always be possible to reduce the full 256 values to one term with degraded precision).
107  * In practice, we have seen up to 300 terms in most cases (index with 500,000 metadata records
108  * and a uniform value distribution).</p>
109  *
110  * <a name="precisionStepDesc"><h3>Precision Step</h3>
111  * <p>You can choose any <code>precisionStep</code> when encoding values.
112  * Lower step values mean more precisions and so more terms in index (and index gets larger).
113  * On the other hand, the maximum number of terms to match reduces, which optimized query speed.
114  * The formula to calculate the maximum term count is:
115  * <pre>
116  *  n = [ (bitsPerValue/precisionStep - 1) * (2^precisionStep - 1 ) * 2 ] + (2^precisionStep - 1 )
117  * </pre>
118  * <p><em>(this formula is only correct, when <code>bitsPerValue/precisionStep</code> is an integer;
119  * in other cases, the value must be rounded up and the last summand must contain the modulo of the division as
120  * precision step)</em>.
121  * For longs stored using a precision step of 4, <code>n = 15*15*2 + 15 = 465</code>, and for a precision
122  * step of 2, <code>n = 31*3*2 + 3 = 189</code>. But the faster search speed is reduced by more seeking
123  * in the term enum of the index. Because of this, the ideal <code>precisionStep</code> value can only
124  * be found out by testing. <b>Important:</b> You can index with a lower precision step value and test search speed
125  * using a multiple of the original step value.</p>
126  *
127  * <p>Good values for <code>precisionStep</code> are depending on usage and data type:
128  * <ul>
129  *  <li>The default for all data types is <b>4</b>, which is used, when no <code>precisionStep</code> is given.
130  *  <li>Ideal value in most cases for <em>64 bit</em> data types <em>(long, double)</em> is <b>6</b> or <b>8</b>.
131  *  <li>Ideal value in most cases for <em>32 bit</em> data types <em>(int, float)</em> is <b>4</b>.
132  *  <li>For low cardinality fields larger precision steps are good. If the cardinality is &lt; 100, it is
133  *  fair to use {@link Integer#MAX_VALUE} (see below).
134  *  <li>Steps <b>&ge;64</b> for <em>long/double</em> and <b>&ge;32</b> for <em>int/float</em> produces one token
135  *  per value in the index and querying is as slow as a conventional {@link TermRangeQuery}. But it can be used
136  *  to produce fields, that are solely used for sorting (in this case simply use {@link Integer#MAX_VALUE} as
137  *  <code>precisionStep</code>). Using {@link NumericField NumericFields} for sorting
138  *  is ideal, because building the field cache is much faster than with text-only numbers.
139  *  These fields have one term per value and therefore also work with term enumeration for building distinct lists
140  *  (e.g. facets / preselected values to search for).
141  *  Sorting is also possible with range query optimized fields using one of the above <code>precisionSteps</code>.
142  * </ul>
143  *
144  * <p>Comparisons of the different types of RangeQueries on an index with about 500,000 docs showed
145  * that {@link TermRangeQuery} in boolean rewrite mode (with raised {@link BooleanQuery} clause count)
146  * took about 30-40 secs to complete, {@link TermRangeQuery} in constant score filter rewrite mode took 5 secs
147  * and executing this class took &lt;100ms to complete (on an Opteron64 machine, Java 1.5, 8 bit
148  * precision step). This query type was developed for a geographic portal, where the performance for
149  * e.g. bounding boxes or exact date/time stamps is important.</p>
150  *
151  * @since 2.9
152  **/
153 public final class NumericRangeQuery<T extends Number> extends MultiTermQuery {
154
155   private NumericRangeQuery(final String field, final int precisionStep, final int valSize,
156     T min, T max, final boolean minInclusive, final boolean maxInclusive
157   ) {
158     assert (valSize == 32 || valSize == 64);
159     if (precisionStep < 1)
160       throw new IllegalArgumentException("precisionStep must be >=1");
161     this.field = StringHelper.intern(field);
162     this.precisionStep = precisionStep;
163     this.valSize = valSize;
164     this.min = min;
165     this.max = max;
166     this.minInclusive = minInclusive;
167     this.maxInclusive = maxInclusive;
168
169     // For bigger precisionSteps this query likely
170     // hits too many terms, so set to CONSTANT_SCORE_FILTER right off
171     // (especially as the FilteredTermEnum is costly if wasted only for AUTO tests because it
172     // creates new enums from IndexReader for each sub-range)
173     switch (valSize) {
174       case 64:
175         setRewriteMethod( (precisionStep > 6) ?
176           CONSTANT_SCORE_FILTER_REWRITE : 
177           CONSTANT_SCORE_AUTO_REWRITE_DEFAULT
178         );
179         break;
180       case 32:
181         setRewriteMethod( (precisionStep > 8) ?
182           CONSTANT_SCORE_FILTER_REWRITE : 
183           CONSTANT_SCORE_AUTO_REWRITE_DEFAULT
184         );
185         break;
186       default:
187         // should never happen
188         throw new IllegalArgumentException("valSize must be 32 or 64");
189     }
190     
191     // shortcut if upper bound == lower bound
192     if (min != null && min.equals(max)) {
193       setRewriteMethod(CONSTANT_SCORE_BOOLEAN_QUERY_REWRITE);
194     }
195   }
196   
197   /**
198    * Factory that creates a <code>NumericRangeQuery</code>, that queries a <code>long</code>
199    * range using the given <a href="#precisionStepDesc"><code>precisionStep</code></a>.
200    * You can have half-open ranges (which are in fact &lt;/&le; or &gt;/&ge; queries)
201    * by setting the min or max value to <code>null</code>. By setting inclusive to false, it will
202    * match all documents excluding the bounds, with inclusive on, the boundaries are hits, too.
203    */
204   public static NumericRangeQuery<Long> newLongRange(final String field, final int precisionStep,
205     Long min, Long max, final boolean minInclusive, final boolean maxInclusive
206   ) {
207     return new NumericRangeQuery<Long>(field, precisionStep, 64, min, max, minInclusive, maxInclusive);
208   }
209   
210   /**
211    * Factory that creates a <code>NumericRangeQuery</code>, that queries a <code>long</code>
212    * range using the default <code>precisionStep</code> {@link NumericUtils#PRECISION_STEP_DEFAULT} (4).
213    * You can have half-open ranges (which are in fact &lt;/&le; or &gt;/&ge; queries)
214    * by setting the min or max value to <code>null</code>. By setting inclusive to false, it will
215    * match all documents excluding the bounds, with inclusive on, the boundaries are hits, too.
216    */
217   public static NumericRangeQuery<Long> newLongRange(final String field,
218     Long min, Long max, final boolean minInclusive, final boolean maxInclusive
219   ) {
220     return new NumericRangeQuery<Long>(field, NumericUtils.PRECISION_STEP_DEFAULT, 64, min, max, minInclusive, maxInclusive);
221   }
222   
223   /**
224    * Factory that creates a <code>NumericRangeQuery</code>, that queries a <code>int</code>
225    * range using the given <a href="#precisionStepDesc"><code>precisionStep</code></a>.
226    * You can have half-open ranges (which are in fact &lt;/&le; or &gt;/&ge; queries)
227    * by setting the min or max value to <code>null</code>. By setting inclusive to false, it will
228    * match all documents excluding the bounds, with inclusive on, the boundaries are hits, too.
229    */
230   public static NumericRangeQuery<Integer> newIntRange(final String field, final int precisionStep,
231     Integer min, Integer max, final boolean minInclusive, final boolean maxInclusive
232   ) {
233     return new NumericRangeQuery<Integer>(field, precisionStep, 32, min, max, minInclusive, maxInclusive);
234   }
235   
236   /**
237    * Factory that creates a <code>NumericRangeQuery</code>, that queries a <code>int</code>
238    * range using the default <code>precisionStep</code> {@link NumericUtils#PRECISION_STEP_DEFAULT} (4).
239    * You can have half-open ranges (which are in fact &lt;/&le; or &gt;/&ge; queries)
240    * by setting the min or max value to <code>null</code>. By setting inclusive to false, it will
241    * match all documents excluding the bounds, with inclusive on, the boundaries are hits, too.
242    */
243   public static NumericRangeQuery<Integer> newIntRange(final String field,
244     Integer min, Integer max, final boolean minInclusive, final boolean maxInclusive
245   ) {
246     return new NumericRangeQuery<Integer>(field, NumericUtils.PRECISION_STEP_DEFAULT, 32, min, max, minInclusive, maxInclusive);
247   }
248   
249   /**
250    * Factory that creates a <code>NumericRangeQuery</code>, that queries a <code>double</code>
251    * range using the given <a href="#precisionStepDesc"><code>precisionStep</code></a>.
252    * You can have half-open ranges (which are in fact &lt;/&le; or &gt;/&ge; queries)
253    * by setting the min or max value to <code>null</code>. By setting inclusive to false, it will
254    * match all documents excluding the bounds, with inclusive on, the boundaries are hits, too.
255    */
256   public static NumericRangeQuery<Double> newDoubleRange(final String field, final int precisionStep,
257     Double min, Double max, final boolean minInclusive, final boolean maxInclusive
258   ) {
259     return new NumericRangeQuery<Double>(field, precisionStep, 64, min, max, minInclusive, maxInclusive);
260   }
261   
262   /**
263    * Factory that creates a <code>NumericRangeQuery</code>, that queries a <code>double</code>
264    * range using the default <code>precisionStep</code> {@link NumericUtils#PRECISION_STEP_DEFAULT} (4).
265    * You can have half-open ranges (which are in fact &lt;/&le; or &gt;/&ge; queries)
266    * by setting the min or max value to <code>null</code>. By setting inclusive to false, it will
267    * match all documents excluding the bounds, with inclusive on, the boundaries are hits, too.
268    */
269   public static NumericRangeQuery<Double> newDoubleRange(final String field,
270     Double min, Double max, final boolean minInclusive, final boolean maxInclusive
271   ) {
272     return new NumericRangeQuery<Double>(field, NumericUtils.PRECISION_STEP_DEFAULT, 64, min, max, minInclusive, maxInclusive);
273   }
274   
275   /**
276    * Factory that creates a <code>NumericRangeQuery</code>, that queries a <code>float</code>
277    * range using the given <a href="#precisionStepDesc"><code>precisionStep</code></a>.
278    * You can have half-open ranges (which are in fact &lt;/&le; or &gt;/&ge; queries)
279    * by setting the min or max value to <code>null</code>. By setting inclusive to false, it will
280    * match all documents excluding the bounds, with inclusive on, the boundaries are hits, too.
281    */
282   public static NumericRangeQuery<Float> newFloatRange(final String field, final int precisionStep,
283     Float min, Float max, final boolean minInclusive, final boolean maxInclusive
284   ) {
285     return new NumericRangeQuery<Float>(field, precisionStep, 32, min, max, minInclusive, maxInclusive);
286   }
287   
288   /**
289    * Factory that creates a <code>NumericRangeQuery</code>, that queries a <code>float</code>
290    * range using the default <code>precisionStep</code> {@link NumericUtils#PRECISION_STEP_DEFAULT} (4).
291    * You can have half-open ranges (which are in fact &lt;/&le; or &gt;/&ge; queries)
292    * by setting the min or max value to <code>null</code>. By setting inclusive to false, it will
293    * match all documents excluding the bounds, with inclusive on, the boundaries are hits, too.
294    */
295   public static NumericRangeQuery<Float> newFloatRange(final String field,
296     Float min, Float max, final boolean minInclusive, final boolean maxInclusive
297   ) {
298     return new NumericRangeQuery<Float>(field, NumericUtils.PRECISION_STEP_DEFAULT, 32, min, max, minInclusive, maxInclusive);
299   }
300   
301   @Override
302   protected FilteredTermEnum getEnum(final IndexReader reader) throws IOException {
303     return new NumericRangeTermEnum(reader);
304   }
305
306   /** Returns the field name for this query */
307   public String getField() { return field; }
308
309   /** Returns <code>true</code> if the lower endpoint is inclusive */
310   public boolean includesMin() { return minInclusive; }
311   
312   /** Returns <code>true</code> if the upper endpoint is inclusive */
313   public boolean includesMax() { return maxInclusive; }
314
315   /** Returns the lower value of this range query */
316   public T getMin() { return min; }
317
318   /** Returns the upper value of this range query */
319   public T getMax() { return max; }
320   
321   /** Returns the precision step. */
322   public int getPrecisionStep() { return precisionStep; }
323   
324   @Override
325   public String toString(final String field) {
326     final StringBuilder sb = new StringBuilder();
327     if (!this.field.equals(field)) sb.append(this.field).append(':');
328     return sb.append(minInclusive ? '[' : '{')
329       .append((min == null) ? "*" : min.toString())
330       .append(" TO ")
331       .append((max == null) ? "*" : max.toString())
332       .append(maxInclusive ? ']' : '}')
333       .append(ToStringUtils.boost(getBoost()))
334       .toString();
335   }
336
337   @Override
338   public final boolean equals(final Object o) {
339     if (o==this) return true;
340     if (!super.equals(o))
341       return false;
342     if (o instanceof NumericRangeQuery) {
343       final NumericRangeQuery q=(NumericRangeQuery)o;
344       return (
345         field==q.field &&
346         (q.min == null ? min == null : q.min.equals(min)) &&
347         (q.max == null ? max == null : q.max.equals(max)) &&
348         minInclusive == q.minInclusive &&
349         maxInclusive == q.maxInclusive &&
350         precisionStep == q.precisionStep
351       );
352     }
353     return false;
354   }
355
356   @Override
357   public final int hashCode() {
358     int hash = super.hashCode();
359     hash += field.hashCode()^0x4565fd66 + precisionStep^0x64365465;
360     if (min != null) hash += min.hashCode()^0x14fa55fb;
361     if (max != null) hash += max.hashCode()^0x733fa5fe;
362     return hash +
363       (Boolean.valueOf(minInclusive).hashCode()^0x14fa55fb)+
364       (Boolean.valueOf(maxInclusive).hashCode()^0x733fa5fe);
365   }
366   
367   // field must be interned after reading from stream
368   private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException {
369     in.defaultReadObject();
370     field = StringHelper.intern(field);
371   }
372
373   // members (package private, to be also fast accessible by NumericRangeTermEnum)
374   String field;
375   final int precisionStep, valSize;
376   final T min, max;
377   final boolean minInclusive,maxInclusive;
378
379   /**
380    * Subclass of FilteredTermEnum for enumerating all terms that match the
381    * sub-ranges for trie range queries.
382    * <p>
383    * WARNING: This term enumeration is not guaranteed to be always ordered by
384    * {@link Term#compareTo}.
385    * The ordering depends on how {@link NumericUtils#splitLongRange} and
386    * {@link NumericUtils#splitIntRange} generates the sub-ranges. For
387    * {@link MultiTermQuery} ordering is not relevant.
388    */
389   private final class NumericRangeTermEnum extends FilteredTermEnum {
390
391     private final IndexReader reader;
392     private final LinkedList<String> rangeBounds = new LinkedList<String>();
393     private final Term termTemplate = new Term(field);
394     private String currentUpperBound = null;
395
396     NumericRangeTermEnum(final IndexReader reader) throws IOException {
397       this.reader = reader;
398       
399       switch (valSize) {
400         case 64: {
401           // lower
402           long minBound = Long.MIN_VALUE;
403           if (min instanceof Long) {
404             minBound = min.longValue();
405           } else if (min instanceof Double) {
406             minBound = NumericUtils.doubleToSortableLong(min.doubleValue());
407           }
408           if (!minInclusive && min != null) {
409             if (minBound == Long.MAX_VALUE) break;
410             minBound++;
411           }
412           
413           // upper
414           long maxBound = Long.MAX_VALUE;
415           if (max instanceof Long) {
416             maxBound = max.longValue();
417           } else if (max instanceof Double) {
418             maxBound = NumericUtils.doubleToSortableLong(max.doubleValue());
419           }
420           if (!maxInclusive && max != null) {
421             if (maxBound == Long.MIN_VALUE) break;
422             maxBound--;
423           }
424           
425           NumericUtils.splitLongRange(new NumericUtils.LongRangeBuilder() {
426             @Override
427             public final void addRange(String minPrefixCoded, String maxPrefixCoded) {
428               rangeBounds.add(minPrefixCoded);
429               rangeBounds.add(maxPrefixCoded);
430             }
431           }, precisionStep, minBound, maxBound);
432           break;
433         }
434           
435         case 32: {
436           // lower
437           int minBound = Integer.MIN_VALUE;
438           if (min instanceof Integer) {
439             minBound = min.intValue();
440           } else if (min instanceof Float) {
441             minBound = NumericUtils.floatToSortableInt(min.floatValue());
442           }
443           if (!minInclusive && min != null) {
444             if (minBound == Integer.MAX_VALUE) break;
445             minBound++;
446           }
447           
448           // upper
449           int maxBound = Integer.MAX_VALUE;
450           if (max instanceof Integer) {
451             maxBound = max.intValue();
452           } else if (max instanceof Float) {
453             maxBound = NumericUtils.floatToSortableInt(max.floatValue());
454           }
455           if (!maxInclusive && max != null) {
456             if (maxBound == Integer.MIN_VALUE) break;
457             maxBound--;
458           }
459           
460           NumericUtils.splitIntRange(new NumericUtils.IntRangeBuilder() {
461             @Override
462             public final void addRange(String minPrefixCoded, String maxPrefixCoded) {
463               rangeBounds.add(minPrefixCoded);
464               rangeBounds.add(maxPrefixCoded);
465             }
466           }, precisionStep, minBound, maxBound);
467           break;
468         }
469           
470         default:
471           // should never happen
472           throw new IllegalArgumentException("valSize must be 32 or 64");
473       }
474       
475       // seek to first term
476       next();
477     }
478
479     @Override
480     public float difference() {
481       return 1.0f;
482     }
483     
484     /** this is a dummy, it is not used by this class. */
485     @Override
486     protected boolean endEnum() {
487       throw new UnsupportedOperationException("not implemented");
488     }
489
490     /** this is a dummy, it is not used by this class. */
491     @Override
492     protected void setEnum(TermEnum tenum) {
493       throw new UnsupportedOperationException("not implemented");
494     }
495     
496     /**
497      * Compares if current upper bound is reached.
498      * In contrast to {@link FilteredTermEnum}, a return value
499      * of <code>false</code> ends iterating the current enum
500      * and forwards to the next sub-range.
501      */
502     @Override
503     protected boolean termCompare(Term term) {
504       return (term.field() == field && term.text().compareTo(currentUpperBound) <= 0);
505     }
506     
507     /** Increments the enumeration to the next element.  True if one exists. */
508     @Override
509     public boolean next() throws IOException {
510       // if a current term exists, the actual enum is initialized:
511       // try change to next term, if no such term exists, fall-through
512       if (currentTerm != null) {
513         assert actualEnum != null;
514         if (actualEnum.next()) {
515           currentTerm = actualEnum.term();
516           if (termCompare(currentTerm))
517             return true;
518         }
519       }
520       
521       // if all above fails, we go forward to the next enum,
522       // if one is available
523       currentTerm = null;
524       while (rangeBounds.size() >= 2) {
525         assert rangeBounds.size() % 2 == 0;
526         // close the current enum and read next bounds
527         if (actualEnum != null) {
528           actualEnum.close();
529           actualEnum = null;
530         }
531         final String lowerBound = rangeBounds.removeFirst();
532         this.currentUpperBound = rangeBounds.removeFirst();
533         // create a new enum
534         actualEnum = reader.terms(termTemplate.createTerm(lowerBound));
535         currentTerm = actualEnum.term();
536         if (currentTerm != null && termCompare(currentTerm))
537           return true;
538         // clear the current term for next iteration
539         currentTerm = null;
540       }
541       
542       // no more sub-range enums available
543       assert rangeBounds.size() == 0 && currentTerm == null;
544       return false;
545     }
546
547     /** Closes the enumeration to further activity, freeing resources.  */
548     @Override
549     public void close() throws IOException {
550       rangeBounds.clear();
551       currentUpperBound = null;
552       super.close();
553     }
554
555   }
556   
557 }