1 package net.sf.openrocket.util;
3 import java.util.Iterator;
5 import net.sf.openrocket.logging.LogHelper;
6 import net.sf.openrocket.logging.TraceException;
7 import net.sf.openrocket.startup.Application;
10 * A list of listeners of a specific type. This class contains various utility,
11 * safety and debugging methods for handling listeners.
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.
18 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
19 * @param <T> the type of the listeners.
21 public class ListenerList<T> implements Invalidatable, Iterable<T> {
22 private static final LogHelper log = Application.getLogger();
24 private final ArrayList<ListenerData<T>> listeners = new ArrayList<ListenerData<T>>();
25 private final TraceException instantiationLocation;
27 private TraceException invalidated = null;
33 public ListenerList() {
34 this.instantiationLocation = new TraceException(1, 1);
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.
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.
47 public boolean addListener(T listener) {
50 ListenerData<T> data = new ListenerData<T>(listener);
51 if (listeners.contains(data)) {
52 log.warn(1, "Attempting to add duplicate listener " + listener);
61 * Remove the specified listener from the list. The listener is removed based on the
62 * quality operator ==, not by the equals() method.
64 * @param listener the listener to remove.
65 * @return whether the listener was actually removed.
67 public boolean removeListener(T listener) {
70 Iterator<ListenerData<T>> iterator = listeners.iterator();
71 while (iterator.hasNext()) {
72 if (iterator.next().listener == listener) {
74 log.verbose(1, "Removing listener " + listener);
78 log.info(1, "Attempting to remove non-existant listener " + listener);
84 * Return the number of listeners in this list.
86 public int getListenerCount() {
87 return listeners.size();
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.
98 public Iterator<T> iterator() {
100 return new ListenerDataIterator();
104 * Return the instantiation location of this listener list.
105 * @return the location where this listener list was instantiated.
107 public TraceException getInstantiationLocation() {
108 return instantiationLocation;
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.
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);
127 public boolean isInvalidated() {
128 return this.invalidated != null;
132 private void checkState(boolean error) {
133 if (this.invalidated != null) {
135 throw new BugException(this + ": this ListenerList has been invalidated", invalidated);
137 log.warn(1, this + ": this ListenerList has been invalidated",
138 new TraceException("ListenerList was attempted to be used here", invalidated));
145 public String toString() {
146 StringBuilder sb = new StringBuilder();
147 sb.append("ListenerList[");
149 if (this.invalidated != null) {
150 sb.append("INVALIDATED]");
151 return sb.toString();
154 if (listeners.isEmpty()) {
157 boolean first = true;
158 for (ListenerData<T> l : listeners) {
167 return sb.toString();
172 * A class containing data about a listener.
174 * @author Sampo Niskanen <sampo.niskanen@iki.fi>
175 * @param <T> the listener type
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;
186 private ListenerData(T listener) {
187 if (listener == null) {
188 throw new NullPointerException("listener is null");
190 this.listener = listener;
191 this.addTimestamp = System.currentTimeMillis();
192 this.accessTimestamp = this.addTimestamp;
193 this.addLocation = new TraceException("Listener " + listener + " add position");
197 public boolean equals(Object obj) {
200 if (!(obj instanceof ListenerData))
202 ListenerData<?> other = (ListenerData<?>) obj;
203 return this.listener == other.listener;
207 public int hashCode() {
208 return listener.hashCode();
212 * Return the listener.
214 public T getListener() {
219 * Return the millisecond timestamp when this listener was added to the
222 public long getAddTimestamp() {
227 * Return the location where this listener was added to the listener list.
229 public TraceException getAddLocation() {
234 * Return the millisecond timestamp when this listener was last accessed through
235 * the listener list iterator.
237 public long getAccessTimestamp() {
238 return accessTimestamp;
243 private class ListenerDataIterator implements Iterator<T> {
244 private final Iterator<ListenerData<T>> iterator = listeners.clone().iterator();
247 public boolean hasNext() {
248 return iterator.hasNext();
253 ListenerData<T> data = iterator.next();
254 data.accessTimestamp = System.currentTimeMillis();
255 return data.listener;
259 public void remove() {
260 throw new UnsupportedOperationException("Remove not supported");