org.apache.mina.util.CircularQueue.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.mina.util.CircularQueue.java

Source

    /*
     *  Licensed to the Apache Software Foundation (ASF) under one
     *  or more contributor license agreements.  See the NOTICE file
     *  distributed with this work for additional information
     *  regarding copyright ownership.  The ASF licenses this file
     *  to you under the Apache License, Version 2.0 (the
     *  "License"); you may not use this file except in compliance
     *  with the License.  You may obtain a copy of the License at
     *  
     *    http://www.apache.org/licenses/LICENSE-2.0
     *  
     *  Unless required by applicable law or agreed to in writing,
     *  software distributed under the License is distributed on an
     *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
     *  KIND, either express or implied.  See the License for the
     *  specific language governing permissions and limitations
     *  under the License. 
     *  
     */

import java.io.Serializable;
    import java.util.AbstractList;
    import java.util.Arrays;
    import java.util.List;
    import java.util.NoSuchElementException;
    import java.util.Queue;

    /**
     * A unbounded circular queue based on array.
     * 
     * @author The Apache MINA Project (dev@mina.apache.org)
     * @version $Rev: 762170 $, $Date: 2009-04-06 00:01:18 +0200 (Mon, 06 Apr 2009) $
     */
    public class CircularQueue<E> extends AbstractList<E> implements List<E>, Queue<E>, Serializable {
        /** The serialVersionUID : mandatory for serializable classes */
        private static final long serialVersionUID = 3993421269224511264L;

        /** Minimal size fo the underlying attay */
        private static final int DEFAULT_CAPACITY = 4;

        /** The initial capacity of the list */
        private final int initialCapacity;

        // XXX: This volatile keyword here is a workaround for SUN Java Compiler bug,
        //      which produces buggy byte code.  I don't event know why adding a volatile
        //      fixes the problem.  Eclipse Java Compiler seems to produce correct byte code.
        private volatile Object[] items;
        private int mask;
        private int first = 0;
        private int last = 0;
        private boolean full;
        private int shrinkThreshold;

        /**
         * Construct a new, empty queue.
         */
        public CircularQueue() {
            this(DEFAULT_CAPACITY);
        }

        public CircularQueue(int initialCapacity) {
            int actualCapacity = normalizeCapacity(initialCapacity);
            items = new Object[actualCapacity];
            mask = actualCapacity - 1;
            this.initialCapacity = actualCapacity;
            this.shrinkThreshold = 0;
        }

        /**
         * The capacity must be a power of 2.
         */
        private static int normalizeCapacity(int initialCapacity) {
            int actualCapacity = 1;

            while (actualCapacity < initialCapacity) {
                actualCapacity <<= 1;
                if (actualCapacity < 0) {
                    actualCapacity = 1 << 30;
                    break;
                }
            }
            return actualCapacity;
        }

        /**
         * Returns the capacity of this queue.
         */
        public int capacity() {
            return items.length;
        }

        @Override
        public void clear() {
            if (!isEmpty()) {
                Arrays.fill(items, null);
                first = 0;
                last = 0;
                full = false;
                shrinkIfNeeded();
            }
        }

        @SuppressWarnings("unchecked")
        public E poll() {
            if (isEmpty()) {
                return null;
            }

            Object ret = items[first];
            items[first] = null;
            decreaseSize();

            if (first == last) {
                first = last = 0;
            }

            shrinkIfNeeded();
            return (E) ret;
        }

        public boolean offer(E item) {
            if (item == null) {
                throw new NullPointerException("item");
            }

            expandIfNeeded();
            items[last] = item;
            increaseSize();
            return true;
        }

        @SuppressWarnings("unchecked")
        public E peek() {
            if (isEmpty()) {
                return null;
            }

            return (E) items[first];
        }

        @SuppressWarnings("unchecked")
        @Override
        public E get(int idx) {
            checkIndex(idx);
            return (E) items[getRealIndex(idx)];
        }

        @Override
        public boolean isEmpty() {
            return (first == last) && !full;
        }

        @Override
        public int size() {
            if (full) {
                return capacity();
            }

            if (last >= first) {
                return last - first;
            } else {
                return last - first + capacity();
            }
        }

        @Override
        public String toString() {
            return "first=" + first + ", last=" + last + ", size=" + size() + ", mask = " + mask;
        }

        private void checkIndex(int idx) {
            if (idx < 0 || idx >= size()) {
                throw new IndexOutOfBoundsException(String.valueOf(idx));
            }
        }

        private int getRealIndex(int idx) {
            return (first + idx) & mask;
        }

        private void increaseSize() {
            last = (last + 1) & mask;
            full = first == last;
        }

        private void decreaseSize() {
            first = (first + 1) & mask;
            full = false;
        }

        private void expandIfNeeded() {
            if (full) {
                // expand queue
                final int oldLen = items.length;
                final int newLen = oldLen << 1;
                Object[] tmp = new Object[newLen];

                if (first < last) {
                    System.arraycopy(items, first, tmp, 0, last - first);
                } else {
                    System.arraycopy(items, first, tmp, 0, oldLen - first);
                    System.arraycopy(items, 0, tmp, oldLen - first, last);
                }

                first = 0;
                last = oldLen;
                items = tmp;
                mask = tmp.length - 1;
                if (newLen >>> 3 > initialCapacity) {
                    shrinkThreshold = newLen >>> 3;
                }
            }
        }

        private void shrinkIfNeeded() {
            int size = size();
            if (size <= shrinkThreshold) {
                // shrink queue
                final int oldLen = items.length;
                int newLen = normalizeCapacity(size);
                if (size == newLen) {
                    newLen <<= 1;
                }

                if (newLen >= oldLen) {
                    return;
                }

                if (newLen < initialCapacity) {
                    if (oldLen == initialCapacity) {
                        return;
                    } else {
                        newLen = initialCapacity;
                    }
                }

                Object[] tmp = new Object[newLen];

                // Copy only when there's something to copy.
                if (size > 0) {
                    if (first < last) {
                        System.arraycopy(items, first, tmp, 0, last - first);
                    } else {
                        System.arraycopy(items, first, tmp, 0, oldLen - first);
                        System.arraycopy(items, 0, tmp, oldLen - first, last);
                    }
                }

                first = 0;
                last = size;
                items = tmp;
                mask = tmp.length - 1;
                shrinkThreshold = 0;
            }
        }

        @Override
        public boolean add(E o) {
            return offer(o);
        }

        @SuppressWarnings("unchecked")
        @Override
        public E set(int idx, E o) {
            checkIndex(idx);

            int realIdx = getRealIndex(idx);
            Object old = items[realIdx];
            items[realIdx] = o;
            return (E) old;
        }

        @Override
        public void add(int idx, E o) {
            if (idx == size()) {
                offer(o);
                return;
            }

            checkIndex(idx);
            expandIfNeeded();

            int realIdx = getRealIndex(idx);

            // Make a room for a new element.
            if (first < last) {
                System.arraycopy(items, realIdx, items, realIdx + 1, last - realIdx);
            } else {
                if (realIdx >= first) {
                    System.arraycopy(items, 0, items, 1, last);
                    items[0] = items[items.length - 1];
                    System.arraycopy(items, realIdx, items, realIdx + 1, items.length - realIdx - 1);
                } else {
                    System.arraycopy(items, realIdx, items, realIdx + 1, last - realIdx);
                }
            }

            items[realIdx] = o;
            increaseSize();
        }

        @SuppressWarnings("unchecked")
        @Override
        public E remove(int idx) {
            if (idx == 0) {
                return poll();
            }

            checkIndex(idx);

            int realIdx = getRealIndex(idx);
            Object removed = items[realIdx];

            // Remove a room for the removed element.
            if (first < last) {
                System.arraycopy(items, first, items, first + 1, realIdx - first);
            } else {
                if (realIdx >= first) {
                    System.arraycopy(items, first, items, first + 1, realIdx - first);
                } else {
                    System.arraycopy(items, 0, items, 1, realIdx);
                    items[0] = items[items.length - 1];
                    System.arraycopy(items, first, items, first + 1, items.length - first - 1);
                }
            }

            items[first] = null;
            decreaseSize();

            shrinkIfNeeded();
            return (E) removed;
        }

        public E remove() {
            if (isEmpty()) {
                throw new NoSuchElementException();
            }
            return poll();
        }

        public E element() {
            if (isEmpty()) {
                throw new NoSuchElementException();
            }
            return peek();
        }
    }
    ///////////////////////////////////////////////
    /*
     *  Licensed to the Apache Software Foundation (ASF) under one
     *  or more contributor license agreements.  See the NOTICE file
     *  distributed with this work for additional information
     *  regarding copyright ownership.  The ASF licenses this file
     *  to you under the Apache License, Version 2.0 (the
     *  "License"); you may not use this file except in compliance
     *  with the License.  You may obtain a copy of the License at
     *  
     *    http://www.apache.org/licenses/LICENSE-2.0
     *  
     *  Unless required by applicable law or agreed to in writing,
     *  software distributed under the License is distributed on an
     *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
     *  KIND, either express or implied.  See the License for the
     *  specific language governing permissions and limitations
     *  under the License. 
     *  
     */
    package org.apache.mina.util;

    import junit.framework.TestCase;

    import java.util.Iterator;

    /**
     * Tests {@link org.apache.mina.util.CircularQueue}
     * 
     * @author The Apache MINA Project (dev@mina.apache.org)
     * @version $Rev: 755170 $, $Date: 2009-03-17 10:46:58 +0100 (Tue, 17 Mar 2009) $
     */
    public class CircularQueueTest extends TestCase {
        private volatile int pushCount;
        private volatile int popCount;

        public void setUp() {
            pushCount = 0;
            popCount = 0;
        }

        public void testRotation() {
            CircularQueue<Integer> q = new CircularQueue<Integer>(); // DEFAULT_CAPACITY = 4
            testRotation0(q);
        }

        public void testExpandingRotation() {
            CircularQueue<Integer> q = new CircularQueue<Integer>(); // DEFAULT_CAPACITY = 4
            for (int i = 0; i < 10; i++) {
                testRotation0(q);

                // make expansion happen
                int oldCapacity = q.capacity();
                for (int j = q.capacity(); j >= 0; j--) {
                    q.offer(new Integer(++pushCount));
                }

                assertTrue(q.capacity() > oldCapacity);
                testRotation0(q);
            }
        }

        private void testRotation0(CircularQueue<Integer> q) {
            for (int i = 0; i < q.capacity() * 7 / 4; i++) {
                q.offer(new Integer(++pushCount));
                assertEquals(++popCount, q.poll().intValue());
            }
        }

        public void testRandomAddOnQueue() {
            CircularQueue<Integer> q = new CircularQueue<Integer>();
            // Create a queue with 5 elements and capacity 8;
            for (int i = 0; i < 5; i++) {
                q.offer(new Integer(i));
            }

            q.add(0, new Integer(100));
            q.add(3, new Integer(200));
            q.add(7, new Integer(300));

            Iterator<Integer> i = q.iterator();
            assertEquals(8, q.size());
            assertEquals(new Integer(100), i.next());
            assertEquals(new Integer(0), i.next());
            assertEquals(new Integer(1), i.next());
            assertEquals(new Integer(200), i.next());
            assertEquals(new Integer(2), i.next());
            assertEquals(new Integer(3), i.next());
            assertEquals(new Integer(4), i.next());
            assertEquals(new Integer(300), i.next());

            try {
                i.next();
                fail();
            } catch (Exception e) {
                // an exception signifies a successfull test case
                assertTrue(true);
            }
        }

        public void testRandomAddOnRotatedQueue() {
            CircularQueue<Integer> q = getRotatedQueue();

            q.add(0, new Integer(100)); // addFirst
            q.add(2, new Integer(200));
            q.add(4, new Integer(300));
            q.add(10, new Integer(400));
            q.add(12, new Integer(500)); // addLast

            Iterator<Integer> i = q.iterator();
            assertEquals(13, q.size());
            assertEquals(new Integer(100), i.next());
            assertEquals(new Integer(0), i.next());
            assertEquals(new Integer(200), i.next());
            assertEquals(new Integer(1), i.next());
            assertEquals(new Integer(300), i.next());
            assertEquals(new Integer(2), i.next());
            assertEquals(new Integer(3), i.next());
            assertEquals(new Integer(4), i.next());
            assertEquals(new Integer(5), i.next());
            assertEquals(new Integer(6), i.next());
            assertEquals(new Integer(400), i.next());
            assertEquals(new Integer(7), i.next());
            assertEquals(new Integer(500), i.next());

            try {
                i.next();
                fail();
            } catch (Exception e) {
                // an exception signifies a successfull test case
                assertTrue(true);
            }
        }

        public void testRandomRemoveOnQueue() {
            CircularQueue<Integer> q = new CircularQueue<Integer>();

            // Create a queue with 5 elements and capacity 8;
            for (int i = 0; i < 5; i++) {
                q.offer(new Integer(i));
            }

            q.remove(0);
            q.remove(2);
            q.remove(2);

            Iterator<Integer> i = q.iterator();
            assertEquals(2, q.size());
            assertEquals(new Integer(1), i.next());
            assertEquals(new Integer(2), i.next());

            try {
                i.next();
                fail();
            } catch (Exception e) {
                // an exception signifies a successfull test case
                assertTrue(true);
            }
        }

        public void testRandomRemoveOnRotatedQueue() {
            CircularQueue<Integer> q = getRotatedQueue();

            q.remove(0); // removeFirst
            q.remove(2); // removeLast in the first half
            q.remove(2); // removeFirst in the first half
            q.remove(4); // removeLast

            Iterator<Integer> i = q.iterator();
            assertEquals(4, q.size());
            assertEquals(new Integer(1), i.next());
            assertEquals(new Integer(2), i.next());
            assertEquals(new Integer(5), i.next());
            assertEquals(new Integer(6), i.next());

            try {
                i.next();
                fail();
            } catch (Exception e) {
                // an exception signifies a successfull test case
                assertTrue(true);
            }
        }

        public void testExpandAndShrink() throws Exception {
            CircularQueue<Integer> q = new CircularQueue<Integer>();
            for (int i = 0; i < 1024; i++) {
                q.offer(i);
            }

            assertEquals(1024, q.capacity());

            for (int i = 0; i < 512; i++) {
                q.offer(i);
                q.poll();
            }

            assertEquals(2048, q.capacity());

            for (int i = 0; i < 1024; i++) {
                q.poll();
            }

            assertEquals(4, q.capacity());
        }

        private CircularQueue<Integer> getRotatedQueue() {
            CircularQueue<Integer> q = new CircularQueue<Integer>();

            // Ensure capacity: 16
            for (int i = 0; i < 16; i++) {
                q.offer(new Integer(-1));
            }
            q.clear();

            // Rotate it
            for (int i = 0; i < 12; i++) {
                q.offer(new Integer(-1));
                q.poll();
            }

            // Now push items
            for (int i = 0; i < 8; i++) {
                q.offer(new Integer(i));
            }

            return q;
        }

        public static void main(String[] args) {
            junit.textui.TestRunner.run(CircularQueueTest.class);
        }
    }