create changelog entry
[debian/openrocket] / core / src / net / sf / openrocket / util / ListenerList.java
1 package net.sf.openrocket.util;
2
3 import java.util.Iterator;
4
5 import net.sf.openrocket.logging.LogHelper;
6 import net.sf.openrocket.logging.TraceException;
7 import net.sf.openrocket.startup.Application;
8
9 /**
10  * A list of listeners of a specific type.  This class contains various utility,
11  * safety and debugging methods for handling listeners.
12  * <p>
13  * Note that unlike normal listener implementations, this list does NOT allow the 
14  * exact same listener (equality using ==) twice.  While adding a listener twice to
15  * a event source would in principle be valid, in practice it's most likely a bug.
16  * For example the Swing implementation Sun JRE contains such bugs.
17  * 
18  * @author Sampo Niskanen <sampo.niskanen@iki.fi>
19  * @param <T>   the type of the listeners.
20  */
21 public class ListenerList<T> implements Invalidatable, Iterable<T> {
22         private static final LogHelper log = Application.getLogger();
23         
24         private final ArrayList<ListenerData<T>> listeners = new ArrayList<ListenerData<T>>();
25         private final TraceException instantiationLocation;
26         
27         private TraceException invalidated = null;
28         
29         
30         /**
31          * Sole contructor.
32          */
33         public ListenerList() {
34                 this.instantiationLocation = new TraceException(1, 1);
35         }
36         
37         
38         /**
39          * Adds the specified listener to this list.  The listener is not added if it
40          * already is in the list (checked by the equality operator ==).  This method throws
41          * a BugException if {@link #invalidate()} has been called.
42          * 
43          * @param listener      the listener to add.
44          * @return                      whether the listeners was actually added to the list.
45          * @throws BugException         if this listener list has been invalidated.
46          */
47         public boolean addListener(T listener) {
48                 checkState(true);
49                 
50                 ListenerData<T> data = new ListenerData<T>(listener);
51                 if (listeners.contains(data)) {
52                         log.warn(1, "Attempting to add duplicate listener " + listener);
53                         return false;
54                 }
55                 listeners.add(data);
56                 return true;
57         }
58         
59         
60         /**
61          * Remove the specified listener from the list.  The listener is removed based on the
62          * quality operator ==, not by the equals() method.
63          * 
64          * @param listener      the listener to remove.
65          * @return                      whether the listener was actually removed.
66          */
67         public boolean removeListener(T listener) {
68                 checkState(false);
69                 
70                 Iterator<ListenerData<T>> iterator = listeners.iterator();
71                 while (iterator.hasNext()) {
72                         if (iterator.next().listener == listener) {
73                                 iterator.remove();
74                                 log.verbose(1, "Removing listener " + listener);
75                                 return true;
76                         }
77                 }
78                 log.info(1, "Attempting to remove non-existant listener " + listener);
79                 return false;
80         }
81         
82         
83         /**
84          * Return the number of listeners in this list.
85          */
86         public int getListenerCount() {
87                 return listeners.size();
88         }
89         
90         
91         /**
92          * Return an iterator that iterates of the listeners.  This iterator is backed by
93          * a copy of the iterator list, so {@link #addListener(Object)} and {@link #removeListener(Object)}
94          * may be called while iterating the list without effect on the iteration.  The returned
95          * iterator does not support the {@link Iterator#remove()} method.
96          */
97         @Override
98         public Iterator<T> iterator() {
99                 checkState(false);
100                 return new ListenerDataIterator();
101         }
102         
103         /**
104          * Return the instantiation location of this listener list.
105          * @return      the location where this listener list was instantiated.
106          */
107         public TraceException getInstantiationLocation() {
108                 return instantiationLocation;
109         }
110         
111         
112         /**
113          * Invalidate this listener list.  Invalidation removes all listeners from the list.
114          * After invalidation {@link #addListener(Object)} will throw an exception, the other
115          * methods produce a warning log message.
116          */
117         @Override
118         public void invalidate() {
119                 this.invalidated = new TraceException("Invalidation occurred at this point");
120                 if (!listeners.isEmpty()) {
121                         log.info("Invalidating " + this + " while still having listeners " + listeners);
122                 }
123                 listeners.clear();
124         }
125         
126         
127         public boolean isInvalidated() {
128                 return this.invalidated != null;
129         }
130         
131         
132         private void checkState(boolean error) {
133                 if (this.invalidated != null) {
134                         if (error) {
135                                 throw new BugException(this + ": this ListenerList has been invalidated", invalidated);
136                         } else {
137                                 log.warn(1, this + ": this ListenerList has been invalidated",
138                                                 new TraceException("ListenerList was attempted to be used here", invalidated));
139                         }
140                 }
141         }
142         
143         
144         @Override
145         public String toString() {
146                 StringBuilder sb = new StringBuilder();
147                 sb.append("ListenerList[");
148                 
149                 if (this.invalidated != null) {
150                         sb.append("INVALIDATED]");
151                         return sb.toString();
152                 }
153                 
154                 if (listeners.isEmpty()) {
155                         sb.append("empty");
156                 } else {
157                         boolean first = true;
158                         for (ListenerData<T> l : listeners) {
159                                 if (!first) {
160                                         sb.append("; ");
161                                 }
162                                 first = false;
163                                 sb.append(l);
164                         }
165                 }
166                 sb.append("]");
167                 return sb.toString();
168         }
169         
170         
171         /**
172          * A class containing data about a listener.
173          * 
174          * @author Sampo Niskanen <sampo.niskanen@iki.fi>
175          * @param <T>   the listener type
176          */
177         public static class ListenerData<T> {
178                 private final T listener;
179                 private final long addTimestamp;
180                 private final TraceException addLocation;
181                 private long accessTimestamp;
182                 
183                 /**
184                  * Sole constructor.
185                  */
186                 private ListenerData(T listener) {
187                         if (listener == null) {
188                                 throw new NullPointerException("listener is null");
189                         }
190                         this.listener = listener;
191                         this.addTimestamp = System.currentTimeMillis();
192                         this.accessTimestamp = this.addTimestamp;
193                         this.addLocation = new TraceException("Listener " + listener + " add position");
194                 }
195                 
196                 @Override
197                 public boolean equals(Object obj) {
198                         if (this == obj)
199                                 return true;
200                         if (!(obj instanceof ListenerData))
201                                 return false;
202                         ListenerData<?> other = (ListenerData<?>) obj;
203                         return this.listener == other.listener;
204                 }
205                 
206                 @Override
207                 public int hashCode() {
208                         return listener.hashCode();
209                 }
210                 
211                 /**
212                  * Return the listener.
213                  */
214                 public T getListener() {
215                         return listener;
216                 }
217                 
218                 /**
219                  * Return the millisecond timestamp when this listener was added to the
220                  * listener list.
221                  */
222                 public long getAddTimestamp() {
223                         return addTimestamp;
224                 }
225                 
226                 /**
227                  * Return the location where this listener was added to the listener list.
228                  */
229                 public TraceException getAddLocation() {
230                         return addLocation;
231                 }
232                 
233                 /**
234                  * Return the millisecond timestamp when this listener was last accessed through
235                  * the listener list iterator.
236                  */
237                 public long getAccessTimestamp() {
238                         return accessTimestamp;
239                 }
240         }
241         
242         
243         private class ListenerDataIterator implements Iterator<T> {
244                 private final Iterator<ListenerData<T>> iterator = listeners.clone().iterator();
245                 
246                 @Override
247                 public boolean hasNext() {
248                         return iterator.hasNext();
249                 }
250                 
251                 @Override
252                 public T next() {
253                         ListenerData<T> data = iterator.next();
254                         data.accessTimestamp = System.currentTimeMillis();
255                         return data.listener;
256                 }
257                 
258                 @Override
259                 public void remove() {
260                         throw new UnsupportedOperationException("Remove not supported");
261                 }
262         }
263         
264 }