package org.apache.lucene.index;
import java.io.IOException;
import java.util.List;
import org.apache.lucene.index.MergeState.DocMap;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.SortField;
import org.apache.lucene.util.Bits;
import org.apache.lucene.util.LongValues;
import org.apache.lucene.util.NumericUtils;
import org.apache.lucene.util.PriorityQueue;
import org.apache.lucene.util.packed.PackedInts;
import org.apache.lucene.util.packed.PackedLongValues;
final class MultiSorter {
static MergeState.DocMap[] sort(Sort sort, List<CodecReader> readers) throws IOException {
SortField fields[] = sort.getSort();
final ComparableProvider[][] comparables = new ComparableProvider[fields.length][];
final int[] reverseMuls = new int[fields.length];
for(int i=0;i<fields.length;i++) {
comparables[i] = getComparableProviders(readers, fields[i]);
reverseMuls[i] = fields[i].getReverse() ? -1 : 1;
}
int leafCount = readers.size();
PriorityQueue<LeafAndDocID> queue = new PriorityQueue<LeafAndDocID>(leafCount) {
@Override
public boolean lessThan(LeafAndDocID a, LeafAndDocID b) {
for(int i=0;i<comparables.length;i++) {
int cmp = Long.compare(a.valuesAsComparableLongs[i], b.valuesAsComparableLongs[i]);
if (cmp != 0) {
return reverseMuls[i] * cmp < 0;
}
}
if (a.readerIndex != b.readerIndex) {
return a.readerIndex < b.readerIndex;
} else {
return a.docID < b.docID;
}
}
};
PackedLongValues.Builder[] builders = new PackedLongValues.Builder[leafCount];
for(int i=0;i<leafCount;i++) {
CodecReader reader = readers.get(i);
LeafAndDocID leaf = new LeafAndDocID(i, reader.getLiveDocs(), reader.maxDoc(), comparables.length);
for(int j=0;j<comparables.length;j++) {
leaf.valuesAsComparableLongs[j] = comparables[j][i].getAsComparableLong(leaf.docID);
}
queue.add(leaf);
builders[i] = PackedLongValues.monotonicBuilder(PackedInts.COMPACT);
}
int mappedDocID = 0;
int lastReaderIndex = 0;
boolean isSorted = true;
while (queue.size() != 0) {
LeafAndDocID top = queue.top();
if (lastReaderIndex > top.readerIndex) {
isSorted = false;
}
lastReaderIndex = top.readerIndex;
builders[top.readerIndex].add(mappedDocID);
if (top.liveDocs == null || top.liveDocs.get(top.docID)) {
mappedDocID++;
}
top.docID++;
if (top.docID < top.maxDoc) {
for(int j=0;j<comparables.length;j++) {
top.valuesAsComparableLongs[j] = comparables[j][top.readerIndex].getAsComparableLong(top.docID);
}
queue.updateTop();
} else {
queue.pop();
}
}
if (isSorted) {
return null;
}
MergeState.DocMap[] docMaps = new MergeState.DocMap[leafCount];
for(int i=0;i<leafCount;i++) {
final PackedLongValues remapped = builders[i].build();
final Bits liveDocs = readers.get(i).getLiveDocs();
docMaps[i] = new MergeState.DocMap() {
@Override
public int get(int docID) {
if (liveDocs == null || liveDocs.get(docID)) {
return (int) remapped.get(docID);
} else {
return -1;
}
}
};
}
return docMaps;
}
private static class LeafAndDocID {
final int readerIndex;
final Bits liveDocs;
final int maxDoc;
final long[] valuesAsComparableLongs;
int docID;
public LeafAndDocID(int readerIndex, Bits liveDocs, int maxDoc, int numComparables) {
this.readerIndex = readerIndex;
this.liveDocs = liveDocs;
this.maxDoc = maxDoc;
this.valuesAsComparableLongs = new long[numComparables];
}
}
private interface ComparableProvider {
long getAsComparableLong(int docID) throws IOException;
}
private static ComparableProvider[] getComparableProviders(List<CodecReader> readers, SortField sortField) throws IOException {
ComparableProvider[] providers = new ComparableProvider[readers.size()];
final SortField.Type sortType = Sorter.getSortFieldType(sortField);
switch(sortType) {
case STRING:
{
final SortedDocValues[] values = new SortedDocValues[readers.size()];
for(int i=0;i<readers.size();i++) {
final SortedDocValues sorted = Sorter.getOrWrapSorted(readers.get(i), sortField);
values[i] = sorted;
}
OrdinalMap ordinalMap = OrdinalMap.build(null, values, PackedInts.DEFAULT);
final int missingOrd;
if (sortField.getMissingValue() == SortField.STRING_LAST) {
missingOrd = Integer.MAX_VALUE;
} else {
missingOrd = Integer.MIN_VALUE;
}
for(int readerIndex=0;readerIndex<readers.size();readerIndex++) {
final SortedDocValues readerValues = values[readerIndex];
final LongValues globalOrds = ordinalMap.getGlobalOrds(readerIndex);
providers[readerIndex] = new ComparableProvider() {
@Override
public long getAsComparableLong(int docID) throws IOException {
if (readerValues.advanceExact(docID)) {
return globalOrds.get(readerValues.ordValue());
} else {
return missingOrd;
}
}
};
}
}
break;
case LONG:
case INT:
{
final long missingValue;
if (sortField.getMissingValue() != null) {
missingValue = ((Number) sortField.getMissingValue()).longValue();
} else {
missingValue = 0L;
}
for(int readerIndex=0;readerIndex<readers.size();readerIndex++) {
final NumericDocValues values = Sorter.getOrWrapNumeric(readers.get(readerIndex), sortField);
providers[readerIndex] = new ComparableProvider() {
@Override
public long getAsComparableLong(int docID) throws IOException {
if (values.advanceExact(docID)) {
return values.longValue();
} else {
return missingValue;
}
}
};
}
}
break;
case DOUBLE:
{
final double missingValue;
if (sortField.getMissingValue() != null) {
missingValue = (Double) sortField.getMissingValue();
} else {
missingValue = 0.0;
}
for(int readerIndex=0;readerIndex<readers.size();readerIndex++) {
final NumericDocValues values = Sorter.getOrWrapNumeric(readers.get(readerIndex), sortField);
providers[readerIndex] = new ComparableProvider() {
@Override
public long getAsComparableLong(int docID) throws IOException {
double value = missingValue;
if (values.advanceExact(docID)) {
value = Double.longBitsToDouble(values.longValue());
}
return NumericUtils.doubleToSortableLong(value);
}
};
}
}
break;
case FLOAT:
{
final float missingValue;
if (sortField.getMissingValue() != null) {
missingValue = (Float) sortField.getMissingValue();
} else {
missingValue = 0.0f;
}
for(int readerIndex=0;readerIndex<readers.size();readerIndex++) {
final NumericDocValues values = Sorter.getOrWrapNumeric(readers.get(readerIndex), sortField);
providers[readerIndex] = new ComparableProvider() {
@Override
public long getAsComparableLong(int docID) throws IOException {
float value = missingValue;
if (values.advanceExact(docID)) {
value = Float.intBitsToFloat((int) values.longValue());
}
return NumericUtils.floatToSortableInt(value);
}
};
}
}
break;
default:
throw new IllegalArgumentException("unhandled SortField.getType()=" + sortField.getType());
}
return providers;
}
}