blob: ec21cba36a995c062fd76436bfd7b706fefa0dfa [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 org.apache.lucene.index.DocValues;
import org.apache.lucene.index.IndexSorter;
import org.apache.lucene.index.LeafReader;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.NumericDocValues;
import org.apache.lucene.index.PointValues;
import org.apache.lucene.index.SortFieldProvider;
import org.apache.lucene.index.SortedNumericDocValues;
import org.apache.lucene.search.comparators.DoubleComparator;
import org.apache.lucene.search.comparators.FloatComparator;
import org.apache.lucene.search.comparators.IntComparator;
import org.apache.lucene.search.comparators.LongComparator;
import org.apache.lucene.store.DataInput;
import org.apache.lucene.store.DataOutput;
import org.apache.lucene.util.NumericUtils;
/**
* SortField for {@link SortedNumericDocValues}.
* <p>
* A SortedNumericDocValues contains multiple values for a field, so sorting with
* this technique "selects" a value as the representative sort value for the document.
* <p>
* By default, the minimum value in the list is selected as the sort value, but
* this can be customized.
* <p>
* Like sorting by string, this also supports sorting missing values as first or last,
* via {@link #setMissingValue(Object)}.
* @see SortedNumericSelector
*/
public class SortedNumericSortField extends SortField {
private final SortedNumericSelector.Type selector;
private final SortField.Type type;
/**
* Creates a sort, by the minimum value in the set
* for the document.
* @param field Name of field to sort by. Must not be null.
* @param type Type of values
*/
public SortedNumericSortField(String field, SortField.Type type) {
this(field, type, false);
}
/**
* Creates a sort, possibly in reverse, by the minimum value in the set
* for the document.
* @param field Name of field to sort by. Must not be null.
* @param type Type of values
* @param reverse True if natural order should be reversed.
*/
public SortedNumericSortField(String field, SortField.Type type, boolean reverse) {
this(field, type, reverse, SortedNumericSelector.Type.MIN);
}
/**
* Creates a sort, possibly in reverse, specifying how the sort value from
* the document's set is selected.
* @param field Name of field to sort by. Must not be null.
* @param type Type of values
* @param reverse True if natural order should be reversed.
* @param selector custom selector type for choosing the sort value from the set.
*/
public SortedNumericSortField(String field, SortField.Type type, boolean reverse, SortedNumericSelector.Type selector) {
super(field, SortField.Type.CUSTOM, reverse);
if (selector == null) {
throw new NullPointerException();
}
if (type == null) {
throw new NullPointerException();
}
this.selector = selector;
this.type = type;
}
/** A SortFieldProvider for this sort field */
public static final class Provider extends SortFieldProvider {
/** The name this provider is registered under */
public static final String NAME = "SortedNumericSortField";
/** Creates a new Provider */
public Provider() {
super(NAME);
}
@Override
public SortField readSortField(DataInput in) throws IOException {
SortedNumericSortField sf = new SortedNumericSortField(in.readString(), readType(in), in.readInt() == 1, readSelectorType(in));
if (in.readInt() == 1) {
switch (sf.type) {
case INT:
sf.setMissingValue(in.readInt());
break;
case LONG:
sf.setMissingValue(in.readLong());
break;
case FLOAT:
sf.setMissingValue(NumericUtils.sortableIntToFloat(in.readInt()));
break;
case DOUBLE:
sf.setMissingValue(NumericUtils.sortableLongToDouble(in.readLong()));
break;
default:
throw new AssertionError();
}
}
return sf;
}
@Override
public void writeSortField(SortField sf, DataOutput out) throws IOException {
assert sf instanceof SortedNumericSortField;
((SortedNumericSortField)sf).serialize(out);
}
}
private static SortedNumericSelector.Type readSelectorType(DataInput in) throws IOException {
int selectorType = in.readInt();
if (selectorType >= SortedNumericSelector.Type.values().length) {
throw new IllegalArgumentException("Can't deserialize SortedNumericSortField - unknown selector type " + selectorType);
}
return SortedNumericSelector.Type.values()[selectorType];
}
private void serialize(DataOutput out) throws IOException {
out.writeString(getField());
out.writeString(type.toString());
out.writeInt(reverse ? 1 : 0);
out.writeInt(selector.ordinal());
if (missingValue == null) {
out.writeInt(0);
}
else {
out.writeInt(1);
// oh for switch expressions...
switch (type) {
case INT:
out.writeInt((int)missingValue);
break;
case LONG:
out.writeLong((long)missingValue);
break;
case FLOAT:
out.writeInt(NumericUtils.floatToSortableInt((float)missingValue));
break;
case DOUBLE:
out.writeLong(NumericUtils.doubleToSortableLong((double)missingValue));
break;
default:
throw new AssertionError();
}
}
}
/** Returns the numeric type in use for this sort */
public SortField.Type getNumericType() {
return type;
}
/** Returns the selector in use for this sort */
public SortedNumericSelector.Type getSelector() {
return selector;
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + selector.hashCode();
result = prime * result + type.hashCode();
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!super.equals(obj)) return false;
if (getClass() != obj.getClass()) return false;
SortedNumericSortField other = (SortedNumericSortField) obj;
if (selector != other.selector) return false;
if (type != other.type) return false;
return true;
}
@Override
public String toString() {
StringBuilder buffer = new StringBuilder();
buffer.append("<sortednumeric" + ": \"").append(getField()).append("\">");
if (getReverse()) buffer.append('!');
if (missingValue != null) {
buffer.append(" missingValue=");
buffer.append(missingValue);
}
buffer.append(" selector=");
buffer.append(selector);
buffer.append(" type=");
buffer.append(type);
return buffer.toString();
}
@Override
public void setMissingValue(Object missingValue) {
this.missingValue = missingValue;
}
@Override
public FieldComparator<?> getComparator(int numHits, int sortPos) {
final FieldComparator<?> fieldComparator;
switch (type) {
case INT:
fieldComparator =
new IntComparator(numHits, getField(), (Integer) missingValue, reverse, sortPos) {
@Override
public LeafFieldComparator getLeafComparator(LeafReaderContext context)
throws IOException {
return new IntLeafComparator(context) {
@Override
protected NumericDocValues getNumericDocValues(
LeafReaderContext context, String field) throws IOException {
return SortedNumericSelector.wrap(
DocValues.getSortedNumeric(context.reader(), field), selector, type);
}
// we can use sort optimization with points if selector is MIN or MAX,
// because we can still build successful iterator over points in this case.
@Override
protected PointValues getPointValues(LeafReaderContext context, String field)
throws IOException {
if (selector == SortedNumericSelector.Type.MAX
|| selector == SortedNumericSelector.Type.MIN) {
return super.getPointValues(context, field);
} else {
return null;
}
}
};
}
};
break;
case FLOAT:
fieldComparator =
new FloatComparator(numHits, getField(), (Float) missingValue, reverse, sortPos) {
@Override
public LeafFieldComparator getLeafComparator(LeafReaderContext context)
throws IOException {
return new FloatLeafComparator(context) {
@Override
protected NumericDocValues getNumericDocValues(
LeafReaderContext context, String field) throws IOException {
return SortedNumericSelector.wrap(
DocValues.getSortedNumeric(context.reader(), field), selector, type);
}
// we can use sort optimization with points if selector is MIN or MAX,
// because we can still build successful iterator over points in this case.
@Override
protected PointValues getPointValues(LeafReaderContext context, String field)
throws IOException {
if (selector == SortedNumericSelector.Type.MAX
|| selector == SortedNumericSelector.Type.MIN) {
return super.getPointValues(context, field);
} else {
return null;
}
}
};
}
};
break;
case LONG:
fieldComparator =
new LongComparator(numHits, getField(), (Long) missingValue, reverse, sortPos) {
@Override
public LeafFieldComparator getLeafComparator(LeafReaderContext context)
throws IOException {
return new LongLeafComparator(context) {
@Override
protected NumericDocValues getNumericDocValues(
LeafReaderContext context, String field) throws IOException {
return SortedNumericSelector.wrap(
DocValues.getSortedNumeric(context.reader(), field), selector, type);
}
// we can use sort optimization with points if selector is MIN or MAX,
// because we can still build successful iterator over points in this case.
@Override
protected PointValues getPointValues(LeafReaderContext context, String field)
throws IOException {
if (selector == SortedNumericSelector.Type.MAX
|| selector == SortedNumericSelector.Type.MIN) {
return super.getPointValues(context, field);
} else {
return null;
}
}
};
}
};
break;
case DOUBLE:
fieldComparator =
new DoubleComparator(numHits, getField(), (Double) missingValue, reverse, sortPos) {
@Override
public LeafFieldComparator getLeafComparator(LeafReaderContext context)
throws IOException {
return new DoubleLeafComparator(context) {
@Override
protected NumericDocValues getNumericDocValues(
LeafReaderContext context, String field) throws IOException {
return SortedNumericSelector.wrap(
DocValues.getSortedNumeric(context.reader(), field), selector, type);
}
// we can use sort optimization with points if selector is MIN or MAX,
// because we can still build successful iterator over points in this case.
@Override
protected PointValues getPointValues(LeafReaderContext context, String field)
throws IOException {
if (selector == SortedNumericSelector.Type.MAX
|| selector == SortedNumericSelector.Type.MIN) {
return super.getPointValues(context, field);
} else {
return null;
}
}
};
}
};
break;
case CUSTOM:
case DOC:
case REWRITEABLE:
case STRING_VAL:
case SCORE:
case STRING:
default:
throw new AssertionError();
}
if (getCanUsePoints() == false) {
fieldComparator.disableSkipping();
}
return fieldComparator;
}
private NumericDocValues getValue(LeafReader reader) throws IOException {
return SortedNumericSelector.wrap(DocValues.getSortedNumeric(reader, getField()), selector, type);
}
@Override
public IndexSorter getIndexSorter() {
switch(type) {
case INT:
return new IndexSorter.IntSorter(Provider.NAME, (Integer)missingValue, reverse, this::getValue);
case LONG:
return new IndexSorter.LongSorter(Provider.NAME, (Long)missingValue, reverse, this::getValue);
case DOUBLE:
return new IndexSorter.DoubleSorter(Provider.NAME, (Double)missingValue, reverse, this::getValue);
case FLOAT:
return new IndexSorter.FloatSorter(Provider.NAME, (Float)missingValue, reverse, this::getValue);
default:
throw new AssertionError();
}
}
}