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.ref.Reference;
17 import org.djutils.event.remote.RemoteEventListenerInterface;
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-2020 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<EventListenerInterface>>> map = Collections.synchronizedMap(new LinkedHashMap<>());
42
43 /**
44 * Return the size of the EventListenerMap, i.e. the number of EventTypes that are registered.
45 * @return int; the size of the EventListenerMap, i.e. the number of EventTypes that are registered
46 */
47 public int size()
48 {
49 return this.map.size();
50 }
51
52 /**
53 * Clears the EventListenerMap.
54 */
55 public void clear()
56 {
57 this.map.clear();
58 }
59
60 /**
61 * Return whether the EventListenerMap is empty.
62 * @return boolean; whether the EventListenerMap is empty
63 */
64 public boolean isEmpty()
65 {
66 return this.map.isEmpty();
67 }
68
69 /**
70 * Return whether the EventListenerMap contains the EventType as a key.
71 * @param eventType EventType; the EventType key to search for
72 * @return boolean; whether the EventListenerMap contains the EventType as a key
73 */
74 public boolean containsKey(final EventType eventType)
75 {
76 Throw.whenNull(eventType, "Cannot search for a null EventType");
77 return this.map.containsKey(eventType);
78 }
79
80 /**
81 * Return whether the EventListenerMap contains the eventListener as one of the subscribers.
82 * @param eventListener EventListenerInterface; the EventListener value to search for
83 * @return boolean; true if the EventListenerMap contains the eventListener as one of the subscribers; false otherwise
84 */
85 public boolean containsValue(final EventListenerInterface eventListener)
86 {
87 Throw.whenNull(eventListener, "Cannot search for a null EventListener");
88 for (List<Reference<EventListenerInterface>> refList : this.map.values())
89 {
90 for (Reference<EventListenerInterface> ref : refList)
91 {
92 if (eventListener.equals(ref.get()))
93 {
94 return true;
95 }
96 }
97 }
98 return false;
99 }
100
101 /**
102 * Returns whether the EventListenerMap contains the reference to the eventListener as one of the subscribers.
103 * @param reference EventListenerInterface; the reference pointer an EventListener to search for
104 * @return boolean; true if the EventListenerMap contains the reference to the eventListener as one of the subscribers;
105 * false otherwise
106 */
107 public boolean containsValue(final Reference<EventListenerInterface> reference)
108 {
109 Throw.whenNull(reference, "Cannot search for a null reference");
110 for (List<Reference<EventListenerInterface>> refList : this.map.values())
111 {
112 for (Reference<EventListenerInterface> ref : refList)
113 {
114 if (reference.equals(ref))
115 {
116 return true;
117 }
118 }
119 }
120 return false;
121 }
122
123 /**
124 * Returns a safe copy of the collection of lists of references to EventListeners, i.e. all the listeners registered in the
125 * map
126 * @return Collection<List<Reference<EventListenerInterface>>>; a safe copy of the collection of lists of
127 * references to EventListeners, i.e. all the listeners registered in the map
128 */
129 public Collection<List<Reference<EventListenerInterface>>> values()
130 {
131 Collection<List<Reference<EventListenerInterface>>> result = new LinkedHashSet<>();
132 for (List<Reference<EventListenerInterface>> list : this.map.values())
133 {
134 result.add(new ArrayList<>(list));
135 }
136 return result;
137 }
138
139 /**
140 * Add all entries of the map to the EventListenerMap. The lists of listeners are added as a safe copy, so the list will not
141 * be changed when the entries from copied map will be changed.
142 * @param m EventListenerMap; the map with references to event listeners to add to the current EventListenerMap
143 */
144 public void putAll(final EventListenerMap m)
145 {
146 Throw.whenNull(m, "Cannot use putAll for a null map");
147 for (Map.Entry<EventType, List<Reference<EventListenerInterface>>> entry : m.entrySet())
148 {
149 put(entry.getKey(), new ArrayList<>(entry.getValue()));
150 }
151 }
152
153 /**
154 * Returns the Set of Entry types holding pairs of a key (EventType) and a value (List of references to EventListeners for
155 * that EventType). Note: this is a map with the real values, so not a safe copy. This entrySet can be used to change the
156 * underlying map.
157 * @return Set<Map.Entry<EventType, List<Reference<EventListenerInterface>>>>;the Set of Entry types
158 * holding pairs of a key (EventType) and a value (List of references to EventListeners for that EventType). Note:
159 * this is <b>not</b> a safe copy!
160 */
161 public Set<Map.Entry<EventType, List<Reference<EventListenerInterface>>>> entrySet()
162 {
163 return this.map.entrySet();
164 }
165
166 /**
167 * Returns a safe copy of the Set of EventTypes for which listeners are registered.
168 * @return Set<EventType>; a safe copy of the Set of EventType keys for which listeners are registered
169 */
170 public Set<EventType> keySet()
171 {
172 return new LinkedHashSet<EventType>(this.map.keySet());
173 }
174
175 /**
176 * Returns the original List of references to EventListeners for the given EventType. Note: this is <b>not</b> a safe copy,
177 * so the list is backed by the original data structure and will change when listeners are added or removed. The method will
178 * return null when the EventType is not found.
179 * @param key EventType; the eventType to look up the listeners for
180 * @return List<Reference<EventListenerInterface>; the List of references to EventListeners for the given
181 * EventType, or null when the EventType is not found. Note: this is <b>not</b> a safe copy.
182 */
183 public List<Reference<EventListenerInterface>> get(final EventType key)
184 {
185 Throw.whenNull(key, "Cannot use get for a null EventType key");
186 return this.map.get(key);
187 }
188
189 /**
190 * Remove the List of references to EventListeners for the given EventType.
191 * @param key EventType; the eventType to remove the listeners for
192 * @return List<Reference<EventListenerInterface>>; the removed List of references to EventListeners for the
193 * given EventType
194 */
195 public List<Reference<EventListenerInterface>> remove(final EventType key)
196 {
197 Throw.whenNull(key, "Cannot use remove for a null EventType key");
198 return this.map.remove(key);
199 }
200
201 /**
202 * Add the List of references to EventListeners for the given EventType to the underlying Map. A safe copy will be added, so
203 * the original list will not be affected when listeners are removed or added, nor will the underlying map be affected when
204 * the provided list is changed.
205 * @param key EventType; the eventType to store the listeners for
206 * @param value List<Reference<EventListenerInterface>>; the references to EventListeners to store for the given
207 * EventType
208 * @return List<Reference<EventListenerInterface>; the previous List of references to EventListeners for the given
209 * EventType, or null when there was no previous mapping
210 */
211 public List<Reference<EventListenerInterface>> put(final EventType key, final List<Reference<EventListenerInterface>> value)
212 {
213 Throw.whenNull(key, "Cannot use put with a null EventType key");
214 Throw.whenNull(value, "Cannot use put with a null List as value");
215 return this.map.put(key, new ArrayList<>(value));
216 }
217
218 /**
219 * Write the EventListenerMap to a stream. RemoteEventListeners are not written, as they are fully dependent on the state
220 * of the network, which might not be the same when the EventListenerMap is read back. Weak references and strong references
221 * are both written to the stream.
222 * @param out ObjectOutputStream; the output stream
223 * @throws IOException on IOException
224 */
225 private synchronized void writeObject(final ObjectOutputStream out) throws IOException
226 {
227 Map<EventType, List<Reference<EventListenerInterface>>> outMap = new LinkedHashMap<>();
228 for (Map.Entry<EventType, List<Reference<EventListenerInterface>>> entry : this.map.entrySet())
229 {
230 outMap.put(entry.getKey(), new ArrayList<>(entry.getValue()));
231 }
232 for (Entry<EventType, List<Reference<EventListenerInterface>>> entry : this.map.entrySet())
233 {
234 for (Reference<EventListenerInterface> reference : entry.getValue())
235 {
236 if (reference.get() instanceof RemoteEventListenerInterface)
237 {
238 outMap.get(entry.getKey()).remove(reference);
239 }
240 }
241 if (outMap.get(entry.getKey()).isEmpty())
242 {
243 outMap.remove(entry.getKey());
244 }
245 }
246 out.writeObject(outMap);
247 }
248
249 /**
250 * Read an EventListenerMap from a stream and use it to replace the internal map.
251 * @param in java.io.ObjectInputStream; the input stream
252 * @throws IOException on IOException
253 * @throws ClassNotFoundException on ClassNotFoundException
254 */
255 @SuppressWarnings("unchecked")
256 private void readObject(final java.io.ObjectInputStream in) throws IOException, ClassNotFoundException
257 {
258 this.map = (LinkedHashMap<EventType, List<Reference<EventListenerInterface>>>) in.readObject();
259 }
260
261 }