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