All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.divxdede.collection.CyclicBuffer Maven / Gradle / Ivy

/*
 * Copyright (c) 2010 ANDRE S?bastien (divxdede).  All rights reserved.
 * CyclicBuffer.java is a part of this Commons library
 * ====================================================================
 *
 * Commons library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3 of the License,
 * or any later version.
 *
 * This is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, see .
 */
package org.divxdede.collection;

import java.util.AbstractCollection;
import java.util.Arrays;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Queue;

/**
 * Cyclic buffer implementing the {@link Queue} interface.
 * 

* This buffer has a fixed size ({@link #getCapacity()}, {@link #setCapacity(int)}.
* When a object is inserted inside this buffer, if the buffer is out of capacity (too small for receive the new object), * the last object inserted inide this buffer will be removed for let the place to the new one.
* The fact to privilegiate newers insertion over oldest make this buffer a cyclic buffer. *

* This buffer don't implements {@link java.util.List} collection but own somme confortable methods like: *

    *
  • {@link #get(int)}
  • *
  • {@link #set(int, java.lang.Object)}
  • *
*

* Methods from the {@link Queue} interface are thread-safe but others are not. * Methods from the {@link Collection} interface are not thread safe. * * @author Andr? S?bastien (divxdede) */ public class CyclicBuffer extends AbstractCollection implements Queue { private Object[] array = null; private int offset = 0; private int count = 0; private int modCount = 0; /** Create a default cyclic buffer with a capacity of 10 */ public CyclicBuffer() { this(10); } /** Creates a new instance of CyclicBuffer */ public CyclicBuffer(int capacity) { setCapacity(capacity); } /* Returns the number of elements in this collection. If this collection * contains more than Integer.MAX_VALUE elements, returns * Integer.MAX_VALUE. * * @return the number of elements in this collection */ public int size() { return count; } /** * Returns an iterator over the elements in this collection. * Older for most recent order * * @return an Iterator over the elements in this collection */ public Iterator iterator() { return new Itr(); } /** * Returns an array containing all of the elements in this collection. If * the collection makes any guarantees as to what order its elements are * returned by its iterator, this method must return the elements in the * same order.

* * The returned array will be "safe" in that no references to it are * maintained by this collection. (In other words, this method must * allocate a new array even if this collection is backed by an array). * The caller is thus free to modify the returned array.

* * This method acts as bridge between array-based and collection-based * APIs. * * @return an array containing all of the elements in this collection */ public Object[] toArray() { Object[] a = new Object[ size() ]; if( a.length == 0 ) return a; return toArrayImpl(a); } /** * Returns an array containing all of the elements in this collection; * the runtime type of the returned array is that of the specified array. * If the collection fits in the specified array, it is returned therein. * Otherwise, a new array is allocated with the runtime type of the * specified array and the size of this collection.

* * If the collection fits in the specified array with room to spare (i.e., * the array has more elements than the collection), the element in the * array immediately following the end of the collection is set to * null. This is useful in determining the length of the * collection only if the caller knows that the collection does * not contain any null elements.)

* * If this collection makes any guarantees as to what order its elements * are returned by its iterator, this method must return the elements in * the same order.

* * If the array isn't big-enough, only most-recents elements on the array * * @param a the array into which the elements of the collection are to * be stored, if it is big enough; otherwise, a new array of the * same runtime type is allocated for this purpose. * @return an array containing the elements of the collection. * * @throws NullPointerException if the specified array is null. * * @throws ArrayStoreException if the runtime type of the specified array * is not a supertype of the runtime type of every element in this * collection. */ public T[] toArray(T[] a) { return (T[])toArrayImpl( a); } /** * Ensures that this collection contains the specified element (optional * operation). Returns true if the collection changed as a * result of the call. (Returns false if this collection does * not permit duplicates and already contains the specified element.) * Collections that support this operation may place limitations on what * elements may be added to the collection. In particular, some * collections will refuse to add null elements, and others will * impose restrictions on the type of elements that may be added. * Collection classes should clearly specify in their documentation any * restrictions on what elements may be added.

* * This implementation always throws an * UnsupportedOperationException. * * @param o element whose presence in this collection is to be ensured. * @return true if the collection changed as a result of the call. * * @throws UnsupportedOperationException if the add method is not * supported by this collection. * * @throws NullPointerException if this collection does not permit * null elements, and the specified element is * null. * * @throws ClassCastException if the class of the specified element * prevents it from being added to this collection. * * @throws IllegalArgumentException if some aspect of this element * prevents it from being added to this collection. */ public boolean add(E o) { array[offset++] = o; if( count < getCapacity() ) count++; if( offset >= getCapacity() ) offset = 0; modCount++; return true; } /** Retrieve a element from it's index inside this buffer. *

* Be careful, this buffer is cyclic, that mean that an object can be moved inside this buffer.
* This method is not thread-safe, and should be used carefully */ public E get(int index) { if( index < 0 || index >= size() ) throw new IllegalArgumentException("index " + index + " out of bound"); return (E)array[ logicalIndexToPhysicalIndex(index) ]; } /** Set an object at a specified index * Be careful, this buffer is cyclic, that mean that an object can be moved inside this buffer.
* This method is not thread-safe, and should be used carefully */ public void set(int index , E object ) { if( index < 0 || index >= size() ) throw new IllegalArgumentException("index " + index + " out of bound"); array[ logicalIndexToPhysicalIndex(index) ] = object; } /** Return the physical index inside the backed array for the first element of this buffer */ private int getFirstElementPhysicalIndex() { if( size() < getCapacity() ) return 0; int start = offset + 1; return start == getCapacity() ? 0 : start; } /** Convert a logical index (index in the buffer coordinate) into a physical index (index in the backed array coordinate) */ private int logicalIndexToPhysicalIndex( int logicalIndex ) { return ( ( getFirstElementPhysicalIndex() + logicalIndex ) % getCapacity() ); } /** Unsupported operation */ public boolean remove(Object o) { throw new UnsupportedOperationException("remove(Object) is unsupported by CyclicBuffer, use peek() or remove() for removing last entry"); } /** Unsupported operation */ public boolean removeAll(Collection c) { throw new UnsupportedOperationException("removeAll(Collection) is unsupported by CyclicBuffer"); } /** Unsupported operation */ public boolean retainAll(Collection c) { throw new UnsupportedOperationException("retainAll(Collection) is unsupported by CyclicBuffer"); } /** * Removes all of the elements from this collection (optional operation). * The collection will be empty after this call returns (unless it throws * an exception).

* * This implementation iterates over this collection, removing each * element using the Iterator.remove operation. Most * implementations will probably choose to override this method for * efficiency.

* * Note that this implementation will throw an * UnsupportedOperationException if the iterator returned by this * collection's iterator method does not implement the * remove method and this collection is non-empty. * * @throws UnsupportedOperationException if the clear method is * not supported by this collection. */ public void clear() { count = 0; offset = 0; Arrays.fill( array , 0 , array.length , null ); modCount++; } /** * Inserts the specified element into this buffer. * If the buffer is full, the oldest-entry is removed for allowing this insertion. * * @param o the element to insert. * @return true */ public synchronized boolean offer(E o) { return add(o); } /** * Retrieves and removes the head of this queue, or null * if this queue is empty. * * @return the head of this queue, or null if this * queue is empty. */ public synchronized E poll() { if( count == 0 ) return null; offset--; if( offset < 0 ) offset = getCapacity() - 1; modCount++; return (E)array[offset]; } /** * Retrieves and removes the head of this queue. This method * differs from the poll method in that it throws an * exception if this queue is empty. * * @return the head of this queue. * @throws NoSuchElementException if this queue is empty. */ public synchronized E remove() { if( count == 0 ) throw new NoSuchElementException(); return poll(); } /** * Retrieves, but does not remove, the head of this queue, * returning null if this queue is empty. * * @return the head of this queue, or null if this queue * is empty. */ public synchronized E peek() { if( count == 0 ) return null; int index = offset - 1; if( index < 0 ) index = getCapacity() - 1; modCount++; return (E)array[index]; } /** * Retrieves, but does not remove, the head of this queue. This method * differs from the peek method only in that it throws an * exception if this queue is empty. * * @return the head of this queue. * @throws NoSuchElementException if this queue is empty. */ public synchronized E element() { if( count == 0 ) throw new NoSuchElementException(); return peek(); } /** Define the new capacity of this cyclic buffer. * If the new capacity is less than current, only most-recent elements are preserved to fit the new capacity. * @param new capacity, must be > 0 */ public void setCapacity(int newCapacity) { if( newCapacity <= 0 ) throw new IllegalArgumentException("capacity can't be less than 1"); this.array = toArrayImpl( new Object[newCapacity] ); this.count = size() > newCapacity ? newCapacity : size(); this.offset = this.count; if( this.offset >= size() ) this.offset = 0; modCount++; } /** Retrieve the capacity of this cyclic buffer */ public int getCapacity() { return this.array == null ? 0 : this.array.length; } /** Internal method for populate a new array with datas from this buffer */ private Object[] toArrayImpl( Object[] result ) { int newSize = result.length; if( newSize <= 0) throw new IllegalArgumentException("size can't be less than 1"); if( array != null ) { /** * OFFSET * 0 1 2 3 4 5 6 7 8 9 10 11 * Source [10] [11] [12] [04] [05] [06] [07] [08] [09] (offset=3, count=9 , size=9) * Cas A (>>) [04] [05] [06] [07] [08] [09] [10] [11] [12] [ ] [ ] [ ] (offset=9, count=9 , size=11) * Cas B (<<) [07] [08] [09] [10] [11] [12] (offset=0, count=6 , size=6) * */ // Nombre de donn?es ? reprendre int newCount = count > newSize ? newSize : count; // Cas A = 9 ;;; Cas B = 6 int copied = newCount; // Cas A = 9 ;;; Cas B = 6 int skipped = count - copied; // Cas A = 0 ;;; Cas B = 3 int traited = 0; int newOffset = 0; if( count > offset ) { // Si les donn?es ? partir d'offset sont occup?es (c'est d'anciennes donn?es), alors on commence par les recopier. int skippable = getCapacity() - offset; // Cas A = 6 ;;; Cas B = 6 if( skipped < skippable ) { // Arrive dans les deux cas int length = skippable - skipped; // Cas A = 6 ;;; Cas B = 3 // Cas A : On copie ? partir de 3 jusqu'a 8 => [04] [05] [06] [07] [08] [09] // Cas B : On copie ? partir de 6 jusqu'a 8 => [07] [08] [09] System.arraycopy( array , offset + skipped , result , newOffset , length ); skipped = 0; // Cas A = 0 ;;; Cas B = 0 copied = copied - length; // Cas A = 3 ;;; Cas B = 3 newOffset += length; // Cas A = 6 ;;; Cas B = 3 } else { // On veut ignorer plus de slots que l'on peut en copier lors de cette phase, on ne fait rien, si ce n'est compter ceux d?ja ignorer skipped = skipped - skippable; } } if( copied > 0) { // 0 1 2 3 4 5 6 7 8 // Cas A : On copie ? partir de 0 jusqu'a 2 EN 6 => [04] [05] [06] [07] [08] [09] + [10] [11] [12] // Cas B : On copie ? partir de 0 jusqu'a 2 EN 3 => [07] [08] [09] + [10] [11] [12] System.arraycopy( array , 0 + skipped , result , newOffset , copied ); } } return result; } /** Internal iterator */ private class Itr implements Iterator { int shift = size() > offset ? offset : 0; int current = 0; int expectedModCount = modCount; public boolean hasNext() { checkForComodification(); return current < size(); } public E next() { if( ! hasNext() ) throw new NoSuchElementException(); try { int index = current + shift; if( index >= getCapacity() ) index = index - getCapacity(); Object result = array[index]; current++; return (E)result; } catch(Exception e) { e.printStackTrace() ; checkForComodification(); } return null; } public void remove() { throw new UnsupportedOperationException(); } private void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy