content: package org.apache.lucene.search;
  
  /**
   * 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.
   */
  
  import java.io.IOException;
  
  import org.apache.lucene.util.PriorityQueue;
  
  /**
   * Expert: A hit queue for sorting by hits by terms in more than one field.
   * Uses <code>FieldCache.DEFAULT</code> for maintaining
   * internal term lookup tables.
   * 
   * @lucene.experimental
   * @since 2.9
   * @see Searcher#search(Query,Filter,int,Sort)
   * @see FieldCache
   */
  public abstract class FieldValueHitQueue<T extends FieldValueHitQueue.Entry> extends PriorityQueue<T> {
  
    public static class Entry extends ScoreDoc {
      public int slot;
  
      public Entry(int slot, int doc, float score) {
        super(doc, score);
        this.slot = slot;
      }
      
      @Override
      public String toString() {
        return "slot:" + slot + " " + super.toString();
      }
    }
  
    /**
     * An implementation of {@link FieldValueHitQueue} which is optimized in case
     * there is just one comparator.
     */
    private static final class OneComparatorFieldValueHitQueue<T extends FieldValueHitQueue.Entry> extends FieldValueHitQueue<T> {
  
      private final FieldComparator comparator;
      private final int oneReverseMul;
      
      public OneComparatorFieldValueHitQueue(SortField[] fields, int size)
          throws IOException {
        super(fields);
  
        SortField field = fields[0];
        comparator = field.getComparator(size, 0);
        oneReverseMul = field.reverse ? -1 : 1;
  
        comparators[0] = comparator;
        reverseMul[0] = oneReverseMul;
        
        initialize(size);
      }
  
      /**
       * Returns whether <code>a</code> is less relevant than <code>b</code>.
       * @param a ScoreDoc
       * @param b ScoreDoc
       * @return <code>true</code> if document <code>a</code> should be sorted after document <code>b</code>.
       */
      @Override
      protected boolean lessThan(final Entry hitA, final Entry hitB) {
  
        assert hitA != hitB;
        assert hitA.slot != hitB.slot;
  
        final int c = oneReverseMul * comparator.compare(hitA.slot, hitB.slot);
        if (c != 0) {
          return c > 0;
        }
  
        // avoid random sort order that could lead to duplicates (bug #31241):
        return hitA.doc > hitB.doc;
      }
  
    }
    
    /**
     * An implementation of {@link FieldValueHitQueue} which is optimized in case
     * there is more than one comparator.
     */
    private static final class MultiComparatorsFieldValueHitQueue<T extends FieldValueHitQueue.Entry> extends FieldValueHitQueue<T> {
  
      public MultiComparatorsFieldValueHitQueue(SortField[] fields, int size)
          throws IOException {
        super(fields);
  
        int numComparators = comparators.length;
        for (int i = 0; i < numComparators; ++i) {
          SortField field = fields[i];
  
          reverseMul[i] = field.reverse ? -1 : 1;
          comparators[i] = field.getComparator(size, i);
        }
  
        initialize(size);
      }
    
      @Override
      protected boolean lessThan(final Entry hitA, final Entry hitB) {
  
        assert hitA != hitB;
        assert hitA.slot != hitB.slot;
  
        int numComparators = comparators.length;
        for (int i = 0; i < numComparators; ++i) {
          final int c = reverseMul[i] * comparators[i].compare(hitA.slot, hitB.slot);
          if (c != 0) {
            // Short circuit
            return c > 0;
          }
        }
  
        // avoid random sort order that could lead to duplicates (bug #31241):
        return hitA.doc > hitB.doc;
      }
      
    }
    
    // prevent instantiation and extension.
    private FieldValueHitQueue(SortField[] fields) {
      // When we get here, fields.length is guaranteed to be > 0, therefore no
      // need to check it again.
      
      // All these are required by this class's API - need to return arrays.
      // Therefore even in the case of a single comparator, create an array
      // anyway.
      this.fields = fields;
      int numComparators = fields.length;
      comparators = new FieldComparator[numComparators];
      reverseMul = new int[numComparators];
    }
  
    /**
     * Creates a hit queue sorted by the given list of fields.
     * 
     * <p><b>NOTE</b>: The instances returned by this method
     * pre-allocate a full array of length <code>numHits</code>.
     * 
     * @param fields
     *          SortField array we are sorting by in priority order (highest
     *          priority first); cannot be <code>null</code> or empty
     * @param size
     *          The number of hits to retain. Must be greater than zero.
     * @throws IOException
     */
    public static <T extends FieldValueHitQueue.Entry> FieldValueHitQueue<T> create(SortField[] fields, int size) throws IOException {
  
      if (fields.length == 0) {
        throw new IllegalArgumentException("Sort must contain at least one field");
      }
  
      if (fields.length == 1) {
        return new OneComparatorFieldValueHitQueue<T>(fields, size);
      } else {
        return new MultiComparatorsFieldValueHitQueue<T>(fields, size);
      }
    }
    
    public FieldComparator[] getComparators() {
      return comparators;
    }
  
    public int[] getReverseMul() {
      return reverseMul;
    }
  
    /** Stores the sort criteria being used. */
    protected final SortField[] fields;
    protected final FieldComparator[] comparators;
    protected final int[] reverseMul;
  
    @Override
    protected abstract boolean lessThan (final Entry a, final Entry b);
  
    /**
     * Given a queue Entry, creates a corresponding FieldDoc
     * that contains the values used to sort the given document.
     * These values are not the raw values out of the index, but the internal
     * representation of them. This is so the given search hit can be collated by
     * a MultiSearcher with other search hits.
     * 
     * @param entry The Entry used to create a FieldDoc
     * @return The newly created FieldDoc
     * @see Searchable#search(Weight,Filter,int,Sort)
     */
    public FieldDoc fillFields(final Entry entry) {
      final int n = comparators.length;
      final Object[] fields = new Object[n];
      for (int i = 0; i < n; ++i) {
        fields[i] = comparators[i].value(entry.slot);
      }
      //if (maxscore > 1.0f) doc.score /= maxscore;   // normalize scores
      return new FieldDoc(entry.doc, entry.score, fields);
    }
  
    /** Returns the SortFields being used by this hit queue. */
    public SortField[] getFields() {
      return fields;
    }
  }
  