Package com.senseidb.search.node

Source Code of com.senseidb.search.node.ResultMerger

/**
* This software is licensed to you under the Apache License, Version 2.0 (the
* "Apache License").
*
* LinkedIn's contributions are made under the Apache License. If you contribute
* to the Software, the contributions will be deemed to have been made under the
* Apache License, unless you expressly indicate otherwise. Please do not make any
* contributions that would be inconsistent with the Apache License.
*
* You may obtain a copy of the Apache License at http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, this software
* distributed under the Apache License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the Apache
* License for the specific language governing permissions and limitations for the
* software governed under the Apache License.
*
* © 2012 LinkedIn Corp. All Rights Reserved. 
*/
package com.senseidb.search.node;

import com.senseidb.servlet.SenseiSearchServletParams;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.log4j.Logger;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.SortField;
import org.apache.lucene.util.PriorityQueue;

import proj.zoie.api.ZoieIndexReader;

import com.browseengine.bobo.api.BoboIndexReader;
import com.browseengine.bobo.api.BrowseFacet;
import com.browseengine.bobo.api.BrowseSelection;
import com.browseengine.bobo.api.FacetAccessible;
import com.browseengine.bobo.api.FacetIterator;
import com.browseengine.bobo.api.FacetSpec;
import com.browseengine.bobo.api.FacetSpec.FacetSortSpec;
import com.browseengine.bobo.facets.CombinedFacetAccessible;
import com.browseengine.bobo.facets.FacetHandler;
import com.browseengine.bobo.facets.data.FacetDataCache;
import com.browseengine.bobo.facets.data.PrimitiveLongArrayWrapper;
import com.browseengine.bobo.sort.DocComparator;
import com.browseengine.bobo.sort.DocIDPriorityQueue;
import com.browseengine.bobo.sort.SortCollector;
import com.browseengine.bobo.sort.SortCollector.CollectorContext;
import com.browseengine.bobo.util.ListMerger;
import com.senseidb.search.req.ErrorType;
import com.senseidb.search.req.SenseiError;
import com.senseidb.search.req.SenseiHit;
import com.senseidb.search.req.SenseiRequest;
import com.senseidb.search.req.SenseiResult;
import com.senseidb.search.req.mapred.impl.SenseiReduceFunctionWrapper;

public class ResultMerger
{
  private final static Logger logger = Logger.getLogger(ResultMerger.class.getName());

  private final static class MyScoreDoc extends ScoreDoc
  {
    private static final long serialVersionUID = 1L;

    private BoboIndexReader reader;
    private int finalDoc;
    public int groupPos;
    public Object rawGroupValue;
    public Object[] distinctValues;
    public Comparable sortValue;

    public MyScoreDoc(int docid, float score, int finalDoc, BoboIndexReader reader)
    {
      super(docid, score);
      this.finalDoc = finalDoc;
      this.reader = reader;
    }

    SenseiHit getSenseiHit(SenseiRequest req)
    {
      SenseiHit hit = new SenseiHit();
      if (req.isFetchStoredFields() || req.isFetchStoredValue())
      {
        if (req.isFetchStoredFields())
        {
          try
          {
            hit.setStoredFields(reader.document(doc));
          }
          catch(Exception e)
          {
            logger.error(e.getMessage(),e);
          }
        }
        try
        {
          IndexReader innerReader = reader.getInnerReader();
          if (innerReader instanceof ZoieIndexReader)
          {
            hit.setStoredValue(
                ((ZoieIndexReader)innerReader).getStoredValue(
                    ((ZoieIndexReader)innerReader).getUID(doc)));
          }
        }
        catch(Exception e)
        {
        }
      }

      List<FacetHandler<?>> facetHandlers= new ArrayList<FacetHandler<?>>(reader.getFacetHandlerMap().values());
      if (reader.getRuntimeFacetHandlerMap() != null)
      {
        facetHandlers.addAll(reader.getRuntimeFacetHandlerMap().values());
      }
      Map<String,String[]> map = new HashMap<String,String[]>();
      Map<String,Object[]> rawMap = new HashMap<String,Object[]>();
      Set<String> selectSet = req.getSelectSet();
      for (FacetHandler<?> facetHandler : facetHandlers)
      {
        if (selectSet == null ||
            selectSet.size() == 0 ||
            selectSet.contains(facetHandler.getName()))
        {
          map.put(facetHandler.getName(),facetHandler.getFieldValues(reader,doc));
          rawMap.put(facetHandler.getName(),facetHandler.getRawFieldValues(reader,doc));
        }
      }
      hit.setFieldValues(map);
      hit.setRawFieldValues(rawMap);
      hit.setUID(((ZoieIndexReader<BoboIndexReader>)reader.getInnerReader()).getUID(doc));
      hit.setDocid(finalDoc);
      hit.setScore(score);
      hit.setComparable(sortValue);
      hit.setGroupPosition(groupPos);
      String[] groupBy = req.getGroupBy();
      if (groupBy != null && groupBy.length > groupPos && groupBy[groupPos] != null)
      {
        hit.setGroupField(groupBy[groupPos]);
        hit.setGroupValue(hit.getField(groupBy[groupPos]));
        hit.setRawGroupValue(hit.getRawField(groupBy[groupPos]));
      }
      return hit;
    }
  }
  private final static class HitWithGroupQueue
  {
    public SenseiHit hit;
    public DocIDPriorityQueue queue;
    public Map<Object, MyScoreDoc>[] distinctMap;
    public ArrayList<Iterator<SenseiHit>> iterList = new ArrayList<Iterator<SenseiHit>>();

    public HitWithGroupQueue(SenseiHit hit, int maxPerGroup, int distinctLength)
    {
      this.hit = hit;
      this.queue = maxPerGroup <= 1 ? null :
          new DocIDPriorityQueue(MY_DOC_COMPARATOR,
              maxPerGroup,
              0);
      this.distinctMap = new Map[distinctLength];
      for (int i = 0; i < distinctLength; ++i)
      {
        this.distinctMap[i] = new HashMap<Object, MyScoreDoc>(maxPerGroup);
      }
    }
  }

  // all sub accessibles are CombinedFacetAccessible
  private final static class MyCombinedFacetAccessible extends CombinedFacetAccessible
  {
    public MyCombinedFacetAccessible(FacetSpec fspec,List<FacetAccessible> list)
    {
      super(fspec, list);
    }

    public int getCappedFacetCount(Object value, int cap)
    {
      if (_closed)
      {
        throw new IllegalStateException("This instance of count collector was already closed");
      }
      int sum = 0;
      if (_list != null)
      {
        for (FacetAccessible facetAccessor : _list)
        {
          sum += ((CombinedFacetAccessible)facetAccessor).getCappedFacetCount(value, cap-sum);
          if (sum >= cap)
            return cap;
        }
      }
      return sum;
    }
  }

  private final static DocComparator MY_DOC_COMPARATOR = new DocComparator()
  {
    public int compare(ScoreDoc doc1, ScoreDoc doc2)
    {
      return ((MyScoreDoc)doc1).sortValue.compareTo(((MyScoreDoc)doc2).sortValue);
    }

    public Comparable value(ScoreDoc doc)
    {
      return ((MyScoreDoc)doc).sortValue;
    }
  };

  private static Map<String, FacetAccessible> mergeFacetContainer(Collection<Map<String, FacetAccessible>> subMaps,
                                                                  SenseiRequest req)
  {
    Map<String, Map<String, Integer>> counts = new HashMap<String, Map<String, Integer>>();
    for (Map<String, FacetAccessible> subMap : subMaps)
    {
      for (Map.Entry<String, FacetAccessible> entry : subMap.entrySet())
      {
        String facetname = entry.getKey();
        Map<String, Integer> count = counts.get(facetname);
        if(count == null)
        {
          count = new HashMap<String, Integer>();
          counts.put(facetname, count);
        }
        Set<String> values = new HashSet<String>();
        String[] rawvalues = null;
        BrowseSelection selection = req.getSelection(facetname);
        if (selection!=null&&(rawvalues = selection.getValues())!=null)
        {
          values.addAll(Arrays.asList(rawvalues));
        }
        FacetAccessible facetAccessible = entry.getValue();
        for(BrowseFacet facet : facetAccessible.getFacets())
        {
          if (facet == null) continue;
          String val = facet.getValue();
          int oldValue = count.containsKey(val) ? count.get(val) : 0;
          count.put(val, oldValue + facet.getFacetValueHitCount());
          values.remove(val);
        }
        if (!values.isEmpty())
        {
          for(String val : values)
          {
            int oldValue = count.containsKey(val) ? count.get(val) : 0;
            BrowseFacet facet = facetAccessible.getFacet(val);
            int delta = 0;
            if (facet!=null)
            {
              delta = facet.getFacetValueHitCount();
              count.put(val, oldValue + delta);
            }

          }
        }
        facetAccessible.close();
      }
    }

    Map<String, FacetAccessible> mergedFacetMap = new HashMap<String, FacetAccessible>();
    for (Entry<String,Map<String, Integer>> entry : counts.entrySet())
    {
      String facet = entry.getKey();
      Map<String, Integer> facetValueCounts = entry.getValue();
      List<BrowseFacet> facets = new ArrayList<BrowseFacet>(facetValueCounts.size());
      for (Entry<String, Integer> subEntry : facetValueCounts.entrySet())
      {
        facets.add(new BrowseFacet(subEntry.getKey(), subEntry.getValue()));
      }
      FacetSpec fspec = null;
      Set<String> values = new HashSet<String>();
      String[] rawvalues = null;
      if (req != null)
      {
        fspec = req.getFacetSpec(facet);
        BrowseSelection selection = req.getSelection(facet);
        if (selection!=null&&(rawvalues = selection.getValues())!=null)
        {
          values.addAll(Arrays.asList(rawvalues));
        }
      }
      Comparator<BrowseFacet> facetComp = getComparator(fspec);
      Collections.sort(facets, facetComp);
      if (fspec != null)
      {
        int maxCount = fspec.getMaxCount();
        int numToShow = facets.size();
        if (maxCount > 0)
        {
          numToShow = Math.min(maxCount, numToShow);
        }
        for(int i = facets.size() - 1; i >= numToShow; i--)
        {
          if (!values.contains(facets.get(i).getValue()))
          {
            facets.remove(i);
          }
        }
      }
      MappedFacetAccessible mergedFacetAccessible = new MappedFacetAccessible(facets.toArray(new BrowseFacet[facets.size()]));
      mergedFacetMap.put(facet, mergedFacetAccessible);
    }
    return mergedFacetMap;
  }
  private static Map<String, FacetAccessible> mergeFacetContainerServerSide(Collection<Map<String, FacetAccessible>> subMaps, SenseiRequest req)
  {
    Map<String, List<FacetAccessible>> counts = new HashMap<String, List<FacetAccessible>>();
    for (Map<String, FacetAccessible> subMap : subMaps)
    {
      for (Map.Entry<String, FacetAccessible> entry : subMap.entrySet())
      {
        String facetname = entry.getKey();
        List<FacetAccessible> count = counts.get(facetname);
        if(count == null)
        {
          count = new LinkedList<FacetAccessible>();
          counts.put(facetname, count);
        }
        count.add(entry.getValue());
      }
    }
    // create combinedFacetAccessibles
    Map<String, FacetAccessible> fieldMap = new HashMap<String, FacetAccessible>();
    for(Entry<String,List<FacetAccessible>> entry : counts.entrySet())
    {
      String fieldname = entry.getKey();
      List<FacetAccessible> facetAccs = entry.getValue();
      if (facetAccs.size() == 1)
      {
        fieldMap.put(fieldname, facetAccs.get(0));
      } else
      {
        fieldMap.put(fieldname, new CombinedFacetAccessible(req.getFacetSpec(fieldname), facetAccs));
      }
    }
    Map<String, FacetAccessible> mergedFacetMap = new HashMap<String, FacetAccessible>();
    for(Entry<String,FacetAccessible> entry : fieldMap.entrySet())
    {
      String fieldname = entry.getKey();
      FacetAccessible facetAcc = entry.getValue();
      FacetSpec fspec = req.getFacetSpec(fieldname);
      BrowseSelection sel = req.getSelection(fieldname);
      Set<String> values = new HashSet<String>();
      String[] rawvalues = null;
      if (sel!=null&&(rawvalues = sel.getValues())!=null)
      {
        values.addAll(Arrays.asList(rawvalues));
      }
      List<BrowseFacet> facets = new ArrayList<BrowseFacet>();
      facets.addAll(facetAcc.getFacets());
      for(BrowseFacet bf : facets)
      {
        values.remove(bf.getValue());
      }
      if (values.size()>0)
      {
        for(String value : values)
        {
          facets.add(facetAcc.getFacet(value));
        }
      }
      facetAcc.close();
      // sorting
      Comparator<BrowseFacet> facetComp = getComparator(fspec);
      Collections.sort(facets, facetComp);
      MappedFacetAccessible mergedFacetAccessible = new MappedFacetAccessible(facets.toArray(new BrowseFacet[facets.size()]));
      mergedFacetMap.put(fieldname, mergedFacetAccessible);
    }
    return mergedFacetMap;
  }


  private static Comparator<BrowseFacet> getComparator(FacetSpec fspec)
  {
    Comparator<BrowseFacet> facetComp;
    if ((fspec == null) || fspec.getOrderBy() == FacetSortSpec.OrderHitsDesc)
    {
      facetComp = new BrowseFacetHitsDescComparator();
    } else
    {
      if (fspec.getOrderBy() == FacetSortSpec.OrderValueAsc)
      {
        facetComp = new BrowseFacetValueAscComparator();
      } else
      {
        facetComp = fspec.getCustomComparatorFactory().newComparator();
      }
    }
    return facetComp;
  }

  private static final class BrowseFacetValueAscComparator implements Comparator<BrowseFacet>
  {
    public int compare(BrowseFacet f1, BrowseFacet f2)
    {
      if (f1==null && f2==null){
        return 0;
      }
      if (f1==null){
        return -1;
      }
      if (f2==null){
        return 1;
      }
      int ret = f1.getValue().compareTo(f2.getValue());
      if (f1.getValue().startsWith("-") && f2.getValue().startsWith("-")) {
        ret *= -1;
      }
      return ret;
    }
  }

  private static final class BrowseFacetHitsDescComparator implements Comparator<BrowseFacet>
  {
    public int compare(BrowseFacet f1, BrowseFacet f2)
    {
      if (f1==null && f2==null){
        return 0;
      }
      if (f1==null){
        return -1;
      }
      if (f2==null){
        return 1;
      }
      int h1 = f1.getFacetValueHitCount();
      int h2 = f2.getFacetValueHitCount();

      int val = h2 - h1;

      if (val == 0)
      {
        val = f1.getValue().compareTo(f2.getValue());
      }
      return val;
    }
  }

  private static final class SenseiHitComparator implements Comparator<SenseiHit>
  {
    SortField[] _sortFields;

    public SenseiHitComparator(SortField[] sortFields)
    {
      _sortFields = sortFields;
    }

    public int compare(SenseiHit o1, SenseiHit o2)
    {
      if (_sortFields.length == 0)
      {
        return o1.getDocid() - o2.getDocid();
      }
      else
      {
        int equalCount = 0;
        for (int i = 0; i < _sortFields.length; ++i)
        {
          String field = _sortFields[i].getField();
          int reverse = _sortFields[i].getReverse() ? -1 : 1;

          if (_sortFields[i].getType() == SortField.SCORE)
          {
            float score1 = o1.getScore();
            float score2 = o2.getScore();
            if (score1 == score2)
            {
              equalCount++;
              continue;
            }
            else
            {
              return (score1 > score2) ? -reverse : reverse;
            }
          }
          else if (_sortFields[i].getType() == SortField.DOC)
          {
            return o1.getDocid() - o2.getDocid();
          }
          else if (field.equals(SenseiSearchServletParams.PARAM_RESULT_HIT_UID))
          {
            return o1.getUID() > o2.getUID() ? 1 : (o1.getUID() < o2.getUID() ? -1 : 0);
          }
          else // A regular sort field
          {
            String value1 = o1.getField(field);
            String value2 = o2.getField(field);

            if (value1 == null && value2 == null)
            {
              equalCount++;
              continue;
            }
            else if (value1 == null)
              return -reverse;
            else if (value2 == null)
              return reverse;
            else
            {
              int comp = value1.compareTo(value2);
              if (value1.startsWith("-") && value2.startsWith("-")) {
                comp *= -1;
              }
              if (comp != 0)
              {
                return comp * reverse;
              }
              else
              {
                equalCount++;
                continue;
              }
            }
          } // A regular sort field
        }

        if (equalCount == _sortFields.length)
        {
          return o1.getDocid() - o2.getDocid();
        }
        else
        {
          return 0;
        }
      }
    }
  }

  private static class MappedFacetAccessible implements FacetAccessible, Serializable
  {

    /**
     *
     */
    private static final long serialVersionUID = 1L;

    private final HashMap<String, BrowseFacet> _facetMap;
    private final BrowseFacet[] _facets;

    public MappedFacetAccessible(BrowseFacet[] facets)
    {
      _facetMap = new HashMap<String, BrowseFacet>();
      for (BrowseFacet facet : facets)
      {
        if (facet!=null){
          _facetMap.put(facet.getValue(), facet);
        }
      }
      _facets = facets;
    }

    public BrowseFacet getFacet(String value)
    {
      return _facetMap.get(value);
    }

    public int getFacetHitsCount(Object value)
    {
      BrowseFacet facet = _facetMap.get(value);
      if (facet != null)
        return facet.getHitCount();
      return 0;
    }

    public List<BrowseFacet> getFacets()
    {
      return Arrays.asList(_facets);
    }

    @Override
    public void close()
    {
      // TODO Auto-generated method stub

    }

    @Override
    public FacetIterator iterator()
    {
      throw new IllegalStateException("FacetIterator should not be obtained at merge time");
    }

  }

  public static int getNumHits(Collection<SenseiResult> results) {
    int numHits = 0;
    for(SenseiResult res : results)
    {
      numHits += res.getNumHits();
    }
    return numHits;
  }

  public static int getTotalDocs(Collection<SenseiResult> results) {
    int totalDocs = 0;
    for(SenseiResult res : results) {
      totalDocs += res.getTotalDocs();
    }
    return totalDocs;
  }

  public static int getNumGroups(Collection<SenseiResult> results) {
    int numGroups = 0;
    for(SenseiResult res : results) {
      numGroups += res.getNumGroups();
    }
    return numGroups;
  }

  public static long findLongestTime(Collection<SenseiResult> results) {
    long time = 0L;
    for (SenseiResult res : results)
    {
      time = Math.max(time,res.getTime());
    }
    return time;
  }

  public static String findParsedQuery(Collection<SenseiResult> results) {
    for(SenseiResult res : results)
    {
      String parsedQuery = res.getParsedQuery();
      if(parsedQuery != null && parsedQuery.length()>0)
        return parsedQuery;
    }
    return "";
  }

  public static boolean hasSortCollector(Collection<SenseiResult> results) {
    for(SenseiResult res : results)
    {
      if (res.getSortCollector() != null && res.getSortCollector().contextList != null)
      {
        return true;
      }
    }
    return false;
  }

  public static void createUniqueDocIds(Collection<SenseiResult> results) {
    int totalDocs= 0;
    for (SenseiResult res : results)
    {
      SenseiHit[] hits = res.getSenseiHits();
      if (hits != null)
      {
        for (SenseiHit hit : hits)
        {
          hit.setDocid(hit.getDocid() + totalDocs);
        }
      }
      totalDocs += res.getTotalDocs();
    }
  }

  public static List<Iterator<SenseiHit>> flattenHits(Collection<SenseiResult> results) {
    List<Iterator<SenseiHit>> hitList = new ArrayList<Iterator<SenseiHit>>(results.size());

    for (SenseiResult res : results)
    {
      hitList.add(Arrays.asList(res.getSenseiHits()).iterator());
    }
    return hitList;
  }

  private static final int UNKNOWN_GROUP_VALUE_TYPE = 0;
  private static final int NORMAL_GROUP_VALUE_TYPE = 1;
  private static final int LONG_ARRAY_GROUP_VALUE_TYPE = 2;

  public static SenseiResult merge(final SenseiRequest req, Collection<SenseiResult> results, boolean onSearchNode)
  {
    long start = System.currentTimeMillis();
    List<Map<String, FacetAccessible>> facetList = new ArrayList<Map<String, FacetAccessible>>(results.size());

    // Compute the size of hits priority queue
    final int topHits = req.getOffset() + req.getCount();

    // Sum the hits, groups, totalDocs, etc from all the results
    final int numHits = getNumHits(results);
    final int numGroups = getNumGroups(results);
    int totalDocs = getTotalDocs(results);
    final long longestTime = findLongestTime(results);

    final String parsedQuery = findParsedQuery(results);
    final boolean hasSortCollector = hasSortCollector(results);

    // Assign each hit document a unique "document id"
    createUniqueDocIds(results);

    // Extract the hits from the results
    List<Iterator<SenseiHit>> hitLists = flattenHits(results);

    List<FacetAccessible>[] groupAccessibles = extractFacetAccessible(results);

    // Merge your facets
    for (SenseiResult res : results)
    {
      Map<String, FacetAccessible> facetMap = res.getFacetMap();
      if (facetMap != null)
      {
        facetList.add(facetMap);
      }
    }

    Map<String, FacetAccessible> mergedFacetMap = null;
    if (onSearchNode)
    {
      mergedFacetMap = mergeFacetContainerServerSide(facetList, req);
    } else
    {
      mergedFacetMap = mergeFacetContainer(facetList, req);
    }
    Comparator<SenseiHit> comparator = new SenseiHitComparator(req.getSort());

    SenseiHit[] hits;
    if (req.getGroupBy() == null || req.getGroupBy().length == 0)
    {
      List<SenseiHit> mergedList = ListMerger.mergeLists(req.getOffset(), req.getCount(), hitLists
          .toArray(new Iterator[hitLists.size()]), comparator);
      hits = mergedList.toArray(new SenseiHit[mergedList.size()]);
    }
    else
    {
      int[] rawGroupValueType = new int[req.getGroupBy().length]// 0: unknown, 1: normal, 2: long[]

      PrimitiveLongArrayWrapper primitiveLongArrayWrapperTmp = new PrimitiveLongArrayWrapper(null);

      Iterator<SenseiHit> mergedIter = ListMerger.mergeLists(hitLists, comparator);

      List<SenseiHit> hitsList = null;
      if (!hasSortCollector)
      {
        hitsList = buildHitsListNoSortCollector(req, topHits, rawGroupValueType, mergedIter, req.getOffset());
        //numGroups = (int)(numGroups*(groupHitMap.size()/(float)preGroups));
      }
      else
      {
        int offsetLeft = req.getOffset();

        MyScoreDoc pre = null;

        if (topHits > 0 && groupAccessibles != null && groupAccessibles.length != 0)
        {
          hitsList = buildHitsList(req, results, topHits, groupAccessibles, rawGroupValueType, primitiveLongArrayWrapperTmp);
        }
        else
        {
          hitsList = buildHitsListNoGroupAccessibles(req, topHits, rawGroupValueType, primitiveLongArrayWrapperTmp, mergedIter, offsetLeft);
        }
        //for (int i=0; i<combinedFacetAccessibles.length; ++i) combinedFacetAccessibles[i].close();
      }
      hits = hitsList.toArray(new SenseiHit[hitsList.size()]);

      primitiveLongArrayWrapperTmp = new PrimitiveLongArrayWrapper(null);

      PrepareGroupMappings prepareGroupMappings = new PrepareGroupMappings(req, results, hasSortCollector, hits, rawGroupValueType, primitiveLongArrayWrapperTmp).invoke();
      Map<Object, HitWithGroupQueue>[] groupMaps = prepareGroupMappings.getGroupMaps();
      totalDocs = prepareGroupMappings.getTotalDocs();

      if (hasSortCollector)
      {
        // Fix group position
        for (SenseiHit hit : hits)
        {
          if (hit.getGroupHitsCount() <= 1)
          {
            hit.setGroupPosition(0);
            hit.setGroupField(req.getGroupBy()[0]);
            hit.setGroupValue(hit.getField(req.getGroupBy()[0]));
            hit.setRawGroupValue(hit.getRawField(req.getGroupBy()[0]));
          }
        }

        for (Map<Object, HitWithGroupQueue> map : groupMaps)
        {
          for (HitWithGroupQueue hwg : map.values())
          {
            if (hwg.queue == null) continue;
            int index = hwg.queue.size - 1;
            if (index >= 0)
            {
              SenseiHit[] groupHits = new SenseiHit[index+1];
              while (index >=0)
              {
                groupHits[index] = ((MyScoreDoc)hwg.queue.pop()).getSenseiHit(req);
                --index;
              }
              hwg.hit.setGroupHits(groupHits);
            }
          }
        }
      }
      else
      {
        Set<Object>[] distinctSets = null;
        Object[] distinctValues = null;
        int[] distinctValueType = null;
        if (req.getDistinct() != null && req.getDistinct().length != 0)
        {
          distinctSets = new Set[req.getDistinct().length];
          for (int i = 0; i < distinctSets.length; ++i)
          {
            distinctSets[i] = new HashSet<Object>(req.getMaxPerGroup());
          }
          distinctValues = new Object[distinctSets.length];
          distinctValueType = new int[distinctSets.length];
          primitiveLongArrayWrapperTmp = new PrimitiveLongArrayWrapper(null);
        }
        for (Map<Object, HitWithGroupQueue> map : groupMaps)
        {
          for (HitWithGroupQueue hwg : map.values())
          {
            if (distinctSets != null)
            {
              for (int i = 0; i < distinctSets.length; ++i)
              {
                distinctSets[i].clear();
              }
            }
            Iterator<SenseiHit> groupMergedIter = ListMerger.mergeLists(hwg.iterList
                .toArray(new Iterator[hwg.iterList.size()]), comparator);
            List<SenseiHit> mergedList = new ArrayList<SenseiHit>(req.getMaxPerGroup());
            while (mergedList.size() < req.getMaxPerGroup() && groupMergedIter.hasNext())
            {
              SenseiHit hit = groupMergedIter.next();
              if (distinctSets == null || !checkDuplicate(hit,
                  distinctSets,
                  req.getDistinct(),
                  distinctValues,
                  distinctValueType,
                  primitiveLongArrayWrapperTmp))
              {
                mergedList.add(hit);
              }
            }
            SenseiHit[] groupHits = mergedList.toArray(new SenseiHit[mergedList.size()]);
            hwg.hit.setGroupHits(groupHits);
          }
        }
      }
    }

    if (groupAccessibles != null)
    {
      for (List<FacetAccessible> list : groupAccessibles)
      {
        if (list != null)
        {
          for (FacetAccessible acc : list)
          {
            if (acc != null)
              acc.close();
          }
        }
      }
    }

    SenseiResult merged = new SenseiResult();
    merged.setHits(hits);
    merged.setNumHits(numHits);
    merged.setNumGroups(numGroups);
    merged.setTotalDocs(totalDocs);
    merged.addAll(mergedFacetMap);

    long end = System.currentTimeMillis();

    merged.setTime(longestTime + end - start);
    mergerErrors(merged, req, results, parsedQuery);
    if (req.getMapReduceFunction() != null) {
      if (onSearchNode) {
        merged.setMapReduceResult(SenseiReduceFunctionWrapper.combine(req.getMapReduceFunction(), SenseiReduceFunctionWrapper.extractMapReduceResults(results)));
      } else {
        //on broker level
        merged.setMapReduceResult(SenseiReduceFunctionWrapper.reduce(req.getMapReduceFunction(), SenseiReduceFunctionWrapper.extractMapReduceResults(results)));
      }
    }
    return merged;
  }

  private static boolean checkDuplicate(SenseiHit hit,
                                        Set<Object>[] distinctSets,
                                        String[] distinct,
                                        Object[] distinctValues,
                                        int[] distinctValueType,
                                        PrimitiveLongArrayWrapper primitiveLongArrayWrapperTmp)
  {
    for (int i = 0; i < distinctValues.length; ++i)
    {
      distinctValues[i] = hit.getRawField(distinct[i]);
      distinctValues[i] = extractRawGroupValue(distinctValueType, i,
          primitiveLongArrayWrapperTmp, distinctValues[i]);

      if (distinctSets[i].contains(distinctValues[i])) return true;
    }
    for (int i = 0; i < distinctValues.length; ++i)
    {
      if (distinctValueType[i] == LONG_ARRAY_GROUP_VALUE_TYPE)
        distinctSets[i].add(new PrimitiveLongArrayWrapper(((PrimitiveLongArrayWrapper)distinctValues[i]).data));
      else
        distinctSets[i].add(distinctValues[i]);
    }
    return false;
  }

  private static List<SenseiHit> buildHitsListNoGroupAccessibles(SenseiRequest req,
                                                                 int topHits,
                                                                 int[] rawGroupValueType,
                                                                 PrimitiveLongArrayWrapper primitiveLongArrayWrapperTmp,
                                                                 Iterator<SenseiHit> mergedIter,
                                                                 int offsetLeft) {
    List<SenseiHit> hitsList = new ArrayList<SenseiHit>(req.getCount());

    Object rawGroupValue = null;
    Object firstRawGroupValue = null;
    Set<Object>[] groupSets = new Set[1];
    groupSets[0] = new HashSet<Object>(topHits);
    while(mergedIter.hasNext())
    {

      SenseiHit hit = mergedIter.next();
      firstRawGroupValue = null;
      int i=0;
      for (; i<groupSets.length; ++i)
      {
        //rawGroupValue = hit.getRawField(req.getGroupBy()[i]);

        rawGroupValue = extractRawGroupValue(rawGroupValueType, i,
            primitiveLongArrayWrapperTmp, hit);

        if (firstRawGroupValue == null) firstRawGroupValue = rawGroupValue;
        if (groupSets[i].contains(rawGroupValue))
        {
          i = -1;
          break;
        }
      }
      if (i >= 0)
      {
        if (i >= groupSets.length)
        {
          i = 0;
          rawGroupValue = firstRawGroupValue;
        }
        if (offsetLeft > 0)
          --offsetLeft;
        else {
          //hit.setGroupHitsCount(combinedFacetAccessibles[i].getFacetHitsCount(hit.getRawGroupValue()));
          hitsList.add(hit);
          if (hitsList.size() >= req.getCount())
            break;
        }
        if (rawGroupValueType[i] == LONG_ARRAY_GROUP_VALUE_TYPE)
          groupSets[i].add(new PrimitiveLongArrayWrapper(primitiveLongArrayWrapperTmp.data));
        else
          groupSets[i].add(rawGroupValue);
      }
    }

    return hitsList;
  }

  private static List<SenseiHit> buildHitsList(SenseiRequest req,
                                               Collection<SenseiResult> results,
                                               int topHits,
                                               List<FacetAccessible>[] groupAccessibles,
                                               int[] rawGroupValueType,
                                               PrimitiveLongArrayWrapper primitiveLongArrayWrapperTmp) {
    List<SenseiHit> hitsList = new ArrayList<SenseiHit>(req.getCount());

    MyScoreDoc pre = null;
    Object rawGroupValue = null;
    MyCombinedFacetAccessible[] combinedFacetAccessibles = new MyCombinedFacetAccessible[groupAccessibles.length];
    for(int i = 0; i < groupAccessibles.length; i++)
    {
      combinedFacetAccessibles[i] = new MyCombinedFacetAccessible(new FacetSpec(), groupAccessibles[i]);
    }

    Set<Object>[] groupSets = new Set[rawGroupValueType.length];
    for (int i = 0; i < rawGroupValueType.length; ++i)
    {
      groupSets[i] = new HashSet<Object>(topHits);
    }

    Map<Object, MyScoreDoc>[] valueDocMaps = new Map[rawGroupValueType.length];
    for (int i = 0; i < rawGroupValueType.length; ++i)
    {
      valueDocMaps[i] = new HashMap<Object, MyScoreDoc>(topHits);
    }

    int totalDocs = 0;

    MyScoreDoc tmpScoreDoc = new MyScoreDoc(0, 0.0f, 0, null);

    MyScoreDoc bottom = null;
    boolean queueFull = false;

    DocIDPriorityQueue docQueue = new DocIDPriorityQueue(MY_DOC_COMPARATOR, topHits, 0);

    for (SenseiResult res : results)
    {
      SortCollector sortCollector = res.getSortCollector();
      if (sortCollector == null)
        continue;

      int end = (res.getNumHits() % SortCollector.BLOCK_SIZE) - 1;

      Iterator<CollectorContext> contextIter = sortCollector.contextList.descendingIterator();

      // Populate dataCaches and contextLeft
      CollectorContext currentContext = null;
      int contextLeft = 0;
      FacetDataCache[] dataCaches = new FacetDataCache[sortCollector.groupByMulti.length];
      while (contextIter.hasNext()) {
        currentContext = contextIter.next();
        currentContext.restoreRuntimeFacets();
        contextLeft = currentContext.length;
        if (contextLeft > 0)
        {
          for (int j=0; j<sortCollector.groupByMulti.length; ++j)
            dataCaches[j] = (FacetDataCache)sortCollector.groupByMulti[j].getFacetData(currentContext.reader);
          break;
        }
      }

      Iterator<float[]> scoreArrayIter = sortCollector.scorearraylist != null ? sortCollector.scorearraylist.descendingIterator():null;

      if (contextLeft > 0)
      {
        Iterator<int[]> docArrayIter = sortCollector.docidarraylist.descendingIterator();
        while (docArrayIter.hasNext())
        {
          int[] docs = docArrayIter.next();
          float[] scores = scoreArrayIter != null ? scoreArrayIter.next():null;

          for (int i = end; i >= 0; --i)
          {
            tmpScoreDoc.doc = docs[i];
            tmpScoreDoc.score = scores != null ? scores[i] : 0.0f;
            tmpScoreDoc.finalDoc = currentContext.base + totalDocs + tmpScoreDoc.doc;
            tmpScoreDoc.reader = currentContext.reader;
            tmpScoreDoc.sortValue = currentContext.comparator.value(tmpScoreDoc);

            int j=0;

            if (!queueFull || tmpScoreDoc.sortValue.compareTo(bottom.sortValue) < 0)
            {
              for (;; ++j)
              {
                rawGroupValue = dataCaches[j].valArray.getRawValue(dataCaches[j].orderArray.get(tmpScoreDoc.doc));

                rawGroupValue = extractRawGroupValue(rawGroupValueType, j,
                    primitiveLongArrayWrapperTmp, rawGroupValue);

                pre = valueDocMaps[j].get(rawGroupValue);
                if (pre != null)
                {
                  j = -1;
                  break;
                }

                if (j >= combinedFacetAccessibles.length) break;

                if (rawGroupValueType[j] == LONG_ARRAY_GROUP_VALUE_TYPE)
                {
                  if (combinedFacetAccessibles[j].getCappedFacetCount(primitiveLongArrayWrapperTmp.data, 2) != 1)
                    break;
                }
                else
                {
                  if (combinedFacetAccessibles[j].getCappedFacetCount(rawGroupValue, 2) != 1)
                    break;
                }
              }
              if (j < 0)
              {
                if (tmpScoreDoc.sortValue.compareTo(pre.sortValue) < 0)
                {
                  tmpScoreDoc.groupPos = pre.groupPos;
                  tmpScoreDoc.rawGroupValue = pre.rawGroupValue;

                  // Pre has a higher score. Pop it in the queue!
                  bottom = (MyScoreDoc)docQueue.replace(tmpScoreDoc, pre);
                  valueDocMaps[tmpScoreDoc.groupPos].put(tmpScoreDoc.rawGroupValue, tmpScoreDoc);
                  tmpScoreDoc = pre;
                }
              }
              else
              {
                if (queueFull)
                {
                  tmpScoreDoc.groupPos = j;
                  tmpScoreDoc.rawGroupValue = rawGroupValue;
                  MyScoreDoc tmp = bottom;

                  valueDocMaps[tmp.groupPos].remove(tmp.rawGroupValue);

                  bottom = (MyScoreDoc)docQueue.replace(tmpScoreDoc);
                  valueDocMaps[j].put(rawGroupValue, tmpScoreDoc);
                  tmpScoreDoc = tmp;
                  if (rawGroupValueType[tmpScoreDoc.groupPos] == LONG_ARRAY_GROUP_VALUE_TYPE)
                  {
                    primitiveLongArrayWrapperTmp = (PrimitiveLongArrayWrapper)tmpScoreDoc.rawGroupValue;
                  }
                  else
                  {
                    primitiveLongArrayWrapperTmp = new PrimitiveLongArrayWrapper(null);
                  }
                }
                else
                {
                  MyScoreDoc tmp = new MyScoreDoc(tmpScoreDoc.doc, tmpScoreDoc.score, currentContext.base + totalDocs + tmpScoreDoc.doc, currentContext.reader);
                  tmp.groupPos = j;
                  tmp.rawGroupValue = rawGroupValue;
                  tmp.sortValue = tmpScoreDoc.sortValue;
                  bottom = (MyScoreDoc)docQueue.add(tmp);
                  valueDocMaps[j].put(rawGroupValue, tmp);
                  queueFull = (docQueue.size >= topHits);
                  if (rawGroupValueType[j] == LONG_ARRAY_GROUP_VALUE_TYPE)
                  {
                    primitiveLongArrayWrapperTmp = new PrimitiveLongArrayWrapper(null);
                  }
                }
              }
            }

            --contextLeft;
            if (contextLeft <= 0)
            {
              while (contextIter.hasNext())
              {
                currentContext = contextIter.next();
                currentContext.restoreRuntimeFacets();
                contextLeft = currentContext.length;
                if (contextLeft > 0)
                {
                  for (j=0; j<sortCollector.groupByMulti.length; ++j)
                    dataCaches[j] = (FacetDataCache)sortCollector.groupByMulti[j].getFacetData(currentContext.reader);
                  break;
                }
              }
              if (contextLeft <= 0) // No more docs left.
                break;
            }
          }
          end = SortCollector.BLOCK_SIZE - 1;
        }
      }
      totalDocs += res.getTotalDocs();
    }


    int len = docQueue.size() - req.getOffset();
    if (len < 0) len = 0;
    SenseiHit[] hitArray = new SenseiHit[len];
    for (int i = hitArray.length-1; i>=0; --i)
    {
      tmpScoreDoc = (MyScoreDoc)docQueue.pop();
      hitArray[i] = tmpScoreDoc.getSenseiHit(req);
    }

    for (int i=0; i<hitArray.length; ++i)
      hitsList.add(hitArray[i]);

    return hitsList;
  }

  private static List<SenseiHit> buildHitsListNoSortCollector(SenseiRequest req,
                                                              int topHits,
                                                              int[] rawGroupValueType,
                                                              Iterator<SenseiHit> mergedIter,
                                                              int offsetLeft) {

    List<SenseiHit> hitsList = new ArrayList<SenseiHit>(req.getCount());

    // TODO: Pull out the sensei hits extraction from this function
    PrimitiveLongArrayWrapper primitiveLongArrayWrapperTmp = new PrimitiveLongArrayWrapper(null);

    Map<Object, SenseiHit>[] groupHitMaps = new Map[req.getGroupBy().length];
    for (int i=0; i < groupHitMaps.length; ++i)
    {
      groupHitMaps[i] = new HashMap<Object, SenseiHit>(topHits);
    }

    while(mergedIter.hasNext())
    {
      SenseiHit hit = mergedIter.next();
      Object rawGroupValue = extractRawGroupValue(rawGroupValueType, hit.getGroupPosition(), primitiveLongArrayWrapperTmp, hit);

      SenseiHit pre = groupHitMaps[hit.getGroupPosition()].get(rawGroupValue);
      if (pre != null)
      {
        if (offsetLeft <= 0) {
          pre.setGroupHitsCount(pre.getGroupHitsCount()+hit.getGroupHitsCount());
        }
      }
      else
      {
        if (offsetLeft > 0)
          --offsetLeft;
        else if (hitsList.size() < req.getCount())
          hitsList.add(hit);

        if (rawGroupValueType[0] == 2)
          groupHitMaps[hit.getGroupPosition()].put(new PrimitiveLongArrayWrapper(primitiveLongArrayWrapperTmp.data), hit);
        else
          groupHitMaps[hit.getGroupPosition()].put(rawGroupValue, hit);
      }
    }
    return hitsList;
  }

  private static Object extractRawGroupValue(int[] rawGroupValueType, int groupPosition,
                                             PrimitiveLongArrayWrapper primitiveLongArrayWrapperTmp, SenseiHit hit) {
    return extractRawGroupValue(rawGroupValueType, groupPosition, primitiveLongArrayWrapperTmp, hit.getRawGroupValue());
  }

  private static Object extractRawGroupValue(int[] rawGroupValueType, int groupPosition,
                                             PrimitiveLongArrayWrapper primitiveLongArrayWrapperTmp, Object rawGroupValue) {

    if (rawGroupValueType[groupPosition] == LONG_ARRAY_GROUP_VALUE_TYPE)
    {
      // We already know this group position is a long[]
      primitiveLongArrayWrapperTmp.data = (long[])rawGroupValue;
      rawGroupValue = primitiveLongArrayWrapperTmp;
    }
    else if (rawGroupValueType[groupPosition] == UNKNOWN_GROUP_VALUE_TYPE)
    {
      // Unknown
      if (rawGroupValue != null)
      {
        if (rawGroupValue instanceof long[])
        {
          // It's a long array, so set the position
          rawGroupValueType[groupPosition] = LONG_ARRAY_GROUP_VALUE_TYPE;
          primitiveLongArrayWrapperTmp.data = (long[])rawGroupValue;
          rawGroupValue = primitiveLongArrayWrapperTmp;
        }
        else
          rawGroupValueType[groupPosition] = NORMAL_GROUP_VALUE_TYPE;
      }
    }
    return rawGroupValue;
  }

  private static List<FacetAccessible>[] extractFacetAccessible(Collection<SenseiResult> results) {
    List<FacetAccessible>[] groupAccessibles = null;
    for (SenseiResult res : results)
    {
      if (res.getGroupAccessibles() != null)
      {
        if (groupAccessibles == null)
        {
          groupAccessibles = new List[res.getGroupAccessibles().length];
          for (int i=0; i<groupAccessibles.length; ++i)
          {
            groupAccessibles[i] = new ArrayList<FacetAccessible>(results.size());
          }
        }
        for (int i=0; i<groupAccessibles.length; ++i)
        {
          groupAccessibles[i].add(res.getGroupAccessibles()[i]);
        }
      }
    }
    return groupAccessibles;
  }

  public static class PrepareGroupMappings {
    private final SenseiRequest req;
    private final int maxPerGroup;
    private final int distinctLength;
    private final Collection<SenseiResult> results;
    private final boolean hasSortCollector;
    private final SenseiHit[] hits;
    private final int[] rawGroupValueType;
    private final PrimitiveLongArrayWrapper primitiveLongArrayWrapperTmp;
    private final int[] distinctValueType;
    private final PrimitiveLongArrayWrapper distinctPrimitiveLongArrayWrapperTmp;

    private int totalDocs;
    private Map<Object, HitWithGroupQueue>[] groupMaps;

    public PrepareGroupMappings(SenseiRequest req,
                                Collection<SenseiResult> results,
                                boolean hasSortCollector,
                                SenseiHit[] hits,
                                int[] rawGroupValueType,
                                PrimitiveLongArrayWrapper primitiveLongArrayWrapperTmp) {
      this.req = req;
      this.maxPerGroup = req.getMaxPerGroup() <= 1 ? 0 : req.getMaxPerGroup();
      this.distinctLength = req.getDistinct() == null ? 0 : req.getDistinct().length;
      this.results = results;
      this.hasSortCollector = hasSortCollector;
      this.hits = hits;
      this.rawGroupValueType = rawGroupValueType;
      this.primitiveLongArrayWrapperTmp = primitiveLongArrayWrapperTmp;
      this.distinctValueType = new int[distinctLength];
      this.distinctPrimitiveLongArrayWrapperTmp = new PrimitiveLongArrayWrapper(null);

      groupMaps = new Map[req.getGroupBy().length];
      for (int i=0; i< groupMaps.length; ++i)
      {
        groupMaps[i] = new HashMap<Object, HitWithGroupQueue>(hits.length*2);
      }
    }

    public int getTotalDocs() {
      return totalDocs;
    }

    public Map<Object, HitWithGroupQueue>[] getGroupMaps() {
      return groupMaps;
    }

    public PrepareGroupMappings invoke() {
      Object rawGroupValue;

      for (SenseiHit hit : hits)
      {
        rawGroupValue = hit.getRawField(req.getGroupBy()[hit.getGroupPosition()]);

        rawGroupValue = extractRawGroupValue(rawGroupValueType, hit.getGroupPosition(),
            primitiveLongArrayWrapperTmp, rawGroupValue);

        if (rawGroupValueType[hit.getGroupPosition()] == LONG_ARRAY_GROUP_VALUE_TYPE)
        {
          groupMaps[hit.getGroupPosition()].put(new PrimitiveLongArrayWrapper(primitiveLongArrayWrapperTmp.data),
              new HitWithGroupQueue(hit,
                  maxPerGroup,
                  distinctLength));
        }
        else
        {
          groupMaps[hit.getGroupPosition()].put(rawGroupValue,
              new HitWithGroupQueue(hit,
                  maxPerGroup,
                  distinctLength));
        }
      }

      MyScoreDoc tmpScoreDoc = null, pre = null;
      int doc = 0;
      float score = 0.0f;
      int i = 0, j = 0, k = 0;
      HitWithGroupQueue hitWithGroupQueue = null;

      totalDocs = 0;
      for (SenseiResult res : results)
      {
        if (hasSortCollector)
        {
          SortCollector sortCollector = res.getSortCollector();
          if (sortCollector == null) continue;
          int end = (res.getNumHits() % SortCollector.BLOCK_SIZE) - 1;
          Iterator<CollectorContext> contextIter = sortCollector.contextList.descendingIterator();
          CollectorContext currentContext = null;
          int contextLeft = 0;
          FacetDataCache[] dataCaches = new FacetDataCache[sortCollector.groupByMulti.length];
          FacetDataCache[] distinctDataCaches = new FacetDataCache[distinctLength];
          while (contextIter.hasNext()) {
            currentContext = contextIter.next();
            currentContext.restoreRuntimeFacets();
            contextLeft = currentContext.length;
            if (contextLeft > 0)
            {
              for (j=0; j<sortCollector.groupByMulti.length; ++j)
              {
                dataCaches[j] = (FacetDataCache)sortCollector.groupByMulti[j].getFacetData(currentContext.reader);
              }
              for (j=0; j<distinctLength; ++j)
              {
                distinctDataCaches[j] = (FacetDataCache)currentContext.
                    reader.
                    getFacetHandler(req.getDistinct()[j]).
                    getFacetData(currentContext.reader);
              }
              break;
            }
          }

          Iterator<float[]> scoreArrayIter = sortCollector.scorearraylist != null ? sortCollector.scorearraylist.descendingIterator():null;
          if (contextLeft > 0)
          {
            Iterator<int[]> docArrayIter = sortCollector.docidarraylist.descendingIterator();
            while (docArrayIter.hasNext())
            {
              int[] docs = docArrayIter.next();
              float[] scores = scoreArrayIter != null ? scoreArrayIter.next():null;
              for (i = end; i >= 0; --i)
              {
                doc = docs[i];
                score = scores != null ? scores[i]:0.0f;
                for (j=0; j<sortCollector.groupByMulti.length; ++j)
                {
                  rawGroupValue = extractRawGroupValue(rawGroupValueType, j, primitiveLongArrayWrapperTmp,
                      dataCaches[j].valArray.getRawValue(dataCaches[j].orderArray.get(doc)));

                  hitWithGroupQueue = groupMaps[j].get(rawGroupValue);
                  if (hitWithGroupQueue != null)
                  {
                    hitWithGroupQueue.hit.setGroupHitsCount(hitWithGroupQueue.hit.getGroupHitsCount() + 1);
                    if (hitWithGroupQueue.queue == null) break;
                    // Collect this hit.
                    if (tmpScoreDoc == null)
                      tmpScoreDoc = new MyScoreDoc(doc, score, currentContext.base + totalDocs + doc, currentContext.reader);
                    else
                    {
                      tmpScoreDoc.doc = doc;
                      tmpScoreDoc.score = score;
                      tmpScoreDoc.finalDoc = currentContext.base + totalDocs + doc;
                      tmpScoreDoc.reader = currentContext.reader;
                    }
                    tmpScoreDoc.sortValue = currentContext.comparator.value(tmpScoreDoc);
                    tmpScoreDoc.groupPos = j;
                    if (distinctLength > 0)
                    {
                      tmpScoreDoc.distinctValues = new Object[distinctLength];
                    }

                    if (hitWithGroupQueue.queue.size < maxPerGroup ||
                        tmpScoreDoc.sortValue.compareTo(((MyScoreDoc)hitWithGroupQueue.queue.top()).sortValue) < 0)
                    {
                      pre = null;
                      for (k = 0; k < distinctLength; ++k)
                      {
                        tmpScoreDoc.distinctValues[k] = extractRawGroupValue(distinctValueType,
                            k,
                            distinctPrimitiveLongArrayWrapperTmp,
                            distinctDataCaches[k].valArray.getRawValue(
                                distinctDataCaches[k].orderArray.get(doc)
                            ));
                        if (pre == null)
                          pre = hitWithGroupQueue.distinctMap[k].get(tmpScoreDoc.distinctValues[k]);
                      }
                      if (pre != null)
                      {
                        if (tmpScoreDoc.sortValue.compareTo(pre.sortValue) < 0)
                        {
                          hitWithGroupQueue.queue.replace(tmpScoreDoc, pre);
                          for (k = 0; k < distinctLength; ++k)
                          {
                            hitWithGroupQueue.distinctMap[k].remove(pre.distinctValues[k]);
                            hitWithGroupQueue.distinctMap[k].put(tmpScoreDoc.distinctValues[k], tmpScoreDoc);
                          }
                          tmpScoreDoc = pre;
                        }
                      }
                      else
                      {
                        if (hitWithGroupQueue.queue.size >= maxPerGroup) // queue full
                        {
                          pre = (MyScoreDoc)hitWithGroupQueue.queue.top();
                          hitWithGroupQueue.queue.replace(tmpScoreDoc);
                          for (k = 0; k < distinctLength; ++k)
                          {
                            hitWithGroupQueue.distinctMap[k].remove(pre.distinctValues[k]);
                            hitWithGroupQueue.distinctMap[k].put(tmpScoreDoc.distinctValues[k], tmpScoreDoc);
                          }
                          tmpScoreDoc = pre;
                        }
                        else
                        {
                          hitWithGroupQueue.queue.add(tmpScoreDoc);
                          for (k = 0; k < distinctLength; ++k)
                          {
                            hitWithGroupQueue.distinctMap[k].put(tmpScoreDoc.distinctValues[k], tmpScoreDoc);
                          }
                          tmpScoreDoc = null;
                        }
                      }
                    }
                    break;
                  }
                }
                --contextLeft;
                if (contextLeft <= 0)
                {
                  while (contextIter.hasNext()) {
                    currentContext = contextIter.next();
                    currentContext.restoreRuntimeFacets();
                    contextLeft = currentContext.length;
                    if (contextLeft > 0)
                    {
                      for (j=0; j<sortCollector.groupByMulti.length; ++j)
                      {
                        dataCaches[j] = (FacetDataCache)sortCollector.groupByMulti[j].getFacetData(currentContext.reader);
                      }
                      for (j=0; j<distinctLength; ++j)
                      {
                        distinctDataCaches[j] = (FacetDataCache)currentContext.
                            reader.
                            getFacetHandler(req.getDistinct()[j]).
                            getFacetData(currentContext.reader);
                      }
                      break;
                    }
                  }
                  if (contextLeft <= 0) // No more docs left.
                    break;
                }
              }
              end = SortCollector.BLOCK_SIZE - 1;
            }
          }
        }
        else
        {
          if (res.getSenseiHits() != null)
          {
            for (SenseiHit hit : res.getSenseiHits())
            {
              if (hit.getGroupHits() != null)
              {
                rawGroupValue = hit.getRawGroupValue();
                if (rawGroupValueType[hit.getGroupPosition()] == LONG_ARRAY_GROUP_VALUE_TYPE)
                {
                  primitiveLongArrayWrapperTmp.data = (long[])rawGroupValue;
                  rawGroupValue = primitiveLongArrayWrapperTmp;
                }

                hitWithGroupQueue = groupMaps[hit.getGroupPosition()].get(rawGroupValue);
                if (hitWithGroupQueue != null)
                  hitWithGroupQueue.iterList.add(Arrays.asList(hit.getSenseiGroupHits()).iterator());
              }
            }
          }
        }
        totalDocs += res.getTotalDocs();
      }
      return this;
    }
  }
  private static void mergerErrors(SenseiResult merged, final SenseiRequest req, Collection<SenseiResult> results, String parsedQuery) {
    merged.setParsedQuery(parsedQuery);
    merged.getErrors().addAll(req.getErrors());
    for (SenseiResult res : results) {
      merged.getErrors().addAll(res.getErrors());
      if (res.getBoboErrors().size() > 0) {
        for (String boboError : res.getBoboErrors()) {
          merged.addError(new SenseiError(boboError, ErrorType.BoboExecutionError));
        }
      }
    }
  }
}
TOP

Related Classes of com.senseidb.search.node.ResultMerger

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.