blob: 6dae17b90349b82350b1e2c890f858b95549ae69 [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 org.apache.lucene.util.PriorityQueue;
/**
* Expert: A hit queue for sorting by hits by terms in more than one field.
*
* @lucene.experimental
* @since 2.9
* @see IndexSearcher#search(Query,Filter,int,Sort)
*/
public abstract class FieldValueHitQueue<T extends FieldValueHitQueue.Entry> extends PriorityQueue<T> {
/**
* Extension of ScoreDoc to also store the
* {@link FieldComparator} slot.
*/
public static class Entry extends ScoreDoc {
public int slot;
public Entry(int slot, int doc, float score) {
super(doc, score);
this.slot = slot;
}
@Override
public String toString() {
return "slot:" + slot + " " + super.toString();
}
}
/**
* An implementation of {@link FieldValueHitQueue} which is optimized in case
* there is just one comparator.
*/
private static final class OneComparatorFieldValueHitQueue<T extends FieldValueHitQueue.Entry> extends FieldValueHitQueue<T> {
private final int oneReverseMul;
public OneComparatorFieldValueHitQueue(SortField[] fields, int size)
throws IOException {
super(fields, size);
SortField field = fields[0];
setComparator(0,field.getComparator(size, 0));
oneReverseMul = field.reverse ? -1 : 1;
reverseMul[0] = oneReverseMul;
}
/**
* Returns whether <code>hitA</code> is less relevant than <code>hitB</code>.
* @param hitA Entry
* @param hitB Entry
* @return <code>true</code> if document <code>hitA</code> should be sorted after document <code>hitB</code>.
*/
@Override
protected boolean lessThan(final Entry hitA, final Entry hitB) {
assert hitA != hitB;
assert hitA.slot != hitB.slot;
final int c = oneReverseMul * firstComparator.compare(hitA.slot, hitB.slot);
if (c != 0) {
return c > 0;
}
// avoid random sort order that could lead to duplicates (bug #31241):
return hitA.doc > hitB.doc;
}
}
/**
* An implementation of {@link FieldValueHitQueue} which is optimized in case
* there is more than one comparator.
*/
private static final class MultiComparatorsFieldValueHitQueue<T extends FieldValueHitQueue.Entry> extends FieldValueHitQueue<T> {
public MultiComparatorsFieldValueHitQueue(SortField[] fields, int size)
throws IOException {
super(fields, size);
int numComparators = comparators.length;
for (int i = 0; i < numComparators; ++i) {
SortField field = fields[i];
reverseMul[i] = field.reverse ? -1 : 1;
setComparator(i, field.getComparator(size, i));
}
}
@Override
protected boolean lessThan(final Entry hitA, final Entry hitB) {
assert hitA != hitB;
assert hitA.slot != hitB.slot;
int numComparators = comparators.length;
for (int i = 0; i < numComparators; ++i) {
final int c = reverseMul[i] * comparators[i].compare(hitA.slot, hitB.slot);
if (c != 0) {
// Short circuit
return c > 0;
}
}
// avoid random sort order that could lead to duplicates (bug #31241):
return hitA.doc > hitB.doc;
}
}
// prevent instantiation and extension.
@SuppressWarnings({"rawtypes","unchecked"})
private FieldValueHitQueue(SortField[] fields, int size) {
super(size);
// When we get here, fields.length is guaranteed to be > 0, therefore no
// need to check it again.
// All these are required by this class's API - need to return arrays.
// Therefore even in the case of a single comparator, create an array
// anyway.
this.fields = fields;
int numComparators = fields.length;
comparators = new FieldComparator[numComparators];
reverseMul = new int[numComparators];
}
/**
* Creates a hit queue sorted by the given list of fields.
*
* <p><b>NOTE</b>: The instances returned by this method
* pre-allocate a full array of length <code>numHits</code>.
*
* @param fields
* SortField array we are sorting by in priority order (highest
* priority first); cannot be <code>null</code> or empty
* @param size
* The number of hits to retain. Must be greater than zero.
* @throws IOException if there is a low-level IO error
*/
public static <T extends FieldValueHitQueue.Entry> FieldValueHitQueue<T> create(SortField[] fields, int size) throws IOException {
if (fields.length == 0) {
throw new IllegalArgumentException("Sort must contain at least one field");
}
if (fields.length == 1) {
return new OneComparatorFieldValueHitQueue<>(fields, size);
} else {
return new MultiComparatorsFieldValueHitQueue<>(fields, size);
}
}
public FieldComparator<?>[] getComparators() {
return comparators;
}
public int[] getReverseMul() {
return reverseMul;
}
public void setComparator(int pos, FieldComparator<?> comparator) {
if (pos==0) firstComparator = comparator;
comparators[pos] = comparator;
}
/** Stores the sort criteria being used. */
protected final SortField[] fields;
protected final FieldComparator<?>[] comparators; // use setComparator to change this array
protected FieldComparator<?> firstComparator; // this must always be equal to comparators[0]
protected final int[] reverseMul;
@Override
protected abstract boolean lessThan (final Entry a, final Entry b);
/**
* Given a queue Entry, creates a corresponding FieldDoc
* that contains the values used to sort the given document.
* These values are not the raw values out of the index, but the internal
* representation of them. This is so the given search hit can be collated by
* a MultiSearcher with other search hits.
*
* @param entry The Entry used to create a FieldDoc
* @return The newly created FieldDoc
* @see IndexSearcher#search(Query,Filter,int,Sort)
*/
FieldDoc fillFields(final Entry entry) {
final int n = comparators.length;
final Object[] fields = new Object[n];
for (int i = 0; i < n; ++i) {
fields[i] = comparators[i].value(entry.slot);
}
//if (maxscore > 1.0f) doc.score /= maxscore; // normalize scores
return new FieldDoc(entry.doc, entry.score, fields);
}
/** Returns the SortFields being used by this hit queue. */
SortField[] getFields() {
return fields;
}
}