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 org.apache.lucene.index.IndexReader;
import org.apache.lucene.util.PriorityQueue;
import java.io.IOException;
import java.text.Collator;
import java.util.Locale;
/**
* Expert: A hit queue for sorting by hits by terms in more than one field.
* Uses <code>FieldCache.DEFAULT</code> for maintaining internal term lookup tables.
*
* <p>Created: Dec 8, 2003 12:56:03 PM
*
* @since lucene 1.4
* @version $Id: FieldSortedHitQueue.java 811070 2009-09-03 18:31:41Z hossman $
* @see Searcher#search(Query,Filter,int,Sort)
* @see FieldCache
* @deprecated see {@link FieldValueHitQueue}
*/
public class FieldSortedHitQueue
extends PriorityQueue {
/**
* Creates a hit queue sorted by the given list of fields.
* @param reader Index to use.
* @param fields Fieldable names, 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
*/
public FieldSortedHitQueue (IndexReader reader, SortField[] fields, int size)
throws IOException {
final int n = fields.length;
comparators = new ScoreDocComparator[n];
this.fields = new SortField[n];
for (int i=0; i<n; ++i) {
String fieldname = fields[i].getField();
comparators[i] = getCachedComparator (reader, fieldname, fields[i].getType(), fields[i].getParser(), fields[i].getLocale(), fields[i].getFactory());
// new SortField instances must only be created when auto-detection is in use
if (fields[i].getType() == SortField.AUTO) {
if (comparators[i].sortType() == SortField.STRING) {
this.fields[i] = new SortField (fieldname, fields[i].getLocale(), fields[i].getReverse());
} else {
this.fields[i] = new SortField (fieldname, comparators[i].sortType(), fields[i].getReverse());
}
} else {
assert comparators[i].sortType() == fields[i].getType();
this.fields[i] = fields[i];
}
}
initialize (size);
}
/** Stores a comparator corresponding to each field being sorted by */
protected ScoreDocComparator[] comparators;
/** Stores the sort criteria being used. */
protected SortField[] fields;
/** Stores the maximum score value encountered, needed for normalizing. */
protected float maxscore = Float.NEGATIVE_INFINITY;
/** returns the maximum score encountered by elements inserted via insert()
*/
public float getMaxScore() {
return maxscore;
}
// Update maxscore.
private final void updateMaxScore(FieldDoc fdoc) {
maxscore = Math.max(maxscore, fdoc.score);
}
// The signature of this method takes a FieldDoc in order to avoid
// the unneeded cast to retrieve the score.
// inherit javadoc
public boolean insert(FieldDoc fdoc) {
updateMaxScore(fdoc);
return super.insert(fdoc);
}
// This overrides PriorityQueue.insert() so that insert(FieldDoc) that
// keeps track of the score isn't accidentally bypassed.
// inherit javadoc
public boolean insert(Object fdoc) {
return insert((FieldDoc)fdoc);
}
// This overrides PriorityQueue.insertWithOverflow() so that
// updateMaxScore(FieldDoc) that keeps track of the score isn't accidentally
// bypassed.
public Object insertWithOverflow(Object element) {
updateMaxScore((FieldDoc) element);
return super.insertWithOverflow(element);
}
/**
* Returns whether <code>a</code> is less relevant than <code>b</code>.
* @param a ScoreDoc
* @param b ScoreDoc
* @return <code>true</code> if document <code>a</code> should be sorted after document <code>b</code>.
*/
protected boolean lessThan (final Object a, final Object b) {
final ScoreDoc docA = (ScoreDoc) a;
final ScoreDoc docB = (ScoreDoc) b;
// run comparators
final int n = comparators.length;
int c = 0;
for (int i=0; i<n && c==0; ++i) {
c = (fields[i].reverse) ? comparators[i].compare (docB, docA)
: comparators[i].compare (docA, docB);
}
// avoid random sort order that could lead to duplicates (bug #31241):
if (c == 0)
return docA.doc > docB.doc;
return c > 0;
}
/**
* Given a FieldDoc object, stores 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 doc The FieldDoc to store sort values into.
* @return The same FieldDoc passed in.
* @see Searchable#search(Weight,Filter,int,Sort)
*/
FieldDoc fillFields (final FieldDoc doc) {
final int n = comparators.length;
final Comparable[] fields = new Comparable[n];
for (int i=0; i<n; ++i)
fields[i] = comparators[i].sortValue(doc);
doc.fields = fields;
//if (maxscore > 1.0f) doc.score /= maxscore; // normalize scores
return doc;
}
/** Returns the SortFields being used by this hit queue. */
SortField[] getFields() {
return fields;
}
static ScoreDocComparator getCachedComparator (IndexReader reader, String field, int type, FieldCache.Parser parser, Locale locale, SortComparatorSource factory)
throws IOException {
if (type == SortField.DOC) return ScoreDocComparator.INDEXORDER;
if (type == SortField.SCORE) return ScoreDocComparator.RELEVANCE;
FieldCacheImpl.Entry entry = (factory != null)
? new FieldCacheImpl.Entry (field, factory)
: ( (parser != null)
? new FieldCacheImpl.Entry (field, type, parser)
: new FieldCacheImpl.Entry (field, type, locale)
);
return (ScoreDocComparator)Comparators.get(reader, entry);
}
/** Internal cache of comparators. Similar to FieldCache, only
* caches comparators instead of term values. */
static final FieldCacheImpl.Cache Comparators = new FieldCacheImpl.Cache() {
protected Object createValue(IndexReader reader, FieldCacheImpl.Entry entryKey)
throws IOException {
FieldCacheImpl.Entry entry = (FieldCacheImpl.Entry) entryKey;
String fieldname = entry.field;
int type = entry.type;
Locale locale = entry.locale;
FieldCache.Parser parser = null;
SortComparatorSource factory = null;
if (entry.custom instanceof SortComparatorSource) {
factory = (SortComparatorSource) entry.custom;
} else {
parser = (FieldCache.Parser) entry.custom;
}
ScoreDocComparator comparator;
switch (type) {
case SortField.AUTO:
comparator = comparatorAuto (reader, fieldname);
break;
case SortField.INT:
comparator = comparatorInt (reader, fieldname, (FieldCache.IntParser)parser);
break;
case SortField.FLOAT:
comparator = comparatorFloat (reader, fieldname, (FieldCache.FloatParser)parser);
break;
case SortField.LONG:
comparator = comparatorLong(reader, fieldname, (FieldCache.LongParser)parser);
break;
case SortField.DOUBLE:
comparator = comparatorDouble(reader, fieldname, (FieldCache.DoubleParser)parser);
break;
case SortField.SHORT:
comparator = comparatorShort(reader, fieldname, (FieldCache.ShortParser)parser);
break;
case SortField.BYTE:
comparator = comparatorByte(reader, fieldname, (FieldCache.ByteParser)parser);
break;
case SortField.STRING:
if (locale != null) comparator = comparatorStringLocale (reader, fieldname, locale);
else comparator = comparatorString (reader, fieldname);
break;
case SortField.CUSTOM:
comparator = factory.newComparator (reader, fieldname);
break;
default:
throw new RuntimeException ("unknown field type: "+type);
}
return comparator;
}
};
/**
* Returns a comparator for sorting hits according to a field containing bytes.
* @param reader Index to use.
* @param fieldname Fieldable containing integer values.
* @return Comparator for sorting hits.
* @throws IOException If an error occurs reading the index.
*/
static ScoreDocComparator comparatorByte(final IndexReader reader, final String fieldname, final FieldCache.ByteParser parser)
throws IOException {
final String field = fieldname.intern();
final byte[] fieldOrder = FieldCache.DEFAULT.getBytes(reader, field, parser);
return new ScoreDocComparator() {
public final int compare (final ScoreDoc i, final ScoreDoc j) {
final int fi = fieldOrder[i.doc];
final int fj = fieldOrder[j.doc];
if (fi < fj) return -1;
if (fi > fj) return 1;
return 0;
}
public Comparable sortValue (final ScoreDoc i) {
return new Byte(fieldOrder[i.doc]);
}
public int sortType() {
return SortField.BYTE;
}
};
}
/**
* Returns a comparator for sorting hits according to a field containing shorts.
* @param reader Index to use.
* @param fieldname Fieldable containing integer values.
* @return Comparator for sorting hits.
* @throws IOException If an error occurs reading the index.
*/
static ScoreDocComparator comparatorShort(final IndexReader reader, final String fieldname, final FieldCache.ShortParser parser)
throws IOException {
final String field = fieldname.intern();
final short[] fieldOrder = FieldCache.DEFAULT.getShorts(reader, field, parser);
return new ScoreDocComparator() {
public final int compare (final ScoreDoc i, final ScoreDoc j) {
final int fi = fieldOrder[i.doc];
final int fj = fieldOrder[j.doc];
if (fi < fj) return -1;
if (fi > fj) return 1;
return 0;
}
public Comparable sortValue (final ScoreDoc i) {
return new Short(fieldOrder[i.doc]);
}
public int sortType() {
return SortField.SHORT;
}
};
}
/**
* Returns a comparator for sorting hits according to a field containing integers.
* @param reader Index to use.
* @param fieldname Fieldable containing integer values.
* @return Comparator for sorting hits.
* @throws IOException If an error occurs reading the index.
*/
static ScoreDocComparator comparatorInt (final IndexReader reader, final String fieldname, final FieldCache.IntParser parser)
throws IOException {
final String field = fieldname.intern();
final int[] fieldOrder = FieldCache.DEFAULT.getInts(reader, field, parser);
return new ScoreDocComparator() {
public final int compare (final ScoreDoc i, final ScoreDoc j) {
final int fi = fieldOrder[i.doc];
final int fj = fieldOrder[j.doc];
if (fi < fj) return -1;
if (fi > fj) return 1;
return 0;
}
public Comparable sortValue (final ScoreDoc i) {
return new Integer (fieldOrder[i.doc]);
}
public int sortType() {
return SortField.INT;
}
};
}
/**
* Returns a comparator for sorting hits according to a field containing integers.
* @param reader Index to use.
* @param fieldname Fieldable containing integer values.
* @return Comparator for sorting hits.
* @throws IOException If an error occurs reading the index.
*/
static ScoreDocComparator comparatorLong (final IndexReader reader, final String fieldname, final FieldCache.LongParser parser)
throws IOException {
final String field = fieldname.intern();
final long[] fieldOrder = FieldCache.DEFAULT.getLongs (reader, field, parser);
return new ScoreDocComparator() {
public final int compare (final ScoreDoc i, final ScoreDoc j) {
final long li = fieldOrder[i.doc];
final long lj = fieldOrder[j.doc];
if (li < lj) return -1;
if (li > lj) return 1;
return 0;
}
public Comparable sortValue (final ScoreDoc i) {
return new Long(fieldOrder[i.doc]);
}
public int sortType() {
return SortField.LONG;
}
};
}
/**
* Returns a comparator for sorting hits according to a field containing floats.
* @param reader Index to use.
* @param fieldname Fieldable containing float values.
* @return Comparator for sorting hits.
* @throws IOException If an error occurs reading the index.
*/
static ScoreDocComparator comparatorFloat (final IndexReader reader, final String fieldname, final FieldCache.FloatParser parser)
throws IOException {
final String field = fieldname.intern();
final float[] fieldOrder = FieldCache.DEFAULT.getFloats (reader, field, parser);
return new ScoreDocComparator () {
public final int compare (final ScoreDoc i, final ScoreDoc j) {
final float fi = fieldOrder[i.doc];
final float fj = fieldOrder[j.doc];
if (fi < fj) return -1;
if (fi > fj) return 1;
return 0;
}
public Comparable sortValue (final ScoreDoc i) {
return new Float (fieldOrder[i.doc]);
}
public int sortType() {
return SortField.FLOAT;
}
};
}
/**
* Returns a comparator for sorting hits according to a field containing doubles.
* @param reader Index to use.
* @param fieldname Fieldable containing float values.
* @return Comparator for sorting hits.
* @throws IOException If an error occurs reading the index.
*/
static ScoreDocComparator comparatorDouble(final IndexReader reader, final String fieldname, final FieldCache.DoubleParser parser)
throws IOException {
final String field = fieldname.intern();
final double[] fieldOrder = FieldCache.DEFAULT.getDoubles (reader, field, parser);
return new ScoreDocComparator () {
public final int compare (final ScoreDoc i, final ScoreDoc j) {
final double di = fieldOrder[i.doc];
final double dj = fieldOrder[j.doc];
if (di < dj) return -1;
if (di > dj) return 1;
return 0;
}
public Comparable sortValue (final ScoreDoc i) {
return new Double (fieldOrder[i.doc]);
}
public int sortType() {
return SortField.DOUBLE;
}
};
}
/**
* Returns a comparator for sorting hits according to a field containing strings.
* @param reader Index to use.
* @param fieldname Fieldable containing string values.
* @return Comparator for sorting hits.
* @throws IOException If an error occurs reading the index.
*/
static ScoreDocComparator comparatorString (final IndexReader reader, final String fieldname)
throws IOException {
final String field = fieldname.intern();
final FieldCache.StringIndex index = FieldCache.DEFAULT.getStringIndex (reader, field);
return new ScoreDocComparator () {
public final int compare (final ScoreDoc i, final ScoreDoc j) {
final int fi = index.order[i.doc];
final int fj = index.order[j.doc];
if (fi < fj) return -1;
if (fi > fj) return 1;
return 0;
}
public Comparable sortValue (final ScoreDoc i) {
return index.lookup[index.order[i.doc]];
}
public int sortType() {
return SortField.STRING;
}
};
}
/**
* Returns a comparator for sorting hits according to a field containing strings.
* @param reader Index to use.
* @param fieldname Fieldable containing string values.
* @return Comparator for sorting hits.
* @throws IOException If an error occurs reading the index.
*/
static ScoreDocComparator comparatorStringLocale (final IndexReader reader, final String fieldname, final Locale locale)
throws IOException {
final Collator collator = Collator.getInstance (locale);
final String field = fieldname.intern();
final String[] index = FieldCache.DEFAULT.getStrings (reader, field);
return new ScoreDocComparator() {
public final int compare(final ScoreDoc i, final ScoreDoc j) {
String is = index[i.doc];
String js = index[j.doc];
if (is == js) {
return 0;
} else if (is == null) {
return -1;
} else if (js == null) {
return 1;
} else {
return collator.compare(is, js);
}
}
public Comparable sortValue (final ScoreDoc i) {
return index[i.doc];
}
public int sortType() {
return SortField.STRING;
}
};
}
/**
* Returns a comparator for sorting hits according to values in the given field.
* The terms in the field are looked at to determine whether they contain integers,
* floats or strings. Once the type is determined, one of the other static methods
* in this class is called to get the comparator.
* @param reader Index to use.
* @param fieldname Fieldable containing values.
* @return Comparator for sorting hits.
* @throws IOException If an error occurs reading the index.
*/
static ScoreDocComparator comparatorAuto (final IndexReader reader, final String fieldname)
throws IOException {
final String field = fieldname.intern();
Object lookupArray = FieldCache.DEFAULT.getAuto (reader, field);
if (lookupArray instanceof FieldCache.StringIndex) {
return comparatorString (reader, field);
} else if (lookupArray instanceof int[]) {
return comparatorInt (reader, field, null);
} else if (lookupArray instanceof long[]) {
return comparatorLong (reader, field, null);
} else if (lookupArray instanceof float[]) {
return comparatorFloat (reader, field, null);
} else if (lookupArray instanceof String[]) {
return comparatorString (reader, field);
} else {
throw new RuntimeException ("unknown data type in field '"+field+"'");
}
}
}