blob: 705ba00ab1d67afdb93ba51e72f612b877eaad28 [file] [log] [blame]
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.Iterator;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
/**
* The {@link IterablePaging} class allows for easy paging through lucene hits.
* @author Aaron McCurry
*/
public class IterablePaging implements Iterable<ScoreDoc> {
private static int DEFAULT_NUMBER_OF_HITS_TO_COLLECT = 1000;
private Searcher searcher;
private Query query;
private TotalHitsRef totalHitsRef = new TotalHitsRef();
private ProgressRef progressRef = new ProgressRef();
private int skipTo;
private int numHitsToCollect = DEFAULT_NUMBER_OF_HITS_TO_COLLECT;
private int gather = -1;
public IterablePaging(Searcher searcher, Query query) throws IOException {
this(searcher,query,DEFAULT_NUMBER_OF_HITS_TO_COLLECT,null,null);
}
public IterablePaging(Searcher searcher, Query query, int numHitsToCollect) throws IOException {
this(searcher,query,numHitsToCollect,null,null);
}
public IterablePaging(Searcher searcher, Query query, int numHitsToCollect, TotalHitsRef totalHitsRef, ProgressRef progressRef) throws IOException {
this.query = searcher.rewrite(query);
this.searcher = searcher;
this.numHitsToCollect = numHitsToCollect;
this.totalHitsRef = totalHitsRef == null ? this.totalHitsRef : totalHitsRef;
this.progressRef = progressRef == null ? this.progressRef : progressRef;
}
public static class TotalHitsRef {
//This is an atomic integer because more than likely if there is
//any status sent to the user, it will be done in another thread.
protected AtomicInteger totalHits = new AtomicInteger(0);
public int totalHits() {
return totalHits.get();
}
}
public static class ProgressRef {
//These are atomic integers because more than likely if there is
//any status sent to the user, it will be done in another thread.
protected AtomicInteger skipTo = new AtomicInteger(0);
protected AtomicInteger currentHitPosition = new AtomicInteger(0);
protected AtomicInteger searchesPerformed = new AtomicInteger(0);
protected AtomicLong queryTime = new AtomicLong(0);
public int skipTo() {
return skipTo.get();
}
public int currentHitPosition() {
return currentHitPosition.get();
}
public int searchesPerformed() {
return searchesPerformed.get();
}
public long queryTime() {
return queryTime.get();
}
}
/**
* Gets the total hits of the search.
* @return the total hits.
*/
public int getTotalHits() {
return totalHitsRef.totalHits();
}
/**
* Allows for gathering of the total hits of this search.
* @param ref {@link TotalHitsRef}.
* @return this.
*/
public IterablePaging totalHits(TotalHitsRef ref) {
totalHitsRef = ref;
return this;
}
/**
* Skips the first x number of hits.
* @param skipTo the number hits to skip.
* @return this.
*/
public IterablePaging skipTo(int skipTo) {
this.skipTo = skipTo;
return this;
}
/**
* Only gather up to x number of hits.
* @param gather the number of hits to gather.
* @return this.
*/
public IterablePaging gather(int gather) {
this.gather = gather;
return this;
}
/**
* Allows for gathering the progress of the paging.
* @param ref the {@link ProgressRef}.
* @return this.
*/
public IterablePaging progress(ProgressRef ref) {
this.progressRef = ref;
return this;
}
/**
* The {@link ScoreDoc} iterator.
*/
@Override
public Iterator<ScoreDoc> iterator() {
return skipHits(new PagingIterator());
}
class PagingIterator implements Iterator<ScoreDoc> {
private PagingCollector collector;
private ScoreDoc[] scoreDocs;
private int counter = 0;
private int offset = 0;
private int endPosition = gather == -1 ? Integer.MAX_VALUE : skipTo + gather;
PagingIterator() {
search();
}
void search() {
long s = System.currentTimeMillis();
progressRef.searchesPerformed.incrementAndGet();
if (collector == null) {
collector = new PagingCollector(numHitsToCollect);
} else {
collector = new PagingCollector(numHitsToCollect,scoreDocs[scoreDocs.length - 1]);
}
try {
searcher.search(query, collector);
} catch (IOException e) {
throw new RuntimeException(e);
}
totalHitsRef.totalHits.set(collector.getTotalHits());
scoreDocs = collector.topDocs().scoreDocs;
long e = System.currentTimeMillis();
progressRef.queryTime.addAndGet(e-s);
}
@Override
public boolean hasNext() {
return counter < totalHitsRef.totalHits() && counter < endPosition ? true : false;
}
@Override
public ScoreDoc next() {
if (isCurrentCollectorExhausted()) {
search();
offset = 0;
}
progressRef.currentHitPosition.set(counter);
counter++;
return scoreDocs[offset++];
}
private boolean isCurrentCollectorExhausted() {
return offset < scoreDocs.length ? false : true;
}
@Override
public void remove() {
throw new RuntimeException("read only");
}
}
private Iterator<ScoreDoc> skipHits(Iterator<ScoreDoc> iterator) {
progressRef.skipTo.set(skipTo);
for (int i = 0; i < skipTo && iterator.hasNext(); i++) {
//eats the hits, and moves the iterator to the desired skip to position.
progressRef.currentHitPosition.set(i);
iterator.next();
}
return iterator;
}
public static void setDefaultNumberOfHitsToCollect(int num) {
DEFAULT_NUMBER_OF_HITS_TO_COLLECT = num;
}
}