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 }