View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *  
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *  
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License. 
18   *  
19   */
20  package org.apache.mina.util;
21  
22  import java.io.Serializable;
23  import java.util.AbstractList;
24  import java.util.Arrays;
25  import java.util.NoSuchElementException;
26  import java.util.Queue;
27  
28  /**
29   * A unbounded circular queue based on array.
30   * 
31   * @param <E> The type of elements stored inthe queue
32   * 
33   * @author <a href="http://mina.apache.org">Apache MINA Project</a>
34   */
35  public class CircularQueue<E> extends AbstractList<E> implements Queue<E>, Serializable {
36      /** The serialVersionUID : mandatory for serializable classes */
37      private static final long serialVersionUID = 3993421269224511264L;
38  
39      /** Minimal size of the underlying array */
40      private static final int DEFAULT_CAPACITY = 4;
41  
42      /** The initial capacity of the list */
43      private final int initialCapacity;
44  
45      // XXX: This volatile keyword here is a workaround for SUN Java Compiler bug,
46      //      which produces buggy byte code.  I don't event know why adding a volatile
47      //      fixes the problem.  Eclipse Java Compiler seems to produce correct byte code.
48      private volatile Object[] items;
49  
50      private int mask;
51  
52      private int first = 0;
53  
54      private int last = 0;
55  
56      private boolean full;
57  
58      private int shrinkThreshold;
59  
60      /**
61       * Construct a new, empty, circular queue.
62       */
63      public CircularQueue() {
64          this(DEFAULT_CAPACITY);
65      }
66  
67      /**
68       * Construct a new circular queue with an initial capacity.
69       * 
70       * @param initialCapacity The initial capacity of this circular queue
71       */
72      public CircularQueue(int initialCapacity) {
73          int actualCapacity = normalizeCapacity(initialCapacity);
74          items = new Object[actualCapacity];
75          mask = actualCapacity - 1;
76          this.initialCapacity = actualCapacity;
77          this.shrinkThreshold = 0;
78      }
79  
80      /**
81       * The capacity must be a power of 2.
82       */
83      private static int normalizeCapacity(int initialCapacity) {
84          int actualCapacity = 1;
85  
86          while (actualCapacity < initialCapacity) {
87              actualCapacity <<= 1;
88              if (actualCapacity < 0) {
89                  actualCapacity = 1 << 30;
90                  break;
91              }
92          }
93          return actualCapacity;
94      }
95  
96      /**
97       * @return the capacity of this queue.
98       */
99      public int capacity() {
100         return items.length;
101     }
102 
103     /**
104      * {@inheritDoc}
105      */
106     @Override
107     public void clear() {
108         if (!isEmpty()) {
109             Arrays.fill(items, null);
110             first = 0;
111             last = 0;
112             full = false;
113             shrinkIfNeeded();
114         }
115     }
116 
117     /**
118      * {@inheritDoc}
119      */
120     @Override
121     @SuppressWarnings("unchecked")
122     public E poll() {
123         if (isEmpty()) {
124             return null;
125         }
126 
127         Object ret = items[first];
128         items[first] = null;
129         decreaseSize();
130 
131         if (first == last) {
132             first = last = 0;
133         }
134 
135         shrinkIfNeeded();
136         return (E) ret;
137     }
138 
139     /**
140      * {@inheritDoc}
141      */
142     @Override
143     public boolean offer(E item) {
144         if (item == null) {
145             throw new IllegalArgumentException("item");
146         }
147 
148         expandIfNeeded();
149         items[last] = item;
150         increaseSize();
151         return true;
152     }
153 
154     /**
155      * {@inheritDoc}
156      */
157     @Override
158     @SuppressWarnings("unchecked")
159     public E peek() {
160         if (isEmpty()) {
161             return null;
162         }
163 
164         return (E) items[first];
165     }
166 
167     /**
168      * {@inheritDoc}
169      */
170     @SuppressWarnings("unchecked")
171     @Override
172     public E get(int idx) {
173         checkIndex(idx);
174         return (E) items[getRealIndex(idx)];
175     }
176 
177     /**
178      * {@inheritDoc}
179      */
180     @Override
181     public boolean isEmpty() {
182         return (first == last) && !full;
183     }
184 
185     /**
186      * {@inheritDoc}
187      */
188     @Override
189     public int size() {
190         if (full) {
191             return capacity();
192         }
193 
194         if (last >= first) {
195             return last - first;
196         }
197 
198         return last - first + capacity();
199     }
200 
201     /**
202      * {@inheritDoc}
203      */
204     @Override
205     public String toString() {
206         return "first=" + first + ", last=" + last + ", size=" + size() + ", mask = " + mask;
207     }
208 
209     private void checkIndex(int idx) {
210         if (idx < 0 || idx >= size()) {
211             throw new IndexOutOfBoundsException(String.valueOf(idx));
212         }
213     }
214 
215     private int getRealIndex(int idx) {
216         return (first + idx) & mask;
217     }
218 
219     private void increaseSize() {
220         last = (last + 1) & mask;
221         full = first == last;
222     }
223 
224     private void decreaseSize() {
225         first = (first + 1) & mask;
226         full = false;
227     }
228 
229     private void expandIfNeeded() {
230         if (full) {
231             // expand queue
232             final int oldLen = items.length;
233             final int newLen = oldLen << 1;
234             Object[] tmp = new Object[newLen];
235 
236             if (first < last) {
237                 System.arraycopy(items, first, tmp, 0, last - first);
238             } else {
239                 System.arraycopy(items, first, tmp, 0, oldLen - first);
240                 System.arraycopy(items, 0, tmp, oldLen - first, last);
241             }
242 
243             first = 0;
244             last = oldLen;
245             items = tmp;
246             mask = tmp.length - 1;
247             
248             if (newLen >>> 3 > initialCapacity) {
249                 shrinkThreshold = newLen >>> 3;
250             }
251         }
252     }
253 
254     private void shrinkIfNeeded() {
255         int size = size();
256         
257         if (size <= shrinkThreshold) {
258             // shrink queue
259             final int oldLen = items.length;
260             int newLen = normalizeCapacity(size);
261             
262             if (size == newLen) {
263                 newLen <<= 1;
264             }
265 
266             if (newLen >= oldLen) {
267                 return;
268             }
269 
270             if (newLen < initialCapacity) {
271                 if (oldLen == initialCapacity) {
272                     return;
273                 }
274 
275                 newLen = initialCapacity;
276             }
277 
278             Object[] tmp = new Object[newLen];
279 
280             // Copy only when there's something to copy.
281             if (size > 0) {
282                 if (first < last) {
283                     System.arraycopy(items, first, tmp, 0, last - first);
284                 } else {
285                     System.arraycopy(items, first, tmp, 0, oldLen - first);
286                     System.arraycopy(items, 0, tmp, oldLen - first, last);
287                 }
288             }
289 
290             first = 0;
291             last = size;
292             items = tmp;
293             mask = tmp.length - 1;
294             shrinkThreshold = 0;
295         }
296     }
297 
298     /**
299      * {@inheritDoc}
300      */
301     @Override
302     public boolean add(E o) {
303         return offer(o);
304     }
305 
306     /**
307      * {@inheritDoc}
308      */
309     @SuppressWarnings("unchecked")
310     @Override
311     public E set(int idx, E o) {
312         checkIndex(idx);
313 
314         int realIdx = getRealIndex(idx);
315         Object old = items[realIdx];
316         items[realIdx] = o;
317         return (E) old;
318     }
319 
320     /**
321      * {@inheritDoc}
322      */
323     @Override
324     public void add(int idx, E o) {
325         if (idx == size()) {
326             offer(o);
327             return;
328         }
329 
330         checkIndex(idx);
331         expandIfNeeded();
332 
333         int realIdx = getRealIndex(idx);
334 
335         // Make a room for a new element.
336         if (first < last) {
337             System.arraycopy(items, realIdx, items, realIdx + 1, last - realIdx);
338         } else {
339             if (realIdx >= first) {
340                 System.arraycopy(items, 0, items, 1, last);
341                 items[0] = items[items.length - 1];
342                 System.arraycopy(items, realIdx, items, realIdx + 1, items.length - realIdx - 1);
343             } else {
344                 System.arraycopy(items, realIdx, items, realIdx + 1, last - realIdx);
345             }
346         }
347 
348         items[realIdx] = o;
349         increaseSize();
350     }
351 
352     /**
353      * {@inheritDoc}
354      */
355     @Override
356     @SuppressWarnings("unchecked")
357     public E remove(int idx) {
358         if (idx == 0) {
359             return poll();
360         }
361 
362         checkIndex(idx);
363 
364         int realIdx = getRealIndex(idx);
365         Object removed = items[realIdx];
366 
367         // Remove a room for the removed element.
368         if (first < last) {
369             System.arraycopy(items, first, items, first + 1, realIdx - first);
370         } else {
371             if (realIdx >= first) {
372                 System.arraycopy(items, first, items, first + 1, realIdx - first);
373             } else {
374                 System.arraycopy(items, 0, items, 1, realIdx);
375                 items[0] = items[items.length - 1];
376                 System.arraycopy(items, first, items, first + 1, items.length - first - 1);
377             }
378         }
379 
380         items[first] = null;
381         decreaseSize();
382 
383         shrinkIfNeeded();
384         return (E) removed;
385     }
386 
387     /**
388      * {@inheritDoc}
389      */
390     @Override
391     public E remove() {
392         if (isEmpty()) {
393             throw new NoSuchElementException();
394         }
395         return poll();
396     }
397 
398     /**
399      * {@inheritDoc}
400      */
401     @Override
402     public E element() {
403         if (isEmpty()) {
404             throw new NoSuchElementException();
405         }
406         return peek();
407     }
408 }