430 lines
12 KiB
Java
430 lines
12 KiB
Java
/*
|
|
* 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 net.i2p.itoopie.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;
|
|
}
|
|
|
|
}
|