/*
* Copyright 2013 Websquared, Inc.
*
* Licensed 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.fastcatsearch.ir.search;
import org.apache.lucene.util.BytesRef;
import org.fastcatsearch.ir.field.DistanceField;
import org.fastcatsearch.ir.field.HitField;
import org.fastcatsearch.ir.field.ScoreField;
import org.fastcatsearch.ir.io.IOUtil;
import org.fastcatsearch.ir.query.RankInfo;
import org.fastcatsearch.ir.query.Sort;
import org.fastcatsearch.ir.settings.Schema;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* 각 쿼리당 개별적인 SortGenerator가 생성되어 소팅을 수행한다.
* getHitElement메소드에서 RankInfo를 받아서 sort필드를 참고하여, 최종적으로 HitElement를 리턴한다.
* HitElement는 정렬시 필요한 데이터를 가지고 있으므로 각 세그먼트로 부터 모인 결과들을 재정렬할수 있게된다.
* @author sangwook.song
*
*/
public class SortGenerator {
private Logger logger = LoggerFactory.getLogger(SortGenerator.class);
private int[] fieldIndex; //쿼리 소트필드들의 필드번호
private boolean[] isAscending; //쿼리 소트필드들의 정렬방식
// private int dataSize; //쿼리 소트필드들의 데이터길이
private IndexRef<FieldIndexReader> indexRef;
private BytesRef[] dataList;
private int sortSize;//다중정렬갯수.
public SortGenerator() throws IOException{
}
public SortGenerator(List<Sort> querySortList, Schema schema, FieldIndexesReader fieldIndexesReader) throws IOException{
// this.dataSize = 0;
if(querySortList != null && querySortList.size() > 0) {
this.sortSize = querySortList.size();
this.fieldIndex = new int[sortSize];
this.isAscending = new boolean[sortSize];
this.dataList = new BytesRef[sortSize];
List<String> fieldIdList = new ArrayList<String>(sortSize);
for (int i = 0; i < sortSize; i++) {
Sort sort = querySortList.get(i);
String fieldId = sort.fieldIndexId();
int idx = schema.getFieldIndexSequence(fieldId);
//save each sort field number in order
if (idx == -1) {
if (fieldId.equalsIgnoreCase(ScoreField.fieldName)) {
fieldIndex[i] = ScoreField.fieldNumber;
// dataSize += ScoreField.fieldSize;
} else if (fieldId.equalsIgnoreCase(HitField.fieldName)) {
fieldIndex[i] = HitField.fieldNumber;
// dataSize += HitField.fieldSize;
} else if (fieldId.equalsIgnoreCase(DistanceField.fieldName)) {
fieldIndex[i] = DistanceField.fieldNumber;
} else {
throw new IOException("Unknown sort field name = " + fieldId);
}
fieldIdList.add(null);
} else {
fieldIndex[i] = idx;
fieldIdList.add(fieldId);
// dataSize += schema.getFieldSetting(fieldId).getByteSize();
}
isAscending[i] = sort.asc();
logger.debug("##SortGenerator-{} => {}, isAscending[{}]={}", i, fieldId, isAscending[i]);
}
indexRef = fieldIndexesReader.selectIndexRef(fieldIdList.toArray(new String[0]));
for (int sequence = 0; sequence < sortSize; sequence++) {
//데이터와 연결되어 있는 필드만 추가해준다.
if (fieldIndex[sequence] >= 0) {
dataList[sequence] = indexRef.getDataRef(sequence).bytesRef();
}
//score, hit 필드등은 여기서는 null이며, 아래 getHitElement 에서 읽을때 객체를 생성한다.
}
}
}
public HitElement[] getHitElement(RankInfo[] rankInfoList, int n) throws IOException{
HitElement[] result = new HitElement[n];
if(sortSize > 0) {
for (int i = 0; i < n; i++) {
RankInfo ri = rankInfoList[i];
indexRef.read(ri.docNo());
BytesRef[] rankData = readRankData(ri);
result[i] = new HitElement(ri.docNo(), ri.score(), ri.hit(), rankData, rankInfoList[i].rowExplanations());
result[i].setDistance(ri.distance());
}
} else {
for (int i = 0; i < n; i++) {
RankInfo ri = rankInfoList[i];
result[i] = new HitElement(ri.docNo(), ri.score(), ri.hit(), null, rankInfoList[i].rowExplanations());
result[i].setDistance(ri.distance());
}
}
return result;
}
public void getHitElement(RankInfo[] rankInfoList, HitElement[] result, int n) throws IOException{
if(sortSize > 0) {
for (int i = 0; i < n; i++) {
RankInfo ri = rankInfoList[i];
indexRef.read(ri.docNo());
BytesRef[] rankData = readRankData(ri);
result[i] = new HitElement(ri.docNo(), ri.score(), ri.hit(), rankData, rankInfoList[i].rowExplanations());
result[i].setDistance(ri.distance());
}
} else {
for (int i = 0; i < n; i++) {
RankInfo ri = rankInfoList[i];
result[i] = new HitElement(ri.docNo(), ri.score(), ri.hit(), null, rankInfoList[i].rowExplanations());
result[i].setDistance(ri.distance());
}
}
}
protected BytesRef[] readRankData(RankInfo ri) {
BytesRef[] rankData = new BytesRef[sortSize];
for (int j = 0; j < sortSize; j++) {
//정렬은 멀티밸류를 지원하지 않으며, 싱글밸류이기 때문에 즉시 bytesRef로 읽도록 한다.
if(fieldIndex[j] == ScoreField.fieldNumber){
rankData[j] = new BytesRef(ScoreField.fieldSize);
IOUtil.writeInt(rankData[j], Float.floatToIntBits(ri.score()));
rankData[j].flip();
}else if(fieldIndex[j] == HitField.fieldNumber){
rankData[j] = new BytesRef(HitField.fieldSize);
IOUtil.writeInt(rankData[j], ri.hit());
rankData[j].flip();
}else if(fieldIndex[j] == DistanceField.fieldNumber){
rankData[j] = new BytesRef(DistanceField.fieldSize);
IOUtil.writeInt(rankData[j], Float.floatToIntBits(ri.distance()));
rankData[j].flip();
}else{
// BytesRef bytesRef = indexRef.getDataRef(j).bytesRef();
// rankData[j] = bytesRef.duplicate();
rankData[j] = dataList[j].duplicate();
}
}
return rankData;
}
}