| 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.index.IndexReader.AtomicReaderContext; |
| import org.apache.lucene.index.values.IndexDocValues; |
| import org.apache.lucene.index.values.IndexDocValues.Source; |
| import org.apache.lucene.search.FieldCache.DocTerms; |
| import org.apache.lucene.search.FieldCache.DocTermsIndex; |
| import org.apache.lucene.search.cache.ByteValuesCreator; |
| import org.apache.lucene.search.cache.CachedArray; |
| import org.apache.lucene.search.cache.CachedArrayCreator; |
| import org.apache.lucene.search.cache.DoubleValuesCreator; |
| import org.apache.lucene.search.cache.FloatValuesCreator; |
| import org.apache.lucene.search.cache.IntValuesCreator; |
| import org.apache.lucene.search.cache.LongValuesCreator; |
| import org.apache.lucene.search.cache.ShortValuesCreator; |
| import org.apache.lucene.search.cache.CachedArray.ByteValues; |
| import org.apache.lucene.search.cache.CachedArray.DoubleValues; |
| import org.apache.lucene.search.cache.CachedArray.FloatValues; |
| import org.apache.lucene.search.cache.CachedArray.IntValues; |
| import org.apache.lucene.search.cache.CachedArray.LongValues; |
| import org.apache.lucene.search.cache.CachedArray.ShortValues; |
| import org.apache.lucene.util.Bits; |
| import org.apache.lucene.util.BytesRef; |
| import org.apache.lucene.util.packed.Direct16; |
| import org.apache.lucene.util.packed.Direct32; |
| import org.apache.lucene.util.packed.Direct8; |
| import org.apache.lucene.util.packed.PackedInts; |
| |
| /** |
| * Expert: a FieldComparator compares hits so as to determine their |
| * sort order when collecting the top results with {@link |
| * TopFieldCollector}. The concrete public FieldComparator |
| * classes here correspond to the SortField types. |
| * |
| * <p>This API is designed to achieve high performance |
| * sorting, by exposing a tight interaction with {@link |
| * FieldValueHitQueue} as it visits hits. Whenever a hit is |
| * competitive, it's enrolled into a virtual slot, which is |
| * an int ranging from 0 to numHits-1. The {@link |
| * FieldComparator} is made aware of segment transitions |
| * during searching in case any internal state it's tracking |
| * needs to be recomputed during these transitions.</p> |
| * |
| * <p>A comparator must define these functions:</p> |
| * |
| * <ul> |
| * |
| * <li> {@link #compare} Compare a hit at 'slot a' |
| * with hit 'slot b'. |
| * |
| * <li> {@link #setBottom} This method is called by |
| * {@link FieldValueHitQueue} to notify the |
| * FieldComparator of the current weakest ("bottom") |
| * slot. Note that this slot may not hold the weakest |
| * value according to your comparator, in cases where |
| * your comparator is not the primary one (ie, is only |
| * used to break ties from the comparators before it). |
| * |
| * <li> {@link #compareBottom} Compare a new hit (docID) |
| * against the "weakest" (bottom) entry in the queue. |
| * |
| * <li> {@link #copy} Installs a new hit into the |
| * priority queue. The {@link FieldValueHitQueue} |
| * calls this method when a new hit is competitive. |
| * |
| * <li> {@link #setNextReader(IndexReader.AtomicReaderContext)} Invoked |
| * when the search is switching to the next segment. |
| * You may need to update internal state of the |
| * comparator, for example retrieving new values from |
| * the {@link FieldCache}. |
| * |
| * <li> {@link #value} Return the sort value stored in |
| * the specified slot. This is only called at the end |
| * of the search, in order to populate {@link |
| * FieldDoc#fields} when returning the top results. |
| * </ul> |
| * |
| * @lucene.experimental |
| */ |
| public abstract class FieldComparator { |
| |
| /** |
| * Compare hit at slot1 with hit at slot2. |
| * |
| * @param slot1 first slot to compare |
| * @param slot2 second slot to compare |
| * @return any N < 0 if slot2's value is sorted after |
| * slot1, any N > 0 if the slot2's value is sorted before |
| * slot1 and 0 if they are equal |
| */ |
| public abstract int compare(int slot1, int slot2); |
| |
| /** |
| * Set the bottom slot, ie the "weakest" (sorted last) |
| * entry in the queue. When {@link #compareBottom} is |
| * called, you should compare against this slot. This |
| * will always be called before {@link #compareBottom}. |
| * |
| * @param slot the currently weakest (sorted last) slot in the queue |
| */ |
| public abstract void setBottom(final int slot); |
| |
| /** |
| * Compare the bottom of the queue with doc. This will |
| * only invoked after setBottom has been called. This |
| * should return the same result as {@link |
| * #compare(int,int)}} as if bottom were slot1 and the new |
| * document were slot 2. |
| * |
| * <p>For a search that hits many results, this method |
| * will be the hotspot (invoked by far the most |
| * frequently).</p> |
| * |
| * @param doc that was hit |
| * @return any N < 0 if the doc's value is sorted after |
| * the bottom entry (not competitive), any N > 0 if the |
| * doc's value is sorted before the bottom entry and 0 if |
| * they are equal. |
| */ |
| public abstract int compareBottom(int doc) throws IOException; |
| |
| /** |
| * This method is called when a new hit is competitive. |
| * You should copy any state associated with this document |
| * that will be required for future comparisons, into the |
| * specified slot. |
| * |
| * @param slot which slot to copy the hit to |
| * @param doc docID relative to current reader |
| */ |
| public abstract void copy(int slot, int doc) throws IOException; |
| |
| /** |
| * Set a new {@link AtomicReaderContext}. All subsequent docIDs are relative to |
| * the current reader (you must add docBase if you need to |
| * map it to a top-level docID). |
| * |
| * @param context current reader context |
| * @return the comparator to use for this segment; most |
| * comparators can just return "this" to reuse the same |
| * comparator across segments |
| * @throws IOException |
| */ |
| public abstract FieldComparator setNextReader(AtomicReaderContext context) throws IOException; |
| |
| /** Sets the Scorer to use in case a document's score is |
| * needed. |
| * |
| * @param scorer Scorer instance that you should use to |
| * obtain the current hit's score, if necessary. */ |
| public void setScorer(Scorer scorer) { |
| // Empty implementation since most comparators don't need the score. This |
| // can be overridden by those that need it. |
| } |
| |
| /** |
| * Return the actual value in the slot. |
| * |
| * @param slot the value |
| * @return value in this slot upgraded to Comparable |
| */ |
| public abstract Comparable<?> value(int slot); |
| |
| |
| |
| public static abstract class NumericComparator<T extends CachedArray> extends FieldComparator { |
| protected final CachedArrayCreator<T> creator; |
| protected T cached; |
| protected final boolean checkMissing; |
| protected Bits valid; |
| |
| public NumericComparator( CachedArrayCreator<T> c, boolean checkMissing ) { |
| this.creator = c; |
| this.checkMissing = checkMissing; |
| } |
| |
| protected FieldComparator setup(T cached) { |
| this.cached = cached; |
| if (checkMissing) |
| valid = cached.valid; |
| return this; |
| } |
| } |
| |
| /** Parses field's values as byte (using {@link |
| * FieldCache#getBytes} and sorts by ascending value */ |
| public static final class ByteComparator extends NumericComparator<ByteValues> { |
| private byte[] docValues; |
| private final byte[] values; |
| private final byte missingValue; |
| private byte bottom; |
| |
| ByteComparator(int numHits, ByteValuesCreator creator, Byte missingValue ) { |
| super( creator, missingValue!=null ); |
| values = new byte[numHits]; |
| this.missingValue = checkMissing |
| ? missingValue.byteValue() : 0; |
| } |
| |
| @Override |
| public int compare(int slot1, int slot2) { |
| return values[slot1] - values[slot2]; |
| } |
| |
| @Override |
| public int compareBottom(int doc) { |
| byte v2 = docValues[doc]; |
| if (valid != null && v2==0 && !valid.get(doc)) |
| v2 = missingValue; |
| |
| return bottom - v2; |
| } |
| |
| @Override |
| public void copy(int slot, int doc) { |
| byte v2 = docValues[doc]; |
| if (valid != null && v2==0 && !valid.get(doc)) |
| v2 = missingValue; |
| |
| values[slot] = v2; |
| } |
| |
| @Override |
| public FieldComparator setNextReader(AtomicReaderContext context) throws IOException { |
| setup(FieldCache.DEFAULT.getBytes(context.reader, creator.field, creator)); |
| docValues = cached.values; |
| return this; |
| } |
| |
| @Override |
| public void setBottom(final int bottom) { |
| this.bottom = values[bottom]; |
| } |
| |
| @Override |
| public Comparable<?> value(int slot) { |
| return Byte.valueOf(values[slot]); |
| } |
| } |
| |
| |
| /** Parses field's values as double (using {@link |
| * FieldCache#getDoubles} and sorts by ascending value */ |
| public static final class DoubleComparator extends NumericComparator<DoubleValues> { |
| private double[] docValues; |
| private final double[] values; |
| private final double missingValue; |
| private double bottom; |
| |
| |
| DoubleComparator(int numHits, DoubleValuesCreator creator, Double missingValue ) { |
| super( creator, missingValue != null ); |
| values = new double[numHits]; |
| this.missingValue = checkMissing |
| ? missingValue.doubleValue() : 0; |
| } |
| |
| @Override |
| 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; |
| } |
| } |
| |
| @Override |
| public int compareBottom(int doc) { |
| double v2 = docValues[doc]; |
| if (valid != null && v2==0 && !valid.get(doc)) |
| v2 = missingValue; |
| |
| if (bottom > v2) { |
| return 1; |
| } else if (bottom < v2) { |
| return -1; |
| } else { |
| return 0; |
| } |
| } |
| |
| @Override |
| public void copy(int slot, int doc) { |
| double v2 = docValues[doc]; |
| if (valid != null && v2==0 && !valid.get(doc)) |
| v2 = missingValue; |
| |
| values[slot] = v2; |
| } |
| |
| @Override |
| public FieldComparator setNextReader(AtomicReaderContext context) throws IOException { |
| setup(FieldCache.DEFAULT.getDoubles(context.reader, creator.field, creator)); |
| docValues = cached.values; |
| return this; |
| } |
| |
| @Override |
| public void setBottom(final int bottom) { |
| this.bottom = values[bottom]; |
| } |
| |
| @Override |
| public Comparable<?> value(int slot) { |
| return Double.valueOf(values[slot]); |
| } |
| } |
| |
| /** Uses float index values to sort by ascending value */ |
| public static final class FloatDocValuesComparator extends FieldComparator { |
| private final double[] values; |
| private Source currentReaderValues; |
| private final String field; |
| private double bottom; |
| |
| FloatDocValuesComparator(int numHits, String field) { |
| values = new double[numHits]; |
| this.field = field; |
| } |
| |
| @Override |
| 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; |
| } |
| } |
| |
| @Override |
| public int compareBottom(int doc) { |
| final double v2 = currentReaderValues.getFloat(doc); |
| if (bottom > v2) { |
| return 1; |
| } else if (bottom < v2) { |
| return -1; |
| } else { |
| return 0; |
| } |
| } |
| |
| @Override |
| public void copy(int slot, int doc) { |
| values[slot] = currentReaderValues.getFloat(doc); |
| } |
| |
| @Override |
| public FieldComparator setNextReader(AtomicReaderContext context) throws IOException { |
| final IndexDocValues docValues = context.reader.docValues(field); |
| if (docValues != null) { |
| currentReaderValues = docValues.getSource(); |
| } |
| return this; |
| } |
| |
| @Override |
| public void setBottom(final int bottom) { |
| this.bottom = values[bottom]; |
| } |
| |
| @Override |
| public Comparable<Double> value(int slot) { |
| return Double.valueOf(values[slot]); |
| } |
| } |
| |
| /** Parses field's values as float (using {@link |
| * FieldCache#getFloats} and sorts by ascending value */ |
| public static final class FloatComparator extends NumericComparator<FloatValues> { |
| private float[] docValues; |
| private final float[] values; |
| private final float missingValue; |
| private float bottom; |
| |
| FloatComparator(int numHits, FloatValuesCreator creator, Float missingValue ) { |
| super( creator, missingValue != null ); |
| values = new float[numHits]; |
| this.missingValue = checkMissing |
| ? missingValue.floatValue() : 0; |
| } |
| |
| @Override |
| 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; |
| } |
| } |
| |
| @Override |
| public int compareBottom(int doc) { |
| // TODO: are there sneaky non-branch ways to compute sign of float? |
| float v2 = docValues[doc]; |
| if (valid != null && v2==0 && !valid.get(doc)) |
| v2 = missingValue; |
| |
| |
| if (bottom > v2) { |
| return 1; |
| } else if (bottom < v2) { |
| return -1; |
| } else { |
| return 0; |
| } |
| } |
| |
| @Override |
| public void copy(int slot, int doc) { |
| float v2 = docValues[doc]; |
| if (valid != null && v2==0 && !valid.get(doc)) |
| v2 = missingValue; |
| |
| values[slot] = v2; |
| } |
| |
| @Override |
| public FieldComparator setNextReader(AtomicReaderContext context) throws IOException { |
| setup(FieldCache.DEFAULT.getFloats(context.reader, creator.field, creator)); |
| docValues = cached.values; |
| return this; |
| } |
| |
| @Override |
| public void setBottom(final int bottom) { |
| this.bottom = values[bottom]; |
| } |
| |
| @Override |
| public Comparable<?> value(int slot) { |
| return Float.valueOf(values[slot]); |
| } |
| } |
| |
| /** Parses field's values as short (using {@link |
| * FieldCache#getShorts} and sorts by ascending value */ |
| public static final class ShortComparator extends NumericComparator<ShortValues> { |
| private short[] docValues; |
| private final short[] values; |
| private short bottom; |
| private final short missingValue; |
| |
| ShortComparator(int numHits, ShortValuesCreator creator, Short missingValue ) { |
| super( creator, missingValue != null ); |
| values = new short[numHits]; |
| this.missingValue = checkMissing |
| ? missingValue.shortValue() : 0; |
| } |
| |
| @Override |
| public int compare(int slot1, int slot2) { |
| return values[slot1] - values[slot2]; |
| } |
| |
| @Override |
| public int compareBottom(int doc) { |
| short v2 = docValues[doc]; |
| if (valid != null && v2==0 && !valid.get(doc)) |
| v2 = missingValue; |
| |
| return bottom - v2; |
| } |
| |
| @Override |
| public void copy(int slot, int doc) { |
| short v2 = docValues[doc]; |
| if (valid != null && v2==0 && !valid.get(doc)) |
| v2 = missingValue; |
| |
| values[slot] = v2; |
| } |
| |
| @Override |
| public FieldComparator setNextReader(AtomicReaderContext context) throws IOException { |
| setup( FieldCache.DEFAULT.getShorts(context.reader, creator.field, creator)); |
| docValues = cached.values; |
| return this; |
| } |
| |
| @Override |
| public void setBottom(final int bottom) { |
| this.bottom = values[bottom]; |
| } |
| |
| @Override |
| public Comparable<?> value(int slot) { |
| return Short.valueOf(values[slot]); |
| } |
| } |
| |
| /** Parses field's values as int (using {@link |
| * FieldCache#getInts} and sorts by ascending value */ |
| public static final class IntComparator extends NumericComparator<IntValues> { |
| private int[] docValues; |
| private final int[] values; |
| private int bottom; // Value of bottom of queue |
| final int missingValue; |
| |
| IntComparator(int numHits, IntValuesCreator creator, Integer missingValue ) { |
| super( creator, missingValue != null ); |
| values = new int[numHits]; |
| this.missingValue = checkMissing |
| ? missingValue.intValue() : 0; |
| } |
| |
| @Override |
| 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; |
| } |
| } |
| |
| @Override |
| 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 |
| int v2 = docValues[doc]; |
| if (valid != null && v2==0 && !valid.get(doc)) |
| v2 = missingValue; |
| |
| if (bottom > v2) { |
| return 1; |
| } else if (bottom < v2) { |
| return -1; |
| } else { |
| return 0; |
| } |
| } |
| |
| @Override |
| public void copy(int slot, int doc) { |
| int v2 = docValues[doc]; |
| if (valid != null && v2==0 && !valid.get(doc)) |
| v2 = missingValue; |
| |
| values[slot] = v2; |
| } |
| |
| @Override |
| public FieldComparator setNextReader(AtomicReaderContext context) throws IOException { |
| setup(FieldCache.DEFAULT.getInts(context.reader, creator.field, creator)); |
| docValues = cached.values; |
| return this; |
| } |
| |
| @Override |
| public void setBottom(final int bottom) { |
| this.bottom = values[bottom]; |
| } |
| |
| @Override |
| public Comparable<?> value(int slot) { |
| return Integer.valueOf(values[slot]); |
| } |
| } |
| |
| /** Loads int index values and sorts by ascending value. */ |
| public static final class IntDocValuesComparator extends FieldComparator { |
| private final long[] values; |
| private Source currentReaderValues; |
| private final String field; |
| private long bottom; |
| |
| IntDocValuesComparator(int numHits, String field) { |
| values = new long[numHits]; |
| this.field = field; |
| } |
| |
| @Override |
| 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; |
| } |
| } |
| |
| @Override |
| public int compareBottom(int doc) { |
| // TODO: there are sneaky non-branch ways to compute |
| // -1/+1/0 sign |
| final long v2 = currentReaderValues.getInt(doc); |
| if (bottom > v2) { |
| return 1; |
| } else if (bottom < v2) { |
| return -1; |
| } else { |
| return 0; |
| } |
| } |
| |
| @Override |
| public void copy(int slot, int doc) { |
| values[slot] = currentReaderValues.getInt(doc); |
| } |
| |
| @Override |
| public FieldComparator setNextReader(AtomicReaderContext context) throws IOException { |
| IndexDocValues docValues = context.reader.docValues(field); |
| if (docValues != null) { |
| currentReaderValues = docValues.getSource(); |
| } |
| return this; |
| } |
| |
| @Override |
| public void setBottom(final int bottom) { |
| this.bottom = values[bottom]; |
| } |
| |
| @Override |
| public Comparable<Long> value(int slot) { |
| return Long.valueOf(values[slot]); |
| } |
| } |
| |
| /** Parses field's values as long (using {@link |
| * FieldCache#getLongs} and sorts by ascending value */ |
| public static final class LongComparator extends NumericComparator<LongValues> { |
| private long[] docValues; |
| private final long[] values; |
| private long bottom; |
| private final long missingValue; |
| |
| LongComparator(int numHits, LongValuesCreator creator, Long missingValue ) { |
| super( creator, missingValue != null ); |
| values = new long[numHits]; |
| this.missingValue = checkMissing |
| ? missingValue.longValue() : 0; |
| } |
| |
| @Override |
| 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; |
| } |
| } |
| |
| @Override |
| public int compareBottom(int doc) { |
| // TODO: there are sneaky non-branch ways to compute |
| // -1/+1/0 sign |
| long v2 = docValues[doc]; |
| if (valid != null && v2==0 && !valid.get(doc)) |
| v2 = missingValue; |
| |
| |
| if (bottom > v2) { |
| return 1; |
| } else if (bottom < v2) { |
| return -1; |
| } else { |
| return 0; |
| } |
| } |
| |
| @Override |
| public void copy(int slot, int doc) { |
| long v2 = docValues[doc]; |
| if (valid != null && v2==0 && !valid.get(doc)) |
| v2 = missingValue; |
| |
| values[slot] = v2; |
| } |
| |
| @Override |
| public FieldComparator setNextReader(AtomicReaderContext context) throws IOException { |
| setup(FieldCache.DEFAULT.getLongs(context.reader, creator.field, creator)); |
| docValues = cached.values; |
| return this; |
| } |
| |
| @Override |
| public void setBottom(final int bottom) { |
| this.bottom = values[bottom]; |
| } |
| |
| @Override |
| public Comparable<?> value(int slot) { |
| return Long.valueOf(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; |
| |
| RelevanceComparator(int numHits) { |
| scores = new float[numHits]; |
| } |
| |
| @Override |
| 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); |
| } |
| |
| @Override |
| public int compareBottom(int doc) throws IOException { |
| float score = scorer.score(); |
| return bottom > score ? -1 : (bottom < score ? 1 : 0); |
| } |
| |
| @Override |
| public void copy(int slot, int doc) throws IOException { |
| scores[slot] = scorer.score(); |
| } |
| |
| @Override |
| public FieldComparator setNextReader(AtomicReaderContext context) { |
| return this; |
| } |
| |
| @Override |
| public void setBottom(final int bottom) { |
| this.bottom = scores[bottom]; |
| } |
| |
| @Override |
| 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); |
| } |
| |
| @Override |
| public Comparable<?> value(int slot) { |
| return Float.valueOf(scores[slot]); |
| } |
| } |
| |
| |
| |
| /** Sorts by ascending docID */ |
| public static final class DocComparator extends FieldComparator { |
| private final int[] docIDs; |
| private int docBase; |
| private int bottom; |
| |
| DocComparator(int numHits) { |
| docIDs = new int[numHits]; |
| } |
| |
| @Override |
| public int compare(int slot1, int slot2) { |
| // No overflow risk because docIDs are non-negative |
| return docIDs[slot1] - docIDs[slot2]; |
| } |
| |
| @Override |
| public int compareBottom(int doc) { |
| // No overflow risk because docIDs are non-negative |
| return bottom - (docBase + doc); |
| } |
| |
| @Override |
| public void copy(int slot, int doc) { |
| docIDs[slot] = docBase + doc; |
| } |
| |
| @Override |
| public FieldComparator setNextReader(AtomicReaderContext context) { |
| // TODO: can we "map" our docIDs to the current |
| // reader? saves having to then subtract on every |
| // compare call |
| this.docBase = context.docBase; |
| return this; |
| } |
| |
| @Override |
| public void setBottom(final int bottom) { |
| this.bottom = docIDs[bottom]; |
| } |
| |
| @Override |
| public Comparable<?> value(int slot) { |
| return Integer.valueOf(docIDs[slot]); |
| } |
| } |
| |
| /** Sorts by field's natural Term sort order, using |
| * ordinals. This is functionally equivalent to {@link |
| * TermValComparator}, but it first resolves the string |
| * to their relative ordinal positions (using the index |
| * returned by {@link FieldCache#getTermsIndex}), and |
| * does most comparisons using the ordinals. For medium |
| * to large results, this comparator will be much faster |
| * than {@link TermValComparator}. For very small |
| * result sets it may be slower. */ |
| public static final class TermOrdValComparator extends FieldComparator { |
| /** @lucene.internal */ |
| final int[] ords; |
| /** @lucene.internal */ |
| final BytesRef[] values; |
| /** @lucene.internal */ |
| final int[] readerGen; |
| |
| /** @lucene.internal */ |
| int currentReaderGen = -1; |
| private DocTermsIndex termsIndex; |
| private final String field; |
| |
| /** @lucene.internal */ |
| int bottomSlot = -1; |
| /** @lucene.internal */ |
| int bottomOrd; |
| /** @lucene.internal */ |
| boolean bottomSameReader; |
| /** @lucene.internal */ |
| BytesRef bottomValue; |
| /** @lucene.internal */ |
| final BytesRef tempBR = new BytesRef(); |
| |
| public TermOrdValComparator(int numHits, String field, int sortPos, boolean reversed) { |
| ords = new int[numHits]; |
| values = new BytesRef[numHits]; |
| readerGen = new int[numHits]; |
| this.field = field; |
| } |
| |
| @Override |
| public int compare(int slot1, int slot2) { |
| if (readerGen[slot1] == readerGen[slot2]) { |
| return ords[slot1] - ords[slot2]; |
| } |
| |
| final BytesRef val1 = values[slot1]; |
| final BytesRef val2 = values[slot2]; |
| if (val1 == null) { |
| if (val2 == null) { |
| return 0; |
| } |
| return -1; |
| } else if (val2 == null) { |
| return 1; |
| } |
| return val1.compareTo(val2); |
| } |
| |
| @Override |
| public int compareBottom(int doc) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| public void copy(int slot, int doc) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| /** Base class for specialized (per bit width of the |
| * ords) per-segment comparator. NOTE: this is messy; |
| * we do this only because hotspot can't reliably inline |
| * the underlying array access when looking up doc->ord |
| * @lucene.internal |
| */ |
| abstract class PerSegmentComparator extends FieldComparator { |
| |
| @Override |
| public FieldComparator setNextReader(AtomicReaderContext context) throws IOException { |
| return TermOrdValComparator.this.setNextReader(context); |
| } |
| |
| @Override |
| public int compare(int slot1, int slot2) { |
| return TermOrdValComparator.this.compare(slot1, slot2); |
| } |
| |
| @Override |
| public void setBottom(final int bottom) { |
| TermOrdValComparator.this.setBottom(bottom); |
| } |
| |
| @Override |
| public Comparable<?> value(int slot) { |
| return TermOrdValComparator.this.value(slot); |
| } |
| } |
| |
| // Used per-segment when bit width of doc->ord is 8: |
| private final class ByteOrdComparator extends PerSegmentComparator { |
| private final byte[] readerOrds; |
| private final DocTermsIndex termsIndex; |
| private final int docBase; |
| |
| public ByteOrdComparator(byte[] readerOrds, DocTermsIndex termsIndex, int docBase) { |
| this.readerOrds = readerOrds; |
| this.termsIndex = termsIndex; |
| this.docBase = docBase; |
| } |
| |
| @Override |
| public int compareBottom(int doc) { |
| assert bottomSlot != -1; |
| if (bottomSameReader) { |
| // ord is precisely comparable, even in the equal case |
| return bottomOrd - (readerOrds[doc]&0xFF); |
| } else { |
| // ord is only approx comparable: if they are not |
| // equal, we can use that; if they are equal, we |
| // must fallback to compare by value |
| final int order = readerOrds[doc]&0xFF; |
| final int cmp = bottomOrd - order; |
| if (cmp != 0) { |
| return cmp; |
| } |
| |
| if (bottomValue == null) { |
| if (order == 0) { |
| // unset |
| return 0; |
| } |
| // bottom wins |
| return -1; |
| } else if (order == 0) { |
| // doc wins |
| return 1; |
| } |
| termsIndex.lookup(order, tempBR); |
| return bottomValue.compareTo(tempBR); |
| } |
| } |
| |
| @Override |
| public void copy(int slot, int doc) { |
| final int ord = readerOrds[doc]&0xFF; |
| ords[slot] = ord; |
| if (ord == 0) { |
| values[slot] = null; |
| } else { |
| assert ord > 0; |
| if (values[slot] == null) { |
| values[slot] = new BytesRef(); |
| } |
| termsIndex.lookup(ord, values[slot]); |
| } |
| readerGen[slot] = currentReaderGen; |
| } |
| } |
| |
| // Used per-segment when bit width of doc->ord is 16: |
| private final class ShortOrdComparator extends PerSegmentComparator { |
| private final short[] readerOrds; |
| private final DocTermsIndex termsIndex; |
| private final int docBase; |
| |
| public ShortOrdComparator(short[] readerOrds, DocTermsIndex termsIndex, int docBase) { |
| this.readerOrds = readerOrds; |
| this.termsIndex = termsIndex; |
| this.docBase = docBase; |
| } |
| |
| @Override |
| public int compareBottom(int doc) { |
| assert bottomSlot != -1; |
| if (bottomSameReader) { |
| // ord is precisely comparable, even in the equal case |
| return bottomOrd - (readerOrds[doc]&0xFFFF); |
| } else { |
| // ord is only approx comparable: if they are not |
| // equal, we can use that; if they are equal, we |
| // must fallback to compare by value |
| final int order = readerOrds[doc]&0xFFFF; |
| final int cmp = bottomOrd - order; |
| if (cmp != 0) { |
| return cmp; |
| } |
| |
| if (bottomValue == null) { |
| if (order == 0) { |
| // unset |
| return 0; |
| } |
| // bottom wins |
| return -1; |
| } else if (order == 0) { |
| // doc wins |
| return 1; |
| } |
| termsIndex.lookup(order, tempBR); |
| return bottomValue.compareTo(tempBR); |
| } |
| } |
| |
| @Override |
| public void copy(int slot, int doc) { |
| final int ord = readerOrds[doc]&0xFFFF; |
| ords[slot] = ord; |
| if (ord == 0) { |
| values[slot] = null; |
| } else { |
| assert ord > 0; |
| if (values[slot] == null) { |
| values[slot] = new BytesRef(); |
| } |
| termsIndex.lookup(ord, values[slot]); |
| } |
| readerGen[slot] = currentReaderGen; |
| } |
| } |
| |
| // Used per-segment when bit width of doc->ord is 32: |
| private final class IntOrdComparator extends PerSegmentComparator { |
| private final int[] readerOrds; |
| private final DocTermsIndex termsIndex; |
| private final int docBase; |
| |
| public IntOrdComparator(int[] readerOrds, DocTermsIndex termsIndex, int docBase) { |
| this.readerOrds = readerOrds; |
| this.termsIndex = termsIndex; |
| this.docBase = docBase; |
| } |
| |
| @Override |
| public int compareBottom(int doc) { |
| assert bottomSlot != -1; |
| if (bottomSameReader) { |
| // ord is precisely comparable, even in the equal case |
| return bottomOrd - readerOrds[doc]; |
| } else { |
| // ord is only approx comparable: if they are not |
| // equal, we can use that; if they are equal, we |
| // must fallback to compare by value |
| final int order = readerOrds[doc]; |
| final int cmp = bottomOrd - order; |
| if (cmp != 0) { |
| return cmp; |
| } |
| |
| if (bottomValue == null) { |
| if (order == 0) { |
| // unset |
| return 0; |
| } |
| // bottom wins |
| return -1; |
| } else if (order == 0) { |
| // doc wins |
| return 1; |
| } |
| termsIndex.lookup(order, tempBR); |
| return bottomValue.compareTo(tempBR); |
| } |
| } |
| |
| @Override |
| public void copy(int slot, int doc) { |
| final int ord = readerOrds[doc]; |
| ords[slot] = ord; |
| if (ord == 0) { |
| values[slot] = null; |
| } else { |
| assert ord > 0; |
| if (values[slot] == null) { |
| values[slot] = new BytesRef(); |
| } |
| termsIndex.lookup(ord, values[slot]); |
| } |
| readerGen[slot] = currentReaderGen; |
| } |
| } |
| |
| // Used per-segment when bit width is not a native array |
| // size (8, 16, 32): |
| private final class AnyOrdComparator extends PerSegmentComparator { |
| private final PackedInts.Reader readerOrds; |
| private final DocTermsIndex termsIndex; |
| private final int docBase; |
| |
| public AnyOrdComparator(PackedInts.Reader readerOrds, DocTermsIndex termsIndex, int docBase) { |
| this.readerOrds = readerOrds; |
| this.termsIndex = termsIndex; |
| this.docBase = docBase; |
| } |
| |
| @Override |
| public int compareBottom(int doc) { |
| assert bottomSlot != -1; |
| if (bottomSameReader) { |
| // ord is precisely comparable, even in the equal case |
| return bottomOrd - (int) readerOrds.get(doc); |
| } else { |
| // ord is only approx comparable: if they are not |
| // equal, we can use that; if they are equal, we |
| // must fallback to compare by value |
| final int order = (int) readerOrds.get(doc); |
| final int cmp = bottomOrd - order; |
| if (cmp != 0) { |
| return cmp; |
| } |
| |
| if (bottomValue == null) { |
| if (order == 0) { |
| // unset |
| return 0; |
| } |
| // bottom wins |
| return -1; |
| } else if (order == 0) { |
| // doc wins |
| return 1; |
| } |
| termsIndex.lookup(order, tempBR); |
| return bottomValue.compareTo(tempBR); |
| } |
| } |
| |
| @Override |
| public void copy(int slot, int doc) { |
| final int ord = (int) readerOrds.get(doc); |
| ords[slot] = ord; |
| if (ord == 0) { |
| values[slot] = null; |
| } else { |
| assert ord > 0; |
| if (values[slot] == null) { |
| values[slot] = new BytesRef(); |
| } |
| termsIndex.lookup(ord, values[slot]); |
| } |
| readerGen[slot] = currentReaderGen; |
| } |
| } |
| |
| @Override |
| public FieldComparator setNextReader(AtomicReaderContext context) throws IOException { |
| final int docBase = context.docBase; |
| termsIndex = FieldCache.DEFAULT.getTermsIndex(context.reader, field); |
| final PackedInts.Reader docToOrd = termsIndex.getDocToOrd(); |
| FieldComparator perSegComp; |
| if (docToOrd instanceof Direct8) { |
| perSegComp = new ByteOrdComparator(((Direct8) docToOrd).getArray(), termsIndex, docBase); |
| } else if (docToOrd instanceof Direct16) { |
| perSegComp = new ShortOrdComparator(((Direct16) docToOrd).getArray(), termsIndex, docBase); |
| } else if (docToOrd instanceof Direct32) { |
| perSegComp = new IntOrdComparator(((Direct32) docToOrd).getArray(), termsIndex, docBase); |
| } else { |
| perSegComp = new AnyOrdComparator(docToOrd, termsIndex, docBase); |
| } |
| |
| currentReaderGen++; |
| if (bottomSlot != -1) { |
| perSegComp.setBottom(bottomSlot); |
| } |
| |
| return perSegComp; |
| } |
| |
| @Override |
| public void setBottom(final int bottom) { |
| bottomSlot = bottom; |
| |
| bottomValue = values[bottomSlot]; |
| if (currentReaderGen == readerGen[bottomSlot]) { |
| bottomOrd = ords[bottomSlot]; |
| bottomSameReader = true; |
| } else { |
| if (bottomValue == null) { |
| // 0 ord is null for all segments |
| assert ords[bottomSlot] == 0; |
| bottomOrd = 0; |
| bottomSameReader = true; |
| readerGen[bottomSlot] = currentReaderGen; |
| } else { |
| final int index = binarySearch(tempBR, termsIndex, bottomValue); |
| if (index < 0) { |
| bottomOrd = -index - 2; |
| bottomSameReader = false; |
| } else { |
| bottomOrd = index; |
| // exact value match |
| bottomSameReader = true; |
| readerGen[bottomSlot] = currentReaderGen; |
| ords[bottomSlot] = bottomOrd; |
| } |
| } |
| } |
| } |
| |
| @Override |
| public Comparable<?> value(int slot) { |
| return values[slot]; |
| } |
| } |
| |
| /** Sorts by field's natural Term sort order. All |
| * comparisons are done using BytesRef.compareTo, which is |
| * slow for medium to large result sets but possibly |
| * very fast for very small results sets. */ |
| public static final class TermValComparator extends FieldComparator { |
| |
| private BytesRef[] values; |
| private DocTerms docTerms; |
| private final String field; |
| private BytesRef bottom; |
| private final BytesRef tempBR = new BytesRef(); |
| |
| TermValComparator(int numHits, String field) { |
| values = new BytesRef[numHits]; |
| this.field = field; |
| } |
| |
| @Override |
| public int compare(int slot1, int slot2) { |
| final BytesRef val1 = values[slot1]; |
| final BytesRef val2 = values[slot2]; |
| if (val1 == null) { |
| if (val2 == null) { |
| return 0; |
| } |
| return -1; |
| } else if (val2 == null) { |
| return 1; |
| } |
| |
| return val1.compareTo(val2); |
| } |
| |
| @Override |
| public int compareBottom(int doc) { |
| BytesRef val2 = docTerms.getTerm(doc, tempBR); |
| if (bottom == null) { |
| if (val2 == null) { |
| return 0; |
| } |
| return -1; |
| } else if (val2 == null) { |
| return 1; |
| } |
| return bottom.compareTo(val2); |
| } |
| |
| @Override |
| public void copy(int slot, int doc) { |
| if (values[slot] == null) { |
| values[slot] = new BytesRef(); |
| } |
| docTerms.getTerm(doc, values[slot]); |
| } |
| |
| @Override |
| public FieldComparator setNextReader(AtomicReaderContext context) throws IOException { |
| docTerms = FieldCache.DEFAULT.getTerms(context.reader, field); |
| return this; |
| } |
| |
| @Override |
| public void setBottom(final int bottom) { |
| this.bottom = values[bottom]; |
| } |
| |
| @Override |
| public Comparable<?> value(int slot) { |
| return values[slot]; |
| } |
| } |
| |
| final protected static int binarySearch(BytesRef br, DocTermsIndex a, BytesRef key) { |
| return binarySearch(br, a, key, 1, a.numOrd()-1); |
| } |
| |
| final protected static int binarySearch(BytesRef br, DocTermsIndex a, BytesRef key, int low, int high) { |
| |
| while (low <= high) { |
| int mid = (low + high) >>> 1; |
| BytesRef midVal = a.lookup(mid, br); |
| int cmp; |
| if (midVal != null) { |
| cmp = midVal.compareTo(key); |
| } else { |
| cmp = -1; |
| } |
| |
| if (cmp < 0) |
| low = mid + 1; |
| else if (cmp > 0) |
| high = mid - 1; |
| else |
| return mid; |
| } |
| return -(low + 1); |
| } |
| } |