1 package org.djutils.event;
2
3 import java.io.IOException;
4 import java.io.ObjectOutputStream;
5 import java.io.Serializable;
6 import java.util.ArrayList;
7 import java.util.Collection;
8 import java.util.Collections;
9 import java.util.LinkedHashMap;
10 import java.util.LinkedHashSet;
11 import java.util.List;
12 import java.util.Map;
13 import java.util.Map.Entry;
14 import java.util.Set;
15
16 import org.djutils.event.reference.Reference;
17 import org.djutils.event.rmi.RmiEventListener;
18 import org.djutils.exceptions.Throw;
19
20 /**
21 * The EventListenerMap maps EventTypes on lists of References to EventListeners. The References can be Weak or Strong. The Map
22 * can be serialized. When serializing, the References to RemoteEventListeners are not written as they are fully dependent on a
23 * volatile network state that will almost certainly not be the same when the serialized map is read back.
24 * <p>
25 * Copyright (c) 2002-2023 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
26 * for project information <a href="https://djutils.org" target="_blank"> https://djutils.org</a>. The DJUTILS project is
27 * distributed under a three-clause BSD-style license, which can be found at
28 * <a href="https://djutils.org/docs/license.html" target="_blank"> https://djutils.org/docs/license.html</a>. This class was
29 * originally part of the DSOL project, see <a href="https://simulation.tudelft.nl/dsol/manual" target="_blank">
30 * https://simulation.tudelft.nl/dsol/manual</a>.
31 * </p>
32 * @author <a href="https://www.linkedin.com/in/peterhmjacobs">Peter Jacobs </a>
33 * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
34 */
35 public final class EventListenerMap implements Serializable
36 {
37 /** The default serial version UID for serializable classes. */
38 private static final long serialVersionUID = 20140830L;
39
40 /** The hasMap we map on. */
41 private Map<EventType, List<Reference<EventListener>>> map =
42 Collections.synchronizedMap(new LinkedHashMap<>());
43
44 /**
45 * Return the size of the EventListenerMap, i.e. the number of EventTypes that are registered.
46 * @return int; the size of the EventListenerMap, i.e. the number of EventTypes that are registered
47 */
48 public int size()
49 {
50 return this.map.size();
51 }
52
53 /**
54 * Clears the EventListenerMap.
55 */
56 public void clear()
57 {
58 this.map.clear();
59 }
60
61 /**
62 * Return whether the EventListenerMap is empty.
63 * @return boolean; whether the EventListenerMap is empty
64 */
65 public boolean isEmpty()
66 {
67 return this.map.isEmpty();
68 }
69
70 /**
71 * Return whether the EventListenerMap contains the EventType as a key.
72 * @param eventType EventType; the EventType key to search for
73 * @return boolean; whether the EventListenerMap contains the EventType as a key
74 */
75 public boolean containsKey(final EventType eventType)
76 {
77 Throw.whenNull(eventType, "Cannot search for a null EventType");
78 return this.map.containsKey(eventType);
79 }
80
81 /**
82 * Return whether the EventListenerMap contains the eventListener as one of the subscribers.
83 * @param eventListener EventListenerInterface; the EventListener value to search for
84 * @return boolean; true if the EventListenerMap contains the eventListener as one of the subscribers; false otherwise
85 */
86 public boolean containsValue(final EventListener eventListener)
87 {
88 Throw.whenNull(eventListener, "Cannot search for a null EventListener");
89 for (List<Reference<EventListener>> refList : this.map.values())
90 {
91 for (Reference<EventListener> ref : refList)
92 {
93 if (eventListener.equals(ref.get()))
94 {
95 return true;
96 }
97 }
98 }
99 return false;
100 }
101
102 /**
103 * Returns whether the EventListenerMap contains the reference to the eventListener as one of the subscribers.
104 * @param reference Reference<EventListenerInterface>; the reference pointer an EventListener to search for
105 * @return boolean; true if the EventListenerMap contains the reference to the eventListener as one of the subscribers;
106 * false otherwise
107 */
108 public boolean containsValue(final Reference<EventListener> reference)
109 {
110 Throw.whenNull(reference, "Cannot search for a null reference");
111 for (List<Reference<EventListener>> refList : this.map.values())
112 {
113 for (Reference<EventListener> ref : refList)
114 {
115 if (reference.equals(ref))
116 {
117 return true;
118 }
119 }
120 }
121 return false;
122 }
123
124 /**
125 * Returns a safe copy of the collection of lists of references to EventListeners, i.e. all the listeners registered in the
126 * map
127 * @return Collection<List<Reference<EventListenerInterface>>>; a safe copy of the collection of lists of
128 * references to EventListeners, i.e. all the listeners registered in the map
129 */
130 public Collection<List<Reference<EventListener>>> values()
131 {
132 Collection<List<Reference<EventListener>>> result = new LinkedHashSet<>();
133 for (List<Reference<EventListener>> list : this.map.values())
134 {
135 result.add(new ArrayList<>(list));
136 }
137 return result;
138 }
139
140 /**
141 * Add all entries of the map to the EventListenerMap. The lists of listeners are added as a safe copy, so the list will not
142 * be changed when the entries from copied map will be changed.
143 * @param m EventListenerMap; the map with references to event listeners to add to the current EventListenerMap
144 */
145 public void putAll(final EventListenerMap m)
146 {
147 Throw.whenNull(m, "Cannot use putAll for a null map");
148 for (Map.Entry<EventType, List<Reference<EventListener>>> entry : m.entrySet())
149 {
150 put(entry.getKey(), new ArrayList<>(entry.getValue()));
151 }
152 }
153
154 /**
155 * Returns the Set of Entry types holding pairs of a key (EventType) and a value (List of references to
156 * EventListeners for that EventType). Note: this is a map with the real values, so not a safe copy. This entrySet
157 * can be used to change the underlying map.
158 * @return Set<Map.Entry<EventType, List<Reference<EventListenerInterface>>>>;the Set of
159 * Entry types holding pairs of a key (EventType) and a value (List of references to EventListeners for
160 * that EventType). Note: this is <b>not</b> a safe copy!
161 */
162 public Set<Map.Entry<EventType, List<Reference<EventListener>>>> entrySet()
163 {
164 return this.map.entrySet();
165 }
166
167 /**
168 * Returns a safe copy of the Set of EventTypes for which listeners are registered.
169 * @return Set<EventType>; a safe copy of the Set of EventType keys for which listeners are
170 * registered
171 */
172 public Set<EventType> keySet()
173 {
174 return new LinkedHashSet<EventType>(this.map.keySet());
175 }
176
177 /**
178 * Returns the original List of references to EventListeners for the given EventType. Note: this is <b>not</b> a
179 * safe copy, so the list is backed by the original data structure and will change when listeners are added or removed. The
180 * method will return null when the EventType is not found.
181 * @param key EventType; the eventType to look up the listeners for
182 * @return List<Reference<EventListenerInterface>; the List of references to EventListeners for the given
183 * EventType, or null when the EventType is not found. Note: this is <b>not</b> a safe copy.
184 */
185 public List<Reference<EventListener>> get(final EventType key)
186 {
187 Throw.whenNull(key, "Cannot use get for a null EventType key");
188 return this.map.get(key);
189 }
190
191 /**
192 * Remove the List of references to EventListeners for the given EventType.
193 * @param key EventType; the eventType to remove the listeners for
194 * @return List<Reference<EventListenerInterface>>; the removed List of references to EventListeners for the
195 * given EventType
196 */
197 public List<Reference<EventListener>> remove(final EventType key)
198 {
199 Throw.whenNull(key, "Cannot use remove for a null EventType key");
200 return this.map.remove(key);
201 }
202
203 /**
204 * Add the List of references to EventListeners for the given EventType to the underlying Map. A safe copy will be
205 * added, so the original list will not be affected when listeners are removed or added, nor will the underlying map be
206 * affected when the provided list is changed.
207 * @param key EventType; the eventType to store the listeners for
208 * @param value List<Reference<EventListenerInterface>>; the references to EventListeners to store for the given
209 * EventType
210 * @return List<Reference<EventListenerInterface>; the previous List of references to EventListeners for the given
211 * EventType, or null when there was no previous mapping
212 */
213 public List<Reference<EventListener>> put(final EventType key,
214 final List<Reference<EventListener>> value)
215 {
216 Throw.whenNull(key, "Cannot use put with a null EventType key");
217 Throw.whenNull(value, "Cannot use put with a null List as value");
218 return this.map.put(key, new ArrayList<>(value));
219 }
220
221 /**
222 * Write the EventListenerMap to a stream. RemoteEventListeners are not written, as they are fully dependent on the state of
223 * the network, which might not be the same when the EventListenerMap is read back. Weak references and strong references
224 * are both written to the stream.
225 * @param out ObjectOutputStream; the output stream
226 * @throws IOException on IOException
227 */
228 private synchronized void writeObject(final ObjectOutputStream out) throws IOException
229 {
230 Map<EventType, List<Reference<EventListener>>> outMap = new LinkedHashMap<>();
231 for (Map.Entry<EventType, List<Reference<EventListener>>> entry : this.map.entrySet())
232 {
233 outMap.put(entry.getKey(), new ArrayList<>(entry.getValue()));
234 }
235 for (Entry<EventType, List<Reference<EventListener>>> entry : this.map.entrySet())
236 {
237 for (Reference<EventListener> reference : entry.getValue())
238 {
239 if (reference.get() instanceof RmiEventListener)
240 {
241 outMap.get(entry.getKey()).remove(reference);
242 }
243 }
244 if (outMap.get(entry.getKey()).isEmpty())
245 {
246 outMap.remove(entry.getKey());
247 }
248 }
249 out.writeObject(outMap);
250 }
251
252 /**
253 * Read an EventListenerMap from a stream and use it to replace the internal map.
254 * @param in java.io.ObjectInputStream; the input stream
255 * @throws IOException on IOException
256 * @throws ClassNotFoundException on ClassNotFoundException
257 */
258 @SuppressWarnings("unchecked")
259 private void readObject(final java.io.ObjectInputStream in) throws IOException, ClassNotFoundException
260 {
261 this.map = (LinkedHashMap<EventType, List<Reference<EventListener>>>) in.readObject();
262 }
263
264 }