|  | @@ -20,6 +20,7 @@
 | 
	
		
			
				|  |  |  package org.elasticsearch.search.aggregations.bucket.composite;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  import org.apache.lucene.index.LeafReaderContext;
 | 
	
		
			
				|  |  | +import org.apache.lucene.util.PriorityQueue;
 | 
	
		
			
				|  |  |  import org.elasticsearch.common.lease.Releasable;
 | 
	
		
			
				|  |  |  import org.elasticsearch.common.lease.Releasables;
 | 
	
		
			
				|  |  |  import org.elasticsearch.common.util.BigArrays;
 | 
	
	
		
			
				|  | @@ -27,19 +28,40 @@ import org.elasticsearch.common.util.IntArray;
 | 
	
		
			
				|  |  |  import org.elasticsearch.search.aggregations.LeafBucketCollector;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  import java.io.IOException;
 | 
	
		
			
				|  |  | -import java.util.Set;
 | 
	
		
			
				|  |  | -import java.util.TreeMap;
 | 
	
		
			
				|  |  | +import java.util.HashMap;
 | 
	
		
			
				|  |  | +import java.util.Map;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  /**
 | 
	
		
			
				|  |  | - * A specialized queue implementation for composite buckets
 | 
	
		
			
				|  |  | + * A specialized {@link PriorityQueue} implementation for composite buckets.
 | 
	
		
			
				|  |  |   */
 | 
	
		
			
				|  |  | -final class CompositeValuesCollectorQueue implements Releasable {
 | 
	
		
			
				|  |  | +final class CompositeValuesCollectorQueue extends PriorityQueue<Integer> implements Releasable {
 | 
	
		
			
				|  |  | +    private class Slot {
 | 
	
		
			
				|  |  | +        int value;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        Slot(int initial) {
 | 
	
		
			
				|  |  | +            this.value = initial;
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        @Override
 | 
	
		
			
				|  |  | +        public boolean equals(Object o) {
 | 
	
		
			
				|  |  | +            if (this == o) return true;
 | 
	
		
			
				|  |  | +            if (o == null || getClass() != o.getClass()) return false;
 | 
	
		
			
				|  |  | +            Slot slot = (Slot) o;
 | 
	
		
			
				|  |  | +            return CompositeValuesCollectorQueue.this.equals(value, slot.value);
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        @Override
 | 
	
		
			
				|  |  | +        public int hashCode() {
 | 
	
		
			
				|  |  | +            return CompositeValuesCollectorQueue.this.hashCode(value);
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      // the slot for the current candidate
 | 
	
		
			
				|  |  |      private static final int CANDIDATE_SLOT = Integer.MAX_VALUE;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      private final BigArrays bigArrays;
 | 
	
		
			
				|  |  |      private final int maxSize;
 | 
	
		
			
				|  |  | -    private final TreeMap<Integer, Integer> keys;
 | 
	
		
			
				|  |  | +    private final Map<Slot, Integer> map;
 | 
	
		
			
				|  |  |      private final SingleDimensionValuesSource<?>[] arrays;
 | 
	
		
			
				|  |  |      private IntArray docCounts;
 | 
	
		
			
				|  |  |      private boolean afterKeyIsSet = false;
 | 
	
	
		
			
				|  | @@ -52,10 +74,11 @@ final class CompositeValuesCollectorQueue implements Releasable {
 | 
	
		
			
				|  |  |       * @param afterKey composite key
 | 
	
		
			
				|  |  |       */
 | 
	
		
			
				|  |  |      CompositeValuesCollectorQueue(BigArrays bigArrays, SingleDimensionValuesSource<?>[] sources, int size, CompositeKey afterKey) {
 | 
	
		
			
				|  |  | +        super(size);
 | 
	
		
			
				|  |  |          this.bigArrays = bigArrays;
 | 
	
		
			
				|  |  |          this.maxSize = size;
 | 
	
		
			
				|  |  |          this.arrays = sources;
 | 
	
		
			
				|  |  | -        this.keys = new TreeMap<>(this::compare);
 | 
	
		
			
				|  |  | +        this.map = new HashMap<>(size);
 | 
	
		
			
				|  |  |          if (afterKey != null) {
 | 
	
		
			
				|  |  |              assert afterKey.size() == sources.length;
 | 
	
		
			
				|  |  |              afterKeyIsSet = true;
 | 
	
	
		
			
				|  | @@ -66,25 +89,16 @@ final class CompositeValuesCollectorQueue implements Releasable {
 | 
	
		
			
				|  |  |          this.docCounts = bigArrays.newIntArray(1, false);
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    /**
 | 
	
		
			
				|  |  | -     * The current size of the queue.
 | 
	
		
			
				|  |  | -     */
 | 
	
		
			
				|  |  | -    int size() {
 | 
	
		
			
				|  |  | -        return keys.size();
 | 
	
		
			
				|  |  | +    @Override
 | 
	
		
			
				|  |  | +    protected boolean lessThan(Integer a, Integer b) {
 | 
	
		
			
				|  |  | +        return compare(a, b) > 0;
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      /**
 | 
	
		
			
				|  |  |       * Whether the queue is full or not.
 | 
	
		
			
				|  |  |       */
 | 
	
		
			
				|  |  |      boolean isFull() {
 | 
	
		
			
				|  |  | -        return keys.size() == maxSize;
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -    /**
 | 
	
		
			
				|  |  | -     * Returns a sorted {@link Set} view of the slots contained in this queue.
 | 
	
		
			
				|  |  | -     */
 | 
	
		
			
				|  |  | -    Set<Integer> getSortedSlot() {
 | 
	
		
			
				|  |  | -        return keys.keySet();
 | 
	
		
			
				|  |  | +        return size() >= maxSize;
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      /**
 | 
	
	
		
			
				|  | @@ -92,7 +106,7 @@ final class CompositeValuesCollectorQueue implements Releasable {
 | 
	
		
			
				|  |  |       * the slot if the candidate is already in the queue or null if the candidate is not present.
 | 
	
		
			
				|  |  |       */
 | 
	
		
			
				|  |  |      Integer compareCurrent() {
 | 
	
		
			
				|  |  | -        return keys.get(CANDIDATE_SLOT);
 | 
	
		
			
				|  |  | +        return map.get(new Slot(CANDIDATE_SLOT));
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      /**
 | 
	
	
		
			
				|  | @@ -106,7 +120,7 @@ final class CompositeValuesCollectorQueue implements Releasable {
 | 
	
		
			
				|  |  |       * Returns the upper value (inclusive) of the leading source.
 | 
	
		
			
				|  |  |       */
 | 
	
		
			
				|  |  |      Comparable getUpperValueLeadSource() throws IOException {
 | 
	
		
			
				|  |  | -        return size() >= maxSize ? arrays[0].toComparable(keys.lastKey()) : null;
 | 
	
		
			
				|  |  | +        return size() >= maxSize ? arrays[0].toComparable(top()) : null;
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |      /**
 | 
	
		
			
				|  |  |       * Returns the document count in <code>slot</code>.
 | 
	
	
		
			
				|  | @@ -127,12 +141,17 @@ final class CompositeValuesCollectorQueue implements Releasable {
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      /**
 | 
	
		
			
				|  |  | -     * Compares the values in <code>slot1</code> with <code>slot2</code>.
 | 
	
		
			
				|  |  | +     * Compares the values in <code>slot1</code> with the values in <code>slot2</code>.
 | 
	
		
			
				|  |  |       */
 | 
	
		
			
				|  |  |      int compare(int slot1, int slot2) {
 | 
	
		
			
				|  |  | +        assert slot2 != CANDIDATE_SLOT;
 | 
	
		
			
				|  |  |          for (int i = 0; i < arrays.length; i++) {
 | 
	
		
			
				|  |  | -            int cmp = (slot1 == CANDIDATE_SLOT) ? arrays[i].compareCurrent(slot2) :
 | 
	
		
			
				|  |  | -                arrays[i].compare(slot1, slot2);
 | 
	
		
			
				|  |  | +            final int cmp;
 | 
	
		
			
				|  |  | +            if (slot1 == CANDIDATE_SLOT) {
 | 
	
		
			
				|  |  | +                cmp = arrays[i].compareCurrent(slot2);
 | 
	
		
			
				|  |  | +            } else {
 | 
	
		
			
				|  |  | +                cmp = arrays[i].compare(slot1, slot2);
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  |              if (cmp != 0) {
 | 
	
		
			
				|  |  |                  return cmp;
 | 
	
		
			
				|  |  |              }
 | 
	
	
		
			
				|  | @@ -140,6 +159,36 @@ final class CompositeValuesCollectorQueue implements Releasable {
 | 
	
		
			
				|  |  |          return 0;
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * Returns true if the values in <code>slot1</code> are equals to the value in <code>slot2</code>.
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    boolean equals(int slot1, int slot2) {
 | 
	
		
			
				|  |  | +        assert slot2 != CANDIDATE_SLOT;
 | 
	
		
			
				|  |  | +        for (int i = 0; i < arrays.length; i++) {
 | 
	
		
			
				|  |  | +            final int cmp;
 | 
	
		
			
				|  |  | +            if (slot1 == CANDIDATE_SLOT) {
 | 
	
		
			
				|  |  | +                cmp = arrays[i].compareCurrent(slot2);
 | 
	
		
			
				|  |  | +            } else {
 | 
	
		
			
				|  |  | +                cmp = arrays[i].compare(slot1, slot2);
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            if (cmp != 0) {
 | 
	
		
			
				|  |  | +                return false;
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        return true;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * Returns a hash code value for the values in <code>slot</code>.
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    int hashCode(int slot) {
 | 
	
		
			
				|  |  | +        int result = 1;
 | 
	
		
			
				|  |  | +        for (int i = 0; i < arrays.length; i++) {
 | 
	
		
			
				|  |  | +            result = 31 * result + (slot == CANDIDATE_SLOT ? arrays[i].hashCodeCurrent() : arrays[i].hashCode(slot));
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        return result;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      /**
 | 
	
		
			
				|  |  |       * Compares the after values with the values in <code>slot</code>.
 | 
	
		
			
				|  |  |       */
 | 
	
	
		
			
				|  | @@ -209,28 +258,28 @@ final class CompositeValuesCollectorQueue implements Releasable {
 | 
	
		
			
				|  |  |              // this key is greater than the top value collected in the previous round, skip it
 | 
	
		
			
				|  |  |              return -1;
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  | -        if (keys.size() >= maxSize) {
 | 
	
		
			
				|  |  | -            // the tree map is full, check if the candidate key should be kept
 | 
	
		
			
				|  |  | -            if (compare(CANDIDATE_SLOT, keys.lastKey()) > 0) {
 | 
	
		
			
				|  |  | -                // the candidate key is not competitive, skip it
 | 
	
		
			
				|  |  | -                return -1;
 | 
	
		
			
				|  |  | -            }
 | 
	
		
			
				|  |  | +        if (size() >= maxSize
 | 
	
		
			
				|  |  | +                // the tree map is full, check if the candidate key should be kept
 | 
	
		
			
				|  |  | +                && compare(CANDIDATE_SLOT, top()) > 0) {
 | 
	
		
			
				|  |  | +            // the candidate key is not competitive, skip it
 | 
	
		
			
				|  |  | +            return -1;
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          // the candidate key is competitive
 | 
	
		
			
				|  |  |          final int newSlot;
 | 
	
		
			
				|  |  | -        if (keys.size() >= maxSize) {
 | 
	
		
			
				|  |  | -            // the tree map is full, we replace the last key with this candidate
 | 
	
		
			
				|  |  | -            int slot = keys.pollLastEntry().getKey();
 | 
	
		
			
				|  |  | +        if (size() >= maxSize) {
 | 
	
		
			
				|  |  | +            // the queue is full, we replace the last key with this candidate
 | 
	
		
			
				|  |  | +            int slot = pop();
 | 
	
		
			
				|  |  | +            map.remove(new Slot(slot));
 | 
	
		
			
				|  |  |              // and we recycle the deleted slot
 | 
	
		
			
				|  |  |              newSlot = slot;
 | 
	
		
			
				|  |  |          } else {
 | 
	
		
			
				|  |  | -            newSlot = keys.size();
 | 
	
		
			
				|  |  | -            assert newSlot < maxSize;
 | 
	
		
			
				|  |  | +            newSlot = size();
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |          // move the candidate key to its new slot
 | 
	
		
			
				|  |  |          copyCurrent(newSlot);
 | 
	
		
			
				|  |  | -        keys.put(newSlot, newSlot);
 | 
	
		
			
				|  |  | +        map.put(new Slot(newSlot), newSlot);
 | 
	
		
			
				|  |  | +        add(newSlot);
 | 
	
		
			
				|  |  |          return newSlot;
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 |