Version 3
    package org.drools.util;
    
    import java.io.Serializable;
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.Collections;
    import java.util.Comparator;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    import org.drools.common.AgendaItem;
    import org.drools.conflict.DepthConflictResolver;
    import org.drools.reteoo.ReteTuple;
    import org.drools.util.LazyMultiListQueue.PropagationCounterEntry.ActivationComparator;
    import org.drools.util.LinkedList.LinkedListIterator;
    
    public class LazyMultiListQueue
        implements
        Queue {
    
        //Map map = new HashMap();
        //private SalienceBucket salienceBucket;
    
        private SalienceBucket minus10 = new SalienceBucket( -10 );
        private SalienceBucket plus10  = new SalienceBucket( 10 );
        private SalienceBucket zero   = new SalienceBucket( 5 );
        private SalienceBucket plus5   = new SalienceBucket( 0 );
    
    
        public Queueable dequeue() {        
            SalienceBucket salienceBucket = null;
            if ( !plus10.isEmptry() ) {
                salienceBucket = plus10;
            } else if ( !plus5.isEmptry() ) {
                salienceBucket = plus5;
            } else if ( !zero.isEmptry() ) {
                salienceBucket = zero;
            } else if ( !minus10.isEmptry() ) {
                salienceBucket = minus10;
            }    
            
            return ( salienceBucket != null ) ? salienceBucket.dequeue() : null;
        }
    
        public Queueable dequeue(int index) {
            // not used
            return null;
        }
    
        public void enqueue(Queueable queueable) {
            AgendaItem item = (AgendaItem) queueable;
            int salience =  item.getRule().getSalience();
            
            SalienceBucket salienceBucket = null;
            switch ( salience ) {
                case -10:
                    salienceBucket = minus10;
                    break;
                case 0:
                    salienceBucket = zero;
                break;
                case 5:
                    salienceBucket = plus5;
                    break;
                case 10:
                    salienceBucket = plus10;
                    break;
            }
            
            //SalienceBucket salienceBucket = ( SalienceBucket ) map.get(  salience );
    //        if ( salienceBucket == null ) {
    //            salienceBucket = new SalienceBucket( item.getRule().getSalience() );
    //            map.put( salience,
    //                     salienceBucket );
    //        }
            salienceBucket.add( item );
        }
    
        public boolean isEmpty() {
            return false; //this.map.isEmptry();
        }
    
        public void clear() {
            //this.map.clear();
        }
    
        public int size() {
            //return this.map.size;
            return -1;
        }
    
        public Object[] toArray(Object[] array) {
            //return getQueueable();
            return null;
        }
    
        public Queueable[] getQueueable() {
            //return new Queueable[] { this.current };
            return null;
        }
    
        public static class SalienceBucket
            implements
            Comparable<SalienceBucket> {
            private int        salience;
            private LinkedList list;
            private int        size;
    
            public SalienceBucket(int salience) {
                this.salience = salience;
                this.list = new LinkedList();
            }
    
            public long getNumber() {
                return this.salience;
            }
    
            public void add(AgendaItem item) {
                size++;
    
                //ReteTuple tuple = (ReteTuple) item.getTuple();
                //long newRecency = tuple.getRecency();
                long counter = item.getPropagationContext().getPropagationNumber();
    
                PropagationCounterEntry last = (PropagationCounterEntry) list.getLast();
                if ( last == null || counter > last.getCounter() ) {
                    last = new PropagationCounterEntry( this,
                                                        counter );
                    this.list.add( last );
                }
                last.add( item );
            }
    
            public void remove(PropagationCounterEntry entry) {
                this.list.remove( entry );
            }
    
            public Queueable dequeue() {
                PropagationCounterEntry tier = (PropagationCounterEntry) list.getLast();
                return (tier != null) ? tier.dequeue() : null;
            }
    
            public int size() {
                return this.size;
            }
    
            public boolean isEmptry() {
                return this.size == 0;
            }
    
            public void clear() {
                this.list.clear();
            }
    
            public void decreaseSize() {
                this.size--;
            }
    
            public int compareTo(SalienceBucket object) {
                return (int) (this.salience - object.getNumber());
            }
        }
    
        public static class PropagationCounterEntry extends AbstractBaseLinkedListNode
            implements
            Serializable {
            private long                 counter;
            private SalienceBucket       parent;
    
            private LinkedList           list;
    
            private LinkedListNode       next;
    
            private boolean              sorted;
    
            private ActivationComparator comparator = ActivationComparator.getInstance();
    
            public PropagationCounterEntry(SalienceBucket parent,
                                           long counter) {
                this.counter = counter;
                this.parent = parent;
                this.list = new LinkedList();
                this.sorted = false;
            }
    
            public void setNext(LinkedListNode next) {
                this.next = next;
            }
    
            public LinkedListNode getNext() {
                return this.next;
            }
    
            public long getCounter() {
                return this.counter;
            }
    
            public void add(AgendaItem item) {
                this.list.add( item );
                item.addToRecencyTierEntry( this );
            }
    
            public void remove(AgendaItem item) {
                this.list.remove( item );
                this.parent.decreaseSize();
                if ( this.list.isEmpty() ) {
                    this.parent.remove( this );
                }
    
            }
    
            public Queueable dequeue() {
                // lazy sort on first access
                if ( !this.sorted ) {
                    //jdkMergeSortList( this.list );
                    LinkedListNode node = mergesort( this.list.getFirst(),
                                                     this.list.size() );
                    this.list.setPayload( node );
                    this.sorted = true;
                }
    
                Queueable item = (Queueable) this.list.removeLast();
                this.parent.decreaseSize();
                if ( this.list.isEmpty() ) {
                    this.parent.remove( this );
                }
                return item;
            }
    
            private void jdkMergeSortList(LinkedList list) {
                //int point = list.size() / 2;
                LinkedListNode[] array = new LinkedListNode[list.size()];
                LinkedListNode node = list.removeFirst();
                for ( int i = 0, length = array.length; i < length; i++ ) {
                    array+ = node;
                    node = list.removeFirst();
                }
                list.clear();
    
                // Sort the array and rebuild the linked list
                Arrays.sort( array,
                             new ActivationComparator() );
                for ( int i = 0, length = array.length; i < length; i++ ) {
                    list.add( array+ );
                }
            }
    
            private LinkedListNode mergesort(LinkedListNode head,
                                             int size) {
                // empty or one-node lists are already sorted
                if ( head == null || head.getNext() == null ) {
                    return head;
                }
    
                int firstHalfSize = size / 2;
                int secondHalfSize = size - firstHalfSize;
                // split list in two, get reference to second half
                LinkedListNode secondhalf = split( head,
                                                   firstHalfSize );
    
                // recursively sort sublists
                head = mergesort( head,
                                  firstHalfSize );
                secondhalf = mergesort( secondhalf,
                                        secondHalfSize );
    
                // merge sorted sublists into one sorted list
                return merge( head,
                              secondhalf );
            }
    
            private LinkedListNode split(LinkedListNode head,
                                         int point) {
                LinkedListNode ptr = head;
                for ( int i = 0; i < point - 1; i++ ) {
                    ptr = ptr.getNext();
                }
                LinkedListNode secondHalf = ptr.getNext();
                ptr.setNext( null );
    
                return secondHalf;
            }
    
            private LinkedListNode merge(LinkedListNode head1,
                                         LinkedListNode head2) {
                // merging with an empty list requires no work
                if ( head1 == null ) {
                    return head2;
                }
                if ( head2 == null ) {
                    return head1;
                }
    
                // pick smaller value to be first
                if ( comparator.compare( head1,
                                         head2 ) < 0 ) {
                    LinkedListNode node = merge( head1.getNext(),
                                                 head2 );
                    head1.setNext( node );
                    node.setPrevious( head1 );
                    return head1;
                } else {
                    LinkedListNode node = merge( head1,
                                                 head2.getNext() );
                    head2.setNext( node );
                    node.setPrevious( head2 );
                    return head2;
                }
            }
    
            private void split(LinkedListNode node,
                               int point,
                               SentinalNode left,
                               SentinalNode right) {
                left.node = node;
                for ( int i = 0; i < point; i++ ) {
                    node = node.getNext();
                }
    
                // break the links
                node.getPrevious().setNext( null );
                node.setPrevious( null );
                right.node = node;
            }
    
            public class SentinalNode extends AbstractBaseLinkedListNode {
                public LinkedListNode node;
                public int            size;
            }
    
            private void sortList(LinkedList list) {
                if ( list.size() == 1 ) {
                    return; // only one entry, no need to sort;
                }
    
                // Get the second to last
                AgendaItem current = (AgendaItem) list.getLast().getPrevious();
    
                while ( current != null ) {
                    // find the next item to swap with                     
                    AgendaItem nextItem = (AgendaItem) current.getNext();
                    AgendaItem tempItem = nextItem;
                    while ( tempItem != null ) {
                        if ( insertAfter( current,
                                          tempItem ) ) {
                            nextItem = tempItem;
                        } else {
                            break;
                        }
                        tempItem = (AgendaItem) tempItem.getNext();
                    }
    
                    if ( tempItem != nextItem ) {
                        AgendaItem newCurrent = (AgendaItem) current.getPrevious();
                        this.list.remove( current );
                        this.list.insertAfter( nextItem,
                                               current );
                        current = newCurrent;
                    } else {
                        current = (AgendaItem) current.getPrevious();
                    }
    
                }
            }
    
            public static class ActivationComparator
                implements
                Comparator<AgendaItem> {
                public static final ActivationComparator instance = new ActivationComparator();
    
                public static final ActivationComparator getInstance() {
                    return instance;
                }
    
                private ActivationComparator() {
    
                }
    
                public int compare(AgendaItem object0,
                                   AgendaItem object1) {
                    return (int) (object0.getTuple().getRecency() - object1.getTuple().getRecency());
                }
    
            }
    
            private boolean insertAfter(AgendaItem item1,
                                        AgendaItem item2) {
                ReteTuple tuple1 = (ReteTuple) item1.getTuple();
                ReteTuple tuple2 = (ReteTuple) item2.getTuple();
    
                return tuple1.getRecency() > tuple2.getRecency();
    
                //            if ( tuple1.getParent() != null ) {
                //                if ( tuple2.getParent() != null ) {
                //                    // check with parents
                //                    return insertAfter( tuple1.getParent(),
                //                                        tuple1.getParent() );
                //                } else {
                //                    // tuple2 has no parents, tuple1 does, so tuple1 has priority                    
                //                    return true;
                //                }
                //            } else if ( tuple2.getParent() != null ) {
                //                // tuple1 has no parents, tuple2 does, so tuple2 has priority 
                //                return false;
                //            } else {
                //                // both have null parents, both still the same, so don't bother doing the swap
                //                return false;
                //            }
            }
    
            //        private boolean insertAfter(ReteTuple tuple1,
            //                                    ReteTuple tuple2) {
            //            long recency1 = tuple1.getFactHandle().getRecency();
            //            long recency2 = tuple2.getFactHandle().getRecency();
            //
            //            if ( recency1 == recency2 ) {
            //                if ( tuple1.getParent() != null ) {
            //                    if ( tuple2.getParent() != null ) {
            //                        // check with parents
            //                        return insertAfter( tuple1.getParent(),
            //                                            tuple2.getParent() );
            //                    } else {
            //                        // tuple2 has no parents, tuple1 does, so tuple1 has priority
            //                        return true;
            //                    }
            //                } else if ( tuple2.getParent() != null ) {
            //                    // tuple1 has no parents, tuple2 does, so tuple2 has priority 
            //                    return false;
            //                } else {
            //                    // both have null parents, both still the same, so don't bother doing the swap
            //                    return false;
            //                }
            //            } else if ( recency1 > recency2 ) {
            //                // recency1 is higher tham recency2, so tuple1 has priority
            //                return true;
            //            } else {
            //                // recency1 is lower tham recency2, so tuple2 has priority
            //                return false;
            //            }
            //        }
        }
    }