001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.collections4.queue; 018 019import java.io.IOException; 020import java.io.ObjectInputStream; 021import java.io.ObjectOutputStream; 022import java.io.Serializable; 023import java.util.AbstractCollection; 024import java.util.Arrays; 025import java.util.Collection; 026import java.util.Iterator; 027import java.util.NoSuchElementException; 028import java.util.Queue; 029 030import org.apache.commons.collections4.BoundedCollection; 031 032/** 033 * CircularFifoQueue is a first-in first-out queue with a fixed size that 034 * replaces its oldest element if full. 035 * <p> 036 * The removal order of a {@link CircularFifoQueue} is based on the 037 * insertion order; elements are removed in the same order in which they 038 * were added. The iteration order is the same as the removal order. 039 * </p> 040 * <p> 041 * The {@link #add(Object)}, {@link #remove()}, {@link #peek()}, {@link #poll}, 042 * {@link #offer(Object)} operations all perform in constant time. 043 * All other operations perform in linear time or worse. 044 * </p> 045 * <p> 046 * This queue prevents null objects from being added. 047 * </p> 048 * 049 * @param <E> the type of elements in this collection 050 * @since 4.0 051 */ 052public class CircularFifoQueue<E> extends AbstractCollection<E> 053 implements Queue<E>, BoundedCollection<E>, Serializable { 054 055 /** Serialization version. */ 056 private static final long serialVersionUID = -8423413834657610406L; 057 058 /** Underlying storage array. */ 059 private transient E[] elements; 060 061 /** Array index of first (oldest) queue element. */ 062 private transient int start = 0; 063 064 /** 065 * Index mod maxElements of the array position following the last queue 066 * element. Queue elements start at elements[start] and "wrap around" 067 * elements[maxElements-1], ending at elements[decrement(end)]. 068 * For example, elements = {c,a,b}, start=1, end=1 corresponds to 069 * the queue [a,b,c]. 070 */ 071 private transient int end = 0; 072 073 /** Flag to indicate if the queue is currently full. */ 074 private transient boolean full = false; 075 076 /** Capacity of the queue. */ 077 private final int maxElements; 078 079 /** 080 * Constructor that creates a queue with the default size of 32. 081 */ 082 public CircularFifoQueue() { 083 this(32); 084 } 085 086 /** 087 * Constructor that creates a queue with the specified size. 088 * 089 * @param size the size of the queue (cannot be changed) 090 * @throws IllegalArgumentException if the size is < 1 091 */ 092 @SuppressWarnings("unchecked") 093 public CircularFifoQueue(final int size) { 094 if (size <= 0) { 095 throw new IllegalArgumentException("The size must be greater than 0"); 096 } 097 elements = (E[]) new Object[size]; 098 maxElements = elements.length; 099 } 100 101 /** 102 * Constructor that creates a queue from the specified collection. 103 * The collection size also sets the queue size. 104 * 105 * @param coll the collection to copy into the queue, may not be null 106 * @throws NullPointerException if the collection is null 107 */ 108 public CircularFifoQueue(final Collection<? extends E> coll) { 109 this(coll.size()); 110 addAll(coll); 111 } 112 113 //----------------------------------------------------------------------- 114 /** 115 * Write the queue out using a custom routine. 116 * 117 * @param out the output stream 118 * @throws IOException if an I/O error occurs while writing to the output stream 119 */ 120 private void writeObject(final ObjectOutputStream out) throws IOException { 121 out.defaultWriteObject(); 122 out.writeInt(size()); 123 for (final E e : this) { 124 out.writeObject(e); 125 } 126 } 127 128 /** 129 * Read the queue in using a custom routine. 130 * 131 * @param in the input stream 132 * @throws IOException if an I/O error occurs while writing to the output stream 133 * @throws ClassNotFoundException if the class of a serialized object can not be found 134 */ 135 @SuppressWarnings("unchecked") 136 private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { 137 in.defaultReadObject(); 138 elements = (E[]) new Object[maxElements]; 139 final int size = in.readInt(); 140 for (int i = 0; i < size; i++) { 141 elements[i] = (E) in.readObject(); 142 } 143 start = 0; 144 full = size == maxElements; 145 if (full) { 146 end = 0; 147 } else { 148 end = size; 149 } 150 } 151 152 //----------------------------------------------------------------------- 153 /** 154 * Returns the number of elements stored in the queue. 155 * 156 * @return this queue's size 157 */ 158 @Override 159 public int size() { 160 int size = 0; 161 162 if (end < start) { 163 size = maxElements - start + end; 164 } else if (end == start) { 165 size = full ? maxElements : 0; 166 } else { 167 size = end - start; 168 } 169 170 return size; 171 } 172 173 /** 174 * Returns true if this queue is empty; false otherwise. 175 * 176 * @return true if this queue is empty 177 */ 178 @Override 179 public boolean isEmpty() { 180 return size() == 0; 181 } 182 183 /** 184 * {@inheritDoc} 185 * <p> 186 * A {@code CircularFifoQueue} can never be full, thus this returns always 187 * {@code false}. 188 * 189 * @return always returns {@code false} 190 */ 191 @Override 192 public boolean isFull() { 193 return false; 194 } 195 196 /** 197 * Returns {@code true} if the capacity limit of this queue has been reached, 198 * i.e. the number of elements stored in the queue equals its maximum size. 199 * 200 * @return {@code true} if the capacity limit has been reached, {@code false} otherwise 201 * @since 4.1 202 */ 203 public boolean isAtFullCapacity() { 204 return size() == maxElements; 205 } 206 207 /** 208 * Gets the maximum size of the collection (the bound). 209 * 210 * @return the maximum number of elements the collection can hold 211 */ 212 @Override 213 public int maxSize() { 214 return maxElements; 215 } 216 217 /** 218 * Clears this queue. 219 */ 220 @Override 221 public void clear() { 222 full = false; 223 start = 0; 224 end = 0; 225 Arrays.fill(elements, null); 226 } 227 228 /** 229 * Adds the given element to this queue. If the queue is full, the least recently added 230 * element is discarded so that a new element can be inserted. 231 * 232 * @param element the element to add 233 * @return true, always 234 * @throws NullPointerException if the given element is null 235 */ 236 @Override 237 public boolean add(final E element) { 238 if (null == element) { 239 throw new NullPointerException("Attempted to add null object to queue"); 240 } 241 242 if (isAtFullCapacity()) { 243 remove(); 244 } 245 246 elements[end++] = element; 247 248 if (end >= maxElements) { 249 end = 0; 250 } 251 252 if (end == start) { 253 full = true; 254 } 255 256 return true; 257 } 258 259 /** 260 * Returns the element at the specified position in this queue. 261 * 262 * @param index the position of the element in the queue 263 * @return the element at position {@code index} 264 * @throws NoSuchElementException if the requested position is outside the range [0, size) 265 */ 266 public E get(final int index) { 267 final int sz = size(); 268 if (index < 0 || index >= sz) { 269 throw new NoSuchElementException( 270 String.format("The specified index (%1$d) is outside the available range [0, %2$d)", 271 Integer.valueOf(index), Integer.valueOf(sz))); 272 } 273 274 final int idx = (start + index) % maxElements; 275 return elements[idx]; 276 } 277 278 //----------------------------------------------------------------------- 279 280 /** 281 * Adds the given element to this queue. If the queue is full, the least recently added 282 * element is discarded so that a new element can be inserted. 283 * 284 * @param element the element to add 285 * @return true, always 286 * @throws NullPointerException if the given element is null 287 */ 288 @Override 289 public boolean offer(final E element) { 290 return add(element); 291 } 292 293 @Override 294 public E poll() { 295 if (isEmpty()) { 296 return null; 297 } 298 return remove(); 299 } 300 301 @Override 302 public E element() { 303 if (isEmpty()) { 304 throw new NoSuchElementException("queue is empty"); 305 } 306 return peek(); 307 } 308 309 @Override 310 public E peek() { 311 if (isEmpty()) { 312 return null; 313 } 314 return elements[start]; 315 } 316 317 @Override 318 public E remove() { 319 if (isEmpty()) { 320 throw new NoSuchElementException("queue is empty"); 321 } 322 323 final E element = elements[start]; 324 if (null != element) { 325 elements[start++] = null; 326 327 if (start >= maxElements) { 328 start = 0; 329 } 330 full = false; 331 } 332 return element; 333 } 334 335 //----------------------------------------------------------------------- 336 /** 337 * Increments the internal index. 338 * 339 * @param index the index to increment 340 * @return the updated index 341 */ 342 private int increment(int index) { 343 index++; 344 if (index >= maxElements) { 345 index = 0; 346 } 347 return index; 348 } 349 350 /** 351 * Decrements the internal index. 352 * 353 * @param index the index to decrement 354 * @return the updated index 355 */ 356 private int decrement(int index) { 357 index--; 358 if (index < 0) { 359 index = maxElements - 1; 360 } 361 return index; 362 } 363 364 /** 365 * Returns an iterator over this queue's elements. 366 * 367 * @return an iterator over this queue's elements 368 */ 369 @Override 370 public Iterator<E> iterator() { 371 return new Iterator<E>() { 372 373 private int index = start; 374 private int lastReturnedIndex = -1; 375 private boolean isFirst = full; 376 377 @Override 378 public boolean hasNext() { 379 return isFirst || index != end; 380 } 381 382 @Override 383 public E next() { 384 if (!hasNext()) { 385 throw new NoSuchElementException(); 386 } 387 isFirst = false; 388 lastReturnedIndex = index; 389 index = increment(index); 390 return elements[lastReturnedIndex]; 391 } 392 393 @Override 394 public void remove() { 395 if (lastReturnedIndex == -1) { 396 throw new IllegalStateException(); 397 } 398 399 // First element can be removed quickly 400 if (lastReturnedIndex == start) { 401 CircularFifoQueue.this.remove(); 402 lastReturnedIndex = -1; 403 return; 404 } 405 406 int pos = lastReturnedIndex + 1; 407 if (start < lastReturnedIndex && pos < end) { 408 // shift in one part 409 System.arraycopy(elements, pos, elements, lastReturnedIndex, end - pos); 410 } else { 411 // Other elements require us to shift the subsequent elements 412 while (pos != end) { 413 if (pos >= maxElements) { 414 elements[pos - 1] = elements[0]; 415 pos = 0; 416 } else { 417 elements[decrement(pos)] = elements[pos]; 418 pos = increment(pos); 419 } 420 } 421 } 422 423 lastReturnedIndex = -1; 424 end = decrement(end); 425 elements[end] = null; 426 full = false; 427 index = decrement(index); 428 } 429 430 }; 431 } 432 433}