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 }