1 package org.djutils.event;
2
3 import java.io.Serializable;
4 import java.rmi.RemoteException;
5 import java.util.ArrayList;
6 import java.util.Iterator;
7 import java.util.LinkedHashMap;
8 import java.util.List;
9 import java.util.Map;
10 import java.util.Set;
11
12 import org.djutils.event.ref.Reference;
13 import org.djutils.event.ref.ReferenceType;
14 import org.djutils.event.ref.StrongReference;
15 import org.djutils.event.ref.WeakReference;
16 import org.djutils.exceptions.Throw;
17
18 /**
19 * The EventProducerImpl forms the reference implementation of the EventProducerInterface, and acts as a "helper" class for the
20 * different types of EvenProducer implementations. The storage of the listeners is done in a Map with the EventType as the key,
21 * and a List of References (weak or strong) to the Listeners.
22 * <p>
23 * Copyright (c) 2002-2020 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
24 * for project information <a href="https://djutils.org" target="_blank"> https://djutils.org</a>. The DJUTILS project is
25 * distributed under a three-clause BSD-style license, which can be found at
26 * <a href="https://djutils.org/docs/license.html" target="_blank"> https://djutils.org/docs/license.html</a>. This class was
27 * originally part of the DSOL project, see <a href="https://simulation.tudelft.nl/dsol/manual" target="_blank">
28 * https://simulation.tudelft.nl/dsol/manual</a>.
29 * </p>
30 * @author <a href="https://www.linkedin.com/in/peterhmjacobs">Peter Jacobs </a>
31 * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
32 */
33 public class EventProducerImpl implements EventProducerInterface, Serializable
34 {
35 /** The default serial version UID for serializable classes. */
36 private static final long serialVersionUID = 20200207;
37
38 /** The collection of interested listeners. */
39 private EventListenerMaptListenerMap">EventListenerMap listeners = new EventListenerMap();
40
41 /** The embedding event producer that uses this helper class. */
42 private final EventProducerInterface embeddingEventProducer;
43
44 /**
45 * Construct the helper class to execute the work for registering listeners and firing events.
46 * @param embeddingEventProducer EventProducerInterface; the embedding event producer class
47 */
48 public EventProducerImpl(final EventProducerInterface embeddingEventProducer)
49 {
50 super();
51 this.embeddingEventProducer = embeddingEventProducer;
52 }
53
54 /** {@inheritDoc} */
55 @Override
56 public Serializable getSourceId() // without RemoteException
57 {
58 try
59 {
60 return this.embeddingEventProducer.getSourceId();
61 }
62 catch (RemoteException rme)
63 {
64 throw new RuntimeException(rme);
65 }
66 }
67
68 /** {@inheritDoc} */
69 @Override
70 public final synchronized boolean addListener(final EventListenerInterface listener, final EventType eventType)
71 {
72 return this.addListener(listener, eventType, EventProducerInterface.FIRST_POSITION);
73 }
74
75 /** {@inheritDoc} */
76 @Override
77 public final synchronized boolean addListener(final EventListenerInterface listener, final EventType eventType,
78 final ReferenceType referenceType)
79 {
80 return this.addListener(listener, eventType, EventProducerInterface.FIRST_POSITION, referenceType);
81 }
82
83 /** {@inheritDoc} */
84 @Override
85 public final synchronized boolean addListener(final EventListenerInterface listener, final EventType eventType,
86 final int position)
87 {
88 return this.addListener(listener, eventType, position, ReferenceType.STRONG);
89 }
90
91 /** {@inheritDoc} */
92 @Override
93 public final synchronized boolean addListener(final EventListenerInterface listener, final EventType eventType,
94 final int position, final ReferenceType referenceType)
95 {
96 Throw.whenNull(listener, "listener cannot be null");
97 Throw.whenNull(eventType, "eventType cannot be null");
98 Throw.whenNull(referenceType, "referenceType cannot be null");
99 if (position < EventProducerInterface.LAST_POSITION)
100 {
101 return false;
102 }
103 Reference<EventListenerInterface> reference = null;
104 if (referenceType.isStrong())
105 {
106 reference = new StrongReference<EventListenerInterface>(listener);
107 }
108 else
109 {
110 reference = new WeakReference<EventListenerInterface>(listener);
111 }
112 if (this.listeners.containsKey(eventType))
113 {
114 for (Reference<EventListenerInterface> entry : this.listeners.get(eventType))
115 {
116 if (listener.equals(entry.get()))
117 {
118 return false;
119 }
120 }
121 List<Reference<EventListenerInterface>> entries = this.listeners.get(eventType);
122 if (position == EventProducerInterface.LAST_POSITION)
123 {
124 entries.add(reference);
125 }
126 else
127 {
128 entries.add(position, reference);
129 }
130 }
131 else
132 {
133 List<Reference<EventListenerInterface>> entries = new ArrayList<>();
134 entries.add(reference);
135 this.listeners.put(eventType, entries);
136 }
137 return true;
138 }
139
140 /**
141 * Transmit an event to a listener. This method is a hook method. The default implementation simply invokes the notify on
142 * the listener. In specific cases (filtering, storing, queuing, this method can be overwritten.
143 * @param listener EventListenerInterface; the listener for this event
144 * @param event EventInterface; the event to fire
145 * @return EventInterface; the event
146 * @throws RemoteException on network failure
147 */
148 public EventInterface fireEvent(EventInterfacestrong> EventListenerInterface listener, final EventInterface event) throws RemoteException
149 {
150 listener.notify(event);
151 return event;
152 }
153
154 /**
155 * Transmit an event to all interested listeners.
156 * @param event EventInterface; the event
157 * @return EventInterface; the event (for method chaining)
158 */
159 public synchronized EventInterfacetInterface">EventInterface fireEvent(final EventInterface event)
160 {
161 Throw.whenNull(event, "event may not be null");
162 Throw.whenNull(event.getType(), "event type may not be null");
163 if (this.listeners.containsKey(event.getType()))
164 {
165 // make a safe copy because of possible removeListener() in notify() method during fireEvent
166 List<Reference<EventListenerInterface>> listenerList = new ArrayList<>(this.listeners.get(event.getType()));
167 for (Reference<EventListenerInterface> reference : listenerList)
168 {
169 EventListenerInterface listener = reference.get();
170 try
171 {
172 if (listener != null)
173 {
174 // The garbage collection has not cleaned the referent
175 this.fireEvent(listener, event);
176 }
177 else
178 {
179 // The garbage collection cleaned the referent;
180 // there is no need to keep the subscription
181 this.removeListener(reference, event.getType());
182 }
183 }
184 catch (RemoteException remoteException)
185 {
186 // A network failure prevented the delivery,
187 // subscription is removed.
188 this.removeListener(reference, event.getType());
189 }
190 }
191 }
192 return event;
193 }
194
195 /**
196 * Transmit an event with a serializable object as payload to all interested listeners.
197 * @param eventType EventType; the eventType of the event
198 * @param value Serializable; the object sent with the event
199 * @return Serializable; the payload
200 */
201 public Serializable fireEvent(final EventType eventType, final Serializable value)
202 {
203 this.fireEvent(new Event(eventType, getSourceId(), value));
204 return value;
205 }
206
207 /**
208 * Transmit an event with no payload object to all interested listeners.
209 * @param eventType EventType; the eventType of the event
210 */
211 public void fireEvent(final EventType eventType)
212 {
213 this.fireEvent(new Event(eventType, getSourceId(), null));
214 }
215
216 /**
217 * Transmit a time-stamped event with a Serializable object (payload) to all interested listeners.
218 * @param eventType EventType; the eventType of the event.
219 * @param value Serializable; the payload sent with the event
220 * @param time C; a time stamp for the event
221 * @return Serializable; the payload
222 * @param <C> the comparable type to indicate the time when the event is fired
223 */
224 public <C extends Comparable<C> & Serializable> Serializable fireTimedEvent(final EventType eventType,
225 final Serializable value, final C time)
226 {
227 Throw.whenNull(time, "time may not be null");
228 this.fireEvent(new TimedEvent<C>(eventType, getSourceId(), value, time));
229 return value;
230 }
231
232 /**
233 * Transmit an event with a one byte payload to all interested listeners.
234 * @param eventType EventType; the eventType of the event
235 * @param value byte; the payload
236 * @return byte; the payload
237 */
238 public byte fireEvent(final EventType eventType, final byte value)
239 {
240 this.fireEvent(eventType, Byte.valueOf(value));
241 return value;
242 }
243
244 /**
245 * Transmit a time-stamped event with a one byte payload to all interested listeners.
246 * @param eventType EventType; the eventType of the event
247 * @param value byte; the payload
248 * @param time C; a time stamp for the event
249 * @param <C> the comparable type to indicate the time when the event is fired
250 * @return byte; the payload
251 */
252 public <C extends Comparable<C> & Serializable> byte fireTimedEvent(final EventType eventType, final byte value,
253 final C time)
254 {
255 this.fireTimedEvent(eventType, Byte.valueOf(value), time);
256 return value;
257 }
258
259 /**
260 * Transmit an event with a boolean payload to all interested listeners.
261 * @param eventType EventType; the eventType of the event
262 * @param value boolean; the payload
263 * @return boolean; the payload
264 */
265 public boolean fireEvent(final EventType eventType, final boolean value)
266 {
267 this.fireEvent(eventType, Boolean.valueOf(value));
268 return value;
269 }
270
271 /**
272 * Transmit a time-stamped event with a boolean payload to all interested listeners.
273 * @param eventType EventType; the eventType of the event
274 * @param value boolean; the payload
275 * @param time C; a time stamp for the event
276 * @param <C> the comparable type to indicate the time when the event is fired
277 * @return boolean; the payload
278 */
279 public <C extends Comparable<C> & Serializable> boolean fireTimedEvent(final EventType eventType, final boolean value,
280 final C time)
281 {
282 fireTimedEvent(eventType, Boolean.valueOf(value), time);
283 return value;
284 }
285
286 /**
287 * Transmit an event with a double value payload to all interested listeners.
288 * @param eventType EventType; the eventType of the event
289 * @param value double; the payload
290 * @return double; the payload
291 */
292 public double fireEvent(final EventType eventType, final double value)
293 {
294 this.fireEvent(eventType, Double.valueOf(value));
295 return value;
296 }
297
298 /**
299 * Transmit a time-stamped event with a double value payload to interested listeners.
300 * @param eventType EventType; the eventType of the event
301 * @param value double; the payload
302 * @param time C; a time stamp for the event
303 * @param <C> the comparable type to indicate the time when the event is fired
304 * @return double; the payload
305 */
306 public <C extends Comparable<C> & Serializable> double fireTimedEvent(final EventType eventType, final double value,
307 final C time)
308 {
309 this.fireTimedEvent(eventType, Double.valueOf(value), time);
310 return value;
311 }
312
313 /**
314 * Transmit an event with an integer payload to all interested listeners.
315 * @param eventType EventType; the eventType of the event
316 * @param value int; the payload
317 * @return int; the payload
318 */
319 public int fireEvent(final EventType eventType, final int value)
320 {
321 this.fireEvent(eventType, Integer.valueOf(value));
322 return value;
323 }
324
325 /**
326 * Transmit a time-stamped event with an integer payload to all interested listeners.
327 * @param eventType EventType; the eventType of the event
328 * @param value int; the payload
329 * @param time C; a time stamp for the event
330 * @param <C> the comparable type to indicate the time when the event is fired
331 * @return int; the payload
332 */
333 public <C extends Comparable<C> & Serializable> int fireTimedEvent(final EventType eventType, final int value, final C time)
334 {
335 this.fireTimedEvent(eventType, Integer.valueOf(value), time);
336 return value;
337 }
338
339 /**
340 * Transmit an event with a long payload to all interested listeners.
341 * @param eventType EventType; the eventType of the event
342 * @param value long; the payload
343 * @return long; the payload
344 */
345 public long fireEvent(final EventType eventType, final long value)
346 {
347 this.fireEvent(eventType, Long.valueOf(value));
348 return value;
349 }
350
351 /**
352 * Transmit a time-stamped event with a long payload to all interested listeners.
353 * @param eventType EventType; the eventType of the event
354 * @param value long; the payload
355 * @param time C; a time stamp for the event
356 * @param <C> the comparable type to indicate the time when the event is fired
357 * @return long; the payload
358 */
359 public <C extends Comparable<C> & Serializable> long fireTimedEvent(final EventType eventType, final long value,
360 final C time)
361 {
362 this.fireTimedEvent(eventType, Long.valueOf(value), time);
363 return value;
364 }
365
366 /**
367 * Transmit an event with a short payload to all interested listeners.
368 * @param eventType EventType; the eventType of the event
369 * @param value short; the payload
370 * @return short; the payload
371 */
372 public short fireEvent(final EventType eventType, final short value)
373 {
374 this.fireEvent(eventType, Short.valueOf(value));
375 return value;
376 }
377
378 /**
379 * Transmit a time-stamped event with a short payload to all interested listeners.
380 * @param eventType EventType; the eventType of the event
381 * @param value short; the payload
382 * @param time C; a time stamp for the event
383 * @param <C> the comparable type to indicate the time when the event is fired
384 * @return short; the payload
385 */
386 public <C extends Comparable<C> & Serializable> short fireTimedEvent(final EventType eventType, final short value,
387 final C time)
388 {
389 this.fireTimedEvent(eventType, Short.valueOf(value), time);
390 return value;
391 }
392
393 /**
394 * Remove all the listeners from this event producer.
395 * @return int; the number of removed event types
396 */
397 public synchronized int removeAllListeners()
398 {
399 int result = this.listeners.size();
400 this.listeners = null;
401 this.listeners = new EventListenerMap();
402 return result;
403 }
404
405 /**
406 * Removes all the listeners of a class from this event producer.
407 * @param ofClass Class<?>; the class or superclass
408 * @return int; the number of removed listeners
409 */
410 public synchronized int removeAllListeners(final Class<?> ofClass)
411 {
412 Throw.whenNull(ofClass, "ofClass may not be null");
413 int result = 0;
414 Map<EventType, Reference<EventListenerInterface>> removeMap = new LinkedHashMap<>();
415 for (EventType type : this.listeners.keySet())
416 {
417 for (Iterator<Reference<EventListenerInterface>> ii = this.listeners.get(type).iterator(); ii.hasNext();)
418 {
419 Reference<EventListenerInterface> listener = ii.next();
420 if (listener.get().getClass().isAssignableFrom(ofClass))
421 {
422 removeMap.put(type, listener);
423 result++;
424 }
425 }
426 }
427 for (EventType type : removeMap.keySet())
428 {
429 removeListener(removeMap.get(type).get(), type);
430 }
431 return result;
432 }
433
434 /** {@inheritDoc} */
435 @Override
436 public final synchronized boolean removeListener(final EventListenerInterface listener, final EventType eventType)
437 {
438 Throw.whenNull(listener, "listener may not be null");
439 Throw.whenNull(eventType, "eventType may not be null");
440 if (!this.listeners.containsKey(eventType))
441 {
442 return false;
443 }
444 boolean result = false;
445 for (Iterator<Reference<EventListenerInterface>> i = this.listeners.get(eventType).iterator(); i.hasNext();)
446 {
447 Reference<EventListenerInterface> reference = i.next();
448 EventListenerInterface entry = reference.get();
449 if (entry == null)
450 {
451 i.remove();
452 }
453 else
454 {
455 if (listener.equals(entry))
456 {
457 i.remove();
458 result = true;
459 }
460 }
461 if (this.listeners.get(eventType).size() == 0)
462 {
463 this.listeners.remove(eventType);
464 }
465 }
466 return result;
467 }
468
469 /**
470 * Remove one reference from the subscription list.
471 * @param reference Reference<EventListenerInterface>; the (strong or weak) reference to remove
472 * @param eventType EventType; the eventType for which reference must be removed
473 * @return boolean; true if the reference was removed; otherwise false
474 */
475 private synchronized boolean removeListener(final Reference<EventListenerInterface> reference, final EventType eventType)
476 {
477 Throw.whenNull(reference, "reference may not be null");
478 Throw.whenNull(eventType, "eventType may not be null");
479 boolean success = false;
480 for (Iterator<Reference<EventListenerInterface>> i = this.listeners.get(eventType).iterator(); i.hasNext();)
481 {
482 if (i.next().equals(reference))
483 {
484 i.remove();
485 success = true;
486 }
487 }
488 if (this.listeners.get(eventType).size() == 0)
489 {
490 this.listeners.remove(eventType);
491 }
492 return success;
493 }
494
495 /** {@inheritDoc} */
496 @Override
497 public boolean hasListeners()
498 {
499 return !this.listeners.isEmpty();
500 }
501
502 /** {@inheritDoc} */
503 @Override
504 public synchronized int numberOfListeners(final EventType eventType)
505 {
506 if (this.listeners.containsKey(eventType))
507 {
508 return this.listeners.get(eventType).size();
509 }
510 return 0;
511 }
512
513 /**
514 * Return a safe copy of the list of (strong or weak) references to the registered listeners for the provided event type, or
515 * an empty list when nothing is registered for this event type. The method never returns a null pointer, so it is safe to
516 * use the result directly in an iterator. The references to the listeners are the original references, so not safe copies.
517 * @param eventType EventType; the event type to look up the listeners for
518 * @return List<Reference<EventListenerInterface>>; the list of references to the listeners for this event type,
519 * or an empty list when the event type is not registered
520 */
521 public List<Reference<EventListenerInterface>> getListenerReferences(final EventType eventType)
522 {
523 List<Reference<EventListenerInterface>> result = new ArrayList<>();
524 if (this.listeners.get(eventType) != null)
525 {
526 result.addAll(this.listeners.get(eventType));
527 }
528 return result;
529 }
530
531 /** {@inheritDoc} */
532 @Override
533 public synchronized Set<EventType> getEventTypesWithListeners()
534 {
535 return this.listeners.keySet(); // is already a safe copy
536 }
537
538 }