package org.jactr.eclipse.runtime.session.stream;
/*
* default logging
*/
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.ListIterator;
import java.util.concurrent.Executor;
import java.util.stream.Stream;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jactr.eclipse.runtime.session.data.ISessionData;
import org.jactr.eclipse.runtime.trace.impl.GeneralEventManager;
/**
* Abstract data stream that assumes temporally increasing insertion times. The
* stream accepts some input I, creating
*
* @author harrison
* @param <I>
* @param <T>
*/
public abstract class AbstractRollingSessionDataStream<I, T> extends
AbstractSessionDataStream<T> implements ILiveSessionDataStream<T>
{
/**
* Logger definition
*/
static private final transient Log LOGGER = LogFactory
.getLog(AbstractRollingSessionDataStream.class);
// protected final TreeMap<Double, Collection<T>> _data;
// private final double _windowSize;
// private final double _maxWindowSize;
protected final List<TimedData<T>> _data;
private final int _hardCapacityLimit;
private final int _softCapacityLimit;
private T _lastData;
private TimedData<T> _lastTimedData;
private double _lastTime = Double.MIN_VALUE;
protected final GeneralEventManager<ILiveSessionDataStreamListener<T>, Object[]> _eventManager;
public AbstractRollingSessionDataStream(String streamName,
ISessionData sessionData, int windowSize)
{
super(streamName, sessionData);
// _windowSize = windowSize;
// _maxWindowSize = _windowSize * 1.2;
_softCapacityLimit = windowSize; // tmp assumption of 50ms
_hardCapacityLimit = (int) (1.2 * windowSize);
// _data = new TreeMap<Double, Collection<T>>();
_data = new ArrayList<TimedData<T>>(_hardCapacityLimit + 1); // so we don't
// allocate
// anymore
_eventManager = new GeneralEventManager<ILiveSessionDataStreamListener<T>, Object[]>(
new GeneralEventManager.INotifier<ILiveSessionDataStreamListener<T>, Object[]>() {
public void notify(ILiveSessionDataStreamListener<T> listener,
Object[] event)
{
AbstractRollingSessionDataStream.this.notify(listener,
(Collection) event[0], (Collection) event[1],
(Collection) event[2]);
}
});
}
public void addListener(ILiveSessionDataStreamListener<T> listener,
Executor executor)
{
_eventManager.addListener(listener, executor);
}
public void removeListener(ILiveSessionDataStreamListener<T> listener)
{
_eventManager.removeListener(listener);
}
public void clear()
{
synchronized (_data)
{
_data.clear();
_lastData = null;
_lastTime = Double.MIN_VALUE;
_lastTimedData = null;
}
}
protected void notify(ILiveSessionDataStreamListener<T> listener,
Collection<T> added, Collection<T> modified, Collection<T> removed)
{
listener.dataChanged(this, added, modified, removed);
}
public long getAmountOfDataAvailable(double startTime, double endTime)
{
synchronized (_data)
{
return ranged(startTime, endTime, true).count();
}
}
/**
* assuming within a synch
*
* @param startTime
* @param endTime
* @return
*/
private Stream<TimedData<T>> ranged(double startTime, double endTime,
boolean inclusive)
{
if (inclusive)
return _data.stream().filter(
(td) -> td.getTime() >= startTime && td.getTime() <= endTime);
return _data.stream().filter(
(td) -> td.getTime() >= startTime && td.getTime() < endTime);
}
/**
* returns all the data from startTime to endTime (inclusive)
*/
public Collection<T> getData(double startTime, double endTime,
Collection<T> container)
{
if (container == null) container = new ArrayList<T>();
final Collection<T> fContainer = container;
synchronized (_data)
{
ranged(startTime, endTime, true).forEach(
(td) -> fContainer.addAll(td.getData()));
}
return fContainer;
}
public Collection<T> getLatestData(double endTime, Collection<T> container)
{
if (container == null) container = new ArrayList<T>();
synchronized (_data)
{
// greates that is less than or equal endTime
ListIterator<TimedData<T>> itr = _data.listIterator(_data.size());
while (itr.hasPrevious())
{
TimedData<T> td = itr.previous();
double refTime = td.getTime();
if (refTime <= endTime)
{
container.addAll(td.getData());
break;
}
}
}
return container;
}
public double getStartTime()
{
synchronized (_data)
{
if (_data.size() > 0) return _data.get(0).getTime();
return 0;
}
}
public double getEndTime()
{
synchronized (_data)
{
return _lastTime;
}
}
public void append(Collection<I> dataToAdd)
{
if (dataToAdd.size() == 0) return;
List<T> added = addData(dataToAdd);
if (added.size() == 0) return;
Collection<T> removed = removeExpiredData();
Collection<T> modified = getModifiedData();
_eventManager.notify(new Object[] { added, modified, removed });
}
public void append(I data)
{
append(Collections.singleton(data));
}
/**
* return that data which we know has been modified. default returns empty
*
* @return
*/
protected Collection<T> getModifiedData()
{
return Collections.EMPTY_LIST;
}
/**
* add the data and return the last sample time
*
* @param toAdd
* @return
*/
protected List<T> addData(Collection<I> toAdd)
{
List<T> added = new ArrayList<T>(toAdd.size());
double sampleTime = 0;
for (I input : toAdd)
for (T data : toOutputData(input))
{
sampleTime = getTime(data);
TimedData<T> timedData = null;
synchronized (_data)
{
if (sampleTime > _lastTime || _lastTimedData == null)
{
timedData = new TimedData<T>(sampleTime, data);
_lastTimedData = timedData;
_data.add(_lastTimedData);
}
else
// assuming it's equal..
_lastTimedData.add(data);
_lastData = data;
_lastTime = sampleTime;
added.add(data);
// Collection<T> container = _data.get(sampleTime);
// if (container == null)
// {
// container = FastList.newInstance();
// _data.put(sampleTime, container);
// }
//
// container.add(data);
// added.add(data);
}
}
if (added.size() > 0) added(added);
return added;
}
abstract protected Collection<T> toOutputData(I input);
protected Collection<T> removeExpiredData()
{
Collection<T> removed = Collections.EMPTY_LIST;
// synchronized (_data)
// {
// /*
// * we want to cull from firstKey to (firstKey + lastSampleTime -
// * _maxwindowSize)
// */
// double firstKey = _data.firstKey();
// double delta = lastSampleTime - firstKey;
// if (delta < _maxWindowSize) return removed;
//
// double endKey = firstKey + _maxWindowSize - _windowSize;
// removed = new ArrayList<T>();
//
// Iterator<Map.Entry<Double, Collection<T>>> itr = _data
// .headMap(endKey, false).entrySet().iterator();
//
// while (itr.hasNext())
// {
// /*
// * we just visit each entry once to remove, as opposed to doing a while
// * loop, as that will allow us to do partial removals as necessary.
// */
// Map.Entry<Double, Collection<T>> entry = itr.next();
//
// Collection<T> container = entry.getValue();
//
// removeSubset(entry.getKey(), endKey, container, removed);
//
// if (container.size() == 0)
// {
// itr.remove();
// FastList.recycle((FastList<T>) container);
// }
// }
//
// if (LOGGER.isDebugEnabled())
// LOGGER.debug(String.format(
// "Window(%.2f) exceeded(%.2f), culled %d records %s", _windowSize,
// lastSampleTime, removed.size(), removed));
// }
synchronized (_data)
{
if (_data.size() >= _hardCapacityLimit)
while (_data.size() >= _softCapacityLimit)
{
if (removed.size() == 0)
removed = new ArrayList<T>(_hardCapacityLimit - _softCapacityLimit);
removed.addAll(_data.remove(0).getData());
}
}
if (removed.size() > 0) removed(removed);
return removed;
}
/**
* remove a subset (or all) of the T in data. Anything removed from data
* should be added to removed for proper event notification. By default this
* copies all of data into removed and clears data.
*
* @param key
* @param container
* @return
*/
protected void removeSubset(double timeOfData, double cullToTime,
Collection<T> data, Collection<T> removed)
{
removed.addAll(data);
data.clear();
}
protected void removed(Collection<T> removed)
{
}
protected void added(Collection<T> added)
{
}
public T getLastData()
{
return _lastData;
}
abstract protected double getTime(T data);
private class TimedData<T>
{
double _time;
Collection<T> _data;
public TimedData(double time, T data)
{
_time = time;
_data = new ArrayList<T>(2); // bold assumption here.. not much colliding
// data..
_data.add(data);
}
public void add(T data)
{
_data.add(data);
}
public double getTime()
{
return _time;
}
public Collection<T> getData()
{
return _data;
}
}
}