package org.apache.blur.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 org.apache.lucene.index.AtomicReaderContext; import org.apache.lucene.search.ScoreDoc; import org.apache.lucene.search.Scorer; import org.apache.lucene.search.TopDocs; import org.apache.lucene.search.TopDocsCollector; import org.apache.lucene.util.PriorityQueue; /** * The {@link PagingCollector} allows for paging through lucene hits. */ public class PagingCollector extends TopDocsCollector<ScoreDoc> { private ScoreDoc pqTop; private int docBase; private Scorer scorer; private ScoreDoc previousPassLowest; private int numHits; public PagingCollector(int numHits) { // creates an empty score doc so that i don't have to check for null // each time. this(numHits, new ScoreDoc(-1, Float.MAX_VALUE)); } public PagingCollector(int numHits, ScoreDoc previousPassLowest) { super(new HitQueue(numHits, true)); this.pqTop = pq.top(); this.numHits = numHits; this.previousPassLowest = previousPassLowest; } @Override public boolean acceptsDocsOutOfOrder() { return true; } @Override public void collect(int doc) throws IOException { float score = scorer.score(); totalHits++; doc += docBase; if (score > previousPassLowest.score) { // this hit was gathered on a previous page. return; } else if (score == previousPassLowest.score && doc <= previousPassLowest.doc) { // if the scores are the same and the doc is less than or equal to the // previous pass lowest hit doc then skip because this collector favors // lower number documents. return; } else if (score < pqTop.score || (score == pqTop.score && doc > pqTop.doc)) { return; } pqTop.doc = doc; pqTop.score = score; pqTop = pq.updateTop(); } @Override public void setNextReader(AtomicReaderContext context) throws IOException { this.docBase = context.docBase; } @Override public void setScorer(Scorer scorer) throws IOException { this.scorer = scorer; } public ScoreDoc getLastScoreDoc(TopDocs topDocs) { return topDocs.scoreDocs[(totalHits < numHits ? totalHits : numHits) - 1]; } public ScoreDoc getLastScoreDoc(ScoreDoc[] scoreDocs) { return scoreDocs[(totalHits < numHits ? totalHits : numHits) - 1]; } public static class HitQueue extends PriorityQueue<ScoreDoc> { HitQueue(int size, boolean prePopulate) { super(size, prePopulate); } @Override protected ScoreDoc getSentinelObject() { return new ScoreDoc(Integer.MAX_VALUE, Float.NEGATIVE_INFINITY); } @Override protected final boolean lessThan(ScoreDoc hitA, ScoreDoc hitB) { if (hitA.score == hitB.score) return hitA.doc > hitB.doc; else return hitA.score < hitB.score; } } }