/*
* Created on 23 mars 2004
*
* To change the template for this generated file go to
* Window - Preferences - Java - Code Generation - Code and Comments
*/
package simtools.util;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Iterator;
/**
* This class avoids code duplication in as many place as possible.
* Its goal is to provide a listener lists using reference counted pointers,
* so that multiple listener registration still results in only one notification, and
* there is no memory leaks. See the long comment below.
*
* Warning : use external synchronization on the manager before using the
* size() and get(int) methods, as otherwise the size may vary according
* to the whims of the garbage collector.
* The synchronized block should englobe both the size() and get(int) calls.
*
* @author nicolas brodu
*
* <p>
* Tricky Java...
*
* When an object registered as listener becomes unused, the fact that the listener list holds
* a reference to it prevents it from beeing fred by the garbage collector. This ends up in
* objects not being fred, and growing listener lists of unused objects.
* This is bad, and listener lists are a very common source of memory leak! Beware!
*
* A naive and developper-unfriendly approach would be to tell all registering
* objects to unregister before they are unsued... This is easy to do in C++ where
* there are proper destructors, but in Java the finalize() method is precisely called
* by the garbage collector, which won't call it in the present case since the listener
* list still holds a reference. Back to the beginning. An even-more developer unfriendly
* approach would impose a constraint on all objects using objects registered as listeners
* to do the cleanup, but this is a real mess and does not even work for other reasons (for
* example, think of inner classes registering private members as listeners, or other awful
* cases where the object using another object using another object using... has no way of
* unregistering a listerner without modifying drastically the API just for that).
*
* Weak references were introduced in Java 1.2 to solve this problem. If only weak references
* to an object remains, the garbage collector discards it. The get() method of the weak reference
* holder then returns null. A queue can be used to be notified when this happens, as the
* application does not decides when the object is finalized (this is handled by the garbage
* collector thread).
*
* Thus, weak reference pointers may become invalid any time!
*
* For JSynoptic, we have an additional problem. The same object may be registered as listener
* several times, but should be notified only once. See EndNotificationListener for a concrete
* example of this. Also, a Set is not adapted to hold only one reference of each listener.
* If an object is registered twice by two different paths and unregistered only once, it
* is still expected to be notified by the second registering path. Hence, it must still
* be present in the listener list, which would not be the case using a Set.
*
* The solution is of course reference counting, which is applied on the register/unregister
* operation. This is complementary to the WeakReference mecanism : if an object forgets to
* unregister or doesn't have the occasion to do it before being thrown away, then the weak
* reference mecanism will still discard it even if the refcount field is >1, which is what we
* want. On the other hand, if the refcount field goes down to 0, the object is removed from the
* listener list, but this does not necessarily mean the listener is unused. It may just have
* unregistered and live its life as usual. As in this case the listener list does not hold any
* reference to the object, it cannot be the source for a memory leak.
*
* So, the refcount field is <b>NOT</b> related to the weak reference mecanism. It is necessary for
* another reason entirely, as decribed above.
*
* See the notes below for more fun with the ReferenceCountedPointer class.
* It holds a weak reference to the ListenerManager, so as to avoid the same problem
* as above, but for the managers themselves.
*
* </p>
*
*/
public class ListenerManager {
/**
* This inner class is a Weak Reference using refcounting, as described in the note above.
* It is a static inner class because the ListenerManager reference would otherwise
* prevent the garbage-collector to destroy the ListenerManagers themselves.
*
* Unfortunately, a reference to the listener manager is still required, so
* as to make only one ReferenceQueue helper thread, and not one thread per
* ListenerManager. So, use a weak reference to the lisener manager too!
*/
public class ReferenceCountedPointer extends WeakReference {
public int refcount = 1; // has ref count of one at creation
public WeakReference manager;
public ReferenceCountedPointer(ListenerManager mgr, Object o) {
super(o);
manager = new WeakReference(mgr);
}
}
/**
* This is a common queue, not attached to any listener manager, which handles
* notifications from the garbage collector when a ReferenceCountedPointer has
* become invalid
*/
static protected ReferenceQueue queue;
/**
* Start a thread to manage the queue
* The thread is passive and wakes up when a weak reference is destroyed
* Put this in a static area, so that it is always available, and also because
* it won't hold a reference to Listener managers so they can be fred by the garbage
* collector
*/
static {
queue = new ReferenceQueue();
Thread queueThread = new Thread("ListenerManagerThread") {
public void run() {
// Run till the JVM stops. See setDaemon below.
while (true) {
try {
// remove is a blocking operation, it doesn't hog the CPU.
// Only ReferenceCountedPointer objects are added to the queue
// => can safely cast
ReferenceCountedPointer rcp = (ReferenceCountedPointer)queue.remove();
// mgr may itself be null if the manager was disposed
ListenerManager mgr = (ListenerManager)rcp.manager.get();
if (mgr!=null) mgr.removeReference(rcp);
// Tells the garbage collector to dispose of the Reference object
rcp = null;
}
catch (InterruptedException e) { }
}
}
};
// Allow the application to terminate without waiting for this thread to stop
// => allow this thread to loop forever!
queueThread.setDaemon(true);
// starts the queue in static context before any instance are created
queueThread.start();
}
protected ArrayList listeners; // Lazy created
// Internal use only, synchronized
protected synchronized void removeReference(ReferenceCountedPointer r) {
if (listeners!=null) listeners.remove(r);
}
// Listeners related functions. Take care of duplicates
public synchronized void add(Object l) {
if (l==null) return;
if (listeners == null) listeners = new ArrayList();
for (Iterator it = listeners.iterator(); it.hasNext(); ) {
ReferenceCountedPointer rcp = (ReferenceCountedPointer)it.next();
// get should not be null because queue thread takes care of that
// But thanks to synchronize magic, the queue thread may be blocked waiting
// for the add function to terminate.
// In this case, it doesn't matter, since the equals call will fail and
// another reference will be added to the list
if (l.equals(rcp.get())) {
rcp.refcount++; return;
}
}
listeners.add(new ReferenceCountedPointer(this, l));
}
public synchronized void remove(Object l) {
if (l==null) return;
if (listeners == null) return;
ReferenceCountedPointer toRemove = null;
for (Iterator it = listeners.iterator(); it.hasNext(); ) {
ReferenceCountedPointer rcp = (ReferenceCountedPointer)it.next();
// See comment for get() being null in add()
// This doesn't matter either here but for another reason: a null reference
// means really that the listener has become unused.
// The refcount doesn't matter in this case (see class notes), and
// the queue thread will remove the reference in due time.
// So, toRemove will be false here, but this is OK.
if (l.equals(rcp.get())) {
if (--rcp.refcount>0) return;
toRemove = rcp; break;
}
}
if (toRemove!=null) listeners.remove(toRemove);
}
public synchronized void clear() {
// keep allocation of the internal arraylist's array, don't set it to null
listeners.clear();
}
/**
* Warning, use external synchronization on the manager before using this.
* The synchronized block should englobe both the size() and get(int) calls.
*/
public int size() {
return (listeners==null) ? 0 : listeners.size();
}
/**
* Warning, use external synchronization on the manager before using this.
* The synchronized block should englobe both the size() and get(int) calls.
* Warning2: This method may return null.
*/
public Object get(int i) {
ReferenceCountedPointer rcp = (ReferenceCountedPointer)listeners.get(i);
return rcp.get();
}
}