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<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&lt;List&lt;Reference&lt;EventListenerInterface&gt;&gt;&gt;; 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&lt;Map.Entry&lt;EventType, List&lt;Reference&lt;EventListenerInterface&gt;&gt;&gt;&gt;;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&lt;EventType&gt;; 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&lt;Reference&lt;EventListenerInterface&gt;; 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&lt;Reference&lt;EventListenerInterface&gt;&gt;; 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&lt;Reference&lt;EventListenerInterface&gt;&gt;; the references to EventListeners to store for the given
207      *            EventType
208      * @return List&lt;Reference&lt;EventListenerInterface&gt;; 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 }