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

Last change on this file since 13127 was 12798, checked in by Don-vip, 7 years ago

see #14794 - checkstyle

  • 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
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 private boolean containsListener(T listener) {
116 return listeners.contains(listener) || weakListeners.contains(new WeakListener<>(listener));
117 }
118
119 /**
120 * Removes a listener.
121 * @param listener The listener to remove.
122 * @throws IllegalArgumentException if the listener was not registered before
123 */
124 public synchronized void removeListener(T listener) {
125 if (!listeners.remove(listener) && !weakListeners.remove(new WeakListener<>(listener))) {
126 failRemove(listener);
127 }
128 }
129
130 protected void failRemove(T listener) {
131 throw new IllegalArgumentException(
132 MessageFormat.format("Listener {0} (instance of {1}) was not registered before or already removed.",
133 listener, listener.getClass().getName()));
134 }
135
136 /**
137 * Check if any listeners are registered.
138 * @return <code>true</code> if any are registered.
139 */
140 public boolean hasListeners() {
141 return !listeners.isEmpty();
142 }
143
144 /**
145 * Fires an event to every listener.
146 * @param eventFirerer The firerer to invoke the event method of the listener.
147 */
148 public void fireEvent(EventFirerer<T> eventFirerer) {
149 for (T l : listeners) {
150 eventFirerer.fire(l);
151 }
152 for (Iterator<WeakListener<T>> iterator = weakListeners.iterator(); iterator.hasNext();) {
153 WeakListener<T> weakLink = iterator.next();
154 T l = weakLink.listener.get();
155 if (l != null) {
156 // cleanup during add() should be enough to not cause memory leaks
157 // therefore, we ignore null listeners.
158 eventFirerer.fire(l);
159 }
160 }
161 }
162
163 /**
164 * This is a special {@link ListenerList} that traces calls to the add/remove methods. This may cause memory leaks.
165 * @author Michael Zangl
166 *
167 * @param <T> The type of listener contained in this list
168 */
169 public static class TracingListenerList<T> extends ListenerList<T> {
170 private final HashMap<T, StackTraceElement[]> listenersAdded = new HashMap<>();
171 private final HashMap<T, StackTraceElement[]> listenersRemoved = new HashMap<>();
172
173 protected TracingListenerList() {
174 // hidden
175 }
176
177 @Override
178 public synchronized void addListener(T listener) {
179 super.addListener(listener);
180 listenersRemoved.remove(listener);
181 listenersAdded.put(listener, Thread.currentThread().getStackTrace());
182 }
183
184 @Override
185 public synchronized void addWeakListener(T listener) {
186 super.addWeakListener(listener);
187 listenersRemoved.remove(listener);
188 listenersAdded.put(listener, Thread.currentThread().getStackTrace());
189 }
190
191 @Override
192 public synchronized void removeListener(T listener) {
193 super.removeListener(listener);
194 listenersAdded.remove(listener);
195 listenersRemoved.put(listener, Thread.currentThread().getStackTrace());
196 }
197
198 @Override
199 protected void failAdd(T listener) {
200 Logging.trace("Previous addition of the listener");
201 dumpStack(listenersAdded.get(listener));
202 super.failAdd(listener);
203 }
204
205 @Override
206 protected void failRemove(T listener) {
207 Logging.trace("Previous removal of the listener");
208 dumpStack(listenersRemoved.get(listener));
209 super.failRemove(listener);
210 }
211
212 private static void dumpStack(StackTraceElement... stackTraceElements) {
213 if (stackTraceElements == null) {
214 Logging.trace(" - (no trace recorded)");
215 } else {
216 Stream.of(stackTraceElements).limit(20).forEach(
217 e -> Logging.trace(e.getClassName() + "." + e.getMethodName() + " line " + e.getLineNumber()));
218 }
219 }
220 }
221
222 private static class UncheckedListenerList<T> extends ListenerList<T> {
223 @Override
224 protected void failAdd(T listener) {
225 Logging.warn("Listener was alreaady added: {0}", listener);
226 // ignore
227 }
228
229 @Override
230 protected void failRemove(T listener) {
231 Logging.warn("Listener was removed twice or not added: {0}", listener);
232 // ignore
233 }
234 }
235
236 /**
237 * Create a new listener list
238 * @param <T> The listener type the list should hold.
239 * @return A new list. A tracing list is created if trace is enabled.
240 */
241 public static <T> ListenerList<T> create() {
242 if (Logging.isTraceEnabled()) {
243 return new TracingListenerList<>();
244 } else {
245 return new ListenerList<>();
246 }
247 }
248
249 /**
250 * Creates a new listener list that does not fail if listeners are added ore removed twice.
251 * <p>
252 * Use of this list is discouraged. You should always use {@link #create()} in new implementations and check your listeners.
253 * @param <T> The listener type
254 * @return A new list.
255 * @since 11224
256 */
257 public static <T> ListenerList<T> createUnchecked() {
258 return new UncheckedListenerList<>();
259 }
260}
Note: See TracBrowser for help on using the repository browser.