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-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<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 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 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 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<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 that
160      *         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 registered
170      */
171     public Set<EventTypeInterface> keySet()
172     {
173         return new LinkedHashSet<EventTypeInterface>(this.map.keySet());
174     }
175 
176     /**
177      * Returns the original List of references to EventListeners for the given EventTypeInterface. Note: this is <b>not</b> a
178      * safe copy, so the list is backed by the original data structure and will change when listeners are added or removed. The
179      * method will return null when the EventTypeInterface is not found.
180      * @param key EventTypeInterface; the eventType to look up the listeners for
181      * @return List&lt;Reference&lt;EventListenerInterface&gt;; the List of references to EventListeners for the given
182      *         EventTypeInterface, or null when the EventTypeInterface is not found. Note: this is <b>not</b> a safe copy.
183      */
184     public List<Reference<EventListenerInterface>> get(final EventTypeInterface key)
185     {
186         Throw.whenNull(key, "Cannot use get for a null EventType key");
187         return this.map.get(key);
188     }
189 
190     /**
191      * Remove the List of references to EventListeners for the given EventTypeInterface.
192      * @param key EventTypeInterface; the eventType to remove the listeners for
193      * @return List&lt;Reference&lt;EventListenerInterface&gt;&gt;; the removed List of references to EventListeners for the
194      *         given EventTypeInterface
195      */
196     public List<Reference<EventListenerInterface>> remove(final EventTypeInterface key)
197     {
198         Throw.whenNull(key, "Cannot use remove for a null EventTypeInterface key");
199         return this.map.remove(key);
200     }
201 
202     /**
203      * Add the List of references to EventListeners for the given EventTypeInterface to the underlying Map. A safe copy will be
204      * added, so the original list will not be affected when listeners are removed or added, nor will the underlying map be
205      * affected when the provided list is changed.
206      * @param key EventTypeInterface; the eventType to store the listeners for
207      * @param value List&lt;Reference&lt;EventListenerInterface&gt;&gt;; the references to EventListeners to store for the given
208      *            EventTypeInterface
209      * @return List&lt;Reference&lt;EventListenerInterface&gt;; the previous List of references to EventListeners for the given
210      *         EventTypeInterface, or null when there was no previous mapping
211      */
212     public List<Reference<EventListenerInterface>> put(final EventTypeInterface key,
213             final List<Reference<EventListenerInterface>> value)
214     {
215         Throw.whenNull(key, "Cannot use put with a null EventType key");
216         Throw.whenNull(value, "Cannot use put with a null List as value");
217         return this.map.put(key, new ArrayList<>(value));
218     }
219 
220     /**
221      * Write the EventListenerMap to a stream. RemoteEventListeners are not written, as they are fully dependent on the state of
222      * the network, which might not be the same when the EventListenerMap is read back. Weak references and strong references
223      * are both written to the stream.
224      * @param out ObjectOutputStream; the output stream
225      * @throws IOException on IOException
226      */
227     private synchronized void writeObject(final ObjectOutputStream out) throws IOException
228     {
229         Map<EventTypeInterface, List<Reference<EventListenerInterface>>> outMap = new LinkedHashMap<>();
230         for (Map.Entry<EventTypeInterface, List<Reference<EventListenerInterface>>> entry : this.map.entrySet())
231         {
232             outMap.put(entry.getKey(), new ArrayList<>(entry.getValue()));
233         }
234         for (Entry<EventTypeInterface, List<Reference<EventListenerInterface>>> entry : this.map.entrySet())
235         {
236             for (Reference<EventListenerInterface> reference : entry.getValue())
237             {
238                 if (reference.get() instanceof RemoteEventListenerInterface)
239                 {
240                     outMap.get(entry.getKey()).remove(reference);
241                 }
242             }
243             if (outMap.get(entry.getKey()).isEmpty())
244             {
245                 outMap.remove(entry.getKey());
246             }
247         }
248         out.writeObject(outMap);
249     }
250 
251     /**
252      * Read an EventListenerMap from a stream and use it to replace the internal map.
253      * @param in java.io.ObjectInputStream; the input stream
254      * @throws IOException on IOException
255      * @throws ClassNotFoundException on ClassNotFoundException
256      */
257     @SuppressWarnings("unchecked")
258     private void readObject(final java.io.ObjectInputStream in) throws IOException, ClassNotFoundException
259     {
260         this.map = (LinkedHashMap<EventTypeInterface, List<Reference<EventListenerInterface>>>) in.readObject();
261     }
262 
263 }