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

Last change on this file since 11057 was 11018, checked in by Don-vip, 8 years ago

sonar - pmd:UseVarargs - Use Varargs

  • Property svn:eol-style set to native
File size: 7.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 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 * Adds a listener.
92 * @param listener The listener to add.
93 */
94 public synchronized void addListener(T listener) {
95 ensureNotInList(listener);
96 listeners.add(listener);
97 }
98
99 private void ensureNotInList(T listener) {
100 CheckParameterUtil.ensureParameterNotNull(listener, "listener");
101 if (containsListener(listener)) {
102 failAdd(listener);
103 }
104 }
105
106 protected void failAdd(T listener) {
107 throw new IllegalArgumentException(
108 MessageFormat.format("Listener {0} (instance of {1}) was already registered.", listener,
109 listener.getClass().getName()));
110 }
111
112 private boolean containsListener(T listener) {
113 return listeners.contains(listener) || weakListeners.contains(new WeakListener<>(listener));
114 }
115
116 /**
117 * Removes a listener.
118 * @param listener The listener to remove.
119 * @throws IllegalArgumentException if the listener was not registered before
120 */
121 public synchronized void removeListener(T listener) {
122 if (!listeners.remove(listener) && !weakListeners.remove(new WeakListener<>(listener))) {
123 failRemove(listener);
124 }
125 }
126
127 protected void failRemove(T listener) {
128 throw new IllegalArgumentException(
129 MessageFormat.format("Listener {0} (instance of {1}) was not registered before or already removed.",
130 listener, listener.getClass().getName()));
131 }
132
133 /**
134 * Check if any listeners are registered.
135 * @return <code>true</code> if any are registered.
136 */
137 public boolean hasListeners() {
138 return !listeners.isEmpty();
139 }
140
141 /**
142 * Fires an event to every listener.
143 * @param eventFirerer The firerer to invoke the event method of the listener.
144 */
145 public void fireEvent(EventFirerer<T> eventFirerer) {
146 for (T l : listeners) {
147 eventFirerer.fire(l);
148 }
149 for (Iterator<WeakListener<T>> iterator = weakListeners.iterator(); iterator.hasNext();) {
150 WeakListener<T> weakLink = iterator.next();
151 T l = weakLink.listener.get();
152 if (l != null) {
153 // cleanup during add() should be enough to not cause memory leaks
154 // therefore, we ignore null listeners.
155 eventFirerer.fire(l);
156 }
157 }
158 }
159
160 /**
161 * This is a special {@link ListenerList} that traces calls to the add/remove methods. This may cause memory leaks.
162 * @author Michael Zangl
163 *
164 * @param <T> The type of listener contained in this list
165 */
166 public static class TracingListenerList<T> extends ListenerList<T> {
167 private final HashMap<T, StackTraceElement[]> listenersAdded = new HashMap<>();
168 private final HashMap<T, StackTraceElement[]> listenersRemoved = new HashMap<>();
169
170 protected TracingListenerList() {
171 // hidden
172 }
173
174 @Override
175 public synchronized void addListener(T listener) {
176 super.addListener(listener);
177 listenersRemoved.remove(listener);
178 listenersAdded.put(listener, Thread.currentThread().getStackTrace());
179 }
180
181 @Override
182 public synchronized void addWeakListener(T listener) {
183 super.addWeakListener(listener);
184 listenersRemoved.remove(listener);
185 listenersAdded.put(listener, Thread.currentThread().getStackTrace());
186 }
187
188 @Override
189 public synchronized void removeListener(T listener) {
190 super.removeListener(listener);
191 listenersAdded.remove(listener);
192 listenersRemoved.put(listener, Thread.currentThread().getStackTrace());
193 }
194
195 @Override
196 protected void failAdd(T listener) {
197 Main.trace("Previous addition of the listener");
198 dumpStack(listenersAdded.get(listener));
199 super.failAdd(listener);
200 }
201
202 @Override
203 protected void failRemove(T listener) {
204 Main.trace("Previous removal of the listener");
205 dumpStack(listenersRemoved.get(listener));
206 super.failRemove(listener);
207 }
208
209 private static void dumpStack(StackTraceElement ... stackTraceElements) {
210 if (stackTraceElements == null) {
211 Main.trace(" - (no trace recorded)");
212 } else {
213 Stream.of(stackTraceElements).limit(20).forEach(
214 e -> Main.trace(e.getClassName() + "." + e.getMethodName() + " line " + e.getLineNumber()));
215 }
216 }
217 }
218
219 /**
220 * Create a new listener list
221 * @param <T> The listener type the list should hold.
222 * @return A new list. A tracing list is created if trace is enabled.
223 */
224 public static <T> ListenerList<T> create() {
225 if (Main.isTraceEnabled()) {
226 return new TracingListenerList<>();
227 } else {
228 return new ListenerList<>();
229 }
230 }
231}
Note: See TracBrowser for help on using the repository browser.