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