001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *      http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019package org.apache.oozie.util;
020
021import java.util.AbstractQueue;
022import java.util.ArrayList;
023import java.util.Arrays;
024import java.util.Collection;
025import java.util.ConcurrentModificationException;
026import java.util.Iterator;
027import java.util.List;
028import java.util.concurrent.BlockingQueue;
029import java.util.concurrent.DelayQueue;
030import java.util.concurrent.Delayed;
031import java.util.concurrent.TimeUnit;
032import java.util.concurrent.atomic.AtomicInteger;
033import java.util.concurrent.locks.ReentrantLock;
034
035/**
036 * A Queue implementation that support queuing elements into the future and priority queuing.
037 * <p/>
038 * The {@link PriorityDelayQueue} avoids starvation by raising elements priority as they age.
039 * <p/>
040 * To support queuing elements into the future, the JDK <code>DelayQueue</code> is used.
041 * <p/>
042 * To support priority queuing, an array of <code>DelayQueue</code> sub-queues is used. Elements are consumed from the
043 * higher priority sub-queues first. From a sub-queue, elements are available based on their age.
044 * <p/>
045 * To avoid starvation, there is is maximum wait time for an an element in a sub-queue, after the maximum wait time has
046 * elapsed, the element is promoted to the next higher priority sub-queue. Eventually it will reach the maximum priority
047 * sub-queue and it will be consumed when it is the oldest element in the that sub-queue.
048 * <p/>
049 * Every time an element is promoted to a higher priority sub-queue, a new maximum wait time applies.
050 * <p/>
051 * This class does not use a separate thread for anti-starvation check, instead, the check is performed on polling and
052 * seeking operations. This check is performed, the most every 1/2 second.
053 */
054public class PriorityDelayQueue<E> extends AbstractQueue<PriorityDelayQueue.QueueElement<E>>
055        implements BlockingQueue<PriorityDelayQueue.QueueElement<E>> {
056
057    /**
058     * Element wrapper required by the queue.
059     * <p/>
060     * This wrapper keeps track of the priority and the age of a queue element.
061     */
062    public static class QueueElement<E> implements Delayed {
063        private E element;
064        private int priority;
065        private long baseTime;
066        boolean inQueue;
067
068        /**
069         * Create an Element wrapper.
070         *
071         * @param element element.
072         * @param priority priority of the element.
073         * @param delay delay of the element.
074         * @param unit time unit of the delay.
075         *
076         * @throws IllegalArgumentException if the element is <tt>NULL</tt>, the priority is negative or if the delay is
077         * negative.
078         */
079        public QueueElement(E element, int priority, long delay, TimeUnit unit) {
080            if (element == null) {
081                throw new IllegalArgumentException("element cannot be null");
082            }
083            if (priority < 0) {
084                throw new IllegalArgumentException("priority cannot be negative, [" + element + "]");
085            }
086            if (delay < 0) {
087                throw new IllegalArgumentException("delay cannot be negative");
088            }
089            this.element = element;
090            this.priority = priority;
091            setDelay(delay, unit);
092        }
093
094        /**
095         * Create an Element wrapper with no delay and minimum priority.
096         *
097         * @param element element.
098         */
099        public QueueElement(E element) {
100            this(element, 0, 0, TimeUnit.MILLISECONDS);
101        }
102
103        /**
104         * Return the element from the wrapper.
105         *
106         * @return the element.
107         */
108        public E getElement() {
109            return element;
110        }
111
112        /**
113         * Return the priority of the element.
114         *
115         * @return the priority of the element.
116         */
117        public int getPriority() {
118            return priority;
119        }
120
121        /**
122         * Set the delay of the element.
123         *
124         * @param delay delay of the element.
125         * @param unit time unit of the delay.
126         */
127        public void setDelay(long delay, TimeUnit unit) {
128            baseTime = System.currentTimeMillis() + unit.toMillis(delay);
129        }
130
131        /**
132         * Return the delay of the element.
133         *
134         * @param unit time unit of the delay.
135         *
136         * @return the delay in the specified time unit.
137         */
138        public long getDelay(TimeUnit unit) {
139            return unit.convert(baseTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
140        }
141
142        /**
143         * Compare the age of this wrapper element with another. The priority is not used for the comparision.
144         *
145         * @param o the other wrapper element to compare with.
146         *
147         * @return less than zero if this wrapper is older, zero if both wrapper elements have the same age, greater
148         *         than zero if the parameter wrapper element is older.
149         */
150        public int compareTo(Delayed o) {
151            long diff = (getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS));
152            if(diff > 0) {
153                return 1;
154            } else if(diff < 0) {
155                return -1;
156            } else {
157                return 0;
158            }
159        }
160
161        /**
162         * Return the string representation of the wrapper element.
163         *
164         * @return the string representation of the wrapper element.
165         */
166        @Override
167        public String toString() {
168            StringBuilder sb = new StringBuilder();
169            sb.append("[").append(element).append("] priority=").append(priority).append(" delay=").
170                    append(getDelay(TimeUnit.MILLISECONDS));
171            return sb.toString();
172        }
173
174    }
175
176    /**
177     * Frequency, in milliseconds, of the anti-starvation check.
178     */
179    public static final long ANTI_STARVATION_INTERVAL = 500;
180
181    protected int priorities;
182    protected DelayQueue<QueueElement<E>>[] queues;
183    protected transient final ReentrantLock lock = new ReentrantLock();
184    private transient long lastAntiStarvationCheck = 0;
185    private long maxWait;
186    private int maxSize;
187    protected AtomicInteger currentSize;
188
189    /**
190     * Create a <code>PriorityDelayQueue</code>.
191     *
192     * @param priorities number of priorities the queue will support.
193     * @param maxWait max wait time for elements before they are promoted to the next higher priority.
194     * @param unit time unit of the max wait time.
195     * @param maxSize maximum size of the queue, -1 means unbounded.
196     */
197    @SuppressWarnings("unchecked")
198    public PriorityDelayQueue(int priorities, long maxWait, TimeUnit unit, int maxSize) {
199        if (priorities < 1) {
200            throw new IllegalArgumentException("priorities must be 1 or more");
201        }
202        if (maxWait < 0) {
203            throw new IllegalArgumentException("maxWait must be greater than 0");
204        }
205        if (maxSize < -1 || maxSize == 0) {
206            throw new IllegalArgumentException("maxSize must be -1 or greater than 0");
207        }
208        this.priorities = priorities;
209        queues = new DelayQueue[priorities];
210        for (int i = 0; i < priorities; i++) {
211            queues[i] = new DelayQueue<QueueElement<E>>();
212        }
213        this.maxWait = unit.toMillis(maxWait);
214        this.maxSize = maxSize;
215        if (maxSize != -1) {
216            currentSize = new AtomicInteger();
217        }
218    }
219
220    /**
221     * Return number of priorities the queue supports.
222     *
223     * @return number of priorities the queue supports.
224     */
225    public int getPriorities() {
226        return priorities;
227    }
228
229    /**
230     * Return the max wait time for elements before they are promoted to the next higher priority.
231     *
232     * @param unit time unit of the max wait time.
233     *
234     * @return the max wait time in the specified time unit.
235     */
236    public long getMaxWait(TimeUnit unit) {
237        return unit.convert(maxWait, TimeUnit.MILLISECONDS);
238    }
239
240    /**
241     * Return the maximum queue size.
242     *
243     * @return the maximum queue size. If <code>-1</code> the queue is unbounded.
244     */
245    public long getMaxSize() {
246        return maxSize;
247    }
248
249    /**
250     * Return an iterator over all the {@link QueueElement} elements (both expired and unexpired) in this queue. The
251     * iterator does not return the elements in any particular order.  The returned <tt>Iterator</tt> is a "weakly
252     * consistent" iterator that will never throw {@link ConcurrentModificationException}, and guarantees to traverse
253     * elements as they existed upon construction of the iterator, and may (but is not guaranteed to) reflect any
254     * modifications subsequent to construction.
255     *
256     * @return an iterator over the {@link QueueElement} elements in this queue.
257     */
258    @Override
259    @SuppressWarnings("unchecked")
260    public Iterator<QueueElement<E>> iterator() {
261        QueueElement[][] queueElements = new QueueElement[queues.length][];
262        lock.lock();
263        try {
264            for (int i = 0; i < queues.length; i++) {
265                queueElements[i] = queues[i].toArray(new QueueElement[0]);
266            }
267        }
268        finally {
269            lock.unlock();
270        }
271        List<QueueElement<E>> list = new ArrayList<QueueElement<E>>();
272        for (QueueElement[] elements : queueElements) {
273            list.addAll(Arrays.asList((QueueElement<E>[]) elements));
274        }
275        return list.iterator();
276    }
277
278    /**
279     * Return the number of elements in the queue.
280     *
281     * @return the number of elements in the queue.
282     */
283    @Override
284    public int size() {
285        int size = 0;
286        for (DelayQueue<QueueElement<E>> queue : queues) {
287            size += queue.size();
288        }
289        return size;
290    }
291
292    /**
293     * Return the number of elements on each priority sub-queue.
294     *
295     * @return the number of elements on each priority sub-queue.
296     */
297    public int[] sizes() {
298        int[] sizes = new int[queues.length];
299        for (int i = 0; i < queues.length; i++) {
300            sizes[i] = queues[i].size();
301        }
302        return sizes;
303    }
304
305    /**
306     * Inserts the specified element into this queue if it is possible to do
307     * so immediately without violating capacity restrictions, returning
308     * <tt>true</tt> upon success and throwing an
309     * <tt>IllegalStateException</tt> if no space is currently available.
310     * When using a capacity-restricted queue, it is generally preferable to
311     * use {@link #offer(Object) offer}.
312     *
313     * @param queueElement the {@link QueueElement} element to add.
314     * @return <tt>true</tt> (as specified by {@link Collection#add})
315     * @throws IllegalStateException if the element cannot be added at this
316     *         time due to capacity restrictions
317     * @throws ClassCastException if the class of the specified element
318     *         prevents it from being added to this queue
319     * @throws NullPointerException if the specified element is null
320     * @throws IllegalArgumentException if some property of the specified
321     *         element prevents it from being added to this queue
322     */
323    @Override
324    public boolean add(QueueElement<E> queueElement) {
325        return offer(queueElement, false);
326    }
327
328    /**
329     * Insert the specified {@link QueueElement} element into the queue.
330     *
331     * @param queueElement the {@link QueueElement} element to add.
332     * @param ignoreSize if the queue is bound to a maximum size and the maximum size is reached, this parameter (if set
333     * to <tt>true</tt>) allows to ignore the maximum size and add the element to the queue.
334     *
335     * @return <tt>true</tt> if the element has been inserted, <tt>false</tt> if the element was not inserted (the queue
336     *         has reached its maximum size).
337     *
338     * @throws NullPointerException if the specified element is null
339     */
340    boolean offer(QueueElement<E> queueElement, boolean ignoreSize) {
341        if (queueElement == null) {
342            throw new NullPointerException("queueElement is NULL");
343        }
344        if (queueElement.getPriority() < 0 || queueElement.getPriority() >= priorities) {
345            throw new IllegalArgumentException("priority out of range: " + queueElement);
346        }
347        if (queueElement.inQueue) {
348            throw new IllegalStateException("queueElement already in a queue: " + queueElement);
349        }
350        if (!ignoreSize && currentSize != null && currentSize.get() >= maxSize) {
351            return false;
352        }
353        boolean accepted = queues[queueElement.getPriority()].offer(queueElement);
354        debug("offer([{0}]), to P[{1}] delay[{2}ms] accepted[{3}]", queueElement.getElement().toString(),
355              queueElement.getPriority(), queueElement.getDelay(TimeUnit.MILLISECONDS), accepted);
356        if (accepted) {
357            if (currentSize != null) {
358                currentSize.incrementAndGet();
359            }
360            queueElement.inQueue = true;
361        }
362        return accepted;
363    }
364
365    /**
366     * Insert the specified element into the queue.
367     * <p/>
368     * The element is added with minimun priority and no delay.
369     *
370     * @param queueElement the element to add.
371     *
372     * @return <tt>true</tt> if the element has been inserted, <tt>false</tt> if the element was not inserted (the queue
373     *         has reached its maximum size).
374     *
375     * @throws NullPointerException if the specified element is null
376     */
377    @Override
378    public boolean offer(QueueElement<E> queueElement) {
379        return offer(queueElement, false);
380    }
381
382    /**
383     * Retrieve and remove the head of this queue, or return <tt>null</tt> if this queue has no elements with an expired
384     * delay.
385     * <p/>
386     * The retrieved element is the oldest one from the highest priority sub-queue.
387     * <p/>
388     * Invocations to this method run the anti-starvation (once every interval check).
389     *
390     * @return the head of this queue, or <tt>null</tt> if this queue has no elements with an expired delay.
391     */
392    @Override
393    public QueueElement<E> poll() {
394        lock.lock();
395        try {
396            antiStarvation();
397            QueueElement<E> e = null;
398            int i = priorities;
399            for (; e == null && i > 0; i--) {
400                e = queues[i - 1].poll();
401            }
402            if (e != null) {
403                if (currentSize != null) {
404                    currentSize.decrementAndGet();
405                }
406                e.inQueue = false;
407                debug("poll(): [{0}], from P[{1}]", e.getElement().toString(), i);
408            }
409            return e;
410        }
411        finally {
412            lock.unlock();
413        }
414    }
415
416    /**
417     * Retrieve, but does not remove, the head of this queue, or returns <tt>null</tt> if this queue is empty.  Unlike
418     * <tt>poll</tt>, if no expired elements are available in the queue, this method returns the element that will
419     * expire next, if one exists.
420     *
421     * @return the head of this queue, or <tt>null</tt> if this queue is empty.
422     */
423    @Override
424    public QueueElement<E> peek() {
425        lock.lock();
426        try {
427            antiStarvation();
428            QueueElement<E> e = null;
429
430            QueueElement<E> [] seeks = new QueueElement[priorities];
431            boolean foundElement = false;
432            for (int i = priorities - 1; i > -1; i--) {
433                e = queues[i].peek();
434                debug("peek(): considering [{0}] from P[{1}]", e, i);
435                seeks[priorities - i - 1] = e;
436                foundElement |= e != null;
437            }
438            if (foundElement) {
439                e = null;
440                for (int i = 0; e == null && i < priorities; i++) {
441                    if (seeks[i] != null && seeks[i].getDelay(TimeUnit.MILLISECONDS) > 0) {
442                        debug("peek, ignoring [{0}]", seeks[i]);
443                    }
444                    else {
445                        e = seeks[i];
446                    }
447                }
448                if (e != null) {
449                    debug("peek(): choosing [{0}]", e);
450                }
451                if (e == null) {
452                    int first;
453                    for (first = 0; e == null && first < priorities; first++) {
454                        e = seeks[first];
455                    }
456                    if (e != null) {
457                        debug("peek(): initial choosing [{0}]", e);
458                    }
459                    for (int i = first; i < priorities; i++) {
460                        QueueElement<E> ee = seeks[i];
461                        if (ee != null && ee.getDelay(TimeUnit.MILLISECONDS) < e.getDelay(TimeUnit.MILLISECONDS)) {
462                            debug("peek(): choosing [{0}] over [{1}]", ee, e);
463                            e = ee;
464                        }
465                    }
466                }
467            }
468            if (e != null) {
469                debug("peek(): [{0}], from P[{1}]", e.getElement().toString(), e.getPriority());
470            }
471            else {
472                debug("peek(): NULL");
473            }
474            return e;
475        }
476        finally {
477            lock.unlock();
478        }
479    }
480
481    /**
482     * Run the anti-starvation check every {@link #ANTI_STARVATION_INTERVAL} milliseconds.
483     * <p/>
484     * It promotes elements beyond max wait time to the next higher priority sub-queue.
485     */
486    protected void antiStarvation() {
487        long now = System.currentTimeMillis();
488        if (now - lastAntiStarvationCheck > ANTI_STARVATION_INTERVAL) {
489            for (int i = 0; i < queues.length - 1; i++) {
490                antiStarvation(queues[i], queues[i + 1], "from P[" + i + "] to P[" + (i + 1) + "]");
491            }
492            StringBuilder sb = new StringBuilder();
493            for (int i = 0; i < queues.length; i++) {
494                sb.append("P[").append(i).append("]=").append(queues[i].size()).append(" ");
495            }
496            debug("sub-queue sizes: {0}", sb.toString());
497            lastAntiStarvationCheck = System.currentTimeMillis();
498        }
499    }
500
501    /**
502     * Promote elements beyond max wait time from a lower priority sub-queue to a higher priority sub-queue.
503     *
504     * @param lowerQ lower priority sub-queue.
505     * @param higherQ higher priority sub-queue.
506     * @param msg sub-queues msg (from-to) for debugging purposes.
507     */
508    private void antiStarvation(DelayQueue<QueueElement<E>> lowerQ, DelayQueue<QueueElement<E>> higherQ, String msg) {
509        int moved = 0;
510        QueueElement<E> e = lowerQ.poll();
511        while (e != null && e.getDelay(TimeUnit.MILLISECONDS) < -maxWait) {
512            e.setDelay(0, TimeUnit.MILLISECONDS);
513            if (!higherQ.offer(e)) {
514                throw new IllegalStateException("Could not move element to higher sub-queue, element rejected");
515            }
516            e.priority++;
517            e = lowerQ.poll();
518            moved++;
519        }
520        if (e != null) {
521            if (!lowerQ.offer(e)) {
522                throw new IllegalStateException("Could not reinsert element to current sub-queue, element rejected");
523            }
524        }
525        debug("anti-starvation, moved {0} element(s) {1}", moved, msg);
526    }
527
528    /**
529     * Method for debugging purposes. This implementation is a <tt>NOP</tt>.
530     * <p/>
531     * This method should be overriden for logging purposes.
532     * <p/>
533     * Message templates used by this class are in JDK's <tt>MessageFormat</tt> syntax.
534     *
535     * @param msgTemplate message template.
536     * @param msgArgs arguments for the message template.
537     */
538    protected void debug(String msgTemplate, Object... msgArgs) {
539    }
540
541    /**
542     * Insert the specified element into this queue, waiting if necessary
543     * for space to become available.
544     * <p/>
545     * NOTE: This method is to fulfill the <tt>BlockingQueue<tt/> interface. Not implemented in the most optimal way.
546     *
547     * @param e the element to add
548     * @throws InterruptedException if interrupted while waiting
549     * @throws ClassCastException if the class of the specified element
550     *         prevents it from being added to this queue
551     * @throws NullPointerException if the specified element is null
552     * @throws IllegalArgumentException if some property of the specified
553     *         element prevents it from being added to this queue
554     */
555    @Override
556    public void put(QueueElement<E> e) throws InterruptedException {
557        while (!offer(e, true)) {
558            Thread.sleep(10);
559        }
560    }
561
562    /**
563     * Insert the specified element into this queue, waiting up to the
564     * specified wait time if necessary for space to become available.
565     * <p/>
566     * IMPORTANT: This implementation forces the addition of the element to the queue regardless
567     * of the queue current size. The timeout value is ignored as the element is added immediately.
568     * <p/>
569     * NOTE: This method is to fulfill the <tt>BlockingQueue<tt/> interface. Not implemented in the most optimal way.
570     *
571     * @param e the element to add
572     * @param timeout how long to wait before giving up, in units of
573     *        <tt>unit</tt>
574     * @param unit a <tt>TimeUnit</tt> determining how to interpret the
575     *        <tt>timeout</tt> parameter
576     * @return <tt>true</tt> if successful, or <tt>false</tt> if
577     *         the specified waiting time elapses before space is available
578     * @throws InterruptedException if interrupted while waiting
579     * @throws ClassCastException if the class of the specified element
580     *         prevents it from being added to this queue
581     * @throws NullPointerException if the specified element is null
582     * @throws IllegalArgumentException if some property of the specified
583     *         element prevents it from being added to this queue
584     */
585    @Override
586    public boolean offer(QueueElement<E> e, long timeout, TimeUnit unit) throws InterruptedException {
587        return offer(e, true);
588    }
589
590    /**
591     * Retrieve and removes the head of this queue, waiting if necessary
592     * until an element becomes available.
593     * <p/>
594     * IMPORTANT: This implementation has a delay of up to 10ms (when the queue is empty) to detect a new element
595     * is available. It is doing a 10ms sleep.
596     * <p/>
597     * NOTE: This method is to fulfill the <tt>BlockingQueue<tt/> interface. Not implemented in the most optimal way.
598     *
599     * @return the head of this queue
600     * @throws InterruptedException if interrupted while waiting
601     */
602    @Override
603    public QueueElement<E> take() throws InterruptedException {
604        QueueElement<E> e = poll();
605        while (e == null) {
606            Thread.sleep(10);
607            e = poll();
608        }
609        return e;
610    }
611
612    /**
613     * Retrieve and removes the head of this queue, waiting up to the
614     * specified wait time if necessary for an element to become available.
615     * <p/>
616     * NOTE: This method is to fulfill the <tt>BlockingQueue<tt/> interface. Not implemented in the most optimal way.
617     *
618     * @param timeout how long to wait before giving up, in units of
619     *        <tt>unit</tt>
620     * @param unit a <tt>TimeUnit</tt> determining how to interpret the
621     *        <tt>timeout</tt> parameter
622     * @return the head of this queue, or <tt>null</tt> if the
623     *         specified waiting time elapses before an element is available
624     * @throws InterruptedException if interrupted while waiting
625     */
626    @Override
627    public QueueElement<E> poll(long timeout, TimeUnit unit) throws InterruptedException {
628        QueueElement<E> e = poll();
629        long time = System.currentTimeMillis() + unit.toMillis(timeout);
630        while (e == null && time > System.currentTimeMillis()) {
631            Thread.sleep(10);
632            e = poll();
633        }
634        return poll();
635    }
636
637    /**
638     * Return the number of additional elements that this queue can ideally
639     * (in the absence of memory or resource constraints) accept without
640     * blocking, or <tt>Integer.MAX_VALUE</tt> if there is no intrinsic
641     * limit.
642     *
643     * <p>Note that you <em>cannot</em> always tell if an attempt to insert
644     * an element will succeed by inspecting <tt>remainingCapacity</tt>
645     * because it may be the case that another thread is about to
646     * insert or remove an element.
647     * <p/>
648     * NOTE: This method is to fulfill the <tt>BlockingQueue<tt/> interface. Not implemented in the most optimal way.
649     *
650     * @return the remaining capacity
651     */
652    @Override
653    public int remainingCapacity() {
654        return (maxSize == -1) ? -1 : maxSize - size();
655    }
656
657    /**
658     * Remove all available elements from this queue and adds them
659     * to the given collection.  This operation may be more
660     * efficient than repeatedly polling this queue.  A failure
661     * encountered while attempting to add elements to
662     * collection <tt>c</tt> may result in elements being in neither,
663     * either or both collections when the associated exception is
664     * thrown.  Attempt to drain a queue to itself result in
665     * <tt>IllegalArgumentException</tt>. Further, the behavior of
666     * this operation is undefined if the specified collection is
667     * modified while the operation is in progress.
668     * <p/>
669     * NOTE: This method is to fulfill the <tt>BlockingQueue<tt/> interface. Not implemented in the most optimal way.
670     *
671     * @param c the collection to transfer elements into
672     * @return the number of elements transferred
673     * @throws UnsupportedOperationException if addition of elements
674     *         is not supported by the specified collection
675     * @throws ClassCastException if the class of an element of this queue
676     *         prevents it from being added to the specified collection
677     * @throws NullPointerException if the specified collection is null
678     * @throws IllegalArgumentException if the specified collection is this
679     *         queue, or some property of an element of this queue prevents
680     *         it from being added to the specified collection
681     */
682    @Override
683    public int drainTo(Collection<? super QueueElement<E>> c) {
684        int count = 0;
685        for (DelayQueue<QueueElement<E>> q : queues) {
686            count += q.drainTo(c);
687        }
688        return count;
689    }
690
691    /**
692     * Remove at most the given number of available elements from
693     * this queue and adds them to the given collection.  A failure
694     * encountered while attempting to add elements to
695     * collection <tt>c</tt> may result in elements being in neither,
696     * either or both collections when the associated exception is
697     * thrown.  Attempt to drain a queue to itself result in
698     * <tt>IllegalArgumentException</tt>. Further, the behavior of
699     * this operation is undefined if the specified collection is
700     * modified while the operation is in progress.
701     * <p/>
702     * NOTE: This method is to fulfill the <tt>BlockingQueue<tt/> interface. Not implemented in the most optimal way.
703     *
704     * @param c the collection to transfer elements into
705     * @param maxElements the maximum number of elements to transfer
706     * @return the number of elements transferred
707     * @throws UnsupportedOperationException if addition of elements
708     *         is not supported by the specified collection
709     * @throws ClassCastException if the class of an element of this queue
710     *         prevents it from being added to the specified collection
711     * @throws NullPointerException if the specified collection is null
712     * @throws IllegalArgumentException if the specified collection is this
713     *         queue, or some property of an element of this queue prevents
714     *         it from being added to the specified collection
715     */
716    @Override
717    public int drainTo(Collection<? super QueueElement<E>> c, int maxElements) {
718        int left = maxElements;
719        int count = 0;
720        for (DelayQueue<QueueElement<E>> q : queues) {
721            int drained = q.drainTo(c, left);
722            count += drained;
723            left -= drained;
724        }
725        return count;
726    }
727
728    /**
729     * Removes all of the elements from this queue. The queue will be empty after this call returns.
730     */
731    @Override
732    public void clear() {
733        for (DelayQueue<QueueElement<E>> q : queues) {
734            q.clear();
735        }
736    }
737}