View Javadoc
1   package org.djutils.event.collection;
2   
3   import java.rmi.RemoteException;
4   import java.util.Collection;
5   
6   import org.djutils.event.Event;
7   import org.djutils.event.EventListener;
8   import org.djutils.event.EventType;
9   import org.djutils.event.LocalEventProducer;
10  import org.djutils.event.reference.ReferenceType;
11  import org.djutils.exceptions.Throw;
12  import org.djutils.metadata.MetaData;
13  import org.djutils.metadata.ObjectDescriptor;
14  
15  /**
16   * The Event producing collection provides a set to which one can subscribe interest in entry changes. This class does not keep
17   * track of changes which take place indirectly. One is for example not notified on <code>map.iterator.remove()</code>. A
18   * listener must subscribe to the iterator individually.
19   * <p>
20   * Copyright (c) 2002-2024 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
21   * for project information <a href="https://djutils.org" target="_blank"> https://djutils.org</a>. The DJUTILS project is
22   * distributed under a three-clause BSD-style license, which can be found at
23   * <a href="https://djutils.org/docs/license.html" target="_blank"> https://djutils.org/docs/license.html</a>. This class was
24   * originally part of the DSOL project, see <a href="https://simulation.tudelft.nl/dsol/manual" target="_blank">
25   * https://simulation.tudelft.nl/dsol/manual</a>.
26   * </p>
27   * @author <a href="https://www.linkedin.com/in/peterhmjacobs">Peter Jacobs </a>
28   * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
29   * @param <T> The type of the event producing Collection.
30   */
31  public class EventProducingCollection<T> extends LocalEventProducer implements EventListener, Collection<T>
32  {
33      /** The default serial version UID for serializable classes. */
34      private static final long serialVersionUID = 20191230L;
35  
36      /** OBJECT_ADDED_EVENT is fired on new entries. */
37      public static final EventType OBJECT_ADDED_EVENT =
38              new EventType("OBJECT_ADDED_EVENT", new MetaData("Size of the collection after add", "Size of the collection",
39                      new ObjectDescriptor("Size of the collection after add", "Size of the collection", Integer.class)));
40  
41      /** OBJECT_REMOVED_EVENT is fired on removal of entries. */
42      public static final EventType OBJECT_REMOVED_EVENT =
43              new EventType("OBJECT_REMOVED_EVENT", new MetaData("Size of the collection after remove", "Size of the collection",
44                      new ObjectDescriptor("Size of the collection after remove", "Size of the collection", Integer.class)));
45  
46      /** OBJECT_CHANGED_EVENT is fired on change of one or more entries. */
47      public static final EventType OBJECT_CHANGED_EVENT =
48              new EventType("OBJECT_CHANGED_EVENT", new MetaData("Size of the collection after change", "Size of the collection",
49                      new ObjectDescriptor("Size of the collection after change", "Size of the collection", Integer.class)));
50  
51      /** the wrapped collection. */
52      private final Collection<T> wrappedCollection;
53  
54      /**
55       * constructs a new EventProducingCollection with a local EventProducer.
56       * @param wrappedCollection Collection&lt;T&gt;; the wrapped collection.
57       */
58      public EventProducingCollection(final Collection<T> wrappedCollection)
59      {
60          Throw.whenNull(wrappedCollection, "wrappedCollection cannot be null");
61          this.wrappedCollection = wrappedCollection;
62      }
63  
64      @Override
65      public int size()
66      {
67          return this.wrappedCollection.size();
68      }
69  
70      @Override
71      public boolean isEmpty()
72      {
73          return this.wrappedCollection.isEmpty();
74      }
75  
76      @Override
77      public void clear()
78      {
79          int nr = this.wrappedCollection.size();
80          this.wrappedCollection.clear();
81          if (nr != this.wrappedCollection.size())
82          {
83              fireEvent(OBJECT_REMOVED_EVENT, this.wrappedCollection.size());
84          }
85      }
86  
87      @Override
88      public boolean add(final T o)
89      {
90          boolean changed = this.wrappedCollection.add(o);
91          if (changed)
92          {
93              fireEvent(OBJECT_ADDED_EVENT, this.wrappedCollection.size());
94          }
95          else
96          {
97              fireEvent(OBJECT_CHANGED_EVENT, this.wrappedCollection.size());
98          }
99          return changed;
100     }
101 
102     @Override
103     public boolean addAll(final Collection<? extends T> c)
104     {
105         boolean changed = this.wrappedCollection.addAll(c);
106         if (changed)
107         {
108             fireEvent(OBJECT_ADDED_EVENT, this.wrappedCollection.size());
109         }
110         else
111         {
112             if (!c.isEmpty())
113             {
114                 fireEvent(OBJECT_CHANGED_EVENT, this.wrappedCollection.size());
115             }
116         }
117         return changed;
118     }
119 
120     @Override
121     public boolean contains(final Object o)
122     {
123         return this.wrappedCollection.contains(o);
124     }
125 
126     @Override
127     public boolean containsAll(final Collection<?> c)
128     {
129         return this.wrappedCollection.containsAll(c);
130     }
131 
132     @Override
133     public EventProducingIterator<T> iterator()
134     {
135         EventProducingIterator<T> iterator = new EventProducingIterator<T>(this.wrappedCollection.iterator());
136         // WEAK reference as an iterator is usually local and should be eligible for garbage collection
137         iterator.addListener(this, EventProducingIterator.OBJECT_REMOVED_EVENT, ReferenceType.WEAK);
138         return iterator;
139     }
140 
141     @Override
142     public void notify(final Event event) throws RemoteException
143     {
144         // pass through the OBJECT_REMOVED_EVENT from the iterator
145         if (event.getType().equals(EventProducingIterator.OBJECT_REMOVED_EVENT))
146         {
147             fireEvent(OBJECT_REMOVED_EVENT, this.wrappedCollection.size());
148         }
149     }
150 
151     @Override
152     public boolean remove(final Object o)
153     {
154         boolean changed = this.wrappedCollection.remove(o);
155         if (changed)
156         {
157             fireEvent(OBJECT_REMOVED_EVENT, this.wrappedCollection.size());
158         }
159         return changed;
160     }
161 
162     @Override
163     public boolean removeAll(final Collection<?> c)
164     {
165         boolean changed = this.wrappedCollection.removeAll(c);
166         if (changed)
167         {
168             fireEvent(OBJECT_REMOVED_EVENT, this.wrappedCollection.size());
169         }
170         return changed;
171     }
172 
173     @Override
174     public boolean retainAll(final Collection<?> c)
175     {
176         boolean changed = this.wrappedCollection.retainAll(c);
177         if (changed)
178         {
179             fireEvent(OBJECT_REMOVED_EVENT, this.wrappedCollection.size());
180         }
181         return changed;
182     }
183 
184     @Override
185     public Object[] toArray()
186     {
187         return this.wrappedCollection.toArray();
188     }
189 
190     @Override
191     public <E> E[] toArray(final E[] a)
192     {
193         return this.wrappedCollection.toArray(a);
194     }
195 
196 }