1 package org.apache.lucene.search;
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
10 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 import java.io.IOException;
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
27 * A range filter built on top of a cached single term field (in {@link FieldCache}).
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.
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}.
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).
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.
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.
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}.
55 public abstract class FieldCacheRangeFilter<T> extends Filter {
57 final FieldCache.Parser parser;
60 final boolean includeLower;
61 final boolean includeUpper;
63 private FieldCacheRangeFilter(String field, FieldCache.Parser parser, T lowerVal, T upperVal, boolean includeLower, boolean includeUpper) {
66 this.lowerVal = lowerVal;
67 this.upperVal = upperVal;
68 this.includeLower = includeLower;
69 this.includeUpper = includeUpper;
72 /** This method is implemented for each data type */
74 public abstract DocIdSet getDocIdSet(IndexReader reader) throws IOException;
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>.
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) {
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);
89 final int inclusiveLowerPoint, inclusiveUpperPoint;
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;
103 inclusiveLowerPoint = Math.max(1, -lowerPoint - 1);
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;
114 inclusiveUpperPoint = -upperPoint - 2;
117 if (inclusiveUpperPoint <= 0 || inclusiveLowerPoint > inclusiveUpperPoint)
118 return DocIdSet.EMPTY_DOCIDSET;
120 assert inclusiveLowerPoint > 0 && inclusiveUpperPoint > 0;
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) {
126 final boolean matchDoc(int doc) {
127 return fcsi.order[doc] >= inclusiveLowerPoint && fcsi.order[doc] <= inclusiveUpperPoint;
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>.
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);
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>.
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) {
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));
159 inclusiveLowerPoint = Byte.MIN_VALUE;
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));
167 inclusiveUpperPoint = Byte.MAX_VALUE;
170 if (inclusiveLowerPoint > inclusiveUpperPoint)
171 return DocIdSet.EMPTY_DOCIDSET;
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)) {
177 boolean matchDoc(int doc) {
178 return values[doc] >= inclusiveLowerPoint && values[doc] <= inclusiveUpperPoint;
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>.
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);
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>.
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) {
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));
210 inclusiveLowerPoint = Short.MIN_VALUE;
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));
218 inclusiveUpperPoint = Short.MAX_VALUE;
221 if (inclusiveLowerPoint > inclusiveUpperPoint)
222 return DocIdSet.EMPTY_DOCIDSET;
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)) {
228 boolean matchDoc(int doc) {
229 return values[doc] >= inclusiveLowerPoint && values[doc] <= inclusiveUpperPoint;
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>.
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);
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>.
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) {
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);
261 inclusiveLowerPoint = Integer.MIN_VALUE;
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);
269 inclusiveUpperPoint = Integer.MAX_VALUE;
272 if (inclusiveLowerPoint > inclusiveUpperPoint)
273 return DocIdSet.EMPTY_DOCIDSET;
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)) {
279 boolean matchDoc(int doc) {
280 return values[doc] >= inclusiveLowerPoint && values[doc] <= inclusiveUpperPoint;
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>.
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);
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>.
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) {
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);
312 inclusiveLowerPoint = Long.MIN_VALUE;
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);
320 inclusiveUpperPoint = Long.MAX_VALUE;
323 if (inclusiveLowerPoint > inclusiveUpperPoint)
324 return DocIdSet.EMPTY_DOCIDSET;
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)) {
330 boolean matchDoc(int doc) {
331 return values[doc] >= inclusiveLowerPoint && values[doc] <= inclusiveUpperPoint;
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>.
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);
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>.
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) {
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) );
366 inclusiveLowerPoint = Float.NEGATIVE_INFINITY;
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) );
375 inclusiveUpperPoint = Float.POSITIVE_INFINITY;
378 if (inclusiveLowerPoint > inclusiveUpperPoint)
379 return DocIdSet.EMPTY_DOCIDSET;
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)) {
385 boolean matchDoc(int doc) {
386 return values[doc] >= inclusiveLowerPoint && values[doc] <= inclusiveUpperPoint;
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>.
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);
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>.
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) {
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) );
421 inclusiveLowerPoint = Double.NEGATIVE_INFINITY;
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) );
430 inclusiveUpperPoint = Double.POSITIVE_INFINITY;
433 if (inclusiveLowerPoint > inclusiveUpperPoint)
434 return DocIdSet.EMPTY_DOCIDSET;
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)) {
440 boolean matchDoc(int doc) {
441 return values[doc] >= inclusiveLowerPoint && values[doc] <= inclusiveUpperPoint;
449 public final String toString() {
450 final StringBuilder sb = new StringBuilder(field).append(":");
451 return sb.append(includeLower ? '[' : '{')
452 .append((lowerVal == null) ? "*" : lowerVal.toString())
454 .append((upperVal == null) ? "*" : upperVal.toString())
455 .append(includeUpper ? ']' : '}')
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;
465 if (!this.field.equals(other.field)
466 || this.includeLower != other.includeLower
467 || this.includeUpper != other.includeUpper
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;
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);
486 /** Returns the field name for this filter */
487 public String getField() { return field; }
489 /** Returns <code>true</code> if the lower endpoint is inclusive */
490 public boolean includesLower() { return includeLower; }
492 /** Returns <code>true</code> if the upper endpoint is inclusive */
493 public boolean includesUpper() { return includeUpper; }
495 /** Returns the lower value of this range filter */
496 public T getLowerVal() { return lowerVal; }
498 /** Returns the upper value of this range filter */
499 public T getUpperVal() { return upperVal; }
501 /** Returns the current numeric parser ({@code null} for {@code T} is {@code String}} */
502 public FieldCache.Parser getParser() { return parser; }
504 static abstract class FieldCacheDocIdSet extends DocIdSet {
505 private final IndexReader reader;
506 private boolean mayUseTermDocs;
508 FieldCacheDocIdSet(IndexReader reader, boolean mayUseTermDocs) {
509 this.reader = reader;
510 this.mayUseTermDocs = mayUseTermDocs;
513 /** this method checks, if a doc is a hit, should throw AIOBE, when position invalid */
514 abstract boolean matchDoc(int doc) throws ArrayIndexOutOfBoundsException;
516 /** this DocIdSet is cacheable, if it works solely with FieldCache and no TermDocs */
518 public boolean isCacheable() {
519 return !(mayUseTermDocs && reader.hasDeletions());
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);
532 if (termDocs != null) {
533 // a DocIdSetIterator using TermDocs to iterate valid docIds
534 return new DocIdSetIterator() {
535 private int doc = -1;
543 public int nextDoc() throws IOException {
545 if (!termDocs.next())
546 return doc = NO_MORE_DOCS;
547 } while (!matchDoc(doc = termDocs.doc()));
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;
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;
574 public int nextDoc() {
578 } while (!matchDoc(doc));
580 } catch (ArrayIndexOutOfBoundsException e) {
581 return doc = NO_MORE_DOCS;
586 public int advance(int target) {
589 while (!matchDoc(doc)) {
593 } catch (ArrayIndexOutOfBoundsException e) {
594 return doc = NO_MORE_DOCS;