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

Last change on this file since 12146 was 11224, checked in by michael2402, 7 years ago

Simplify ExpertToggleAction

Make ExpertToggleAction use the listener list to track weak listeners and add a method to set expert mode explicitly. Use it in OsmDataLayerTest to make tracing expert mode problems easier.

Add test case for ExpertToggleAction.

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