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