View Javadoc
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-2021 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<EventTypeInterface, List<Reference<EventListenerInterface>>> 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 EventTypeInterface; the EventType key to search for
73       * @return boolean; whether the EventListenerMap contains the EventType as a key
74       */
75      public boolean containsKey(final EventTypeInterface 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 EventListenerInterface eventListener)
87      {
88          Throw.whenNull(eventListener, "Cannot search for a null EventListener");
89          for (List<Reference<EventListenerInterface>> refList : this.map.values())
90          {
91              for (Reference<EventListenerInterface> 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&lt;EventListenerInterface&gt;; 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<EventListenerInterface> reference)
109     {
110         Throw.whenNull(reference, "Cannot search for a null reference");
111         for (List<Reference<EventListenerInterface>> refList : this.map.values())
112         {
113             for (Reference<EventListenerInterface> 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&lt;List&lt;Reference&lt;EventListenerInterface&gt;&gt;&gt;; 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<EventListenerInterface>>> values()
131     {
132         Collection<List<Reference<EventListenerInterface>>> result = new LinkedHashSet<>();
133         for (List<Reference<EventListenerInterface>> 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<EventTypeInterface, List<Reference<EventListenerInterface>>> 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 (EventTypeInterface) and a value (List of references to
156      * EventListeners for that EventTypeInterface). 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&lt;Map.Entry&lt;EventTypeInterface, List&lt;Reference&lt;EventListenerInterface&gt;&gt;&gt;&gt;;the Set of
159      *         Entry types holding pairs of a key (EventTypeInterface) and a value (List of references to EventListeners for
160      *         that EventTypeInterface). Note: this is <b>not</b> a safe copy!
161      */
162     public Set<Map.Entry<EventTypeInterface, List<Reference<EventListenerInterface>>>> entrySet()
163     {
164         return this.map.entrySet();
165     }
166 
167     /**
168      * Returns a safe copy of the Set of EventTypeInterfaces for which listeners are registered.
169      * @return Set&lt;EventTypeInterface&gt;; a safe copy of the Set of EventTypeInterface keys for which listeners are
170      *         registered
171      */
172     public Set<EventTypeInterface> keySet()
173     {
174         return new LinkedHashSet<EventTypeInterface>(this.map.keySet());
175     }
176 
177     /**
178      * Returns the original List of references to EventListeners for the given EventTypeInterface. 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 EventTypeInterface is not found.
181      * @param key EventTypeInterface; the eventType to look up the listeners for
182      * @return List&lt;Reference&lt;EventListenerInterface&gt;; the List of references to EventListeners for the given
183      *         EventTypeInterface, or null when the EventTypeInterface is not found. Note: this is <b>not</b> a safe copy.
184      */
185     public List<Reference<EventListenerInterface>> get(final EventTypeInterface 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 EventTypeInterface.
193      * @param key EventTypeInterface; the eventType to remove the listeners for
194      * @return List&lt;Reference&lt;EventListenerInterface&gt;&gt;; the removed List of references to EventListeners for the
195      *         given EventTypeInterface
196      */
197     public List<Reference<EventListenerInterface>> remove(final EventTypeInterface key)
198     {
199         Throw.whenNull(key, "Cannot use remove for a null EventTypeInterface key");
200         return this.map.remove(key);
201     }
202 
203     /**
204      * Add the List of references to EventListeners for the given EventTypeInterface 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 EventTypeInterface; the eventType to store the listeners for
208      * @param value List&lt;Reference&lt;EventListenerInterface&gt;&gt;; the references to EventListeners to store for the given
209      *            EventTypeInterface
210      * @return List&lt;Reference&lt;EventListenerInterface&gt;; the previous List of references to EventListeners for the given
211      *         EventTypeInterface, or null when there was no previous mapping
212      */
213     public List<Reference<EventListenerInterface>> put(final EventTypeInterface key,
214             final List<Reference<EventListenerInterface>> 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<EventTypeInterface, List<Reference<EventListenerInterface>>> outMap = new LinkedHashMap<>();
231         for (Map.Entry<EventTypeInterface, List<Reference<EventListenerInterface>>> entry : this.map.entrySet())
232         {
233             outMap.put(entry.getKey(), new ArrayList<>(entry.getValue()));
234         }
235         for (Entry<EventTypeInterface, List<Reference<EventListenerInterface>>> entry : this.map.entrySet())
236         {
237             for (Reference<EventListenerInterface> reference : entry.getValue())
238             {
239                 if (reference.get() instanceof RemoteEventListenerInterface)
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<EventTypeInterface, List<Reference<EventListenerInterface>>>) in.readObject();
262     }
263 
264 }