blob: fa2a2f6771ce86a92112675da4352cd1874edcc0 [file] [log] [blame]
/*
* 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 org.apache.lucene.index.DocValues;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.NumericDocValues;
import org.apache.lucene.index.PointValues;
import org.apache.lucene.search.comparators.LongComparator;
/**
* Base class for producing {@link LongValues}
*
* To obtain a {@link LongValues} object for a leaf reader, clients should
* call {@link #rewrite(IndexSearcher)} against the top-level searcher, and
* then {@link #getValues(LeafReaderContext, DoubleValues)}.
*
* LongValuesSource objects for long and int-valued NumericDocValues fields can
* be obtained by calling {@link #fromLongField(String)} and {@link #fromIntField(String)}.
*
* To obtain a LongValuesSource from a float or double-valued NumericDocValues field,
* use {@link DoubleValuesSource#fromFloatField(String)} or {@link DoubleValuesSource#fromDoubleField(String)}
* and then call {@link DoubleValuesSource#toLongValuesSource()}.
*/
public abstract class LongValuesSource implements SegmentCacheable {
/**
* Returns a {@link LongValues} 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 LongValues getValues(LeafReaderContext ctx, DoubleValues scores) throws IOException;
/**
* Return true if document scores are needed to calculate values
*/
public abstract boolean needsScores();
@Override
public abstract int hashCode();
@Override
public abstract boolean equals(Object obj);
@Override
public abstract String toString();
/**
* Return a LongValuesSource specialised for the given IndexSearcher
*
* Implementations should assume that this will only be called once.
* IndexSearcher-independent implementations can just return {@code this}
*/
public abstract LongValuesSource rewrite(IndexSearcher searcher) 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 LongValuesSortField(this, reverse);
}
/**
* Convert to a DoubleValuesSource by casting long values to doubles
*/
public DoubleValuesSource toDoubleValuesSource() {
return new DoubleLongValuesSource(this);
}
private static class DoubleLongValuesSource extends DoubleValuesSource {
private final LongValuesSource inner;
private DoubleLongValuesSource(LongValuesSource inner) {
this.inner = inner;
}
@Override
public DoubleValues getValues(LeafReaderContext ctx, DoubleValues scores) throws IOException {
LongValues v = inner.getValues(ctx, scores);
return new DoubleValues() {
@Override
public double doubleValue() throws IOException {
return (double) v.longValue();
}
@Override
public boolean advanceExact(int doc) throws IOException {
return v.advanceExact(doc);
}
};
}
@Override
public DoubleValuesSource rewrite(IndexSearcher searcher) throws IOException {
return inner.rewrite(searcher).toDoubleValuesSource();
}
@Override
public boolean isCacheable(LeafReaderContext ctx) {
return inner.isCacheable(ctx);
}
@Override
public String toString() {
return "double(" + inner.toString() + ")";
}
@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;
DoubleLongValuesSource that = (DoubleLongValuesSource) o;
return Objects.equals(inner, that.inner);
}
@Override
public int hashCode() {
return Objects.hash(inner);
}
}
/**
* Creates a LongValuesSource that wraps a long-valued field
*/
public static LongValuesSource fromLongField(String field) {
return new FieldValuesSource(field);
}
/**
* Creates a LongValuesSource that wraps an int-valued field
*/
public static LongValuesSource fromIntField(String field) {
return fromLongField(field);
}
/**
* Creates a LongValuesSource that always returns a constant value
*/
public static LongValuesSource constant(long value) {
return new ConstantLongValuesSource(value);
}
private static class ConstantLongValuesSource extends LongValuesSource {
private final long value;
private ConstantLongValuesSource(long value) {
this.value = value;
}
@Override
public LongValues getValues(LeafReaderContext ctx, DoubleValues scores) throws IOException {
return new LongValues() {
@Override
public long longValue() throws IOException {
return value;
}
@Override
public boolean advanceExact(int doc) throws IOException {
return true;
}
};
}
@Override
public boolean isCacheable(LeafReaderContext ctx) {
return true;
}
@Override
public boolean needsScores() {
return false;
}
@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;
ConstantLongValuesSource that = (ConstantLongValuesSource) o;
return value == that.value;
}
@Override
public String toString() {
return "constant(" + value + ")";
}
@Override
public LongValuesSource rewrite(IndexSearcher searcher) throws IOException {
return this;
}
}
private static class FieldValuesSource extends LongValuesSource {
final String field;
private FieldValuesSource(String field) {
this.field = field;
}
@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);
}
@Override
public String toString() {
return "long(" + field + ")";
}
@Override
public int hashCode() {
return Objects.hash(field);
}
@Override
public LongValues getValues(LeafReaderContext ctx, DoubleValues scores) throws IOException {
final NumericDocValues values = DocValues.getNumeric(ctx.reader(), field);
return toLongValues(values);
}
@Override
public boolean isCacheable(LeafReaderContext ctx) {
return DocValues.isCacheable(ctx, field);
}
@Override
public boolean needsScores() {
return false;
}
@Override
public LongValuesSource rewrite(IndexSearcher searcher) throws IOException {
return this;
}
}
private static class LongValuesSortField extends SortField {
final LongValuesSource producer;
public LongValuesSortField(LongValuesSource producer, boolean reverse) {
super(producer.toString(), new LongValuesComparatorSource(producer), reverse);
this.producer = producer;
}
@Override
public void setMissingValue(Object missingValue) {
if (missingValue instanceof Number) {
this.missingValue = missingValue;
((LongValuesComparatorSource) getComparatorSource()).setMissingValue(((Number) missingValue).longValue());
} 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 {
LongValuesSortField rewritten = new LongValuesSortField(producer.rewrite(searcher), reverse);
if (missingValue != null) {
rewritten.setMissingValue(missingValue);
}
return rewritten;
}
}
private static class LongValuesHolder {
LongValues values;
}
private static class LongValuesComparatorSource extends FieldComparatorSource {
private final LongValuesSource producer;
private long missingValue;
public LongValuesComparatorSource(LongValuesSource producer) {
this.producer = producer;
this.missingValue = 0L;
}
void setMissingValue(long missingValue) {
this.missingValue = missingValue;
}
@Override
public FieldComparator<Long> newComparator(String fieldname, int numHits,
int sortPos, boolean reversed) {
return new LongComparator(numHits, fieldname, missingValue, reversed, sortPos) {
@Override
public LeafFieldComparator getLeafComparator(LeafReaderContext context) throws IOException {
LongValuesHolder holder = new LongValuesHolder();
return new LongComparator.LongLeafComparator(context) {
LeafReaderContext ctx;
@Override
protected NumericDocValues getNumericDocValues(LeafReaderContext context, String field) {
ctx = context;
return asNumericDocValues(holder);
}
@Override
protected PointValues getPointValues(LeafReaderContext context, String field) {
return null;
}
@Override
public void setScorer(Scorable scorer) throws IOException {
holder.values = producer.getValues(ctx, DoubleValuesSource.fromScorer(scorer));
super.setScorer(scorer);
}
};
}
};
}
}
private static LongValues toLongValues(NumericDocValues in) {
return new LongValues() {
@Override
public long longValue() throws IOException {
return in.longValue();
}
@Override
public boolean advanceExact(int target) throws IOException {
return in.advanceExact(target);
}
};
}
private static NumericDocValues asNumericDocValues(LongValuesHolder in) {
return new NumericDocValues() {
@Override
public long longValue() throws IOException {
return in.values.longValue();
}
@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();
}
};
}
}