Java tutorial
/* * 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 {@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 {@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 */ public abstract boolean needsScores(); /** * 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 {@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 * @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 */ 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 * * @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 */ public static DoubleValuesSource fromDoubleField(String field) { return fromField(field, Double::longBitsToDouble); } /** * 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 */ public static DoubleValuesSource fromLongField(String field) { return fromField(field, (v) -> (double) v); } /** * 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 {@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 */ 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 */ 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 */ 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; } } }