/**
* Copyright (c) 2009, 2010 Mark Feber, MulgaSoft
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
*/
package com.mulgasoft.emacsplus;
import java.util.ArrayList;
import java.util.List;
/**
* Ring Buffer implementation
*
* @author Mark Feber - initial API and implementation
*/
public class RingBuffer<T> {
public static final int DEFAULT_RING_SIZE = 16;
public static final int LARGE_RING_SIZE = 30;
public static final int NO_POS = -1;
private ArrayList<IRingBufferElement<T>> ringBuffer = null;
private int size = DEFAULT_RING_SIZE;
private int pos = NO_POS;
// marks the last non-empty location
private int maxPos = 0;
// marks the current yank location (if no intervening cmd has occurred)
private int yankpos = 0;
// holds the current yank location (in case rotate has intervened)
private int lastyankpos = 0;
// mark last command as yank
private boolean yanked = false;
private boolean infiniteLoop = true;
public void setInfiniteLoop(boolean infinite) {
this.infiniteLoop = infinite;
}
// flag last manual rotation - clear as soon as a yank or yank-pop is executed
boolean rotated = false;
public YankRotate rotateDirection = YankRotate.FORWARD;
public RingBuffer() {
this(DEFAULT_RING_SIZE);
}
public RingBuffer(int size) {
this.size = size;
ringBuffer = initArray(size);
}
public RingBuffer(List<T> elements) {
this.size = elements.size();
ringBuffer = initArray(size, elements);
}
private ArrayList<IRingBufferElement<T>> initArray(int size, List<T> elements) {
ArrayList<IRingBufferElement<T>> a = new ArrayList<IRingBufferElement<T>>(size);
IRingBufferElement<T> ele = null;
int tsize = (elements != null ? elements.size() : 0);
// this initializes the internal ArrayList size properly
for (int i=0; i < size; i++) {
if (tsize > 0 && i < tsize) {
ele = this.getNewElement();
ele.set(elements.get(i));
}
a.add(ele);
}
maxPos = (tsize > 0 ? tsize - 1 : 0);
return a;
}
private ArrayList<IRingBufferElement<T>> initArray(int size){
return initArray(size, null);
}
protected static int getDefaultSize() {
return DEFAULT_RING_SIZE;
}
/**
* @return the maximum size of the ring buffer
*/
public int maxSize(){
return size;
}
/**
* @return the current length of the ring buffer
*/
public int length(){
return maxPos + (isEmpty() ? 0 : 1);
}
int getPos() {
return (pos == NO_POS ? 0 : pos);
}
int getYankpos() {
return yankpos;
}
/**
* Is the ring buffer empty
*
* @return true if no elements have been inserted yet, else false
*/
public boolean isEmpty() {
return pos == NO_POS;
}
/**
* Change the size of the ring buffer
* Preserve the contents starting from the current insertion position
*
* @param newSize the new ring buffer size
*/
public synchronized void setSize(int newSize){
if (size != newSize){
// build and copy
ArrayList<IRingBufferElement<T>> newBuffer = initArray(newSize);
// check if current contents fit into new buffer
if (isEmpty()) {
// no elements yet
} else if (newSize > maxPos){
// new size is less than current content size, simple copy
for (int i=0; i <= maxPos; i++){
newBuffer.set(i,ringBuffer.get(i));
}
} else {
// start from the newSize position back from current position
int ii = pos - (newSize-1);
if (ii < 0) {
ii = size+ii;
}
for (int i = 0; i < newSize; i++, ii++){
if (ii >= size){
ii = 0;
}
newBuffer.set(i,ringBuffer.get(ii));
}
maxPos = newSize -1;
lastyankpos = yankpos = pos = maxPos; // reset
}
ringBuffer = newBuffer;
size = newSize;
}
}
// have we just yanked
public boolean isYanked() {
return yanked;
}
// note that we have just yanked
public void setYanked(boolean yanked) {
this.yanked = yanked;
}
/**
* Return the element's string at the specified position
*
* @param pos the element's position
* @return the element's string or null if no element at that position
*/
public T get(int pos) {
T result = null;
IRingBufferElement<T> rbe = getElement(pos);
if (rbe != null) {
result = rbe.get();
}
return result;
}
/**
* Return the element at the specified position
*
* @param pos the element's position
* @return the element or null if no element at that position
*/
protected IRingBufferElement<T> getElement(int pos) {
if (0 > pos || pos > maxPos) {
pos = 0;
}
return ringBuffer.get(pos);
}
/**
* @return the content at the yank position
*/
public T yank() {
recordYank();
return get(yankpos);
}
/**
* @return the element at the yank position
*/
public IRingBufferElement<T> yankElement() {
recordYank();
return getElement(yankpos);
}
/**
* @return String last yanked
*/
public T lastYank() {
return get(lastyankpos);
}
/**
* Return the 'next' yank position content
*
* If the ring has just been manually rotated, return the current yank position
* else, rotate once and return the element there.
*
* @return the content at the 'next' yank position
*/
public T yankPop() {
// don't change yank position if it's
// just been manually rotated
if (!rotated && --yankpos < 0) {
yankpos = maxPos;
}
recordYank();
return yank();
}
/**
* Add the content to the current element
* @param content - the content to be added
*/
private IRingBufferElement<T> put(T content) {
IRingBufferElement<T> rbe = getElement();
rbe.set(content);
return rbe;
}
/**
* Add or append the content to the element, depending on the current state
* @param content - the content to be added
*/
public synchronized IRingBufferElement<T> putNext(T content) {
rotated = false; // clear rotation
IRingBufferElement<T> result = null;
// Never allow null content or empty string as an element
if (content != null && content.toString().length() > 0) {
if (++pos >= size) {
pos = 0;
}
if (pos > maxPos) {
maxPos = pos;
}
yankpos = pos;
result = put(content);
}
return result;
}
/**
* Add the element to the next position in the ring buffer
*
* @param element
* @return the previous contents of the ring position
*/
public synchronized IRingBufferElement<T> putNext(IRingBufferElement<T> element) {
IRingBufferElement<T> result;
if (++pos >= size) {
pos = 0;
}
if (pos > maxPos) {
maxPos = pos;
}
yankpos = pos;
result = getCurrentElement();
setCurrentElement(element);
return result;
}
/**
* Check string against possible 'next' & 'previous' positions
* for duplicate entries
*
* @param dup
* @return true if duplicate (or empty content) detected, else false
*/
public boolean isDuplicate(T dup) {
// null/empty strings are disallowed
boolean result = dup == null || dup.toString().length() == 0;
// check against current position
result = (result ? result : dup.equals(get(getPos())));
// check against last yank
result = (result ? result : dup.equals(lastYank()));
// check against potential yank
result = (result ? result : dup.equals(get(getYankpos())));
if (!result && (pos == maxPos)) {
// if we're at the end, check the beginning
if (result = dup.equals(get(0))) {
// move to this position
yankpos = pos = 0;
}
}
return result;
}
/**
* I remember yank
*/
private void recordYank() {
// clear manual rotated flag
rotated = false;
// remember position of last yank
lastyankpos = yankpos;
}
/**
* Get the direction of a manual rotation
*
* @return the direction of rotation
*/
public YankRotate getRotateDirection() {
return rotateDirection;
}
/**
* Set the direction of a manual rotation
* @param rotateDirection
*/
public void setRotateDirection(YankRotate rotateDirection) {
this.rotateDirection = rotateDirection;
}
/**
* Set the direction of a manual rotation by id
* @param rotateDirectionId
*/
public void setRotateDirection(String rotateDirectionId) {
if (rotateDirectionId != null) {
if (YankRotate.FORWARD.id().equals(rotateDirectionId)){
setRotateDirection(YankRotate.FORWARD);
} else if (YankRotate.BACKWARD.id().equals(rotateDirectionId)) {
setRotateDirection(YankRotate.BACKWARD);
}
}
}
private void invertRotateDirection() {
if (YankRotate.FORWARD == rotateDirection){
setRotateDirection(YankRotate.BACKWARD);
} else if (YankRotate.BACKWARD == rotateDirection) {
setRotateDirection(YankRotate.FORWARD);
}
}
/**
* Rotate the yank position in the specified direction
* @param direction
* @return the element at the new position
*/
public IRingBufferElement<T> rotateYankPos(YankRotate direction){
yankpos += direction.direction();
if (direction.direction() != 0) {
rotated = true;
}
if (yankpos > maxPos){
yankpos = infiniteLoop ? 0 : maxPos;
if (!infiniteLoop) {
Beeper.beep();
}
} else if (yankpos < 0){
yankpos = infiniteLoop ? maxPos : 0;
if (!infiniteLoop) {
Beeper.beep();
}
}
return getElement(yankpos);
}
/**
* rotate the yank position forward
* i.e. in the direction away from normal yank direction. This is the opposite
* of the emacs default, since we currently can't pass C-u arguments
*
* @return the element at the rotated position
*/
public IRingBufferElement<T> rotateYankPos(){
return rotateYankPos(getRotateDirection());
}
/**
* Rotate from the specified ring entry, counting back from the most recent as 1
* So, if the current position points at X, the other positions are:
* -2 -1 0 X 2 3 etc.
* ^
* @param count
*
* @return the element count positions from current or null
*/
public IRingBufferElement<T> rotateYankPos(int count) {
IRingBufferElement<T> result = getElement(lastyankpos);
if (count != 1) {
boolean reverse = count <= 0;
try {
if (reverse) {
invertRotateDirection();
// account for decrement in loop
count = Math.abs(count)+2;
}
System.out.println(result.get());
for (int i = count - 1; i > 0; i--) {
result = rotateYankPos();
}
} finally {
if (reverse) {
invertRotateDirection();
}
}
}
return result;
}
/**
* get or create the ring buffer element
* @return the current element
*/
protected IRingBufferElement<T> getElement() {
IRingBufferElement<T> rbe = getCurrentElement();
if (rbe == null) {
rbe = getNewElement();
ringBuffer.set(pos, rbe);
}
return rbe;
}
protected final IRingBufferElement<T> getCurrentElement() {
return ringBuffer.get(getPos());
}
protected final void setCurrentElement(IRingBufferElement<T> rbe) {
int p = getPos();
if (ringBuffer.get(p) != null) {
ringBuffer.get(p).dispose();
}
ringBuffer.set(p, rbe);
}
/**
* Create a new, unstored, ring buffer element
*
* @return the new IRingBufferElement
*/
protected IRingBufferElement<T> getNewElement() {
return new AbstractRingBufferElement();
}
/**
* Simple Ring Buffer Element
*
* Holds content of type T
*
* @author Mark Feber - initial API and implementation
*/
protected class AbstractRingBufferElement implements IRingBufferElement<T> {
private T content;
public T get() {
return content;
}
public void set(T content) {
this.content = content;
}
public String toString() {
return (content != null ? content.toString() : EmacsPlusUtils.EMPTY_STR);
}
/**
* Generic dispose does nothing
*
* @see com.mulgasoft.emacsplus.RingBuffer.IRingBufferElement#dispose()
*/
public void dispose() {}
}
public interface IRingBufferElement<T> {
/**
* @return the content of this element
*/
T get();
/**
* Set the content of this element
*
* @param content
*/
void set(T content);
/**
* Called when an element is dropped from the RingBuffer
* Implementors can perform any necessary cleanups
*/
void dispose();
}
}