package chatty.util; import java.util.ArrayList; import java.util.List; /** * A simple history with a fixed length, which allows you to add elements to it * and to move backwards and forwards through the history. * * <p> * Fixed lenght means you provide a maximum size in the constructor and if that * number of elements is reached, the oldest element will be removed once * another element is added. * </p> * * @author tduva */ public class WrapHistory<E> { /** * The data */ private final List<E> data; /** * The maximum number of items that can be held at any time */ private final int size; /** * The current position, either the last added item or the position when * moving backwards/forwards in the history */ private int pos = -1; /** * Points to the oldest item */ private int start = -1; /** * Points to the last added item */ private int end; public WrapHistory(int size) { data = new ArrayList<>(); this.size = size; } /** * Adds the element using {@code add(E)}, but only if the element at the * current position isn't equal to the one to add. * * @param item The element to add if the condition is met * @see add(E) */ public void addIfNew(E item) { if (!item.equals(current())) { add(item); } } /** * Moves the position forward and adds an element at that position, removing * any elements after it (if present). If the history is full, it replaces * the oldest element. * * @param item The element to add */ public void add(E item) { pos = (pos + 1) % size; if (data.size() <= pos) { data.add(item); } else { data.set(pos, item); } if (start == -1) { start = 0; } else if (pos == start) { // If the position where this element was added was the current // oldest element, then move the start one foward start = (start+1) % size; } // The latest added element is always the newest one (this won't be // equal anymore once forward()/backward() are being used which may // change pos) end = pos; } /** * Return the next element (if present), without moving the position in the * history. * * @return The next element or {@code null} if none exists */ public E peekForward() { int nextPos = (pos + 1) % size; if (nextPos >= data.size() || pos == end) { // Any index should never be greater than the data size, and if the // current position is already the latest added element, the next // one can't be a newer one return null; } return data.get(nextPos); } /** * Return the previous element (if present), without moving the position in * the history. * * @return The element on the previous position, or {@code null} if none * exists */ public E peekBackwards() { // Add size first to prevent -1 int prevPos = (pos + size - 1) % size; if (prevPos >= data.size() || pos == start) { // Any index should never be greater than the data size, and if the // current position is already the oldest element, the previous one // can't be an older one return null; } return data.get(prevPos); } /** * Return the next element if present, while moving the position in the * history as well. * * @return The next element, or {@code null} if none exists */ public E forward() { E nextItem = peekForward(); if (nextItem == null) { return null; } pos = (pos + 1) % size; //System.out.println("pos moved forward: "+pos); return nextItem; } /** * Return the previous element if present, while moving the position in the * history as well. * * @return The previous element, or {@code null} if none exists */ public E backward() { E prevItem = peekBackwards(); if (prevItem == null) { return null; } pos = (pos + size - 1) % size; //System.out.println("pos moved backwards: "+pos); return prevItem; } /** * Return the element at the current position, if present. * * @return The element on the current position or {@code null} if none * exists. */ public E current() { if (pos < 0 || pos >= data.size()) { return null; } return data.get(pos); } /** * Check if there is an element after the current position. * * @return {@code true} if there is an element, {@code false} otherwise * @see peekForward() */ public boolean hasNext() { return peekForward() != null; } /** * Check if there is an element before the current position. * * @return {@code true} if there is an element, {@code false} otherwise * @see peekBackwards() */ public boolean hasPrevious() { return peekBackwards() != null; } /** * Returns a {@code String} with some debugging information. * * @return The debug {@code String} */ public String debug() { return "Pos: "+pos+" First/Last: "+start+"/"+end+" Data: "+data; } /** * For easier testing. * * @param args */ public static void main(String[] args) { WrapHistory<String> h = new WrapHistory<>(3); System.out.println("peekForward:"+h.peekForward()); System.out.println("peekBackwards:"+h.peekBackwards()); h.add("test1"); System.out.println(h.debug()); System.out.println("peekForward:"+h.peekForward()); System.out.println("peekBackwards:"+h.peekBackwards()); System.out.println(h.hasPrevious()); h.add("test2"); System.out.println(h.debug()); System.out.println("peekForward:"+h.peekForward()); System.out.println("forward:"+h.forward()); System.out.println("peekBackwards:"+h.peekBackwards()); h.add("test3"); System.out.println(h.debug()); h.add("test4"); System.out.println(h.debug()); System.out.println("peekBackwards:"+h.peekBackwards()); System.out.println("backwards:"+h.backward()); System.out.println("backwards:"+h.backward()); //System.out.println("forward:"+h.forward()); h.add("test5"); System.out.println(h.debug()); System.out.println("peekForward:"+h.peekForward()); System.out.println("backwards:"+h.backward()); System.out.println(h.debug()); System.out.println("peekForward:"+h.peekForward()); System.out.println("forward:"+h.forward()); System.out.println("forward:"+h.forward()); System.out.println("backwards:"+h.backward()); System.out.println(h.debug()); System.out.println("backwards:"+h.backward()); System.out.println(h.debug()); System.out.println("backwards:"+h.backward()); System.out.println(h.debug()); System.out.println("forward:"+h.forward()); System.out.println(h.debug()); System.out.println("backwards:"+h.backward()); System.out.println(h.debug()); } }