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.List;
26  import java.util.NoSuchElementException;
27  import java.util.Queue;
28  
29  /**
30   * A unbounded circular queue based on array.
31   * 
32   * @author The Apache MINA Project (dev@mina.apache.org)
33   * @version $Rev: 762170 $, $Date: 2009-04-06 00:01:18 +0200 (Mon, 06 Apr 2009) $
34   */
35  public class CircularQueue<E> extends AbstractList<E> implements List<E>, Queue<E>, Serializable {
36      /** The serialVersionUID : mandatory for serializable classes */
37      private static final long serialVersionUID = 3993421269224511264L;
38  
39      /** Minimal size fo the underlying attay */
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      private int mask;
50      private int first = 0;
51      private int last = 0;
52      private boolean full;
53      private int shrinkThreshold;
54  
55      /**
56       * Construct a new, empty queue.
57       */
58      public CircularQueue() {
59          this(DEFAULT_CAPACITY);
60      }
61      
62      public CircularQueue(int initialCapacity) {
63          int actualCapacity = normalizeCapacity(initialCapacity);
64          items = new Object[actualCapacity];
65          mask = actualCapacity - 1;
66          this.initialCapacity = actualCapacity;
67          this.shrinkThreshold = 0;
68      }
69  
70      /**
71       * The capacity must be a power of 2.
72       */
73      private static int normalizeCapacity(int initialCapacity) {
74          int actualCapacity = 1;
75          
76          while (actualCapacity < initialCapacity) {
77              actualCapacity <<= 1;
78              if (actualCapacity < 0) {
79                  actualCapacity = 1 << 30;
80                  break;
81              }
82          }
83          return actualCapacity;
84      }
85  
86      /**
87       * Returns the capacity of this queue.
88       */
89      public int capacity() {
90          return items.length;
91      }
92  
93      @Override
94      public void clear() {
95          if (!isEmpty()) {
96              Arrays.fill(items, null);
97              first = 0;
98              last = 0;
99              full = false;
100             shrinkIfNeeded();
101         }
102     }
103 
104     @SuppressWarnings("unchecked")
105     public E poll() {
106         if (isEmpty()) {
107             return null;
108         }
109 
110         Object ret = items[first];
111         items[first] = null;
112         decreaseSize();
113         
114         if (first == last) {
115             first = last = 0;
116         }
117 
118         shrinkIfNeeded();
119         return (E) ret;
120     }
121 
122     public boolean offer(E item) {
123         if (item == null) {
124             throw new NullPointerException("item");
125         }
126         
127         expandIfNeeded();
128         items[last] = item;
129         increaseSize();
130         return true;
131     }
132 
133     @SuppressWarnings("unchecked")
134     public E peek() {
135         if (isEmpty()) {
136             return null;
137         }
138 
139         return (E) items[first];
140     }
141 
142     @SuppressWarnings("unchecked")
143     @Override
144     public E get(int idx) {
145         checkIndex(idx);
146         return (E) items[getRealIndex(idx)];
147     }
148 
149     @Override
150     public boolean isEmpty() {
151         return (first == last) && !full;
152     }
153 
154     @Override
155     public int size() {
156         if (full) {
157             return capacity();
158         }
159         
160         if (last >= first) {
161             return last - first;
162         } else {
163             return last - first + capacity();
164         }
165     }
166     
167     @Override
168     public String toString() {
169         return "first=" + first + ", last=" + last + ", size=" + size()
170                 + ", mask = " + mask;
171     }
172 
173     private void checkIndex(int idx) {
174         if (idx < 0 || idx >= size()) {
175             throw new IndexOutOfBoundsException(String.valueOf(idx));
176         }
177     }
178 
179     private int getRealIndex(int idx) {
180         return (first + idx) & mask;
181     }
182 
183     private void increaseSize() {
184         last = (last + 1) & mask;
185         full = first == last;
186     }
187 
188     private void decreaseSize() {
189         first = (first + 1) & mask;
190         full = false;
191     }
192 
193     private void expandIfNeeded() {
194         if (full) {
195             // expand queue
196             final int oldLen = items.length;
197             final int newLen = oldLen << 1;
198             Object[] tmp = new Object[newLen];
199     
200             if (first < last) {
201                 System.arraycopy(items, first, tmp, 0, last - first);
202             } else {
203                 System.arraycopy(items, first, tmp, 0, oldLen - first);
204                 System.arraycopy(items, 0, tmp, oldLen - first, last);
205             }
206     
207             first = 0;
208             last = oldLen;
209             items = tmp;
210             mask = tmp.length - 1;
211             if (newLen >>> 3 > initialCapacity) {
212                 shrinkThreshold = newLen >>> 3;
213             }
214         }
215     }
216     
217     private void shrinkIfNeeded() {
218         int size = size();
219         if (size <= shrinkThreshold) {
220             // shrink queue
221             final int oldLen = items.length;
222             int newLen = normalizeCapacity(size);
223             if (size == newLen) {
224                 newLen <<= 1;
225             }
226             
227             if (newLen >= oldLen) {
228                 return;
229             }
230             
231             if (newLen < initialCapacity) {
232                 if (oldLen == initialCapacity) {
233                     return;
234                 } else {
235                     newLen = initialCapacity;
236                 }
237             }
238             
239             Object[] tmp = new Object[newLen];
240     
241             // Copy only when there's something to copy.
242             if (size > 0) {
243                 if (first < last) {
244                     System.arraycopy(items, first, tmp, 0, last - first);
245                 } else {
246                     System.arraycopy(items, first, tmp, 0, oldLen - first);
247                     System.arraycopy(items, 0, tmp, oldLen - first, last);
248                 }
249             }
250     
251             first = 0;
252             last = size;
253             items = tmp;
254             mask = tmp.length - 1;
255             shrinkThreshold = 0;
256         }
257     }
258 
259     @Override
260     public boolean add(E o) {
261         return offer(o);
262     }
263 
264     @SuppressWarnings("unchecked")
265     @Override
266     public E set(int idx, E o) {
267         checkIndex(idx);
268 
269         int realIdx = getRealIndex(idx);
270         Object old = items[realIdx];
271         items[realIdx] = o;
272         return (E) old;
273     }
274 
275     @Override
276     public void add(int idx, E o) {
277         if (idx == size()) {
278             offer(o);
279             return;
280         }
281 
282         checkIndex(idx);
283         expandIfNeeded();
284 
285         int realIdx = getRealIndex(idx);
286 
287         // Make a room for a new element.
288         if (first < last) {
289             System
290                     .arraycopy(items, realIdx, items, realIdx + 1, last
291                             - realIdx);
292         } else {
293             if (realIdx >= first) {
294                 System.arraycopy(items, 0, items, 1, last);
295                 items[0] = items[items.length - 1];
296                 System.arraycopy(items, realIdx, items, realIdx + 1,
297                         items.length - realIdx - 1);
298             } else {
299                 System.arraycopy(items, realIdx, items, realIdx + 1, last
300                         - realIdx);
301             }
302         }
303 
304         items[realIdx] = o;
305         increaseSize();
306     }
307 
308     @SuppressWarnings("unchecked")
309     @Override
310     public E remove(int idx) {
311         if (idx == 0) {
312             return poll();
313         }
314 
315         checkIndex(idx);
316 
317         int realIdx = getRealIndex(idx);
318         Object removed = items[realIdx];
319 
320         // Remove a room for the removed element.
321         if (first < last) {
322             System.arraycopy(items, first, items, first + 1, realIdx - first);
323         } else {
324             if (realIdx >= first) {
325                 System.arraycopy(items, first, items, first + 1, realIdx
326                         - first);
327             } else {
328                 System.arraycopy(items, 0, items, 1, realIdx);
329                 items[0] = items[items.length - 1];
330                 System.arraycopy(items, first, items, first + 1, items.length
331                         - first - 1);
332             }
333         }
334 
335         items[first] = null;
336         decreaseSize();
337 
338         shrinkIfNeeded();
339         return (E) removed;
340     }
341 
342     public E remove() {
343         if (isEmpty()) {
344             throw new NoSuchElementException();
345         }
346         return poll();
347     }
348 
349     public E element() {
350         if (isEmpty()) {
351             throw new NoSuchElementException();
352         }
353         return peek();
354     }
355 }