View Javadoc
1   package org.djutils.event;
2   
3   import java.util.ArrayList;
4   import java.util.Iterator;
5   import java.util.LinkedHashMap;
6   import java.util.List;
7   import java.util.Map;
8   import java.util.Set;
9   
10  import org.djutils.event.reference.Reference;
11  import org.djutils.event.reference.ReferenceType;
12  import org.djutils.event.reference.StrongReference;
13  import org.djutils.event.reference.WeakReference;
14  import org.djutils.exceptions.Throw;
15  
16  /**
17   * EventProducer is the interface that exposes a few of the methods of the implementation of an EventProducer to the outside
18   * world: the ability to add and remove listeners.
19   * <p>
20   * Copyright (c) 2022-2025 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
21   * for project information <a href="https://djutils.org" target="_blank"> https://djutils.org</a>. The DJUTILS project is
22   * distributed under a three-clause BSD-style license, which can be found at
23   * <a href="https://djutils.org/docs/license.html" target="_blank"> https://djutils.org/docs/license.html</a>. <br>
24   * </p>
25   * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
26   */
27  public interface EventProducer
28  {
29      /** The FIRST_POSITION in the queue. */
30      int FIRST_POSITION = 0;
31  
32      /** The LAST_POSITION in the queue. */
33      int LAST_POSITION = -1;
34  
35      /**
36       * Add a listener to the specified position of a queue of listeners.
37       * @param listener which is interested at certain events
38       * @param eventType the events of interest
39       * @param position the position of the listener in the queue
40       * @param referenceType whether the listener is added as a strong or as a weak reference
41       * @return the success of adding the listener. If a listener was already added or an illegal position is provided false is
42       *         returned
43       */
44      default boolean addListener(final EventListener listener, final EventType eventType, final int position,
45              final ReferenceType referenceType)
46      {
47          Throw.whenNull(listener, "listener cannot be null");
48          Throw.whenNull(eventType, "eventType cannot be null");
49          Throw.whenNull(referenceType, "referenceType cannot be null");
50          if (position < LAST_POSITION)
51          {
52              return false;
53          }
54          Reference<EventListener> reference = null;
55          if (referenceType.isStrong())
56          {
57              reference = new StrongReference<EventListener>(listener);
58          }
59          else
60          {
61              reference = new WeakReference<EventListener>(listener);
62          }
63          EventListenerMap eventListenerMap = getEventListenerMap();
64          if (eventListenerMap.containsKey(eventType))
65          {
66              for (Reference<EventListener> entry : eventListenerMap.get(eventType))
67              {
68                  if (listener.equals(entry.get()))
69                  {
70                      return false;
71                  }
72              }
73              List<Reference<EventListener>> entries = eventListenerMap.get(eventType);
74              if (position == LAST_POSITION)
75              {
76                  entries.add(reference);
77              }
78              else
79              {
80                  entries.add(position, reference);
81              }
82          }
83          else
84          {
85              List<Reference<EventListener>> entries = new ArrayList<>();
86              entries.add(reference);
87              eventListenerMap.put(eventType, entries);
88          }
89          return true;
90      }
91  
92      /**
93       * Add a listener as strong reference to the BEGINNING of a queue of listeners.
94       * @param listener the listener which is interested at events of eventType
95       * @param eventType the events of interest
96       * @return the success of adding the listener. If a listener was already added false is returned
97       */
98      default boolean addListener(final EventListener listener, final EventType eventType)
99      {
100         return addListener(listener, eventType, FIRST_POSITION);
101     }
102 
103     /**
104      * Add a listener to the BEGINNING of a queue of listeners.
105      * @param listener the listener which is interested at events of eventType
106      * @param eventType the events of interest
107      * @param referenceType whether the listener is added as a strong or as a weak reference
108      * @return the success of adding the listener. If a listener was already added false is returned
109      * @see org.djutils.event.reference.WeakReference
110      */
111     default boolean addListener(final EventListener listener, final EventType eventType, final ReferenceType referenceType)
112 
113     {
114         return addListener(listener, eventType, FIRST_POSITION, referenceType);
115     }
116 
117     /**
118      * Add a listener as strong reference to the specified position of a queue of listeners.
119      * @param listener the listener which is interested at events of eventType
120      * @param eventType the events of interest
121      * @param position the position of the listener in the queue
122      * @return the success of adding the listener. If a listener was already added, or an illegal position is provided false is
123      *         returned
124      */
125     default boolean addListener(final EventListener listener, final EventType eventType, final int position)
126 
127     {
128         return addListener(listener, eventType, position, ReferenceType.STRONG);
129     }
130 
131     /**
132      * Return the map with the EventListener entries and the reference types.
133      * @return the map with the EventListener entries and the reference types
134      */
135     EventListenerMap getEventListenerMap();
136 
137     /**
138      * Remove all the listeners from this event producer.
139      * @return the number of removed event types for which listeners existed
140      */
141     default int removeAllListeners()
142     {
143         int result = getEventListenerMap().size();
144         getEventListenerMap().clear();
145         return result;
146     }
147 
148     /**
149      * Removes all the listeners of a class from this event producer.
150      * @param ofClass the class or superclass
151      * @return the number of removed listeners
152      */
153     default int removeAllListeners(final Class<?> ofClass)
154     {
155         Throw.whenNull(ofClass, "ofClass may not be null");
156         int result = 0;
157         Map<EventType, Reference<EventListener>> removeMap = new LinkedHashMap<>();
158         for (EventType type : getEventListenerMap().keySet())
159         {
160             for (Iterator<Reference<EventListener>> ii = getEventListenerMap().get(type).iterator(); ii.hasNext();)
161             {
162                 Reference<EventListener> listener = ii.next();
163                 if (listener.get().getClass().isAssignableFrom(ofClass))
164                 {
165                     removeMap.put(type, listener);
166                     result++;
167                 }
168             }
169         }
170         for (EventType type : removeMap.keySet())
171         {
172             removeListener(removeMap.get(type).get(), type);
173         }
174         return result;
175     }
176 
177     /**
178      * Remove the subscription of a listener for a specific event.
179      * @param listener which is no longer interested
180      * @param eventType the event which is of no interest any more
181      * @return the success of removing the listener. If a listener was not subscribed false is returned
182      */
183     default boolean removeListener(final EventListener listener, final EventType eventType)
184     {
185         Throw.whenNull(listener, "listener may not be null");
186         Throw.whenNull(eventType, "eventType may not be null");
187         EventListenerMap eventListenerMap = getEventListenerMap();
188         if (!eventListenerMap.containsKey(eventType))
189         {
190             return false;
191         }
192         boolean result = false;
193         for (Iterator<Reference<EventListener>> i = eventListenerMap.get(eventType).iterator(); i.hasNext();)
194         {
195             Reference<EventListener> reference = i.next();
196             EventListener entry = reference.get();
197             if (entry == null)
198             {
199                 i.remove();
200             }
201             else
202             {
203                 if (listener.equals(entry))
204                 {
205                     i.remove();
206                     result = true;
207                 }
208             }
209             if (eventListenerMap.get(eventType).size() == 0)
210             {
211                 eventListenerMap.remove(eventType);
212             }
213         }
214         return result;
215     }
216 
217     /**
218      * Return whether the EventProducer has listeners.
219      * @return whether the EventProducer has listeners or not
220      */
221     default boolean hasListeners()
222     {
223         return !getEventListenerMap().isEmpty();
224     }
225 
226     /**
227      * Return the number of listeners for the provided EventType.
228      * @param eventType the event type to return the number of listeners for
229      * @return whether the EventProducer has listeners or not
230      */
231     default int numberOfListeners(final EventType eventType)
232     {
233         if (getEventListenerMap().containsKey(eventType))
234         {
235             return getEventListenerMap().get(eventType).size();
236         }
237         return 0;
238     }
239 
240     /**
241      * Return a safe copy of the list of (strong or weak) references to the registered listeners for the provided event type, or
242      * an empty list when nothing is registered for this event type. The method never returns a null pointer, so it is safe to
243      * use the result directly in an iterator. The references to the listeners are the original references, so not safe copies.
244      * @param eventType the event type to look up the listeners for
245      * @return the list of references to the listeners for this event type, or an empty list when the event type is not
246      *         registered
247      */
248     default List<Reference<EventListener>> getListenerReferences(final EventType eventType)
249     {
250         List<Reference<EventListener>> result = new ArrayList<>();
251         if (getEventListenerMap().get(eventType) != null)
252         {
253             result.addAll(getEventListenerMap().get(eventType));
254         }
255         return result;
256     }
257 
258     /**
259      * Return the EventTypes for which the EventProducer has listeners.
260      * @return the EventTypes for which the EventProducer has registered listeners
261      */
262     default Set<EventType> getEventTypesWithListeners()
263     {
264         return getEventListenerMap().keySet(); // is already a safe copy
265     }
266 
267     /**
268      * Remove one reference from the subscription list.
269      * @param reference the (strong or weak) reference to remove
270      * @param eventType the eventType for which reference must be removed
271      * @return true if the reference was removed; otherwise false
272      */
273     private boolean removeListener(final Reference<EventListener> reference, final EventType eventType)
274     {
275         Throw.whenNull(reference, "reference may not be null");
276         Throw.whenNull(eventType, "eventType may not be null");
277         EventListenerMap eventListenerMap = getEventListenerMap();
278         boolean success = false;
279         for (Iterator<Reference<EventListener>> i = eventListenerMap.get(eventType).iterator(); i.hasNext();)
280         {
281             if (i.next().equals(reference))
282             {
283                 i.remove();
284                 success = true;
285             }
286         }
287         if (eventListenerMap.get(eventType).size() == 0)
288         {
289             eventListenerMap.remove(eventType);
290         }
291         return success;
292     }
293 
294     /**
295      * Transmit an event to all subscribed listeners.
296      * @param event the event
297      */
298     default void fireEvent(final Event event)
299     {
300         Throw.whenNull(event, "event may not be null");
301         EventListenerMap eventListenerMap = getEventListenerMap();
302         if (eventListenerMap.containsKey(event.getType()))
303         {
304             // make a safe copy because of possible removeListener() in notify() method during fireEvent
305             List<Reference<EventListener>> listenerList = new ArrayList<>(eventListenerMap.get(event.getType()));
306             for (Reference<EventListener> reference : listenerList)
307             {
308                 EventListener listener = reference.get();
309                 if (listener != null)
310                 {
311                     // The garbage collection has not cleaned the referent
312                     fireEvent(listener, event);
313                 }
314                 else
315                 {
316                     // The garbage collection cleaned the referent;
317                     // there is no need to keep the subscription
318                     removeListener(reference, event.getType());
319                 }
320             }
321         }
322     }
323 
324     /**
325      * Transmit an event to a listener. This method is a hook method. The default implementation simply invokes the notify on
326      * the listener. In specific cases (filtering, storing, queueing, this method can be overwritten.
327      * @param listener the listener for this event
328      * @param event the event to fire
329      */
330     private void fireEvent(final EventListener listener, final Event event)
331     {
332         listener.notify(event);
333     }
334 
335     /**
336      * Transmit a time-stamped event to all interested listeners.
337      * @param event the event
338      * @param <C> the comparable type to indicate the time when the event is fired
339      */
340     default <C extends Comparable<C>> void fireTimedEvent(final TimedEvent<C> event)
341     {
342         fireEvent(event);
343     }
344 
345     /**
346      * Transmit an event with no payload object to all interested listeners.
347      * @param eventType the eventType of the event
348      */
349     default void fireEvent(final EventType eventType)
350     {
351         fireEvent(new Event(eventType, null, true));
352     }
353 
354     /**
355      * Transmit a time-stamped event with a no payload object to all interested listeners.
356      * @param eventType the eventType of the event.
357      * @param time a time stamp for the event
358      * @param <C> the comparable type to indicate the time when the event is fired
359      */
360     default <C extends Comparable<C>> void fireTimedEvent(final EventType eventType, final C time)
361 
362     {
363         fireEvent(new TimedEvent<C>(eventType, null, time, true));
364     }
365 
366     /**
367      * Transmit an event with an object as payload to all interested listeners.
368      * @param eventType the eventType of the event
369      * @param value the object sent with the event
370      */
371     default void fireEvent(final EventType eventType, final Object value)
372     {
373         fireEvent(new Event(eventType, value, true));
374     }
375 
376     /**
377      * Transmit a time-stamped event with an object (payload) to all interested listeners.
378      * @param eventType the eventType of the event.
379      * @param value the payload sent with the event
380      * @param time a time stamp for the event
381      * @param <C> the comparable type to indicate the time when the event is fired
382      */
383     default <C extends Comparable<C>> void fireTimedEvent(final EventType eventType, final Object value, final C time)
384 
385     {
386         fireEvent(new TimedEvent<C>(eventType, value, time, true));
387     }
388 
389     /**
390      * Transmit an event with no payload object to all interested listeners.
391      * @param eventType the eventType of the event
392      */
393     default void fireUnverifiedEvent(final EventType eventType)
394     {
395         fireEvent(new Event(eventType, null, false));
396     }
397 
398     /**
399      * Transmit a time-stamped event without a payload object to all interested listeners.
400      * @param eventType the eventType of the event.
401      * @param time a time stamp for the event
402      * @param <C> the comparable type to indicate the time when the event is fired
403      */
404     default <C extends Comparable<C>> void fireUnverifiedTimedEvent(final EventType eventType, final C time)
405 
406     {
407         fireEvent(new TimedEvent<C>(eventType, null, time, false));
408     }
409 
410     /**
411      * Transmit an event with an object as payload to all interested listeners.
412      * @param eventType the eventType of the event
413      * @param value the object sent with the event
414      */
415     default void fireUnverifiedEvent(final EventType eventType, final Object value)
416     {
417         fireEvent(new Event(eventType, value, false));
418     }
419 
420     /**
421      * Transmit a time-stamped event with an object (payload) to all interested listeners.
422      * @param eventType the eventType of the event.
423      * @param value the payload sent with the event
424      * @param time a time stamp for the event
425      * @param <C> the comparable type to indicate the time when the event is fired
426      */
427     default <C extends Comparable<C>> void fireUnverifiedTimedEvent(final EventType eventType, final Object value, final C time)
428 
429     {
430         fireEvent(new TimedEvent<C>(eventType, value, time, false));
431     }
432 
433 }