/**
* Copyright (C) 2001-3, Anthony Harrison anh23@pitt.edu This library is free
* software; you can redistribute it and/or modify it under the terms of the GNU
* Lesser General Public License as published by the Free Software Foundation;
* either version 2.1 of the License, or (at your option) any later version.
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details. You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.jactr.core.queue;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.concurrent.Executor;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jactr.core.event.ACTREventDispatcher;
import org.jactr.core.logging.Logger;
import org.jactr.core.model.IModel;
import org.jactr.core.queue.collection.IPrioritizer;
import org.jactr.core.queue.collection.PrioritizedQueue;
import org.jactr.core.queue.event.ITimedEventListener;
import org.jactr.core.queue.event.TimedEventEvent;
/**
* Tracks TimedEvents within the model. A TimedEvent is any event that must
* occur by a specific time. All buffer actions are created and placed into this
* queue so that after the production's execution time has expired, the actions
* will be executed.
*
* @author harrison
* @created February 17, 2003
*/
public class TimedEventQueue
{
private static transient Log LOGGER = LogFactory
.getLog(TimedEventQueue.class
.getName());
private static Comparator<ITimedEvent> _sorter = new Comparator<ITimedEvent>() {
public int compare(
ITimedEvent arg0,
ITimedEvent arg1)
{
if (arg0 == arg1)
return 0;
if (arg0
.getEndTime() < arg1
.getEndTime())
return -1;
if (arg0
.getEndTime() > arg1
.getEndTime())
return 1;
return 0;
}
};
// PriorityQueue<ITimedEvent> _newButExpiredEvents;
//
// PriorityQueue<ITimedEvent> _pendingEvents;
//
// PriorityQueue<ITimedEvent> _pendingIntermediateEvents;
ACTREventDispatcher<TimedEventQueue, ITimedEventListener> _eventDispatcher;
/**
* events that are currently (at this precise moment) being fired
*/
Collection<ITimedEvent> _firingEvents;
IModel _model;
final Lock _lock = new ReentrantLock();
/**
* to compute the priority for the priorizied queue
*/
IPrioritizer<ITimedEvent> _prioritizer;
PrioritizedQueue<ITimedEvent> _priorityQueue;
Collection<ITimedEvent> _intermediateEvents;
public TimedEventQueue(IModel model)
{
// this needs to be a sorted list
_model = model;
_eventDispatcher = new ACTREventDispatcher<TimedEventQueue, ITimedEventListener>();
_firingEvents = new ArrayList<ITimedEvent>();
// _newButExpiredEvents = new PriorityQueue<ITimedEvent>(10, _sorter);
// _pendingEvents = new PriorityQueue<ITimedEvent>(10, _sorter);
// _pendingIntermediateEvents = new PriorityQueue<ITimedEvent>(10, _sorter);
_prioritizer = new IPrioritizer<ITimedEvent>() {
public double getPriority(ITimedEvent object)
{
return object.getEndTime();
}
};
_priorityQueue = new PrioritizedQueue<ITimedEvent>(_prioritizer);
_intermediateEvents = new ArrayList<ITimedEvent>();
}
public void dispose()
{
_eventDispatcher.clear();
_eventDispatcher = null;
_priorityQueue.clear();
_priorityQueue = null;
_intermediateEvents.clear();
_intermediateEvents = null;
_model = null;
}
public IModel getModel()
{
return _model;
}
/**
* is the queue empty?
*/
public boolean isEmpty()
{
try
{
_lock.lock();
return _priorityQueue.isEmpty();
// return (_pendingEvents.size() == 0) && (_newButExpiredEvents.size() ==
// 0);
}
finally
{
_lock.unlock();
}
}
/**
* returns the time of the next expiring event, you should check the size
* first..
*/
public double getNextEndTime()
{
/*
* both newExpired and pending must be empty
*/
// if (isEmpty()) return Double.NaN;
try
{
_lock.lock();
return _priorityQueue.getFirstPriority();
// double rtn = 0;
// if (_pendingEvents.size() == 0)
// {
// rtn = _newButExpiredEvents.peek().getEndTime();
// if (LOGGER.isDebugEnabled()) LOGGER.debug("returning expired time");
// }
// else
// rtn = _pendingEvents.peek().getEndTime();
//
// return rtn;
}
finally
{
_lock.unlock();
}
}
/**
* queue up the event. If the event has already elapsed, it will be fired.
*/
public void enqueue(ITimedEvent te)
{
// double now = ACTRRuntime.getRuntime().getClock(_model).getTime();
try
{
_lock.lock();
_priorityQueue.add(te);
if (te instanceof IIntermediateTimedEvent) _intermediateEvents.add(te);
// if (te.getEndTime() <= now)
// {
// if (LOGGER.isDebugEnabled())
// LOGGER.debug("Enqueued expired event " + te);
// _newButExpiredEvents.add(te);
// }
// else
// {
// if (LOGGER.isDebugEnabled())
// LOGGER.debug("Enqueued pending event " + te);
// _pendingEvents.add(te);
// if (te instanceof IIntermediateTimedEvent)
// _pendingIntermediateEvents.add(te);
// }
}
finally
{
_lock.unlock();
}
if (LOGGER.isDebugEnabled()) LOGGER.debug("Enqueueing " + te);
if (_eventDispatcher.hasListeners())
_eventDispatcher.fire(new TimedEventEvent(this, te,
TimedEventEvent.Type.QUEUED));
if (Logger.hasLoggers(_model))
Logger.log(_model, Logger.Stream.EVENT, "Queued " + te);
/*
* double now = ACTRRuntime.getRuntime().getClock(_model).getTime(); if
* (te.getEndTime() <= now) { synchronized (_eventQueue) {
* _eventQueue.remove(te); _intermediateEventQueue.remove(te); } if
* (LOGGER.isDebugEnabled()) LOGGER.debug("firing " + te + " @ " + now);
* te.fire(now); if (_eventDispatcher.hasListeners())
* _eventDispatcher.fire(new TimedEventEvent(this, te,
* TimedEventEvent.Type.FIRED)); }
*/
}
/**
* return all the events that are pending. this is a costly operation and
* should only be used when absolutely necessary
*/
public Collection<ITimedEvent> getPendingEvents()
{
return _priorityQueue.get();
// ArrayList<ITimedEvent> events = new ArrayList<ITimedEvent>();
// synchronized (_newButExpiredEvents)
// {
// events.addAll(_newButExpiredEvents);
// }
// synchronized (_pendingEvents)
// {
// events.addAll(_pendingEvents);
// }
// return events;
}
/**
* return the events that will fire right now. this has a very limited window
* of viability. This is used to let events that are currently firing get
* access to the other events that will be (or have been) fired. Once the
* firing is complete, this will return an empty list.
*
* @return
*/
public Collection<ITimedEvent> getFiringEvents()
{
return Collections.unmodifiableCollection(_firingEvents);
}
/**
* check the queue for events that should be fired. If an event hasn't
* expired, its timePassed method will be called with the current time. Those
* that have expired will have their timeHasElapsed() methods called, removed,
* and returned to the caller
*
* @param currentTime
* @since
*/
public boolean fireExpiredEvents(double currentTime)
{
boolean firedAny = false;
try
{
_lock.lock();
_firingEvents.clear();
_priorityQueue.remove(currentTime, _firingEvents);
_intermediateEvents.removeAll(_firingEvents);
}
finally
{
_lock.unlock();
}
if (LOGGER.isDebugEnabled())
LOGGER.debug("Will attempt to fire " + _firingEvents);
for (ITimedEvent expiredEvent : _firingEvents)
{
boolean firedEvent = false;
synchronized (expiredEvent)
{
if (!expiredEvent.hasAborted())
try
{
if (LOGGER.isDebugEnabled())
LOGGER.debug("firing " + expiredEvent);
expiredEvent.fire(currentTime);
firedEvent = true;
}
catch (IllegalStateException e)
{
/*
* why might an exception occur at this point? we're not going to
* synchronized on the timed events, so a separate thread may abort
* a timed event even with that check. so we catch the possiblity
* here and log it.
*/
firedEvent = false;
if (LOGGER.isDebugEnabled())
LOGGER.debug("Timed event [" + expiredEvent
+ "] was asynchronously aborted, no worries. ", e);
}
}
if (firedEvent)
{
if (_eventDispatcher.hasListeners())
_eventDispatcher.fire(new TimedEventEvent(this, expiredEvent,
TimedEventEvent.Type.FIRED));
if (Logger.hasLoggers(_model))
Logger.log(_model, Logger.Stream.EVENT, "Fired " + expiredEvent);
}
else
{
if (expiredEvent.hasAborted() && _eventDispatcher.hasListeners())
_eventDispatcher.fire(new TimedEventEvent(this, expiredEvent,
TimedEventEvent.Type.ABORTED));
if (Logger.hasLoggers(_model))
Logger.log(_model, Logger.Stream.EVENT, "Aborted " + expiredEvent);
}
}
firedAny = _firingEvents.size() != 0;
_firingEvents.clear();
/*
* now we need to notify the intermediates, we should not do this from a
* synchronized block..
*/
try
{
_lock.lock();
_firingEvents.addAll(_intermediateEvents);
}
finally
{
_lock.unlock();
}
for (ITimedEvent te : _firingEvents)
{
((IIntermediateTimedEvent) te).currentTime(currentTime);
if (_eventDispatcher.hasListeners())
_eventDispatcher.fire(new TimedEventEvent(this, te,
TimedEventEvent.Type.UPDATED));
}
return firedAny;
}
public void addTimedEventListener(ITimedEventListener tel, Executor executor)
{
_eventDispatcher.addListener(tel, executor);
}
public void removeTimedEventListener(ITimedEventListener tel)
{
_eventDispatcher.removeListener(tel);
}
}