package org.streaminer.stream.model; import java.io.Serializable; import java.util.Collection; import java.util.LinkedList; /** * This class offers a easy to use sliding window implementation. The size of the window is fixed * and can't be modified after initialing the <code>SlidingWindow</code>. You can insert elements * of the same type of objects into the window. This objects may vary in size (e.g. if you want to * save space by adding only new elements to the window whose values vary at least a given * threshold, you would add some kind of summary for all the elements that can be covered using one * value. In this case the element's size would be total number of elements that fall into the * summary).<br> * Because of moving the borders is left to the user this implementation can be used for managing * windows that age per time unit as well as per seen element. * * @author Markus Kokott * * @param <T> any kind of object */ public class SlidingWindow <T> implements Serializable{ /** * */ private static final long serialVersionUID = 5971578246884329784L; private LinkedList<Element<T>> elements; private Integer windowSize; /** * The window size is fixed after initiating an instance of <code>SlidingWindow</code> * * @param windowSize a (positive) {@link Integer} value. */ public SlidingWindow (Integer windowSize){ if (windowSize < 0){ windowSize *= -1; } this.windowSize = windowSize; this.elements = new LinkedList<Element<T>>(); } /** * Adds a new element to the window. The size of this elements is the default size (i.e. 1). * <br> * Please note, that no older elements will be discarded until you slide the window manually! * * @param element */ public void add(T element){ Element<T> newElement = new Element<T>(element, this.windowSize, 0); this.elements.addFirst(newElement); } /** * Adds a new element to the window. The size of this element is specified by the user.<br> * Please note, that no older elements will be discarded until you slide the window manually! * * @param element * @param size - {@link Integer} value that specifies the <code>element</code>s size * @throws RuntimeException if <code>size</code> is greater than {@link #windowSize} or a negative * {@link Integer} */ public void add(T element, Integer size){ if(size > this.windowSize){ throw new RuntimeException("Size of element exceeds the size of the sliding window."); } if(size < 0){ throw new RuntimeException("The size of an element can't be a negative integer."); } Element<T> newElement = new Element<T>(element, this.windowSize - size , size); this.elements.addFirst(newElement); } /** * Just moves the border of the window by one position. */ public void slideWindowByOnePosition(){ this.refreshWindow(); this.shiftElementsByOne(); } /** * Moves the window a given number of positions. * @param positions - a {@link Integer} value representing the elapsed "time" * @throws RuntimeException if <code>positions</code> is a negative value */ public void slideWindow(Integer positions){ if(positions < 0){ throw new RuntimeException("You can't go back in time..."); } this.refreshWindow(); this.shiftElements(positions); } /** * Returns the element at position <code>index</code> * @param index * @return element of type <b>T</b> */ public T get(Integer index){ return this.elements.get(index).getElement(); } /** * Returns the oldest element in the window. * @return element of type <b>T</b> */ public T getOldestElement(){ if (this.elements.isEmpty()){ return null; } return this.elements.getLast().getElement(); } /** * Returns the newest element in the window. * @return element of type <b>T</b> */ public T getNewestElement(){ if (this.elements.isEmpty()){ return null; } return this.elements.getFirst().getElement(); } /** * Returns a collection containing each element in the window * @return a {@link Collection} of element of type <b>T</b> */ public Collection<T> getAll(){ Collection<T> elements = new LinkedList<T>(); for(int i = 0; i < this.elements.size(); i++){ elements.add(this.elements.get(i).getElement()); } return elements; } /** * Returns a collection containing all elements' life times. * @return a {@link Collection} of element of type {@link Integer} */ public Collection<Integer> getAllLifeTimes(){ Collection<Integer> lifeTimes = new LinkedList<Integer>(); for (int i=0; i < this.elements.size(); i++){ lifeTimes.add(this.elements.get(i).getTimeToLive()); } return lifeTimes; } /** * Returns the remaining life time of the specified element. * @param index * @return the life time of an element */ public Integer getLifeTime(Integer index){ return this.elements.get(index).getTimeToLive(); } /** * Returns the size of this sliding window. * @return an {@link Integer} that stands for the size of the window */ public Integer getWindowSize(){ return this.windowSize; } /** * Returns the element's size at position <code>index</code> * @param index * @return {@link Integer} value */ public Integer getSize(Integer index){ return this.elements.get(index).getSize(); } /** * Checks whether the <code>SlidingWindow</code> is empty * @return <code>true</code> if the <code>SlindingWindow</code> contains any element or * <code>false</code> if at least one element in the window is active. */ public boolean isEmpty(){ return this.elements.isEmpty(); } /** * Removes elements, that crossed the border and therefore are out dated. */ private void refreshWindow() { while (!this.elements.isEmpty() && this.elements.getLast().getTimeToLive() < 0){ elements.removeLast(); } } /** * Internally called to decrease the elements' life time by one */ private void shiftElementsByOne() { for (int i = 0; i < this.elements.size(); i++){ this.elements.get(i).decrementTimeToLiveByOne(); } } /** * Internally called to decrease the elements' life time by a given number. * @param positions */ private void shiftElements(Integer positions){ for (int i = 0; i < this.elements.size(); i++){ this.elements.get(i).decrementTimeToLive(positions); } } /** * Wrapper class that contains the elements and their life times. * * @param <E> */ private class Element <E> implements Serializable{ /** * */ private static final long serialVersionUID = 3908165358308721662L; private Integer timeToLive; private Integer size; private E element; Element(E element, Integer timeToLive, Integer size){ this.element = element; this.timeToLive = timeToLive; this.size = size; } public void decrementTimeToLive(Integer amount){ this.timeToLive -= amount; } public void decrementTimeToLiveByOne(){ this.timeToLive--; } public Integer getTimeToLive(){ return this.timeToLive; } public E getElement(){ return this.element; } public Integer getSize() { return size; } @Override public String toString(){ return "[ TTL: " + this.timeToLive + ", size: " + this.size + ", element: " + this.element + " ]"; } } }