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 }