/*
 * 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.queries.function;

import java.io.IOException;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Objects;

import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.search.DoubleValues;
import org.apache.lucene.search.DoubleValuesSource;
import org.apache.lucene.search.Explanation;
import org.apache.lucene.search.FieldComparator;
import org.apache.lucene.search.FieldComparatorSource;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.LongValues;
import org.apache.lucene.search.LongValuesSource;
import org.apache.lucene.search.Scorable;
import org.apache.lucene.search.SimpleFieldComparator;
import org.apache.lucene.search.SortField;

Instantiates FunctionValues for a particular reader.
Often used when creating a FunctionQuery.
/** * Instantiates {@link FunctionValues} for a particular reader. * <br> * Often used when creating a {@link FunctionQuery}. * * */
public abstract class ValueSource {
Gets the values for this reader and the context that was previously passed to createWeight(). The values must be consumed in a forward docID manner, and you must call this method again to iterate through the values again.
/** * Gets the values for this reader and the context that was previously * passed to createWeight(). The values must be consumed in a forward * docID manner, and you must call this method again to iterate through * the values again. */
public abstract FunctionValues getValues(Map context, LeafReaderContext readerContext) throws IOException; @Override public abstract boolean equals(Object o); @Override public abstract int hashCode();
description of field, used in explain()
/** * description of field, used in explain() */
public abstract String description(); @Override public String toString() { return description(); }
Implementations should propagate createWeight to sub-ValueSources which can optionally store weight info in the context. The context object will be passed to getValues() where this info can be retrieved.
/** * Implementations should propagate createWeight to sub-ValueSources which can optionally store * weight info in the context. The context object will be passed to getValues() * where this info can be retrieved. */
public void createWeight(Map context, IndexSearcher searcher) throws IOException { }
Returns a new non-threadsafe context map.
/** * Returns a new non-threadsafe context map. */
public static Map newContext(IndexSearcher searcher) { Map context = new IdentityHashMap(); context.put("searcher", searcher); return context; } private static class ScoreAndDoc extends Scorable { int current = -1; float score = 0; @Override public int docID() { return current; } @Override public float score() { return score; } }
Expose this ValueSource as a LongValuesSource
/** * Expose this ValueSource as a LongValuesSource */
public LongValuesSource asLongValuesSource() { return new WrappedLongValuesSource(this); } private static class WrappedLongValuesSource extends LongValuesSource { private final ValueSource in; private WrappedLongValuesSource(ValueSource in) { this.in = in; } @Override public LongValues getValues(LeafReaderContext ctx, DoubleValues scores) throws IOException { Map context = new IdentityHashMap<>(); ScoreAndDoc scorer = new ScoreAndDoc(); context.put("scorer", scorer); final FunctionValues fv = in.getValues(context, ctx); return new LongValues() { @Override public long longValue() throws IOException { return fv.longVal(scorer.current); } @Override public boolean advanceExact(int doc) throws IOException { scorer.current = doc; if (scores != null && scores.advanceExact(doc)) scorer.score = (float) scores.doubleValue(); else scorer.score = 0; return fv.exists(doc); } }; } @Override public boolean isCacheable(LeafReaderContext ctx) { return false; } @Override public boolean needsScores() { return false; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; WrappedLongValuesSource that = (WrappedLongValuesSource) o; return Objects.equals(in, that.in); } @Override public int hashCode() { return Objects.hash(in); } @Override public String toString() { return in.toString(); } @Override public LongValuesSource rewrite(IndexSearcher searcher) throws IOException { return this; } }
Expose this ValueSource as a DoubleValuesSource
/** * Expose this ValueSource as a DoubleValuesSource */
public DoubleValuesSource asDoubleValuesSource() { return new WrappedDoubleValuesSource(this, null); } static class WrappedDoubleValuesSource extends DoubleValuesSource { final ValueSource in; final IndexSearcher searcher; private WrappedDoubleValuesSource(ValueSource in, IndexSearcher searcher) { this.in = in; this.searcher = searcher; } @Override public DoubleValues getValues(LeafReaderContext ctx, DoubleValues scores) throws IOException { Map context = new HashMap<>(); ScoreAndDoc scorer = new ScoreAndDoc(); context.put("scorer", scorer); context.put("searcher", searcher); FunctionValues fv = in.getValues(context, ctx); return new DoubleValues() { @Override public double doubleValue() throws IOException { return fv.doubleVal(scorer.current); } @Override public boolean advanceExact(int doc) throws IOException { scorer.current = doc; if (scores != null && scores.advanceExact(doc)) { scorer.score = (float) scores.doubleValue(); } else scorer.score = 0; // ValueSource will return values even if exists() is false, generally a default // of some kind. To preserve this behaviour with the iterator, we need to always // return 'true' here. return true; } }; } @Override public boolean needsScores() { return true; // be on the safe side } @Override public boolean isCacheable(LeafReaderContext ctx) { return false; } @Override public Explanation explain(LeafReaderContext ctx, int docId, Explanation scoreExplanation) throws IOException { Map context = new HashMap<>(); ScoreAndDoc scorer = new ScoreAndDoc(); scorer.score = scoreExplanation.getValue().floatValue(); context.put("scorer", scorer); context.put("searcher", searcher); FunctionValues fv = in.getValues(context, ctx); return fv.explain(docId); } @Override public DoubleValuesSource rewrite(IndexSearcher searcher) throws IOException { return new WrappedDoubleValuesSource(in, searcher); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; WrappedDoubleValuesSource that = (WrappedDoubleValuesSource) o; return Objects.equals(in, that.in); } @Override public int hashCode() { return Objects.hash(in); } @Override public String toString() { return in.toString(); } } public static ValueSource fromDoubleValuesSource(DoubleValuesSource in) { return new FromDoubleValuesSource(in); } private static class FromDoubleValuesSource extends ValueSource { final DoubleValuesSource in; private FromDoubleValuesSource(DoubleValuesSource in) { this.in = in; } @Override public FunctionValues getValues(Map context, LeafReaderContext readerContext) throws IOException { Scorable scorer = (Scorable) context.get("scorer"); DoubleValues scores = scorer == null ? null : DoubleValuesSource.fromScorer(scorer); IndexSearcher searcher = (IndexSearcher) context.get("searcher"); DoubleValues inner; if (searcher != null) inner = in.rewrite(searcher).getValues(readerContext, scores); else inner = in.getValues(readerContext, scores); return new FunctionValues() { @Override public String toString(int doc) throws IOException { return in.toString(); } @Override public float floatVal(int doc) throws IOException { if (inner.advanceExact(doc) == false) return 0; return (float) inner.doubleValue(); } @Override public double doubleVal(int doc) throws IOException { if (inner.advanceExact(doc) == false) return 0; return inner.doubleValue(); } @Override public boolean exists(int doc) throws IOException { return inner.advanceExact(doc); } }; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; FromDoubleValuesSource that = (FromDoubleValuesSource) o; return Objects.equals(in, that.in); } @Override public int hashCode() { return Objects.hash(in); } @Override public String description() { return in.toString(); } } // // Sorting by function //
EXPERIMENTAL: This method is subject to change.

Get the SortField for this ValueSource. Uses the getValues(Map, LeafReaderContext) to populate the SortField.

Params:
  • reverse – true if this is a reverse sort.
Returns:The SortField for the ValueSource
/** * EXPERIMENTAL: This method is subject to change. * <p> * Get the SortField for this ValueSource. Uses the {@link #getValues(java.util.Map, org.apache.lucene.index.LeafReaderContext)} * to populate the SortField. * * @param reverse true if this is a reverse sort. * @return The {@link org.apache.lucene.search.SortField} for the ValueSource */
public SortField getSortField(boolean reverse) { return new ValueSourceSortField(reverse); } class ValueSourceSortField extends SortField { public ValueSourceSortField(boolean reverse) { super(description(), SortField.Type.REWRITEABLE, reverse); } @Override public SortField rewrite(IndexSearcher searcher) throws IOException { Map context = newContext(searcher); createWeight(context, searcher); return new SortField(getField(), new ValueSourceComparatorSource(context), getReverse()); } } class ValueSourceComparatorSource extends FieldComparatorSource { private final Map context; public ValueSourceComparatorSource(Map context) { this.context = context; } @Override public FieldComparator<Double> newComparator(String fieldname, int numHits, int sortPos, boolean reversed) { return new ValueSourceComparator(context, numHits); } }
Implement a FieldComparator that works off of the FunctionValues for a ValueSource instead of the normal Lucene FieldComparator that works off of a FieldCache.
/** * Implement a {@link org.apache.lucene.search.FieldComparator} that works * off of the {@link FunctionValues} for a ValueSource * instead of the normal Lucene FieldComparator that works off of a FieldCache. */
class ValueSourceComparator extends SimpleFieldComparator<Double> { private final double[] values; private FunctionValues docVals; private double bottom; private final Map fcontext; private double topValue; ValueSourceComparator(Map fcontext, int numHits) { this.fcontext = fcontext; values = new double[numHits]; } @Override public int compare(int slot1, int slot2) { return Double.compare(values[slot1], values[slot2]); } @Override public int compareBottom(int doc) throws IOException { return Double.compare(bottom, docVals.doubleVal(doc)); } @Override public void copy(int slot, int doc) throws IOException { values[slot] = docVals.doubleVal(doc); } @Override public void doSetNextReader(LeafReaderContext context) throws IOException { docVals = getValues(fcontext, context); } @Override public void setBottom(final int bottom) { this.bottom = values[bottom]; } @Override public void setTopValue(final Double value) { this.topValue = value.doubleValue(); } @Override public Double value(int slot) { return values[slot]; } @Override public int compareTop(int doc) throws IOException { final double docValue = docVals.doubleVal(doc); return Double.compare(topValue, docValue); } } }