/*
 * 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.
 */

package org.apache.lucene.search;

import java.io.IOException;
import java.util.Objects;
import java.util.function.DoubleToLongFunction;
import java.util.function.LongToDoubleFunction;

import org.apache.lucene.index.DocValues;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.NumericDocValues;

Base class for producing DoubleValues To obtain a DoubleValues object for a leaf reader, clients should call rewrite(IndexSearcher) against the top-level searcher, and then call getValues(LeafReaderContext, DoubleValues) on the resulting DoubleValuesSource. DoubleValuesSource objects for NumericDocValues fields can be obtained by calling fromDoubleField(String), fromFloatField(String), fromIntField(String) or fromLongField(String), or from fromField(String, LongToDoubleFunction) if special long-to-double encoding is required. Scores may be used as a source for value calculations by wrapping a Scorer using fromScorer(Scorable) and passing the resulting DoubleValues to getValues(LeafReaderContext, DoubleValues). The scores can then be accessed using the SCORES DoubleValuesSource.
/** * Base class for producing {@link DoubleValues} * * To obtain a {@link DoubleValues} object for a leaf reader, clients should call * {@link #rewrite(IndexSearcher)} against the top-level searcher, and then * call {@link #getValues(LeafReaderContext, DoubleValues)} on the resulting * DoubleValuesSource. * * DoubleValuesSource objects for NumericDocValues fields can be obtained by calling * {@link #fromDoubleField(String)}, {@link #fromFloatField(String)}, {@link #fromIntField(String)} * or {@link #fromLongField(String)}, or from {@link #fromField(String, LongToDoubleFunction)} if * special long-to-double encoding is required. * * Scores may be used as a source for value calculations by wrapping a {@link Scorer} using * {@link #fromScorer(Scorable)} and passing the resulting DoubleValues to {@link #getValues(LeafReaderContext, DoubleValues)}. * The scores can then be accessed using the {@link #SCORES} DoubleValuesSource. */
public abstract class DoubleValuesSource implements SegmentCacheable {
Returns a DoubleValues instance for the passed-in LeafReaderContext and scores If scores are not needed to calculate the values (ie returns false, callers may safely pass null for the scores parameter.
/** * Returns a {@link DoubleValues} instance for the passed-in LeafReaderContext and scores * * If scores are not needed to calculate the values (ie {@link #needsScores() returns false}, callers * may safely pass {@code null} for the {@code scores} parameter. */
public abstract DoubleValues getValues(LeafReaderContext ctx, DoubleValues scores) throws IOException;
Return true if document scores are needed to calculate values
/** * Return true if document scores are needed to calculate values */
public abstract boolean needsScores();
An explanation of the value for the named document.
Params:
  • ctx – the readers context to create the Explanation for.
  • docId – the document's id relative to the given context's reader
Throws:
Returns:an Explanation for the value
/** * An explanation of the value for the named document. * * @param ctx the readers context to create the {@link Explanation} for. * @param docId the document's id relative to the given context's reader * @return an Explanation for the value * @throws IOException if an {@link IOException} occurs */
public Explanation explain(LeafReaderContext ctx, int docId, Explanation scoreExplanation) throws IOException { DoubleValues dv = getValues(ctx, DoubleValuesSource.constant(scoreExplanation.getValue().doubleValue()).getValues(ctx, null)); if (dv.advanceExact(docId)) return Explanation.match(dv.doubleValue(), this.toString()); return Explanation.noMatch(this.toString()); }
Return a DoubleValuesSource specialised for the given IndexSearcher Implementations should assume that this will only be called once. IndexReader-independent implementations can just return this Queries that use DoubleValuesSource objects should call rewrite() during Query.createWeight(IndexSearcher, ScoreMode, float) rather than during Query.rewrite(IndexReader) to avoid IndexReader reference leakage. For the same reason, implementations that cache references to the IndexSearcher should return a new object from this method.
/** * Return a DoubleValuesSource specialised for the given IndexSearcher * * Implementations should assume that this will only be called once. * IndexReader-independent implementations can just return {@code this} * * Queries that use DoubleValuesSource objects should call rewrite() during * {@link Query#createWeight(IndexSearcher, ScoreMode, float)} rather than during * {@link Query#rewrite(IndexReader)} to avoid IndexReader reference leakage. * * For the same reason, implementations that cache references to the IndexSearcher * should return a new object from this method. */
public abstract DoubleValuesSource rewrite(IndexSearcher reader) throws IOException;
Create a sort field based on the value of this producer
Params:
  • reverse – true if the sort should be decreasing
/** * Create a sort field based on the value of this producer * @param reverse true if the sort should be decreasing */
public SortField getSortField(boolean reverse) { return new DoubleValuesSortField(this, reverse); } @Override public abstract int hashCode(); @Override public abstract boolean equals(Object obj); @Override public abstract String toString();
Convert to a LongValuesSource by casting the double values to longs
/** * Convert to a LongValuesSource by casting the double values to longs */
public final LongValuesSource toLongValuesSource() { return new LongDoubleValuesSource(this); } private static class LongDoubleValuesSource extends LongValuesSource { private final DoubleValuesSource inner; private LongDoubleValuesSource(DoubleValuesSource inner) { this.inner = inner; } @Override public LongValues getValues(LeafReaderContext ctx, DoubleValues scores) throws IOException { DoubleValues in = inner.getValues(ctx, scores); return new LongValues() { @Override public long longValue() throws IOException { return (long) in.doubleValue(); } @Override public boolean advanceExact(int doc) throws IOException { return in.advanceExact(doc); } }; } @Override public boolean isCacheable(LeafReaderContext ctx) { return inner.isCacheable(ctx); } @Override public boolean needsScores() { return inner.needsScores(); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; LongDoubleValuesSource that = (LongDoubleValuesSource) o; return Objects.equals(inner, that.inner); } @Override public int hashCode() { return Objects.hash(inner); } @Override public String toString() { return "long(" + inner.toString() + ")"; } @Override public LongValuesSource rewrite(IndexSearcher searcher) throws IOException { return inner.rewrite(searcher).toLongValuesSource(); } }
Creates a DoubleValuesSource that wraps a generic NumericDocValues field
Params:
  • field – the field to wrap, must have NumericDocValues
  • decoder – a function to convert the long-valued doc values to doubles
/** * Creates a DoubleValuesSource that wraps a generic NumericDocValues field * * @param field the field to wrap, must have NumericDocValues * @param decoder a function to convert the long-valued doc values to doubles */
public static DoubleValuesSource fromField(String field, LongToDoubleFunction decoder) { return new FieldValuesSource(field, decoder); }
Creates a DoubleValuesSource that wraps a double-valued field
/** * Creates a DoubleValuesSource that wraps a double-valued field */
public static DoubleValuesSource fromDoubleField(String field) { return fromField(field, Double::longBitsToDouble); }
Creates a DoubleValuesSource that wraps a float-valued field
/** * Creates a DoubleValuesSource that wraps a float-valued field */
public static DoubleValuesSource fromFloatField(String field) { return fromField(field, (v) -> (double)Float.intBitsToFloat((int)v)); }
Creates a DoubleValuesSource that wraps a long-valued field
/** * Creates a DoubleValuesSource that wraps a long-valued field */
public static DoubleValuesSource fromLongField(String field) { return fromField(field, (v) -> (double) v); }
Creates a DoubleValuesSource that wraps an int-valued field
/** * Creates a DoubleValuesSource that wraps an int-valued field */
public static DoubleValuesSource fromIntField(String field) { return fromLongField(field); }
A DoubleValuesSource that exposes a document's score If this source is used as part of a values calculation, then callers must not pass null as the DoubleValues parameter on getValues(LeafReaderContext, DoubleValues)
/** * A DoubleValuesSource that exposes a document's score * * If this source is used as part of a values calculation, then callers must not * pass {@code null} as the {@link DoubleValues} parameter on {@link #getValues(LeafReaderContext, DoubleValues)} */
public static final DoubleValuesSource SCORES = new DoubleValuesSource() { @Override public DoubleValues getValues(LeafReaderContext ctx, DoubleValues scores) throws IOException { assert scores != null; return scores; } @Override public boolean needsScores() { return true; } @Override public boolean isCacheable(LeafReaderContext ctx) { return false; } @Override public Explanation explain(LeafReaderContext ctx, int docId, Explanation scoreExplanation) { return scoreExplanation; } @Override public int hashCode() { return 0; } @Override public boolean equals(Object obj) { return obj == this; } @Override public String toString() { return "scores"; } @Override public DoubleValuesSource rewrite(IndexSearcher searcher) { return this; } };
Creates a DoubleValuesSource that always returns a constant value
/** * Creates a DoubleValuesSource that always returns a constant value */
public static DoubleValuesSource constant(double value) { return new ConstantValuesSource(value); } private static class ConstantValuesSource extends DoubleValuesSource { private final double value; private ConstantValuesSource(double value) { this.value = value; } @Override public DoubleValuesSource rewrite(IndexSearcher searcher) { return this; } @Override public DoubleValues getValues(LeafReaderContext ctx, DoubleValues scores) throws IOException { return new DoubleValues() { @Override public double doubleValue() throws IOException { return value; } @Override public boolean advanceExact(int doc) throws IOException { return true; } }; } @Override public boolean needsScores() { return false; } @Override public boolean isCacheable(LeafReaderContext ctx) { return true; } @Override public Explanation explain(LeafReaderContext ctx, int docId, Explanation scoreExplanation) { return Explanation.match(value, "constant(" + value + ")"); } @Override public int hashCode() { return Objects.hash(value); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ConstantValuesSource that = (ConstantValuesSource) o; return Double.compare(that.value, value) == 0; } @Override public String toString() { return "constant(" + value + ")"; } }
Returns a DoubleValues instance that wraps scores returned by a Scorer
/** * Returns a DoubleValues instance that wraps scores returned by a Scorer */
public static DoubleValues fromScorer(Scorable scorer) { return new DoubleValues() { @Override public double doubleValue() throws IOException { return scorer.score(); } @Override public boolean advanceExact(int doc) throws IOException { assert scorer.docID() == doc; return true; } }; } private static class FieldValuesSource extends DoubleValuesSource { final String field; final LongToDoubleFunction decoder; private FieldValuesSource(String field, LongToDoubleFunction decoder) { this.field = field; this.decoder = decoder; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; FieldValuesSource that = (FieldValuesSource) o; return Objects.equals(field, that.field) && Objects.equals(decoder, that.decoder); } @Override public String toString() { return "double(" + field + ")"; } @Override public int hashCode() { return Objects.hash(field, decoder); } @Override public DoubleValues getValues(LeafReaderContext ctx, DoubleValues scores) throws IOException { final NumericDocValues values = DocValues.getNumeric(ctx.reader(), field); return new DoubleValues() { @Override public double doubleValue() throws IOException { return decoder.applyAsDouble(values.longValue()); } @Override public boolean advanceExact(int target) throws IOException { return values.advanceExact(target); } }; } @Override public boolean needsScores() { return false; } @Override public boolean isCacheable(LeafReaderContext ctx) { return DocValues.isCacheable(ctx, field); } @Override public Explanation explain(LeafReaderContext ctx, int docId, Explanation scoreExplanation) throws IOException { DoubleValues values = getValues(ctx, null); if (values.advanceExact(docId)) return Explanation.match(values.doubleValue(), this.toString()); else return Explanation.noMatch(this.toString()); } public DoubleValuesSource rewrite(IndexSearcher searcher) throws IOException { return this; } } private static class DoubleValuesSortField extends SortField { final DoubleValuesSource producer; DoubleValuesSortField(DoubleValuesSource producer, boolean reverse) { super(producer.toString(), new DoubleValuesComparatorSource(producer), reverse); this.producer = producer; } @Override public void setMissingValue(Object missingValue) { if (missingValue instanceof Number) { this.missingValue = missingValue; ((DoubleValuesComparatorSource) getComparatorSource()).setMissingValue(((Number) missingValue).doubleValue()); } else { super.setMissingValue(missingValue); } } @Override public boolean needsScores() { return producer.needsScores(); } @Override public String toString() { StringBuilder buffer = new StringBuilder("<"); buffer.append(getField()).append(">"); if (reverse) buffer.append("!"); return buffer.toString(); } @Override public SortField rewrite(IndexSearcher searcher) throws IOException { DoubleValuesSortField rewritten = new DoubleValuesSortField(producer.rewrite(searcher), reverse); if (missingValue != null) { rewritten.setMissingValue(missingValue); } return rewritten; } } private static class DoubleValuesHolder { DoubleValues values; } private static class DoubleValuesComparatorSource extends FieldComparatorSource { private final DoubleValuesSource producer; private double missingValue; DoubleValuesComparatorSource(DoubleValuesSource producer) { this.producer = producer; this.missingValue = 0d; } void setMissingValue(double missingValue) { this.missingValue = missingValue; } @Override public FieldComparator<Double> newComparator(String fieldname, int numHits, int sortPos, boolean reversed) { return new FieldComparator.DoubleComparator(numHits, fieldname, missingValue){ LeafReaderContext ctx; DoubleValuesHolder holder = new DoubleValuesHolder(); @Override protected NumericDocValues getNumericDocValues(LeafReaderContext context, String field) throws IOException { ctx = context; return asNumericDocValues(holder, Double::doubleToLongBits); } @Override public void setScorer(Scorable scorer) throws IOException { holder.values = producer.getValues(ctx, fromScorer(scorer)); } }; } } private static NumericDocValues asNumericDocValues(DoubleValuesHolder in, DoubleToLongFunction converter) { return new NumericDocValues() { @Override public long longValue() throws IOException { return converter.applyAsLong(in.values.doubleValue()); } @Override public boolean advanceExact(int target) throws IOException { return in.values.advanceExact(target); } @Override public int docID() { throw new UnsupportedOperationException(); } @Override public int nextDoc() throws IOException { throw new UnsupportedOperationException(); } @Override public int advance(int target) throws IOException { throw new UnsupportedOperationException(); } @Override public long cost() { throw new UnsupportedOperationException(); } }; }
Create a DoubleValuesSource that returns the score of a particular query
/** * Create a DoubleValuesSource that returns the score of a particular query */
public static DoubleValuesSource fromQuery(Query query) { return new QueryDoubleValuesSource(query); } private static class QueryDoubleValuesSource extends DoubleValuesSource { private final Query query; private QueryDoubleValuesSource(Query query) { this.query = query; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; QueryDoubleValuesSource that = (QueryDoubleValuesSource) o; return Objects.equals(query, that.query); } @Override public int hashCode() { return Objects.hash(query); } @Override public DoubleValues getValues(LeafReaderContext ctx, DoubleValues scores) throws IOException { throw new UnsupportedOperationException("This DoubleValuesSource must be rewritten"); } @Override public boolean needsScores() { return false; } @Override public DoubleValuesSource rewrite(IndexSearcher searcher) throws IOException { return new WeightDoubleValuesSource(searcher.rewrite(query).createWeight(searcher, ScoreMode.COMPLETE, 1f)); } @Override public String toString() { return "score(" + query.toString() + ")"; } @Override public boolean isCacheable(LeafReaderContext ctx) { return false; } } private static class WeightDoubleValuesSource extends DoubleValuesSource { private final Weight weight; private WeightDoubleValuesSource(Weight weight) { this.weight = weight; } @Override public DoubleValues getValues(LeafReaderContext ctx, DoubleValues scores) throws IOException { Scorer scorer = weight.scorer(ctx); if (scorer == null) return DoubleValues.EMPTY; DocIdSetIterator it = scorer.iterator(); return new DoubleValues() { @Override public double doubleValue() throws IOException { return scorer.score(); } @Override public boolean advanceExact(int doc) throws IOException { if (it.docID() > doc) return false; return it.docID() == doc || it.advance(doc) == doc; } }; } @Override public Explanation explain(LeafReaderContext ctx, int docId, Explanation scoreExplanation) throws IOException { return weight.explain(ctx, docId); } @Override public boolean needsScores() { return false; } @Override public DoubleValuesSource rewrite(IndexSearcher searcher) throws IOException { return this; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; WeightDoubleValuesSource that = (WeightDoubleValuesSource) o; return Objects.equals(weight, that.weight); } @Override public int hashCode() { return Objects.hash(weight); } @Override public String toString() { return "score(" + weight.parentQuery.toString() + ")"; } @Override public boolean isCacheable(LeafReaderContext ctx) { return false; } } }