package org.apache.lucene.search; import java.io.IOException; import java.text.Collator; import java.util.Locale; import org.apache.lucene.index.IndexReader; import org.apache.lucene.search.FieldCache.ByteParser; import org.apache.lucene.search.FieldCache.DoubleParser; import org.apache.lucene.search.FieldCache.FloatParser; import org.apache.lucene.search.FieldCache.IntParser; import org.apache.lucene.search.FieldCache.LongParser; import org.apache.lucene.search.FieldCache.ShortParser; import org.apache.lucene.search.FieldCache.StringIndex; public abstract class EmbeddedFieldComparator extends FieldComparator { public static final class ByteComparator extends FieldComparator { private final byte[] values; private byte[] currentReaderValues; private final String field; private ByteParser parser; private byte bottom; public ByteComparator(int numHits, String field, FieldCache.Parser parser) { values = new byte[numHits]; this.field = field; this.parser = (ByteParser) parser; } public int compare(int slot1, int slot2) { return values[slot1] - values[slot2]; } public int compareBottom(int doc) { return bottom - currentReaderValues[doc]; } public void copy(int slot, int doc) { values[slot] = currentReaderValues[doc]; } public void setNextReader(IndexReader reader, int docBase) throws IOException { currentReaderValues = FieldCache.DEFAULT.getBytes(reader, field, parser); } public void setBottom(final int bottom) { this.bottom = values[bottom]; } public Comparable value(int slot) { return new Byte(values[slot]); } } /** Sorts by ascending docID */ public static final class DocComparator extends FieldComparator { private final int[] docIDs; private int docBase; private int bottom; public DocComparator(int numHits) { docIDs = new int[numHits]; } public int compare(int slot1, int slot2) { // No overflow risk because docIDs are non-negative return docIDs[slot1] - docIDs[slot2]; } public int compareBottom(int doc) { // No overflow risk because docIDs are non-negative return bottom - (docBase + doc); } public void copy(int slot, int doc) { docIDs[slot] = docBase + doc; } public void setNextReader(IndexReader reader, int docBase) { // TODO: can we "map" our docIDs to the current // reader? saves having to then subtract on every // compare call this.docBase = docBase; } public void setBottom(final int bottom) { this.bottom = docIDs[bottom]; } public Comparable value(int slot) { return new Integer(docIDs[slot]); } } /** * Parses field's values as double (using {@link FieldCache#getDoubles} and * sorts by ascending value */ public static final class DoubleComparator extends FieldComparator { private final double[] values; private double[] currentReaderValues; private final String field; private DoubleParser parser; private double bottom; public DoubleComparator(int numHits, String field, FieldCache.Parser parser) { values = new double[numHits]; this.field = field; this.parser = (DoubleParser) parser; } public int compare(int slot1, int slot2) { final double v1 = values[slot1]; final double v2 = values[slot2]; if (v1 > v2) { return 1; } else if (v1 < v2) { return -1; } else { return 0; } } public int compareBottom(int doc) { final double v2 = currentReaderValues[doc]; if (bottom > v2) { return 1; } else if (bottom < v2) { return -1; } else { return 0; } } public void copy(int slot, int doc) { values[slot] = currentReaderValues[doc]; } public void setNextReader(IndexReader reader, int docBase) throws IOException { currentReaderValues = FieldCache.DEFAULT.getDoubles(reader, field, parser); } public void setBottom(final int bottom) { this.bottom = values[bottom]; } public Comparable value(int slot) { return new Double(values[slot]); } } /** * Parses field's values as float (using {@link FieldCache#getFloats} and * sorts by ascending value */ public static final class FloatComparator extends FieldComparator { private final float[] values; private float[] currentReaderValues; private final String field; private FloatParser parser; private float bottom; public FloatComparator(int numHits, String field, FieldCache.Parser parser) { values = new float[numHits]; this.field = field; this.parser = (FloatParser) parser; } public int compare(int slot1, int slot2) { // TODO: are there sneaky non-branch ways to compute // sign of float? final float v1 = values[slot1]; final float v2 = values[slot2]; if (v1 > v2) { return 1; } else if (v1 < v2) { return -1; } else { return 0; } } public int compareBottom(int doc) { // TODO: are there sneaky non-branch ways to compute // sign of float? final float v2 = currentReaderValues[doc]; if (bottom > v2) { return 1; } else if (bottom < v2) { return -1; } else { return 0; } } public void copy(int slot, int doc) { values[slot] = currentReaderValues[doc]; } public void setNextReader(IndexReader reader, int docBase) throws IOException { currentReaderValues = FieldCache.DEFAULT.getFloats(reader, field, parser); } public void setBottom(final int bottom) { this.bottom = values[bottom]; } public Comparable value(int slot) { return new Float(values[slot]); } } /** * Parses field's values as int (using {@link FieldCache#getInts} and sorts * by ascending value */ public static final class IntComparator extends FieldComparator { private final int[] values; private final String field; private IntParser parser; private int bottom; // Value of bottom of queue private int fieldNumber; private Scorer scorer; public void setScorer(Scorer scorer) { this.scorer = scorer; } public IntComparator(int numHits, String field, FieldCache.Parser parser, int fieldNumber) { values = new int[numHits]; this.field = field; this.parser = (IntParser) parser; this.fieldNumber = fieldNumber; this.setScorer(null); } public int compare(int slot1, int slot2) { // TODO: there are sneaky non-branch ways to compute // -1/+1/0 sign // Cannot return values[slot1] - values[slot2] because that // may overflow final int v1 = values[slot1]; final int v2 = values[slot2]; if (v1 > v2) { return 1; } else if (v1 < v2) { return -1; } else { return 0; } } public int compareBottom(int doc) { // TODO: there are sneaky non-branch ways to compute // -1/+1/0 sign // Cannot return bottom - values[slot2] because that // may overflow final int v2 = this.scorer.getSort(fieldNumber-1); if (bottom > v2) { return 1; } else if (bottom < v2) { return -1; } else { return 0; } } public void copy(int slot, int doc) { values[slot] = this.scorer.getSort(fieldNumber-1); } public void setNextReader(IndexReader reader, int docBase) throws IOException { } public void setBottom(final int bottom) { this.bottom = values[bottom]; } public Comparable value(int slot) { return new Integer(values[slot]); } } /** * Parses field's values as long (using {@link FieldCache#getLongs} and * sorts by ascending value */ public static final class LongComparator extends FieldComparator { private final long[] values; private long[] currentReaderValues; private final String field; private LongParser parser; private long bottom; public LongComparator(int numHits, String field, FieldCache.Parser parser) { values = new long[numHits]; this.field = field; this.parser = (LongParser) parser; } public int compare(int slot1, int slot2) { // TODO: there are sneaky non-branch ways to compute // -1/+1/0 sign final long v1 = values[slot1]; final long v2 = values[slot2]; if (v1 > v2) { return 1; } else if (v1 < v2) { return -1; } else { return 0; } } public int compareBottom(int doc) { // TODO: there are sneaky non-branch ways to compute // -1/+1/0 sign final long v2 = currentReaderValues[doc]; if (bottom > v2) { return 1; } else if (bottom < v2) { return -1; } else { return 0; } } public void copy(int slot, int doc) { values[slot] = currentReaderValues[doc]; } public void setNextReader(IndexReader reader, int docBase) throws IOException { currentReaderValues = FieldCache.DEFAULT.getLongs(reader, field, parser); } public void setBottom(final int bottom) { this.bottom = values[bottom]; } public Comparable value(int slot) { return new Long(values[slot]); } } /** * Sorts by descending relevance. NOTE: if you are sorting only by * descending relevance and then secondarily by ascending docID, performance * is faster using {@link TopScoreDocCollector} directly (which * {@link IndexSearcher#search} uses when no {@link Sort} is specified). */ public static final class RelevanceComparator extends FieldComparator { private final float[] scores; private float bottom; private Scorer scorer; public RelevanceComparator(int numHits) { scores = new float[numHits]; } public int compare(int slot1, int slot2) { final float score1 = scores[slot1]; final float score2 = scores[slot2]; return score1 > score2 ? -1 : (score1 < score2 ? 1 : 0); } public int compareBottom(int doc) throws IOException { float score = scorer.score(); return bottom > score ? -1 : (bottom < score ? 1 : 0); } public void copy(int slot, int doc) throws IOException { scores[slot] = scorer.score(); } public void setNextReader(IndexReader reader, int docBase) { } public void setBottom(final int bottom) { this.bottom = scores[bottom]; } public void setScorer(Scorer scorer) { // wrap with a ScoreCachingWrappingScorer so that successive calls // to // score() will not incur score computation over and over again. this.scorer = new ScoreCachingWrappingScorer(scorer); } public Comparable value(int slot) { return new Float(scores[slot]); } } /** * Parses field's values as short (using {@link FieldCache#getShorts} and * sorts by ascending value */ public static final class ShortComparator extends FieldComparator { private final short[] values; private short[] currentReaderValues; private final String field; private ShortParser parser; private short bottom; public ShortComparator(int numHits, String field, FieldCache.Parser parser) { values = new short[numHits]; this.field = field; this.parser = (ShortParser) parser; } public int compare(int slot1, int slot2) { return values[slot1] - values[slot2]; } public int compareBottom(int doc) { return bottom - currentReaderValues[doc]; } public void copy(int slot, int doc) { values[slot] = currentReaderValues[doc]; } public void setNextReader(IndexReader reader, int docBase) throws IOException { currentReaderValues = FieldCache.DEFAULT.getShorts(reader, field, parser); } public void setBottom(final int bottom) { this.bottom = values[bottom]; } public Comparable value(int slot) { return new Short(values[slot]); } } /** * Sorts by a field's value using the Collator for a given Locale. */ public static final class StringComparatorLocale extends FieldComparator { private final String[] values; private String[] currentReaderValues; private final String field; final Collator collator; private String bottom; public StringComparatorLocale(int numHits, String field, Locale locale) { values = new String[numHits]; this.field = field; collator = Collator.getInstance(locale); } public int compare(int slot1, int slot2) { final String val1 = values[slot1]; final String val2 = values[slot2]; if (val1 == null) { if (val2 == null) { return 0; } return -1; } else if (val2 == null) { return 1; } return collator.compare(val1, val2); } public int compareBottom(int doc) { final String val2 = currentReaderValues[doc]; if (bottom == null) { if (val2 == null) { return 0; } return -1; } else if (val2 == null) { return 1; } return collator.compare(bottom, val2); } public void copy(int slot, int doc) { values[slot] = currentReaderValues[doc]; } public void setNextReader(IndexReader reader, int docBase) throws IOException { currentReaderValues = FieldCache.DEFAULT.getStrings(reader, field); } public void setBottom(final int bottom) { this.bottom = values[bottom]; } public Comparable value(int slot) { return values[slot]; } } /** * Sorts by field's natural String sort order, using ordinals. This is * functionally equivalent to {@link StringValComparator}, but it first * resolves the string to their relative ordinal positions (using the index * returned by {@link FieldCache#getStringIndex}), and does most comparisons * using the ordinals. For medium to large results, this comparator will be * much faster than {@link StringValComparator}. For very small result sets * it may be slower. */ public static final class StringOrdValComparator extends FieldComparator { private final int[] ords; private final String[] values; private final int[] readerGen; private int currentReaderGen = -1; private String[] lookup; private int[] order; private final String field; private int bottomSlot = -1; private int bottomOrd; private String bottomValue; private final boolean reversed; private final int sortPos; public StringOrdValComparator(int numHits, String field, int sortPos, boolean reversed) { ords = new int[numHits]; values = new String[numHits]; readerGen = new int[numHits]; this.sortPos = sortPos; this.reversed = reversed; this.field = field; } public int compare(int slot1, int slot2) { if (readerGen[slot1] == readerGen[slot2]) { int cmp = ords[slot1] - ords[slot2]; if (cmp != 0) { return cmp; } } final String val1 = values[slot1]; final String val2 = values[slot2]; if (val1 == null) { if (val2 == null) { return 0; } return -1; } else if (val2 == null) { return 1; } return val1.compareTo(val2); } public int compareBottom(int doc) { assert bottomSlot != -1; int order = this.order[doc]; final int cmp = bottomOrd - order; if (cmp != 0) { return cmp; } final String val2 = lookup[order]; if (bottomValue == null) { if (val2 == null) { return 0; } // bottom wins return -1; } else if (val2 == null) { // doc wins return 1; } return bottomValue.compareTo(val2); } private void convert(int slot) { readerGen[slot] = currentReaderGen; int index = 0; String value = values[slot]; if (value == null) { ords[slot] = 0; return; } if (sortPos == 0 && bottomSlot != -1 && bottomSlot != slot) { // Since we are the primary sort, the entries in the // queue are bounded by bottomOrd: assert bottomOrd < lookup.length; if (reversed) { index = binarySearch(lookup, value, bottomOrd, lookup.length - 1); } else { index = binarySearch(lookup, value, 0, bottomOrd); } } else { // Full binary search index = binarySearch(lookup, value); } if (index < 0) { index = -index - 2; } ords[slot] = index; } public void copy(int slot, int doc) { final int ord = order[doc]; ords[slot] = ord; assert ord >= 0; values[slot] = lookup[ord]; readerGen[slot] = currentReaderGen; } public void setNextReader(IndexReader reader, int docBase) throws IOException { StringIndex currentReaderValues = FieldCache.DEFAULT.getStringIndex(reader, field); currentReaderGen++; order = currentReaderValues.order; lookup = currentReaderValues.lookup; assert lookup.length > 0; if (bottomSlot != -1) { convert(bottomSlot); bottomOrd = ords[bottomSlot]; } } public void setBottom(final int bottom) { bottomSlot = bottom; if (readerGen[bottom] != currentReaderGen) { convert(bottomSlot); } bottomOrd = ords[bottom]; assert bottomOrd >= 0; assert bottomOrd < lookup.length; bottomValue = values[bottom]; } public Comparable value(int slot) { return values[slot]; } public String[] getValues() { return values; } public int getBottomSlot() { return bottomSlot; } public String getField() { return field; } } /** * Sorts by field's natural String sort order. All comparisons are done * using String.compareTo, which is slow for medium to large result sets but * possibly very fast for very small results sets. */ public static final class StringValComparator extends FieldComparator { private String[] values; private String[] currentReaderValues; private final String field; private String bottom; public StringValComparator(int numHits, String field) { values = new String[numHits]; this.field = field; } public int compare(int slot1, int slot2) { final String val1 = values[slot1]; final String val2 = values[slot2]; if (val1 == null) { if (val2 == null) { return 0; } return -1; } else if (val2 == null) { return 1; } return val1.compareTo(val2); } public int compareBottom(int doc) { final String val2 = currentReaderValues[doc]; if (bottom == null) { if (val2 == null) { return 0; } return -1; } else if (val2 == null) { return 1; } return bottom.compareTo(val2); } public void copy(int slot, int doc) { values[slot] = currentReaderValues[doc]; } public void setNextReader(IndexReader reader, int docBase) throws IOException { currentReaderValues = FieldCache.DEFAULT.getStrings(reader, field); } public void setBottom(final int bottom) { this.bottom = values[bottom]; } public Comparable value(int slot) { return values[slot]; } } }