View Javadoc

1   package cz.cuni.amis.utils.listener;
2   
3   import java.lang.ref.WeakReference;
4   import java.util.EventListener;
5   import java.util.Iterator;
6   import java.util.concurrent.ConcurrentLinkedQueue;
7   import java.util.logging.Level;
8   import java.util.logging.Logger;
9   
10  import cz.cuni.amis.utils.ExceptionToString;
11  import cz.cuni.amis.utils.NullCheck;
12  
13  /**
14   * This object is implementing listeners list, where you may store both type of references to 
15   * the listeners (strong reference / weak reference).
16   * <BR><BR>
17   * It takes quite of effort to maintain the list with both strong / weak references,
18   * therefore the class was created.
19   * <BR><BR>
20   * Because we don't know what method we will be calling on the listeners the public
21   * interface ListenerNotifier exists. If you want to fire all the listeners, just
22   * implement this interface and stuff it to the notify() method of the "Listeners".
23   * (This somehow resembles the Stored Procedure pattern...)
24   * <BR><BR>
25   * Another interface is used for removing listeners. You may want to go through the listeners
26   * list and mark some of them to be removed (according to some rule like 'instanceof'). See
27   * static interface ListenerRemover.
28   * <BR><BR>
29   * The class is thread-safe.
30   * 
31   * @author Jimmy
32   */
33  @SuppressWarnings("hiding")
34  public class Listeners<Listener extends EventListener> {	
35  	
36  	/**
37  	 * Used to raise the event in the listeners.
38  	 * 
39  	 * @author Jimmy
40  	 *
41  	 * @param <Listener>
42  	 */
43  	public static interface ListenerNotifier<Listener extends EventListener> {
44  		
45  		public Object getEvent();
46  		
47  		public void notify(Listener listener);
48  		
49  	}
50  	
51  	public static class AdaptableListenerNotifier<LISTENER extends IListener> implements ListenerNotifier<LISTENER> {
52  
53  		private Object event;
54  		
55  		public AdaptableListenerNotifier<LISTENER> setEvent(Object event) {
56  			this.event = event;
57  			return this;
58  		}
59  		
60  		@Override
61  		public Object getEvent() {
62  			return event;
63  		}
64  
65  		@Override
66  		public void notify(LISTENER listener) {
67  			listener.notify(event);
68  		}
69  		
70  	}
71  	
72  	/**
73  	 * Used as a visitor to the listeners that should tell which listeners to remove...
74  	 * 
75  	 * @author Jimmy
76  	 *
77  	 * @param <Listener>
78  	 */
79  	public static interface ListenerRemover {
80  		 
81  		/**
82  		 * If returns true, the 'listener' will be removed from the list (detached).
83  		 * @param listener
84  		 * @return
85  		 */
86  		public boolean remove(EventListener listener);
87  		
88  	}
89  	
90  	/**
91  	 * Abstract class that stores the Listeners and can fire the event when needed.
92  	 * @author Jimmy
93  	 */	
94      private static abstract class ListenerStore<Listener extends EventListener> {
95      	
96          /**
97           * May return null if listener was gc()ed.
98           * <BR><BR>
99           * If null is returned, we will remove the entry from the listeners list.
100          * @return
101          */      
102         public abstract Listener getListener();
103 
104 		public abstract void clearListener();
105         
106     }
107 
108     /**
109      * Listener store class referencing the FlagListener with "strong" reference.
110      * @author Jimmy
111      */
112     private static class StrongListenerStore<Listener extends EventListener> extends ListenerStore<Listener> {
113 
114         private Listener listener;
115 
116         public StrongListenerStore(Listener listener) {
117             this.listener = listener;
118         }
119 
120         @Override
121 		public Listener getListener() {
122             return listener;
123         }
124         
125         @Override
126 		public void clearListener() {
127         	this.listener = null;
128         }
129 		
130     }
131     
132     /**
133      * Listener store class referencing the FlagListener with weak reference.
134      * @author Jimmy
135      *
136      * @param <T>
137      */
138     private static class WeakListenerStore<Listener extends EventListener> extends ListenerStore<Listener> {
139         
140         private WeakReference<Listener> listenerReference = null;
141         
142         public WeakListenerStore(Listener listener) {
143             this.listenerReference = new WeakReference<Listener>(listener);
144         }
145         
146         @Override
147 		public Listener getListener() {
148             return listenerReference.get();
149         }
150         
151         @Override
152 		public void clearListener() {
153         	this.listenerReference.clear();
154         }
155         
156     }
157     
158     /**
159      * Logger used by this object.
160      */
161     private Logger log;
162 
163     /**
164      * ID used to identify particular Listeners instance in the logs.
165      */
166     private String name;
167     
168     /**
169      * Access to this field is synchronized through this field.
170      */
171     private ConcurrentLinkedQueue<ListenerStore<Listener>> listeners = new ConcurrentLinkedQueue<ListenerStore<Listener>>(); 
172     
173     /**
174      * Set to true whenever the iteration over listeners occurs and to false in the end.
175      * Only if you are the topmost iterator, you may remove listeners from the list!
176      */
177     private boolean listenersIteration = false;
178 
179     /**
180      * Returns logger used by this object (null as default).
181      * @return
182      */
183     public Logger getLog() {
184 		return log;
185 	}
186 
187     /**
188      * Sets logger for this object (logger is null by default).
189      * @param log
190      */
191 	public void setLog(Logger log, String name) {
192 		this.log = log;
193 		this.name = name;
194 	}
195 
196 	/**
197      * Adds listener with strong reference to it.
198      * @param listener
199      */
200     public void addStrongListener(Listener listener) {
201     	NullCheck.check(listener, "listener");
202     	synchronized(listeners) {
203     		listeners.add(new StrongListenerStore<Listener>(listener));
204     	}
205     }
206     
207     /**
208      * Adds listener with weak reference to it.
209      * @param listener
210      */
211     public void addWeakListener(Listener listener) {
212     	NullCheck.check(listener, "listener");
213     	synchronized(listeners) {
214     		listeners.add(new WeakListenerStore<Listener>(listener));
215     	}
216     }
217     
218     /**
219      * Removes all listeners that are equal() to this one.
220      * @param listener
221      * @return how many listeners were removed
222      */
223     public int removeEqualListener(EventListener listener) {
224     	if (listener == null) return 0;
225     	int removed = 0;
226     	synchronized(listeners) {
227     		
228     		boolean listenersIterationOriginal = listenersIteration;
229     		listenersIteration = true;
230     		
231     		try {
232 	    		Iterator<ListenerStore<Listener>> iterator = listeners.iterator();
233 	    		while(iterator.hasNext()) {
234 	    			ListenerStore<Listener> store = iterator.next();
235 	    			Listener storedListener = store.getListener();
236 	    			if (storedListener == null) {
237 	    				if (!listenersIterationOriginal) {
238 	    					if ((store instanceof WeakListenerStore) && log != null && log.isLoggable(Level.FINE)) {
239 	    						log.fine((name == null ? "" : name + ": ") + "Weakly referenced listener was GC()ed.");
240 	    					}
241 	    					iterator.remove();
242 	    				}
243 	    				continue;
244 	    			}
245 	    			if (listener.equals(storedListener)) {
246 	    				store.clearListener();
247 	    				++removed;
248 	    			}
249 	    		}
250     		} finally {
251     			listenersIteration = listenersIterationOriginal;
252     		}
253     	}
254     	return removed;
255     }
256     
257     /**
258      * Removes all listeners that are == to this one (not equal()! must be the same object).
259      * @param listener
260      * @return how many listeners were removed
261      */
262     public int removeListener(EventListener listener) {
263     	if (listener == null) return 0;
264     	int removed = 0;
265     	synchronized(listeners) {
266     		boolean listenersIterationOriginal = listenersIteration;
267     		listenersIteration = true;
268     		
269     		try {
270 	    		Iterator<ListenerStore<Listener>> iterator = listeners.iterator();
271 	    		while(iterator.hasNext()) {
272 	    			ListenerStore<Listener> store = iterator.next(); 
273 	    			Listener storedListener = store.getListener();
274 	    			if (storedListener == null) {
275 	    				if (!listenersIterationOriginal) {
276 	    					if ((store instanceof WeakListenerStore) && log != null && log.isLoggable(Level.FINE)) {
277 	    						log.fine((name == null ? "" : name + ": ") + "Weakly referenced listener was GC()ed.");
278 	    					}
279 	    					iterator.remove();
280 	    				}
281 	    				continue;
282 	    			}
283 	    			if (listener == storedListener) {
284 	    				store.clearListener();
285 	    				++removed;
286 	    			}
287 	    		}
288     		} finally {
289     			listenersIteration = listenersIterationOriginal;
290     		}
291     	}
292     	return removed;
293     }
294     
295     /**
296      * Calls notifier.notify() on each of the stored listeners, allowing you to execute stored
297      * command.
298      * 
299      * @param notifier
300      */
301     public void notify(ListenerNotifier<Listener> notifier) {
302     	    	
303     	synchronized(listeners) {
304     		boolean listenersIterationOriginal = listenersIteration;
305     		listenersIteration = true;
306     		
307     		try {
308     			Iterator<ListenerStore<Listener>> iterator = listeners.iterator();
309 	    		while(iterator.hasNext()) {
310 	    			ListenerStore<Listener> store = iterator.next();
311 	    			Listener storedListener = store.getListener();
312 	    			if (storedListener == null) {
313 	    				if (!listenersIterationOriginal) {
314 	    					if ((store instanceof WeakListenerStore) && log != null && log.isLoggable(Level.FINE)) {
315 	    						log.fine((name == null ? "" : name + ": ") + "Weakly referenced listener was GC()ed.");
316 	    					}
317 	    					iterator.remove();
318 	    				}
319 	    				continue;
320 	    			}
321 	    			notifier.notify(storedListener);
322 	    		}
323     		} finally {
324     			listenersIteration = listenersIterationOriginal;
325     		}
326     	}
327     }
328     
329     /**
330      * Calls notifier.notify() on each of the stored listeners, allowing you to execute stored
331      * command.
332      * <p><p>
333      * Every notification is run inside try/catch block, exceptions are reported into the log
334      * (if not null) and method returns false if some exception is thrown.
335      * 
336      * @param notifier
337      * @param exceptionLog where to log exceptions, may be null
338      * @return true, if no exception happened
339      */
340     public boolean notifySafe(ListenerNotifier<Listener> notifier, Logger exceptionLog) {
341     	
342     	boolean noException = true;
343     	
344     	synchronized(listeners) {
345     		boolean listenersIterationOriginal = listenersIteration;
346     		listenersIteration = true;
347     		try {
348 	    		Iterator<ListenerStore<Listener>> iterator = listeners.iterator();
349 	    		while(iterator.hasNext()) {
350 	    			ListenerStore<Listener> store = iterator.next();
351 	    			Listener storedListener = store.getListener();
352 	    			if (storedListener == null) {
353 	    				if (!listenersIterationOriginal) {
354 	    					if ((store instanceof WeakListenerStore) && log != null && log.isLoggable(Level.FINE)) {
355 	    						log.fine((name == null ? "" : name + ": ") + "Weakly referenced listener was GC()ed.");
356 	    					}
357 	    					iterator.remove();
358 	    				}
359 	    				continue;
360 	    			}
361 	    			try {
362 	    				notifier.notify(storedListener);
363 	    			} catch (Exception e) {
364 	    				noException = false;
365 	    				if (exceptionLog != null) {
366 	    					if (exceptionLog.isLoggable(Level.SEVERE)) exceptionLog.severe(ExceptionToString.process("Exception during event processing (" + notifier.getEvent() + ").", e));
367 	    				}
368 	    			}
369 	    		}
370     		} finally {
371     			listenersIteration = listenersIterationOriginal;
372     		}
373     	}
374     	
375     	return noException;
376     }
377     
378     
379     /**
380      * Returns true if at least one equals listener to the param 'listener' is found.	 
381      * @param listener
382      * @return
383      */
384     public boolean isEqualListening(EventListener listener) {
385     	if (listener == null) return false;
386     	synchronized(listeners) {
387     		boolean listenersIterationOriginal = listenersIteration;
388     		listenersIteration = true;
389     		
390     		try {
391 	    		Iterator<ListenerStore<Listener>> iterator = listeners.iterator();
392 	    		while(iterator.hasNext()) {
393 	    			ListenerStore<Listener> store = iterator.next();
394 	    			Listener storedListener = store.getListener();
395 	    			if (storedListener == null) {
396 	    				if (!listenersIterationOriginal) {
397 	    					if ((store instanceof WeakListenerStore) && log != null && log.isLoggable(Level.FINE)) {
398 	    						log.fine((name == null ? "" : name + ": ") + "Weakly referenced listener was GC()ed.");
399 	    					}
400 	    					iterator.remove();
401 	    				}
402 	    				continue;
403 	    			}
404 	    			if (listener.equals(storedListener)) {
405 	    				return true;
406 	    			}
407 	    		}
408     		} finally {
409     			listenersIteration = listenersIterationOriginal;
410     		}
411     	}
412     	return false;
413     }
414     
415     /**
416      * Returns true if at least one == listener to the param 'listener' is found.
417      * <BR><BR>
418      * Not using equal() but pointer ==.
419      * 	 
420      * @param listener
421      * @return
422      */
423     public boolean isListening(EventListener listener) {
424     	if (listener == null) return false;
425     	synchronized(listeners) {
426     		boolean listenersIterationOriginal = listenersIteration;
427     		listenersIteration = true;
428     		
429     		try {
430 	    		Iterator<ListenerStore<Listener>> iterator = listeners.iterator();
431 	    		while(iterator.hasNext()) {
432 	    			ListenerStore<Listener> store = iterator.next();
433 	    			Listener storedListener = store.getListener();
434 	    			if (storedListener == null) {
435 	    				if (!listenersIterationOriginal) {
436 	    					if ((store instanceof WeakListenerStore) && log != null && log.isLoggable(Level.FINE)) {
437 	    						log.fine((name == null ? "" : name + ": ") + "Weakly referenced listener was GC()ed.");
438 	    					}
439 	    					iterator.remove();
440 	    				}
441 	    				continue;
442 	    			}
443 	    			if (listener == storedListener) {
444 	    				return true;
445 	    			}
446 	    		}
447     		} finally {
448     			listenersIteration = listenersIterationOriginal;
449     		}
450     	}
451     	return false;
452     }
453     
454     public void clearListeners() {
455     	synchronized(listeners) {
456     		if (!listenersIteration) {
457     			listeners.clear();
458     		} else {
459     			for (ListenerStore store : listeners) {
460     				store.clearListener();
461     			}
462     		}
463     	}
464     }
465     
466     /**
467      * Returns count of listners in the list, note that this may not be exact as we store also
468      * listeners with weak listeners, but the list will be purged in next opportunity (like raising
469      * event, removing listener).
470      * <p><p>
471      * Beware that, unlike in most collections, this method is
472      * <em>NOT</em> a constant-time operation. Because of the
473      * asynchronous nature of used queue, determining the current
474      * number of elements requires an O(n) traversal.
475      * 
476      * @return
477      */
478     public int count() {
479     	synchronized(listeners) {
480     		int count = 0;
481     		for (ListenerStore store : listeners) {
482     			if (store.getListener() != null) ++count;
483     		}
484     		return count;
485     	}
486     }
487     
488     /**
489      * This will iterate over all of the listeners and do the query "whether the listner should be
490      * removed from the Listeners object". 
491      * <BR><BR>
492      * If 'remover' returns true to the listener, the listener
493      * is removed.
494      * 
495      * @param remover
496      */
497     public void remove(ListenerRemover remover) {
498     	synchronized(listeners) {
499     		boolean listenersIterationOriginal = listenersIteration;
500     		listenersIteration = true;
501     		try {
502 	    		Iterator<ListenerStore<Listener>> iterator = listeners.iterator();
503 	    		while(iterator.hasNext()) {
504 	    			ListenerStore<Listener> store = iterator.next();
505 	    			Listener storedListener = store.getListener();
506 	    			if (storedListener == null) {
507 	    				if (!listenersIterationOriginal) {
508 	    					if ((store instanceof WeakListenerStore) && log != null && log.isLoggable(Level.FINE)) {
509 	    						log.fine((name == null ? "" : name + ": ") + "Weakly referenced listener was GC()ed.");
510 	    					}
511 	    					iterator.remove();
512 	    				}
513 	    				continue;
514 	    			}
515 	    			if (remover.remove(storedListener)) {
516 	    				if (!listenersIterationOriginal) iterator.remove();
517 	    				else store.clearListener();
518 	    	 		}
519 	    		}
520     		} finally {
521     			listenersIteration = listenersIterationOriginal;
522     		}
523     	}
524     }
525 
526 }