View Javadoc
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&lt;?&gt;; 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&lt;EventListenerInterface&gt;; 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&lt;Reference&lt;EventListenerInterface&gt;&gt;; 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 }