content: 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.LinkedList;
  
  import org.apache.lucene.analysis.NumericTokenStream; // for javadocs
  import org.apache.lucene.document.NumericField; // for javadocs
  import org.apache.lucene.document.NumericField.DataType;
  import org.apache.lucene.util.NumericUtils;
  import org.apache.lucene.util.ToStringUtils;
  import org.apache.lucene.util.StringHelper;
  import org.apache.lucene.index.IndexReader;
  import org.apache.lucene.index.Term;
  import org.apache.lucene.index.TermEnum;
  
  /**
   * <p>A {@link Query} that matches numeric values within a
   * specified range.  To use this, you must first index the
   * numeric values using {@link NumericField} (expert: {@link
   * NumericTokenStream}).  If your terms are instead textual,
   * you should use {@link TermRangeQuery}.  {@link
   * NumericRangeFilter} is the filter equivalent of this
   * query.</p>
   *
   * <p>You create a new NumericRangeQuery with the static
   * factory methods, eg:
   *
   * <pre>
   * Query q = NumericRangeQuery.newFloatRange("weight", 0.03f, 0.10f, true, true);
   * </pre>
   *
   * matches all documents whose float valued "weight" field
   * ranges from 0.03 to 0.10, inclusive.
   *
   * <p>The performance of NumericRangeQuery is much better
   * than the corresponding {@link TermRangeQuery} because the
   * number of terms that must be searched is usually far
   * fewer, thanks to trie indexing, described below.</p>
   *
   * <p>You can optionally specify a <a
   * href="#precisionStepDesc"><code>precisionStep</code></a>
   * when creating this query.  This is necessary if you've
   * changed this configuration from its default (4) during
   * indexing.  Lower values consume more disk space but speed
   * up searching.  Suitable values are between <b>1</b> and
   * <b>8</b>. A good starting point to test is <b>4</b>,
   * which is the default value for all <code>Numeric*</code>
   * classes.  See <a href="#precisionStepDesc">below</a> for
   * details.
   *
   * <p>This query defaults to {@linkplain
   * MultiTermQuery#CONSTANT_SCORE_AUTO_REWRITE_DEFAULT} for
   * 32 bit (int/float) ranges with precisionStep &le;8 and 64
   * bit (long/double) ranges with precisionStep &le;6.
   * Otherwise it uses {@linkplain
   * MultiTermQuery#CONSTANT_SCORE_FILTER_REWRITE} as the
   * number of terms is likely to be high.  With precision
   * steps of &le;4, this query can be run with one of the
   * BooleanQuery rewrite methods without changing
   * BooleanQuery's default max clause count.
   *
   * <br><h3>How it works</h3>
   *
   * <p>See the publication about <a target="_blank" href="http://www.panfmp.org">panFMP</a>,
   * where this algorithm was described (referred to as <code>TrieRangeQuery</code>):
   *
   * <blockquote><strong>Schindler, U, Diepenbroek, M</strong>, 2008.
   * <em>Generic XML-based Framework for Metadata Portals.</em>
   * Computers &amp; Geosciences 34 (12), 1947-1955.
   * <a href="http://dx.doi.org/10.1016/j.cageo.2008.02.023"
   * target="_blank">doi:10.1016/j.cageo.2008.02.023</a></blockquote>
   *
   * <p><em>A quote from this paper:</em> Because Apache Lucene is a full-text
   * search engine and not a conventional database, it cannot handle numerical ranges
   * (e.g., field value is inside user defined bounds, even dates are numerical values).
   * We have developed an extension to Apache Lucene that stores
   * the numerical values in a special string-encoded format with variable precision
   * (all numerical values like doubles, longs, floats, and ints are converted to
   * lexicographic sortable string representations and stored with different precisions
   * (for a more detailed description of how the values are stored,
   * see {@link NumericUtils}). A range is then divided recursively into multiple intervals for searching:
   * The center of the range is searched only with the lowest possible precision in the <em>trie</em>,
   * while the boundaries are matched more exactly. This reduces the number of terms dramatically.</p>
   *
   * <p>For the variant that stores long values in 8 different precisions (each reduced by 8 bits) that
   * uses a lowest precision of 1 byte, the index contains only a maximum of 256 distinct values in the
   * lowest precision. Overall, a range could consist of a theoretical maximum of
   * <code>7*255*2 + 255 = 3825</code> distinct terms (when there is a term for every distinct value of an
   * 8-byte-number in the index and the range covers almost all of them; a maximum of 255 distinct values is used
   * because it would always be possible to reduce the full 256 values to one term with degraded precision).
   * In practice, we have seen up to 300 terms in most cases (index with 500,000 metadata records
   * and a uniform value distribution).</p>
   *
   * <a name="precisionStepDesc"><h3>Precision Step</h3>
   * <p>You can choose any <code>precisionStep</code> when encoding values.
   * Lower step values mean more precisions and so more terms in index (and index gets larger).
   * On the other hand, the maximum number of terms to match reduces, which optimized query speed.
   * The formula to calculate the maximum term count is:
   * <pre>
   *  n = [ (bitsPerValue/precisionStep - 1) * (2^precisionStep - 1 ) * 2 ] + (2^precisionStep - 1 )
   * </pre>
   * <p><em>(this formula is only correct, when <code>bitsPerValue/precisionStep</code> is an integer;
   * in other cases, the value must be rounded up and the last summand must contain the modulo of the division as
   * precision step)</em>.
   * For longs stored using a precision step of 4, <code>n = 15*15*2 + 15 = 465</code>, and for a precision
   * step of 2, <code>n = 31*3*2 + 3 = 189</code>. But the faster search speed is reduced by more seeking
   * in the term enum of the index. Because of this, the ideal <code>precisionStep</code> value can only
   * be found out by testing. <b>Important:</b> You can index with a lower precision step value and test search speed
   * using a multiple of the original step value.</p>
   *
   * <p>Good values for <code>precisionStep</code> are depending on usage and data type:
   * <ul>
   *  <li>The default for all data types is <b>4</b>, which is used, when no <code>precisionStep</code> is given.
   *  <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>.
   *  <li>Ideal value in most cases for <em>32 bit</em> data types <em>(int, float)</em> is <b>4</b>.
   *  <li>For low cardinality fields larger precision steps are good. If the cardinality is &lt; 100, it is
   *  fair to use {@link Integer#MAX_VALUE} (see below).
   *  <li>Steps <b>&ge;64</b> for <em>long/double</em> and <b>&ge;32</b> for <em>int/float</em> produces one token
   *  per value in the index and querying is as slow as a conventional {@link TermRangeQuery}. But it can be used
   *  to produce fields, that are solely used for sorting (in this case simply use {@link Integer#MAX_VALUE} as
   *  <code>precisionStep</code>). Using {@link NumericField NumericFields} for sorting
   *  is ideal, because building the field cache is much faster than with text-only numbers.
   *  These fields have one term per value and therefore also work with term enumeration for building distinct lists
   *  (e.g. facets / preselected values to search for).
   *  Sorting is also possible with range query optimized fields using one of the above <code>precisionSteps</code>.
   * </ul>
   *
   * <p>Comparisons of the different types of RangeQueries on an index with about 500,000 docs showed
   * that {@link TermRangeQuery} in boolean rewrite mode (with raised {@link BooleanQuery} clause count)
   * took about 30-40 secs to complete, {@link TermRangeQuery} in constant score filter rewrite mode took 5 secs
   * and executing this class took &lt;100ms to complete (on an Opteron64 machine, Java 1.5, 8 bit
   * precision step). This query type was developed for a geographic portal, where the performance for
   * e.g. bounding boxes or exact date/time stamps is important.</p>
   *
   * @since 2.9
   **/
  public final class NumericRangeQuery<T extends Number> extends MultiTermQuery {
  
    private NumericRangeQuery(final String field, final int precisionStep, final DataType dataType,
      T min, T max, final boolean minInclusive, final boolean maxInclusive
    ) {
      if (precisionStep < 1)
        throw new IllegalArgumentException("precisionStep must be >=1");
      this.field = StringHelper.intern(field);
      this.precisionStep = precisionStep;
      this.dataType = dataType;
      this.min = min;
      this.max = max;
      this.minInclusive = minInclusive;
      this.maxInclusive = maxInclusive;
  
      // For bigger precisionSteps this query likely
      // hits too many terms, so set to CONSTANT_SCORE_FILTER right off
      // (especially as the FilteredTermEnum is costly if wasted only for AUTO tests because it
      // creates new enums from IndexReader for each sub-range)
      switch (dataType) {
        case LONG:
        case DOUBLE:
          setRewriteMethod( (precisionStep > 6) ?
            CONSTANT_SCORE_FILTER_REWRITE : 
            CONSTANT_SCORE_AUTO_REWRITE_DEFAULT
          );
          break;
        case INT:
        case FLOAT:
          setRewriteMethod( (precisionStep > 8) ?
            CONSTANT_SCORE_FILTER_REWRITE : 
            CONSTANT_SCORE_AUTO_REWRITE_DEFAULT
          );
          break;
        default:
          // should never happen
          throw new IllegalArgumentException("Invalid numeric DataType");
      }
      
      // shortcut if upper bound == lower bound
      if (min != null && min.equals(max)) {
        setRewriteMethod(CONSTANT_SCORE_BOOLEAN_QUERY_REWRITE);
      }
    }
    
    /**
     * Factory that creates a <code>NumericRangeQuery</code>, that queries a <code>long</code>
     * range using the given <a href="#precisionStepDesc"><code>precisionStep</code></a>.
     * You can have half-open ranges (which are in fact &lt;/&le; or &gt;/&ge; queries)
     * by setting the min or max value to <code>null</code>. By setting inclusive to false, it will
     * match all documents excluding the bounds, with inclusive on, the boundaries are hits, too.
     */
    public static NumericRangeQuery<Long> newLongRange(final String field, final int precisionStep,
      Long min, Long max, final boolean minInclusive, final boolean maxInclusive
    ) {
      return new NumericRangeQuery<Long>(field, precisionStep, DataType.LONG, min, max, minInclusive, maxInclusive);
    }
    
    /**
     * Factory that creates a <code>NumericRangeQuery</code>, that queries a <code>long</code>
     * range using the default <code>precisionStep</code> {@link NumericUtils#PRECISION_STEP_DEFAULT} (4).
     * You can have half-open ranges (which are in fact &lt;/&le; or &gt;/&ge; queries)
     * by setting the min or max value to <code>null</code>. By setting inclusive to false, it will
     * match all documents excluding the bounds, with inclusive on, the boundaries are hits, too.
     */
    public static NumericRangeQuery<Long> newLongRange(final String field,
      Long min, Long max, final boolean minInclusive, final boolean maxInclusive
    ) {
      return new NumericRangeQuery<Long>(field, NumericUtils.PRECISION_STEP_DEFAULT, DataType.LONG, min, max, minInclusive, maxInclusive);
    }
    
    /**
     * Factory that creates a <code>NumericRangeQuery</code>, that queries a <code>int</code>
     * range using the given <a href="#precisionStepDesc"><code>precisionStep</code></a>.
     * You can have half-open ranges (which are in fact &lt;/&le; or &gt;/&ge; queries)
     * by setting the min or max value to <code>null</code>. By setting inclusive to false, it will
     * match all documents excluding the bounds, with inclusive on, the boundaries are hits, too.
     */
    public static NumericRangeQuery<Integer> newIntRange(final String field, final int precisionStep,
      Integer min, Integer max, final boolean minInclusive, final boolean maxInclusive
    ) {
      return new NumericRangeQuery<Integer>(field, precisionStep, DataType.INT, min, max, minInclusive, maxInclusive);
    }
    
    /**
     * Factory that creates a <code>NumericRangeQuery</code>, that queries a <code>int</code>
     * range using the default <code>precisionStep</code> {@link NumericUtils#PRECISION_STEP_DEFAULT} (4).
     * You can have half-open ranges (which are in fact &lt;/&le; or &gt;/&ge; queries)
     * by setting the min or max value to <code>null</code>. By setting inclusive to false, it will
     * match all documents excluding the bounds, with inclusive on, the boundaries are hits, too.
     */
    public static NumericRangeQuery<Integer> newIntRange(final String field,
      Integer min, Integer max, final boolean minInclusive, final boolean maxInclusive
    ) {
      return new NumericRangeQuery<Integer>(field, NumericUtils.PRECISION_STEP_DEFAULT, DataType.INT, min, max, minInclusive, maxInclusive);
    }
    
    /**
     * Factory that creates a <code>NumericRangeQuery</code>, that queries a <code>double</code>
     * range using the given <a href="#precisionStepDesc"><code>precisionStep</code></a>.
     * You can have half-open ranges (which are in fact &lt;/&le; or &gt;/&ge; queries)
     * by setting the min or max value to <code>null</code>.
     * {@link Double#NaN} will never match a half-open range, to hit {@code NaN} use a query
     * with {@code min == max == Double.NaN}.  By setting inclusive to false, it will
     * match all documents excluding the bounds, with inclusive on, the boundaries are hits, too.
     */
    public static NumericRangeQuery<Double> newDoubleRange(final String field, final int precisionStep,
      Double min, Double max, final boolean minInclusive, final boolean maxInclusive
    ) {
      return new NumericRangeQuery<Double>(field, precisionStep, DataType.DOUBLE, min, max, minInclusive, maxInclusive);
    }
    
    /**
     * Factory that creates a <code>NumericRangeQuery</code>, that queries a <code>double</code>
     * range using the default <code>precisionStep</code> {@link NumericUtils#PRECISION_STEP_DEFAULT} (4).
     * You can have half-open ranges (which are in fact &lt;/&le; or &gt;/&ge; queries)
     * by setting the min or max value to <code>null</code>.
     * {@link Double#NaN} will never match a half-open range, to hit {@code NaN} use a query
     * with {@code min == max == Double.NaN}.  By setting inclusive to false, it will
     * match all documents excluding the bounds, with inclusive on, the boundaries are hits, too.
     */
    public static NumericRangeQuery<Double> newDoubleRange(final String field,
      Double min, Double max, final boolean minInclusive, final boolean maxInclusive
    ) {
      return new NumericRangeQuery<Double>(field, NumericUtils.PRECISION_STEP_DEFAULT, DataType.DOUBLE, min, max, minInclusive, maxInclusive);
    }
    
    /**
     * Factory that creates a <code>NumericRangeQuery</code>, that queries a <code>float</code>
     * range using the given <a href="#precisionStepDesc"><code>precisionStep</code></a>.
     * You can have half-open ranges (which are in fact &lt;/&le; or &gt;/&ge; queries)
     * by setting the min or max value to <code>null</code>.
     * {@link Float#NaN} will never match a half-open range, to hit {@code NaN} use a query
     * with {@code min == max == Float.NaN}.  By setting inclusive to false, it will
     * match all documents excluding the bounds, with inclusive on, the boundaries are hits, too.
     */
    public static NumericRangeQuery<Float> newFloatRange(final String field, final int precisionStep,
      Float min, Float max, final boolean minInclusive, final boolean maxInclusive
    ) {
      return new NumericRangeQuery<Float>(field, precisionStep, DataType.FLOAT, min, max, minInclusive, maxInclusive);
    }
    
    /**
     * Factory that creates a <code>NumericRangeQuery</code>, that queries a <code>float</code>
     * range using the default <code>precisionStep</code> {@link NumericUtils#PRECISION_STEP_DEFAULT} (4).
     * You can have half-open ranges (which are in fact &lt;/&le; or &gt;/&ge; queries)
     * by setting the min or max value to <code>null</code>.
     * {@link Float#NaN} will never match a half-open range, to hit {@code NaN} use a query
     * with {@code min == max == Float.NaN}.  By setting inclusive to false, it will
     * match all documents excluding the bounds, with inclusive on, the boundaries are hits, too.
     */
    public static NumericRangeQuery<Float> newFloatRange(final String field,
      Float min, Float max, final boolean minInclusive, final boolean maxInclusive
    ) {
      return new NumericRangeQuery<Float>(field, NumericUtils.PRECISION_STEP_DEFAULT, DataType.FLOAT, min, max, minInclusive, maxInclusive);
    }
    
    @Override
    protected FilteredTermEnum getEnum(final IndexReader reader) throws IOException {
      return new NumericRangeTermEnum(reader);
    }
  
    /** Returns the field name for this query */
    public String getField() { return field; }
  
    /** Returns <code>true</code> if the lower endpoint is inclusive */
    public boolean includesMin() { return minInclusive; }
    
    /** Returns <code>true</code> if the upper endpoint is inclusive */
    public boolean includesMax() { return maxInclusive; }
  
    /** Returns the lower value of this range query */
    public T getMin() { return min; }
  
    /** Returns the upper value of this range query */
    public T getMax() { return max; }
    
    /** Returns the precision step. */
    public int getPrecisionStep() { return precisionStep; }
    
    @Override
    public String toString(final String field) {
      final StringBuilder sb = new StringBuilder();
      if (!this.field.equals(field)) sb.append(this.field).append(':');
      return sb.append(minInclusive ? '[' : '{')
        .append((min == null) ? "*" : min.toString())
        .append(" TO ")
        .append((max == null) ? "*" : max.toString())
        .append(maxInclusive ? ']' : '}')
        .append(ToStringUtils.boost(getBoost()))
        .toString();
    }
  
    @Override
    public final boolean equals(final Object o) {
      if (o==this) return true;
      if (!super.equals(o))
        return false;
      if (o instanceof NumericRangeQuery) {
        final NumericRangeQuery q=(NumericRangeQuery)o;
        return (
          field==q.field &&
          (q.min == null ? min == null : q.min.equals(min)) &&
          (q.max == null ? max == null : q.max.equals(max)) &&
          minInclusive == q.minInclusive &&
          maxInclusive == q.maxInclusive &&
          precisionStep == q.precisionStep
        );
      }
      return false;
    }
  
    @Override
    public final int hashCode() {
      int hash = super.hashCode();
      hash += field.hashCode()^0x4565fd66 + precisionStep^0x64365465;
      if (min != null) hash += min.hashCode()^0x14fa55fb;
      if (max != null) hash += max.hashCode()^0x733fa5fe;
      return hash +
        (Boolean.valueOf(minInclusive).hashCode()^0x14fa55fb)+
        (Boolean.valueOf(maxInclusive).hashCode()^0x733fa5fe);
    }
    
    // field must be interned after reading from stream
    private void readObject(java.io.ObjectInputStream in) throws java.io.IOException, ClassNotFoundException {
      in.defaultReadObject();
      field = StringHelper.intern(field);
    }
  
    // members (package private, to be also fast accessible by NumericRangeTermEnum)
    String field;
    final int precisionStep;
    final DataType dataType;
    final T min, max;
    final boolean minInclusive,maxInclusive;
  
    // used to handle float/double infinity correcty
    static final long LONG_NEGATIVE_INFINITY =
      NumericUtils.doubleToSortableLong(Double.NEGATIVE_INFINITY);
    static final long LONG_POSITIVE_INFINITY =
      NumericUtils.doubleToSortableLong(Double.POSITIVE_INFINITY);
    static final int INT_NEGATIVE_INFINITY =
      NumericUtils.floatToSortableInt(Float.NEGATIVE_INFINITY);
    static final int INT_POSITIVE_INFINITY =
      NumericUtils.floatToSortableInt(Float.POSITIVE_INFINITY);
  
    /**
     * Subclass of FilteredTermEnum for enumerating all terms that match the
     * sub-ranges for trie range queries.
     * <p>
     * WARNING: This term enumeration is not guaranteed to be always ordered by
     * {@link Term#compareTo}.
     * The ordering depends on how {@link NumericUtils#splitLongRange} and
     * {@link NumericUtils#splitIntRange} generates the sub-ranges. For
     * {@link MultiTermQuery} ordering is not relevant.
     */
    private final class NumericRangeTermEnum extends FilteredTermEnum {
  
      private final IndexReader reader;
      private final LinkedList<String> rangeBounds = new LinkedList<String>();
      private final Term termTemplate = new Term(field);
      private String currentUpperBound = null;
  
      NumericRangeTermEnum(final IndexReader reader) throws IOException {
        this.reader = reader;
        
        switch (dataType) {
          case LONG:
          case DOUBLE: {
            // lower
            long minBound;
            if (dataType == DataType.LONG) {
              minBound = (min == null) ? Long.MIN_VALUE : min.longValue();
            } else {
              assert dataType == DataType.DOUBLE;
              minBound = (min == null) ? LONG_NEGATIVE_INFINITY
                : NumericUtils.doubleToSortableLong(min.doubleValue());
            }
            if (!minInclusive && min != null) {
              if (minBound == Long.MAX_VALUE) break;
              minBound++;
            }
            
            // upper
            long maxBound;
            if (dataType == DataType.LONG) {
              maxBound = (max == null) ? Long.MAX_VALUE : max.longValue();
            } else {
              assert dataType == DataType.DOUBLE;
              maxBound = (max == null) ? LONG_POSITIVE_INFINITY
                : NumericUtils.doubleToSortableLong(max.doubleValue());
            }
            if (!maxInclusive && max != null) {
              if (maxBound == Long.MIN_VALUE) break;
              maxBound--;
            }
            
            NumericUtils.splitLongRange(new NumericUtils.LongRangeBuilder() {
              @Override
              public final void addRange(String minPrefixCoded, String maxPrefixCoded) {
                rangeBounds.add(minPrefixCoded);
                rangeBounds.add(maxPrefixCoded);
              }
            }, precisionStep, minBound, maxBound);
            break;
          }
            
          case INT:
          case FLOAT: {
            // lower
            int minBound;
            if (dataType == DataType.INT) {
              minBound = (min == null) ? Integer.MIN_VALUE : min.intValue();
            } else {
              assert dataType == DataType.FLOAT;
              minBound = (min == null) ? INT_NEGATIVE_INFINITY
                : NumericUtils.floatToSortableInt(min.floatValue());
            }
            if (!minInclusive && min != null) {
              if (minBound == Integer.MAX_VALUE) break;
              minBound++;
            }
            
            // upper
            int maxBound;
            if (dataType == DataType.INT) {
              maxBound = (max == null) ? Integer.MAX_VALUE : max.intValue();
            } else {
              assert dataType == DataType.FLOAT;
              maxBound = (max == null) ? INT_POSITIVE_INFINITY
                : NumericUtils.floatToSortableInt(max.floatValue());
            }
            if (!maxInclusive && max != null) {
              if (maxBound == Integer.MIN_VALUE) break;
              maxBound--;
            }
            
            NumericUtils.splitIntRange(new NumericUtils.IntRangeBuilder() {
              @Override
              public final void addRange(String minPrefixCoded, String maxPrefixCoded) {
                rangeBounds.add(minPrefixCoded);
                rangeBounds.add(maxPrefixCoded);
              }
            }, precisionStep, minBound, maxBound);
            break;
          }
            
          default:
            // should never happen
            throw new IllegalArgumentException("Invalid numeric DataType");
        }
        
        // seek to first term
        next();
      }
  
      @Override
      public float difference() {
        return 1.0f;
      }
      
      /** this is a dummy, it is not used by this class. */
      @Override
      protected boolean endEnum() {
        throw new UnsupportedOperationException("not implemented");
      }
  
      /** this is a dummy, it is not used by this class. */
      @Override
      protected void setEnum(TermEnum tenum) {
        throw new UnsupportedOperationException("not implemented");
      }
      
      /**
       * Compares if current upper bound is reached.
       * In contrast to {@link FilteredTermEnum}, a return value
       * of <code>false</code> ends iterating the current enum
       * and forwards to the next sub-range.
       */
      @Override
      protected boolean termCompare(Term term) {
        return (term.field() == field && term.text().compareTo(currentUpperBound) <= 0);
      }
      
      /** Increments the enumeration to the next element.  True if one exists. */
      @Override
      public boolean next() throws IOException {
        // if a current term exists, the actual enum is initialized:
        // try change to next term, if no such term exists, fall-through
        if (currentTerm != null) {
          assert actualEnum != null;
          if (actualEnum.next()) {
            currentTerm = actualEnum.term();
            if (termCompare(currentTerm))
              return true;
          }
        }
        
        // if all above fails, we go forward to the next enum,
        // if one is available
        currentTerm = null;
        while (rangeBounds.size() >= 2) {
          assert rangeBounds.size() % 2 == 0;
          // close the current enum and read next bounds
          if (actualEnum != null) {
            actualEnum.close();
            actualEnum = null;
          }
          final String lowerBound = rangeBounds.removeFirst();
          this.currentUpperBound = rangeBounds.removeFirst();
          // create a new enum
          actualEnum = reader.terms(termTemplate.createTerm(lowerBound));
          currentTerm = actualEnum.term();
          if (currentTerm != null && termCompare(currentTerm))
            return true;
          // clear the current term for next iteration
          currentTerm = null;
        }
        
        // no more sub-range enums available
        assert rangeBounds.size() == 0 && currentTerm == null;
        return false;
      }
  
      /** Closes the enumeration to further activity, freeing resources.  */
      @Override
      public void close() throws IOException {
        rangeBounds.clear();
        currentUpperBound = null;
        super.close();
      }
  
    }
    
  }
  