content: /**
   * 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.solr.handler.component;
  
  import org.apache.lucene.search.FieldComparatorSource;
  import org.apache.lucene.search.FieldDoc;
  import org.apache.lucene.search.SortField;
  import org.apache.lucene.util.PriorityQueue;
  import org.apache.solr.common.util.NamedList;
  import org.apache.solr.search.MissingStringLastComparatorSource;
  
  import java.text.Collator;
  import java.util.ArrayList;
  import java.util.Comparator;
  import java.util.List;
  import java.util.Locale;
  
  public class ShardDoc extends FieldDoc {
    public String shard;
    public String shardAddress;  // TODO
    
    int orderInShard;
      // the position of this doc within the shard... this can be used
      // to short-circuit comparisons if the shard is equal, and can
      // also be used to break ties within the same shard.
  
    public Object id;
      // this is currently the uniqueKeyField but
      // may be replaced with internal docid in a future release.
  
    Float score;
  
    NamedList sortFieldValues;
    // sort field values for *all* docs in a particular shard.
    // this doc's values are in position orderInShard
  
    // TODO: store the SolrDocument here?
    // Store the order in the merged list for lookup when getting stored fields?
    // (other components need this ordering to store data in order, like highlighting)
    // but we shouldn't expose uniqueKey (have a map by it) until the stored-field
    // retrieval stage.
  
    public int positionInResponse;
    // the ordinal position in the merged response arraylist  
  
    public ShardDoc(float score, Object[] fields, Object uniqueId, String shard) {
        super(-1, score, fields);
        this.id = uniqueId;
        this.shard = shard;
    }
  
    public ShardDoc() {
      super(-1, Float.NaN);
    }
  
    @Override
    public boolean equals(Object o) {
      if (this == o) return true;
      if (o == null || getClass() != o.getClass()) return false;
  
      ShardDoc shardDoc = (ShardDoc) o;
  
      if (id != null ? !id.equals(shardDoc.id) : shardDoc.id != null) return false;
  
      return true;
    }
  
    @Override
    public int hashCode() {
      return id != null ? id.hashCode() : 0;
    }
  
    @Override
    public String toString(){
      return "id="+id
              +" ,score="+score
              +" ,shard="+shard
              +" ,orderInShard="+orderInShard
              +" ,positionInResponse="+positionInResponse
              +" ,sortFieldValues="+sortFieldValues;
    }
  }
  
  
  
  // used by distributed search to merge results.
  class ShardFieldSortedHitQueue extends PriorityQueue<ShardDoc> {
  
    /** Stores a comparator corresponding to each field being sorted by */
    protected Comparator[] comparators;
  
    /** Stores the sort criteria being used. */
    protected SortField[] fields;
  
    /** The order of these fieldNames should correspond to the order of sort field values retrieved from the shard */
    protected List<String> fieldNames = new ArrayList<String>();
  
    public ShardFieldSortedHitQueue(SortField[] fields, int size) {
      final int n = fields.length;
      comparators = new Comparator[n];
      this.fields = new SortField[n];
      for (int i = 0; i < n; ++i) {
  
        // keep track of the named fields
        int type = fields[i].getType();
        if (type!=SortField.SCORE && type!=SortField.DOC) {
          fieldNames.add(fields[i].getField());
        }
  
        String fieldname = fields[i].getField();
        comparators[i] = getCachedComparator(fieldname, fields[i]
            .getType(), fields[i].getLocale(), fields[i].getComparatorSource());
  
       if (fields[i].getType() == SortField.STRING) {
          this.fields[i] = new SortField(fieldname, fields[i].getLocale(),
              fields[i].getReverse());
        } else {
          this.fields[i] = new SortField(fieldname, fields[i].getType(),
              fields[i].getReverse());
        }
  
        //System.out.println("%%%%%%%%%%%%%%%%%% got "+fields[i].getType() +"   for "+ fieldname +"  fields[i].getReverse(): "+fields[i].getReverse());
      }
  
      initialize(size);
    }
  
    @Override
    protected boolean lessThan(ShardDoc docA, ShardDoc docB) {
      // If these docs are from the same shard, then the relative order
      // is how they appeared in the response from that shard.    
      if (docA.shard == docB.shard) {
        // if docA has a smaller position, it should be "larger" so it
        // comes before docB.
        // This will handle sorting by docid within the same shard
  
        // comment this out to test comparators.
        return !(docA.orderInShard < docB.orderInShard);
      }
  
  
      // run comparators
      final int n = comparators.length;
      int c = 0;
      for (int i = 0; i < n && c == 0; i++) {
        c = (fields[i].getReverse()) ? comparators[i].compare(docB, docA)
            : comparators[i].compare(docA, docB);
      }
  
      // solve tiebreaks by comparing shards (similar to using docid)
      // smaller docid's beat larger ids, so reverse the natural ordering
      if (c == 0) {
        c = -docA.shard.compareTo(docB.shard);
      }
  
      return c < 0;
    }
  
    Comparator getCachedComparator(String fieldname, int type, Locale locale, FieldComparatorSource factory) {
      Comparator comparator = null;
      switch (type) {
      case SortField.SCORE:
        comparator = comparatorScore(fieldname);
        break;
      case SortField.STRING:
        if (locale != null)
          comparator = comparatorStringLocale(fieldname, locale);
        else
          comparator = comparatorNatural(fieldname);
        break;
      case SortField.CUSTOM:
        if (factory instanceof MissingStringLastComparatorSource){
          comparator = comparatorMissingStringLast(fieldname);
        } else {
          // TODO: support other types such as random... is there a way to
          // support generically?  Perhaps just comparing Object
          comparator = comparatorNatural(fieldname);
          // throw new RuntimeException("Custom sort not supported factory is "+factory.getClass());
        }
        break;
      case SortField.DOC:
        // TODO: we can support this!
        throw new RuntimeException("Doc sort not supported");
      default:
        comparator = comparatorNatural(fieldname);
        break;
      }
      return comparator;
    }
  
    class ShardComparator implements Comparator {
      String fieldName;
      int fieldNum;
      public ShardComparator(String fieldName) {
        this.fieldName = fieldName;
        this.fieldNum=0;
        for (int i=0; i<fieldNames.size(); i++) {
          if (fieldNames.get(i).equals(fieldName)) {
            this.fieldNum = i;
            break;
          }
        }
      }
  
      Object sortVal(ShardDoc shardDoc) {
        assert(shardDoc.sortFieldValues.getName(fieldNum).equals(fieldName));
        List lst = (List)shardDoc.sortFieldValues.getVal(fieldNum);
        return lst.get(shardDoc.orderInShard);
      }
  
      public int compare(Object o1, Object o2) {
        return 0;
      }
    }
  
    static Comparator comparatorScore(final String fieldName) {
      return new Comparator() {
        public final int compare(final Object o1, final Object o2) {
          ShardDoc e1 = (ShardDoc) o1;
          ShardDoc e2 = (ShardDoc) o2;
  
          final float f1 = e1.score;
          final float f2 = e2.score;
          if (f1 < f2)
            return -1;
          if (f1 > f2)
            return 1;
          return 0;
        }
      };
    }
  
    // The lucene natural sort ordering corresponds to numeric
    // and string natural sort orderings (ascending).  Since
    // the PriorityQueue keeps the biggest elements by default,
    // we need to reverse the natural compare ordering so that the
    // smallest elements are kept instead of the largest... hence
    // the negative sign on the final compareTo().
    Comparator comparatorNatural(String fieldName) {
      return new ShardComparator(fieldName) {
        @Override
        public final int compare(final Object o1, final Object o2) {
          ShardDoc sd1 = (ShardDoc) o1;
          ShardDoc sd2 = (ShardDoc) o2;
          Comparable v1 = (Comparable)sortVal(sd1);
          Comparable v2 = (Comparable)sortVal(sd2);
          if (v1==v2)
            return 0;
          if (v1==null)
            return 1;
          if(v2==null)
            return -1;
          return -v1.compareTo(v2);
        }
      };
    }
  
  
    Comparator comparatorStringLocale(final String fieldName,
        Locale locale) {
      final Collator collator = Collator.getInstance(locale);
      return new ShardComparator(fieldName) {
        @Override
        public final int compare(final Object o1, final Object o2) {
          ShardDoc sd1 = (ShardDoc) o1;
          ShardDoc sd2 = (ShardDoc) o2;
          Comparable v1 = (Comparable)sortVal(sd1);
          Comparable v2 = (Comparable)sortVal(sd2);
          if (v1==v2)
            return 0;
          if (v1==null)
            return 1;
          if(v2==null)
            return -1;
          return -collator.compare(v1,v2);
        }
      };
    }
  
  
    Comparator comparatorMissingStringLast(final String fieldName) {
       return new ShardComparator(fieldName) {
        @Override
        public final int compare(final Object o1, final Object o2) {
          ShardDoc sd1 = (ShardDoc) o1;
          ShardDoc sd2 = (ShardDoc) o2;
          Comparable v1 = (Comparable)sortVal(sd1);
          Comparable v2 = (Comparable)sortVal(sd2);
          if (v1==v2)
            return 0;
          if (v1==null)
            return -1;
          if(v2==null)
            return 1;
          return -v1.compareTo(v2);
        }
      };
    }
  
  }
  