source: josm/trunk/src/org/openstreetmap/josm/tools/ListenerList.java@ 16966

Last change on this file since 16966 was 15726, checked in by simon04, 4 years ago

Fix typo

  • Property svn:eol-style set to native
File size: 8.6 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.tools;
3
4import java.lang.ref.WeakReference;
5import java.text.MessageFormat;
6import java.util.HashMap;
7import java.util.Iterator;
8import java.util.Objects;
9import java.util.concurrent.CopyOnWriteArrayList;
10import java.util.stream.Stream;
11
12/**
13 * This is a list of listeners. It does error checking and allows you to fire all listeners.
14 *
15 * @author Michael Zangl
16 * @param <T> The type of listener contained in this list.
17 * @since 10824
18 */
19public class ListenerList<T> {
20 /**
21 * This is a function that can be invoked for every listener.
22 * @param <T> the listener type.
23 */
24 @FunctionalInterface
25 public interface EventFirerer<T> {
26 /**
27 * Should fire the event for the given listener.
28 * @param listener The listener to fire the event for.
29 */
30 void fire(T listener);
31 }
32
33 private static final class WeakListener<T> {
34
35 private final WeakReference<T> listener;
36
37 WeakListener(T listener) {
38 this.listener = new WeakReference<>(listener);
39 }
40
41 @Override
42 public boolean equals(Object obj) {
43 if (obj != null && obj.getClass() == WeakListener.class) {
44 return Objects.equals(listener.get(), ((WeakListener<?>) obj).listener.get());
45 } else {
46 return false;
47 }
48 }
49
50 @Override
51 public int hashCode() {
52 T l = listener.get();
53 if (l == null) {
54 return 0;
55 } else {
56 return l.hashCode();
57 }
58 }
59
60 @Override
61 public String toString() {
62 return "WeakListener [listener=" + listener + ']';
63 }
64 }
65
66 private final CopyOnWriteArrayList<T> listeners = new CopyOnWriteArrayList<>();
67 private final CopyOnWriteArrayList<WeakListener<T>> weakListeners = new CopyOnWriteArrayList<>();
68
69 protected ListenerList() {
70 // hide
71 }
72
73 /**
74 * Adds a listener. The listener will not prevent the object from being garbage collected.
75 *
76 * This should be used with care. It is better to add good cleanup code.
77 * @param listener The listener.
78 */
79 public synchronized void addWeakListener(T listener) {
80 if (ensureNotInList(listener)) {
81 // clean the weak listeners, just to be sure...
82 while (weakListeners.remove(new WeakListener<T>(null))) {
83 // continue
84 }
85 weakListeners.add(new WeakListener<>(listener));
86 }
87 }
88
89 /**
90 * Adds a listener.
91 * @param listener The listener to add.
92 */
93 public synchronized void addListener(T listener) {
94 if (ensureNotInList(listener)) {
95 listeners.add(listener);
96 }
97 }
98
99 private boolean ensureNotInList(T listener) {
100 CheckParameterUtil.ensureParameterNotNull(listener, "listener");
101 if (containsListener(listener)) {
102 failAdd(listener);
103 return false;
104 } else {
105 return true;
106 }
107 }
108
109 protected void failAdd(T listener) {
110 throw new IllegalArgumentException(
111 MessageFormat.format("Listener {0} (instance of {1}) was already registered.", listener,
112 listener.getClass().getName()));
113 }
114
115 /**
116 * Determines if this listener list contains the given listener.
117 * @param listener listener to find
118 * @return {@code true} is the listener is known
119 * @since 15649
120 */
121 public synchronized boolean containsListener(T listener) {
122 return listeners.contains(listener) || weakListeners.contains(new WeakListener<>(listener));
123 }
124
125 /**
126 * Removes a listener.
127 * @param listener The listener to remove.
128 * @throws IllegalArgumentException if the listener was not registered before
129 */
130 public synchronized void removeListener(T listener) {
131 if (!listeners.remove(listener) && !weakListeners.remove(new WeakListener<>(listener))) {
132 failRemove(listener);
133 }
134 }
135
136 protected void failRemove(T listener) {
137 throw new IllegalArgumentException(
138 MessageFormat.format("Listener {0} (instance of {1}) was not registered before or already removed.",
139 listener, listener.getClass().getName()));
140 }
141
142 /**
143 * Check if any listeners are registered.
144 * @return <code>true</code> if any are registered.
145 */
146 public boolean hasListeners() {
147 return !listeners.isEmpty();
148 }
149
150 /**
151 * Fires an event to every listener.
152 * @param eventFirerer The firerer to invoke the event method of the listener.
153 */
154 public void fireEvent(EventFirerer<T> eventFirerer) {
155 for (T l : listeners) {
156 eventFirerer.fire(l);
157 }
158 for (Iterator<WeakListener<T>> iterator = weakListeners.iterator(); iterator.hasNext();) {
159 WeakListener<T> weakLink = iterator.next();
160 T l = weakLink.listener.get();
161 if (l != null) {
162 // cleanup during add() should be enough to not cause memory leaks
163 // therefore, we ignore null listeners.
164 eventFirerer.fire(l);
165 }
166 }
167 }
168
169 /**
170 * This is a special {@link ListenerList} that traces calls to the add/remove methods. This may cause memory leaks.
171 * @author Michael Zangl
172 *
173 * @param <T> The type of listener contained in this list
174 */
175 public static class TracingListenerList<T> extends ListenerList<T> {
176 private final HashMap<T, StackTraceElement[]> listenersAdded = new HashMap<>();
177 private final HashMap<T, StackTraceElement[]> listenersRemoved = new HashMap<>();
178
179 protected TracingListenerList() {
180 // hidden
181 }
182
183 @Override
184 public synchronized void addListener(T listener) {
185 super.addListener(listener);
186 listenersRemoved.remove(listener);
187 listenersAdded.put(listener, Thread.currentThread().getStackTrace());
188 }
189
190 @Override
191 public synchronized void addWeakListener(T listener) {
192 super.addWeakListener(listener);
193 listenersRemoved.remove(listener);
194 listenersAdded.put(listener, Thread.currentThread().getStackTrace());
195 }
196
197 @Override
198 public synchronized void removeListener(T listener) {
199 super.removeListener(listener);
200 listenersAdded.remove(listener);
201 listenersRemoved.put(listener, Thread.currentThread().getStackTrace());
202 }
203
204 @Override
205 protected void failAdd(T listener) {
206 Logging.trace("Previous addition of the listener");
207 dumpStack(listenersAdded.get(listener));
208 super.failAdd(listener);
209 }
210
211 @Override
212 protected void failRemove(T listener) {
213 Logging.trace("Previous removal of the listener");
214 dumpStack(listenersRemoved.get(listener));
215 super.failRemove(listener);
216 }
217
218 private static void dumpStack(StackTraceElement... stackTraceElements) {
219 if (stackTraceElements == null) {
220 Logging.trace(" - (no trace recorded)");
221 } else {
222 Stream.of(stackTraceElements).limit(20).forEach(
223 e -> Logging.trace(e.getClassName() + "." + e.getMethodName() + " line " + e.getLineNumber()));
224 }
225 }
226 }
227
228 private static class UncheckedListenerList<T> extends ListenerList<T> {
229 @Override
230 protected void failAdd(T listener) {
231 Logging.warn("Listener was already added: {0}", listener);
232 // ignore
233 }
234
235 @Override
236 protected void failRemove(T listener) {
237 Logging.warn("Listener was removed twice or not added: {0}", listener);
238 // ignore
239 }
240 }
241
242 /**
243 * Create a new listener list
244 * @param <T> The listener type the list should hold.
245 * @return A new list. A tracing list is created if trace is enabled.
246 */
247 public static <T> ListenerList<T> create() {
248 if (Logging.isTraceEnabled()) {
249 return new TracingListenerList<>();
250 } else {
251 return new ListenerList<>();
252 }
253 }
254
255 /**
256 * Creates a new listener list that does not fail if listeners are added or removed twice.
257 * <p>
258 * Use of this list is discouraged. You should always use {@link #create()} in new implementations and check your listeners.
259 * @param <T> The listener type
260 * @return A new list.
261 * @since 11224
262 */
263 public static <T> ListenerList<T> createUnchecked() {
264 return new UncheckedListenerList<>();
265 }
266}
Note: See TracBrowser for help on using the repository browser.