/**************************************************************************************
* Copyright (C) 2008 EsperTech, Inc. All rights reserved. *
* http://esper.codehaus.org *
* http://www.espertech.com *
* ---------------------------------------------------------------------------------- *
* The software in this package is published under the terms of the GPL license *
* a copy of which has been included with this distribution in the license.txt file. *
**************************************************************************************/
package com.espertech.esper.collection;
import com.espertech.esper.client.EventBean;
import java.util.*;
/**
* Container for events per time slot. The time is provided as long milliseconds by client classes.
* Events are for a specified timestamp and the implementation creates and adds the event to a slot for that timestamp.
* Events can be expired from the window via the expireEvents method when their timestamp is before
* (or less then) an expiry timestamp passed in. Expiry removes the event from the window.
* The window allows iteration through its contents.
*
* It is assumed that the timestamp passed to the add method is ascending. The window is backed by a
* collection reflecting the timestamp order rather then any sorted map or linked hash map for performance reasons.
*/
public final class TimeWindow implements Iterable
{
private ArrayDeque<TimeWindowPair> window;
private Map<EventBean, TimeWindowPair> reverseIndex;
/**
* Ctor.
* @param isSupportRemoveStream true to indicate the time window should support effective removal of events
* in the window based on the remove stream events received, or false to not accomodate removal at all
*/
public TimeWindow(boolean isSupportRemoveStream)
{
this.window = new ArrayDeque<TimeWindowPair>();
if (isSupportRemoveStream)
{
reverseIndex = new HashMap<EventBean, TimeWindowPair>();
}
}
/**
* Adjust expiry dates.
* @param delta delta to adjust for
*/
public void adjust(long delta)
{
for (TimeWindowPair data : window)
{
data.setTimestamp(data.getTimestamp() + delta);
}
}
/**
* Adds event to the time window for the specified timestamp.
* @param timestamp - the time slot for the event
* @param bean - event to add
*/
public final void add(long timestamp, EventBean bean)
{
// Empty window
if (window.isEmpty())
{
TimeWindowPair pair = new TimeWindowPair(timestamp, bean);
window.add(pair);
if (reverseIndex != null)
{
reverseIndex.put(bean, pair);
}
return;
}
TimeWindowPair lastPair = window.getLast();
// Windows last timestamp matches the one supplied
if (lastPair.getTimestamp() == timestamp)
{
if (lastPair.getEventHolder() instanceof List) {
List<EventBean> list = (List<EventBean>) lastPair.getEventHolder();
list.add(bean);
}
else if (lastPair.getEventHolder() == null) {
lastPair.setEventHolder(bean);
}
else {
EventBean existing = (EventBean) lastPair.getEventHolder();
List<EventBean> list = new ArrayList<EventBean>(4);
list.add(existing);
list.add(bean);
lastPair.setEventHolder(list);
}
if (reverseIndex != null)
{
reverseIndex.put(bean, lastPair);
}
return;
}
// Append to window
TimeWindowPair pair = new TimeWindowPair(timestamp, bean);
if (reverseIndex != null)
{
reverseIndex.put(bean, pair);
}
window.add(pair);
}
/**
* Removes the event from the window, if remove stream handling is enabled.
* @param theEvent to remove
*/
public final void remove(EventBean theEvent)
{
if (reverseIndex == null)
{
throw new UnsupportedOperationException("Time window does not accept event removal");
}
TimeWindowPair pair = reverseIndex.get(theEvent);
if (pair != null) {
if (pair.getEventHolder() != null && pair.getEventHolder().equals(theEvent)) {
pair.setEventHolder(null);
}
else if (pair.getEventHolder() != null) {
List<EventBean> list = (List<EventBean>) pair.getEventHolder();
list.remove(theEvent);
}
reverseIndex.remove(theEvent);
}
}
/**
* Return and remove events in time-slots earlier (less) then the timestamp passed in,
* returning the list of events expired.
* @param expireBefore is the timestamp from which on to keep events in the window
* @return a list of events expired and removed from the window, or null if none expired
*/
public final ArrayDeque<EventBean> expireEvents(long expireBefore)
{
if (window.isEmpty())
{
return null;
}
TimeWindowPair pair = window.getFirst();
// If the first entry's timestamp is after the expiry date, nothing to expire
if (pair.getTimestamp() >= expireBefore)
{
return null;
}
ArrayDeque<EventBean> resultBeans = new ArrayDeque<EventBean>();
// Repeat until the window is empty or the timestamp is above the expiry time
do
{
if (pair.getEventHolder() != null) {
if (pair.getEventHolder() instanceof EventBean) {
resultBeans.add((EventBean) pair.getEventHolder());
}
else {
resultBeans.addAll((List<EventBean>) pair.getEventHolder());
}
}
window.removeFirst();
if (window.isEmpty())
{
break;
}
pair = window.getFirst();
}
while (pair.getTimestamp() < expireBefore);
if (reverseIndex != null)
{
for (EventBean expired : resultBeans)
{
reverseIndex.remove(expired);
}
}
return resultBeans;
}
/**
* Returns event iterator.
* @return iterator over events currently in window
*/
public final Iterator<EventBean> iterator()
{
return new TimeWindowIterator(window);
}
/**
* Returns the oldest timestamp in the collection if there is at least one entry,
* else it returns null if the window is empty.
* @return null if empty, oldest timestamp if not empty
*/
public final Long getOldestTimestamp()
{
if (window.isEmpty()) {
return null;
}
if (window.getFirst().getEventHolder() != null) {
return window.getFirst().getTimestamp();
}
for (TimeWindowPair pair : window) {
if (pair.getEventHolder() != null) {
return pair.getTimestamp();
}
}
return null;
}
/**
* Returns true if the window is currently empty.
* @return true if empty, false if not
*/
public final boolean isEmpty()
{
return getOldestTimestamp() == null;
}
/**
* Returns the reverse index, for testing purposes.
* @return reverse index
*/
public Map<EventBean, TimeWindowPair> getReverseIndex() {
return reverseIndex;
}
public ArrayDeque<TimeWindowPair> getWindow() {
return window;
}
public void setWindow(ArrayDeque<TimeWindowPair> window) {
this.window = window;
}
public void setReverseIndex(Map<EventBean, TimeWindowPair> reverseIndex) {
this.reverseIndex = reverseIndex;
}
}