package nl.tudelft.simulation.event;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.djutils.logger.CategoryLogger;
import nl.tudelft.simulation.event.ref.Reference;
import nl.tudelft.simulation.event.ref.StrongReference;
import nl.tudelft.simulation.event.ref.WeakReference;
/**
* The EventProducer forms the reference implementation of the EventProducerInterface. Objects extending this class are
* provided all the functionalities for registration and event firing.
*
* Copyright (c) 2002-2018 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights
* reserved. See for project information
* https://simulation.tudelft.nl. The DSOL project is distributed under a three-clause BSD-style license, which can
* be found at
* https://simulation.tudelft.nl/dsol/3.0/license.html.
*
* @author Peter Jacobs
* @author Alexander Verbraeck
*/
public abstract class EventProducer implements EventProducerInterface, Serializable
{
/** The default serial version UID for serializable classes. */
private static final long serialVersionUID = 20140830L;
/** listeners is the collection of interested listeners. */
protected Map>> listeners =
Collections.synchronizedMap(new EventListenerMap());
/**
* the semaphore used to lock on while performing thread sensitive operations.
*/
private transient Object semaphore = new Object();
/** the cache to prevent continuous reflection. */
private transient EventType[] cache = null;
/**
* checks whether no duplicate short values are assigned to the producer. An event producer produces events of a
* certain eventType. This eventType functions as a marker for registration. If the eventProducer defines two
* eventTypes with an equal value, the marker function is lost. This method checks for this particular problem.
* @return returns whether every eventType in this class is unique.
*/
private boolean checkEventType()
{
EventType[] events = this.getEventTypes();
for (int i = 0; i < events.length; i++)
{
for (int j = 0; j < events.length; j++)
{
if (i != j && events[i].equals(events[j]))
{
return false;
}
}
}
return true;
}
/**
* constructs a new EventProducer and checks for double values in events.
*/
public EventProducer()
{
super();
if (!this.checkEventType())
{
throw new RuntimeException("EventProducer failed: " + "more events have the same short value");
}
}
/** {@inheritDoc} */
@Override
public final synchronized boolean addListener(final EventListenerInterface listener, final EventType eventType)
{
return this.addListener(listener, eventType, EventProducerInterface.FIRST_POSITION);
}
/** {@inheritDoc} */
@Override
public final synchronized boolean addListener(final EventListenerInterface listener, final EventType eventType,
final boolean weak)
{
return this.addListener(listener, eventType, EventProducerInterface.FIRST_POSITION, weak);
}
/** {@inheritDoc} */
@Override
public final synchronized boolean addListener(final EventListenerInterface listener, final EventType eventType,
final short position)
{
if (listener == null || position < EventProducerInterface.LAST_POSITION)
{
return false;
}
return this.addListener(listener, eventType, position, false);
}
/** {@inheritDoc} */
@Override
public final synchronized boolean addListener(final EventListenerInterface listener, final EventType eventType,
final short position, final boolean weak)
{
if (listener == null || position < EventProducerInterface.LAST_POSITION)
{
return false;
}
synchronized (this.semaphore)
{
Reference reference = null;
if (!weak)
{
reference = new StrongReference(listener);
}
else
{
reference = new WeakReference(listener);
}
if (this.listeners.containsKey(eventType))
{
for (Reference entry : this.listeners.get(eventType))
{
if (listener.equals(entry.get()))
{
return false;
}
}
List> entries = this.listeners.get(eventType);
if (position == EventProducerInterface.LAST_POSITION)
{
entries.add(reference);
}
else
{
entries.add(position, reference);
}
}
else
{
List> entries = new ArrayList<>();
entries.add(reference);
this.listeners.put(eventType, entries);
}
}
return true;
}
/**
* fires the event to the listener. This method is a hook method. The default implementation simply invokes the
* notify on the listener. In specific cases (filtering, storing, queuing, this method can be overwritten.
* @param listener EventListenerInterface; the listener for this event
* @param event EventInterface; the event to fire
* @return the event
* @throws RemoteException on network failure.
*/
protected synchronized EventInterface fireEvent(final EventListenerInterface listener, final EventInterface event)
throws RemoteException
{
listener.notify(event);
return event;
}
/**
* fires an event to subscribed listeners.
* @param event EventInterface; the event.
* @return the event.
*/
protected synchronized EventInterface fireEvent(final EventInterface event)
{
if (this.listeners.containsKey(event.getType()))
{
synchronized (this.semaphore)
{
// make a safe copy because of possible removeListener() in notify() method during fireEvent
List> listenerList =
new ArrayList<>(this.listeners.get(event.getType()));
for (Reference reference : listenerList)
{
EventListenerInterface listener = reference.get();
try
{
if (listener != null)
{
// The garbage collection has not cleaned the referent
this.fireEvent(listener, event);
}
else
{
// The garbage collection cleaned the referent;
// there is no need to keep the subscription
this.removeListener(reference, event.getType());
}
}
catch (RemoteException remoteException)
{
// A network failure prevented the delivery,
// subscription is removed.
this.removeListener(reference, event.getType());
}
}
}
}
return event;
}
/**
* fires a value to listeners subscribed to eventType.
* @param eventType EventType; the eventType of the event.
* @param value Object; the value of the event.
* @return the Serializable value.
*/
protected synchronized Object fireEvent(final EventType eventType, final Object value)
{
this.fireEvent(new Event(eventType, this, value));
return value;
}
/**
* notifies listeners subscribed to eventType.
* @param eventType EventType; the eventType of the event.
*/
protected synchronized void fireEvent(final EventType eventType)
{
this.fireEvent(new Event(eventType, this, null));
}
/**
* fires a Serializable value to listeners subscribed to eventType. A timed event is fired.
* @param eventType EventType; the eventType of the event.
* @param value Object; the value of the event.
* @param time C; a timestamp for the event.
* @return the Serializable value.
* @param the comparable type to indicate the time when the event is fired.
*/
protected synchronized > Object fireTimedEvent(final EventType eventType,
final Object value, final C time)
{
this.fireEvent(new TimedEvent(eventType, this, value, time));
return value;
}
/**
* fires a byte value to listeners subscribed to eventType.
* @param eventType EventType; the eventType of the event.
* @param value byte; the value of the event.
* @return the byte value.
*/
protected synchronized byte fireEvent(final EventType eventType, final byte value)
{
this.fireEvent(eventType, Byte.valueOf(value));
return value;
}
/**
* fires a byte value to listeners subscribed to eventType. A timed event is fired.
* @param eventType EventType; the eventType of the event.
* @param value byte; the value of the event.
* @param time C; a timestamp for the event.
* @param the comparable type to indicate the time when the event is fired.
* @return the byte value.
*/
protected synchronized > byte fireTimedEvent(final EventType eventType, final byte value,
final C time)
{
this.fireTimedEvent(eventType, Byte.valueOf(value), time);
return value;
}
/**
* fires a boolean value to listeners subscribed to eventType.
* @param eventType EventType; the eventType of the event.
* @param value boolean; the value of the event.
* @return the boolean value.
*/
protected synchronized boolean fireEvent(final EventType eventType, final boolean value)
{
this.fireEvent(eventType, Boolean.valueOf(value));
return value;
}
/**
* fires a boolean value to listeners subscribed to eventType. A timed event is fired.
* @param eventType EventType; the eventType of the event.
* @param value boolean; the value of the event.
* @param time C; a timestamp for the event.
* @param the comparable type to indicate the time when the event is fired.
* @return the boolean value.
*/
protected synchronized > boolean fireTimedEvent(final EventType eventType,
final boolean value, final C time)
{
this.fireTimedEvent(eventType, Boolean.valueOf(value), time);
return value;
}
/**
* fires a double value to listeners subscribed to eventType.
* @param eventType EventType; the eventType of the event.
* @param value double; the value of the event.
* @return the double value.
*/
protected synchronized double fireEvent(final EventType eventType, final double value)
{
this.fireEvent(eventType, Double.valueOf(value));
return value;
}
/**
* fires a double value to listeners subscribed to eventType. A timed event is fired.
* @param eventType EventType; the eventType of the event.
* @param value double; the value of the event.
* @param time C; a timestamp for the event.
* @param the comparable type to indicate the time when the event is fired.
* @return the double value.
*/
protected synchronized > double fireTimedEvent(final EventType eventType,
final double value, final C time)
{
this.fireTimedEvent(eventType, Double.valueOf(value), time);
return value;
}
/**
* fires an integer value to listeners subscribed to eventType.
* @param eventType EventType; the eventType of the event.
* @param value int; the value of the event.
* @return the integer value.
*/
protected synchronized int fireEvent(final EventType eventType, final int value)
{
this.fireEvent(eventType, Integer.valueOf(value));
return value;
}
/**
* fires an integer value to listeners subscribed to eventType. A timed event is fired.
* @param eventType EventType; the eventType of the event.
* @param value int; the value of the event.
* @param time C; a timestamp for the event.
* @param the comparable type to indicate the time when the event is fired.
* @return the integer value.
*/
protected synchronized > int fireTimedEvent(final EventType eventType, final int value,
final C time)
{
this.fireTimedEvent(eventType, Integer.valueOf(value), time);
return value;
}
/**
* fires a long value to listeners subscribed to eventType.
* @param eventType EventType; the eventType of the event.
* @param value long; the value of the event.
* @return the long value.
*/
protected synchronized long fireEvent(final EventType eventType, final long value)
{
this.fireEvent(eventType, Long.valueOf(value));
return value;
}
/**
* fires a long value to listeners subscribed to eventType. A timed event is fired.
* @param eventType EventType; the eventType of the event.
* @param value long; the value of the event.
* @param time C; a timestamp for the event.
* @param the comparable type to indicate the time when the event is fired.
* @return the long value.
*/
protected synchronized > long fireTimedEvent(final EventType eventType, final long value,
final C time)
{
this.fireTimedEvent(eventType, Long.valueOf(value), time);
return value;
}
/**
* fires a short value to listeners subscribed to eventType.
* @param eventType EventType; the eventType of the event.
* @param value short; the value of the event.
* @return the short value.
*/
protected synchronized short fireEvent(final EventType eventType, final short value)
{
this.fireEvent(eventType, Short.valueOf(value));
return value;
}
/**
* fires a short value to listeners subscribed to eventType. A timed event is fired.
* @param eventType EventType; the eventType of the event.
* @param value short; the value of the event.
* @param time C; a timestamp for the event.
* @param the comparable type to indicate the time when the event is fired.
* @return the short value.
*/
protected synchronized > short fireTimedEvent(final EventType eventType, final short value,
final C time)
{
this.fireTimedEvent(eventType, Short.valueOf(value), time);
return value;
}
/**
* @return the event types of this EventProducer
*/
private synchronized EventType[] getEventTypes()
{
if (this.cache != null)
{
return this.cache;
}
List fieldList = new ArrayList();
Class> clazz = this.getClass();
while (clazz != null)
{
Field[] declaredFields = clazz.getDeclaredFields();
for (int i = 0; i < declaredFields.length; i++)
{
fieldList.add(declaredFields[i]);
}
clazz = clazz.getSuperclass();
}
Field[] fields = fieldList.toArray(new Field[fieldList.size()]);
Map result = new HashMap();
for (int i = 0; i < fields.length; i++)
{
if (fields[i].getType().equals(EventType.class))
{
fields[i].setAccessible(true);
try
{
if (!result.containsKey(fields[i].getName()))
{
result.put(fields[i].getName(), fields[i].get(this));
}
}
catch (Exception exception)
{
CategoryLogger.always().error(exception);
}
}
}
this.cache = result.values().toArray(new EventType[result.size()]);
return this.cache;
}
/**
* removes all the listeners from the producer.
* @return the number of removed listeners.
*/
protected synchronized int removeAllListeners()
{
int result = this.listeners.size();
this.listeners = null;
this.listeners = Collections.synchronizedMap(new EventListenerMap());
return result;
}
/**
* removes all the listeners of a class.
* @param ofClass Class<?>; the class or superclass.
* @return the number of listeners which were removed.
*/
protected synchronized int removeAllListeners(final Class> ofClass)
{
int result = 0;
synchronized (this.semaphore)
{
for (EventType type : this.listeners.keySet())
{
for (Iterator> ii = this.listeners.get(type).iterator(); ii
.hasNext();)
{
Reference listener = ii.next();
if (listener.getClass().isAssignableFrom(ofClass))
{
this.removeListener(listener.get(), type);
result++;
}
}
}
}
return result;
}
/** {@inheritDoc} */
@Override
public final synchronized boolean removeListener(final EventListenerInterface listener, final EventType eventType)
{
if (!this.listeners.containsKey(eventType))
{
return false;
}
boolean result = false;
synchronized (this.semaphore)
{
for (Iterator> i = this.listeners.get(eventType).iterator(); i.hasNext();)
{
Reference reference = i.next();
EventListenerInterface entry = reference.get();
if (entry == null)
{
i.remove();
}
else
{
if (listener.equals(entry))
{
i.remove();
result = true;
}
}
}
if (this.listeners.get(eventType).size() == 0)
{
this.listeners.remove(eventType);
}
}
return result;
}
/**
* removes a reference from the subscription list.
* @param reference Reference<EventListenerInterface>; the reference to remove
* @param eventType EventType; the eventType for which reference must be removed
* @return success whenever the reference is removes; otherwise returns false.
*/
private synchronized boolean removeListener(final Reference reference,
final EventType eventType)
{
boolean success = false;
for (Iterator> i = this.listeners.get(eventType).iterator(); i.hasNext();)
{
if (i.next().equals(reference))
{
i.remove();
success = true;
}
}
if (this.listeners.get(eventType).size() == 0)
{
this.listeners.remove(eventType);
}
return success;
}
/**
* writes a serializable method to stream.
* @param out ObjectOutputStream; the output stream
* @throws IOException on IOException
*/
private synchronized void writeObject(final ObjectOutputStream out) throws IOException
{
out.defaultWriteObject();
}
/**
* reads a serializable method from stream.
* @param in java.io.ObjectInputStream; the input stream
* @throws IOException on IOException
* @throws ClassNotFoundException on class cast exeption when reading object
*/
private void readObject(final java.io.ObjectInputStream in) throws IOException, ClassNotFoundException
{
in.defaultReadObject();
this.semaphore = new Object();
}
}