/* * RingBufferArray, an array- based implementation of a RingBuffer, which never * drops stored elements in case of decreasing the buffer size. * Copyright (c) 2004 - 2011 Achim Westermann, Achim.Westermann@gmx.de * * This 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 2.1 of the License, or (at your option) any later version. * * This library 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, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * If you modify or optimize the code in a useful way please let me know. * Achim.Westermann@gmx.de */ package info.monitorenter.util.collections; import info.monitorenter.util.StringUtil; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.NoSuchElementException; /** * A RingBuffer can be used to store a limited number of entries of any type * within a buffer. * <p> * * As soon as the maximum number of entries is reached, the next entry is added * to the end and the first entry is removed from it. In this case, all elements * are stored in a Object[]. But don't worry there will not be a single call to * <code>System.arraycopy</code> caused by invocation of the * <code>add(Object element)</code>- method. Internal indexes into the array for * the head and tail allow to reuse the same memory again and again. <br> * No element is lost: If <code>setBufferSize(int asize)</code> decreases the * size of the buffer and it will get smaller than the actual amount of elements * stored, they will get cached until removed. * <p> * For allowing high performance single-threaded use this implementation and the * implementations of the retrievable <code>Iterator</code>- instances are not * synchronized at all. * <p> * * @author <a href='mailto:Achim.Westermann@gmx.de'>Achim Westermann </a> * * @param <T> * the type to store. * * @version $Revision: 1.10 $ */ public class RingBufferArray<T> extends RingBufferArrayFast<T> { /** * Base class for ring buffer iterators with pending removals (those who do * not drop instances if size is exceeded). * <p> * * @author <a href="mailto:Achim.Westermann@gmx.de">Achim Westermann</a> * * * @version $Revision: 1.10 $ */ abstract class ARingBufferIterator extends RingBufferArrayFast<T>.ARingBufferIterator { /** * The position of the next instance of the pending removed elements to * return. */ protected int m_pendpos; /** * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(final Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (this.getClass() != obj.getClass()) { return false; } final ARingBufferIterator other = (ARingBufferIterator) obj; if (!this.getOuterType().equals(other.getOuterType())) { return false; } if (this.m_pendpos != other.m_pendpos) { return false; } return true; } /** * Returns the outer instance.<p> * * @return the outer instance. */ private RingBufferArray<T> getOuterType() { return RingBufferArray.this; } /** * @see java.lang.Object#hashCode() */ @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + this.getOuterType().hashCode(); result = prime * result + this.m_pendpos; return result; } /** * @see java.util.Iterator#hasNext() */ @Override public boolean hasNext() { return super.hasNext() || (this.m_pendpos >= 0); } /** * @see info.monitorenter.util.collections.RingBufferArrayFast.ARingBufferIterator#incPos() */ @Override protected void incPos() { // TODO Auto-generated method stub } /** * @see java.util.Iterator#next() */ @SuppressWarnings("unchecked") @Override public T next() { Object ret = null; // Pending elements are the oldest if (this.m_pendpos >= 0) { ret = RingBufferArray.this.m_pendingremove.get(this.m_pendpos); this.m_pendpos--; if (ret == null) { System.out .println("RingBufferArray.iteratorF2L returns null: head:" + RingBufferArray.this.m_headpointer + " tail: " + RingBufferArray.this.m_tailpointer); } } else { if (!this.hasNext()) { throw new NoSuchElementException(); } ret = RingBufferArray.this.m_buffer[this.m_pos]; this.m_count++; this.incPos(); } return (T) ret; } } /** * Generated <code>serialVersionUID</code>. */ private static final long serialVersionUID = 3977861774055585593L; /** * Elements that stores elements that have to be removed due to an invocation * to {@link #setBufferSize(int)} with a smaller argument than the amount of * elements stored. */ protected List<T> m_pendingremove = new LinkedList<T>(); /** * Constructs a RingBuffer with the given size. * <p> * * @param aSize * the size of the buffer. */ public RingBufferArray(final int aSize) { super(aSize); } /** * @see java.lang.Object#equals(java.lang.Object) */ @SuppressWarnings("unchecked") @Override public boolean equals(final Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (this.getClass() != obj.getClass()) { return false; } final RingBufferArray<T> other = (RingBufferArray<T>) obj; if (this.m_pendingremove == null) { if (other.m_pendingremove != null) { return false; } } else if (!this.m_pendingremove.equals(other.m_pendingremove)) { return false; } return true; } /** * @see java.lang.Object#hashCode() */ @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((this.m_pendingremove == null) ? 0 : this.m_pendingremove.hashCode()); return result; } /** * @see info.monitorenter.util.collections.IRingBuffer#isEmpty() */ @Override public boolean isEmpty() { return super.isEmpty() && (this.m_pendingremove.size() == 0); } /** * @see info.monitorenter.util.collections.IRingBuffer#iteratorF2L() */ @Override public java.util.Iterator<T> iteratorF2L() { return new ARingBufferIterator() { { this.m_pos = (RingBufferArray.this.m_headpointer == 0) ? RingBufferArray.this.m_size : RingBufferArray.this.m_headpointer - 1; this.m_pendpos = RingBufferArray.this.m_pendingremove.size() - 1; } /** * @see info.monitorenter.util.collections.RingBufferArray.ARingBufferIterator#incPos() */ @Override protected void incPos() { if (this.m_pos == 0) { this.m_pos = RingBufferArray.this.m_size; } else { this.m_pos--; } } }; } /** * @see info.monitorenter.util.collections.IRingBuffer#iteratorL2F() */ @Override public java.util.Iterator<T> iteratorL2F() { return new ARingBufferIterator() { { this.m_pos = RingBufferArray.this.m_tailpointer; this.m_pendpos = 0; } /** * @see info.monitorenter.util.collections.RingBufferArray.ARingBufferIterator#incPos() */ @Override protected void incPos() { if (this.m_pos == RingBufferArray.this.m_size) { this.m_pos = 0; } else { this.m_pos++; } } }; } /** * @see info.monitorenter.util.collections.IRingBuffer#remove() */ @Override public T remove() { T result = null; if (this.m_pendingremove.size() > 0) { if (RingBufferArrayFast.DEBUG) { System.out.println("Removing pending element!!!"); } result = this.m_pendingremove.remove(0); } else { result = super.remove(); } return result; } /** * @see info.monitorenter.util.collections.IRingBuffer#removeAll() */ @SuppressWarnings("unchecked") @Override public T[] removeAll() { final Object[] ret = new Object[this.size() + this.m_pendingremove.size()]; final int stop = this.m_pendingremove.size(); int i; for (i = 0; i < stop; i++) { ret[i] = this.m_pendingremove.remove(0); } for (; i < ret.length; i++) { ret[i] = this.remove(); } return (T[]) ret; } /** * Sets a new buffer- size. * <p> * A new size is assigned but the elements "overhanging" are returned by the * <code>Object remove()</code>- method first. This may take time until the * buffer has its actual size again. Don't pretend on calling this method for * saving of memory very often as the whole buffer has to be copied into a new * array every time- and if newSize < getSize() additional the overhanging * elements references have to be moved to the internal * <code>List pendingremove</code>. * <p> * * @param newSize * the new size of the buffer. * */ @Override public void setBufferSize(final int newSize) { List<T> newpending = null; if (this.size() > newSize) { newpending = new LinkedList<T>(); final int stop = this.size(); for (int i = newSize; i < stop; i++) { final T add = this.remove(); newpending.add(add); } } final Object[] newbuffer = new Object[newSize]; int i = 0; if (RingBufferArrayFast.DEBUG) { System.out.println("setBufferSize(" + newSize + "): isEmpty(): " + this.isEmpty() + " tail: " + this.m_tailpointer + " head: " + this.m_headpointer); } while (!this.isEmpty()) { newbuffer[i] = this.remove(); i++; } this.m_tailpointer = 0; if (newSize == i) { this.m_headpointer = 0; } else { this.m_headpointer = i; } this.m_buffer = newbuffer; this.m_size = newSize - 1; if (newpending != null) { this.m_pendingremove = newpending; } } /** * @see info.monitorenter.util.collections.IRingBuffer#size() */ @Override public int size() { return super.size() + this.m_pendingremove.size(); } /** * Returns a string representation of the RingBuffer and it's contents. * <p> * * Don't call this in your application too often: hard array copy - operation * and memory allocations are triggered. * <p> * * @return a string representation of the RingBuffer and it's contents. */ @Override public String toString() { String result; if (this.isEmpty()) { if (RingBufferArrayFast.DEBUG) { System.out.println("toString(): isEmpty: true"); } result = "[]"; } else { final Object[] actualcontent = new Object[this.size()]; int tmp = this.m_tailpointer; final int stop = this.m_pendingremove.size(); final Iterator<T> it = this.m_pendingremove.iterator(); int i = 0; for (; i < stop; i++) { actualcontent[i] = it.next(); } for (; i < actualcontent.length; i++) { actualcontent[i] = this.m_buffer[tmp]; if (tmp == this.m_size) { tmp = 0; } else { tmp++; } if ((tmp == this.m_headpointer) && this.m_empty) { break; } } result = StringUtil.arrayToString(actualcontent); } return result; } }