package com.cytetech.util; import java.lang.ref.PhantomReference; import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import java.util.logging.Logger; /** * This class provides a way for tracking the loss of reference of one type of * object to allow a secondary reference to be used to perform some cleanup * activity. The most common use of this is with one object which might * contain or refer to another object that needs some cleanup performed * when the referer is no longer referenced. *

* An example might be an object of type Holder, which refers to or uses a * Socket connection. When the reference is lost, the socket should be * closed. Thus, an instance might be created as in *

 *	ReferenceTracker trker = ReferenceTracker() {
 *		public void released( Socket s ) {
 *			try {
 *				s.close();
 *			} catch( Exception ex ) {
 *				log.log( Level.SEVERE, ex.toString(), ex );
 *			}
 *		}
 *  };
 * 
* Somewhere, there might be calls such as the following. *
 *		interface Holder {
 *			public T get();
 *		}
 *		class SocketHolder implements Holder {
 *			Socket s;
 *			public SocketHolder( Socket sock ) {
 *				s = sock;
 *			}
 *			public Socket get() {
 *				return s;
 *			}
 *		}
 * 
* This defines an implementation of the Holder interface which holds * a reference to Socket objects. The use of the trker * object, above, might then include the use of a method for creating * the objects and registering the references as shown below. *
 *	public SocketHolder connect( String host, int port ) throws IOException {
 *		Socket s = new Socket( host, port );
 *		SocketHolder h = new SocketHolder( s );
 *		trker.trackReference( h, s );
 *		return h;
 *	}
 * 
* Software wishing to use a socket connection, and pass it around would * use SocketHolder.get() to reference the Socket instance, in all cases. * then, when all SocketHolder references are dropped, the socket would * be closed by the released(java.net.Socket) method shown * above. */ public abstract class ReferenceTracker { /** * The thread instance that is removing entries from the reference queue, refqueue, as they appear. */ private volatile RefQueuePoll poll; /** * The Logger instance used for this instance. It will include the name as a suffix * if that constructor is used. */ private final Logger log; /** * The name indicating which instance this is for logging and other separation of * instances needed. */ private final String which; /** * Creates a new instance of ReferenceTracker using the passed name to differentiate * the instance in logging and toString() implementation. * @param which The name of this instance for differentiation of multiple instances in logging etc. */ public ReferenceTracker( String which ) { this.which = which; log = Logger.getLogger( getClass().getName()+"."+which ); } /** * Creates a new instance of ReferenceTracker with no qualifying name. */ public ReferenceTracker( ) { this.which = null; log = Logger.getLogger( getClass().getName() ); } /** * Provides access to the name of this instance. * @return The name of this instance. */ public String toString() { if( which == null ) return super.toString(); return "ReferenceTracker["+which+"]"; } /** * Subclasses must implement this method. It will be called when all references to the * associated holder object are dropped. * @param val The value passed as the second argument to a corresponding call to {@link #trackReference(T,K)} */ public abstract void released( K val ); /** The reference queueu for references to the holder objects */ private final ReferenceQueuerefqueue = new ReferenceQueue(); /** * The count of the total number of threads that have been created and then destroyed as entries have * been tracked. When there are zero tracked references, there is no queue running. */ private final AtomicInteger tcnt = new AtomicInteger(); /** * A Thread implementation that polls {@link #refqueue} to subsequently call {@link released(K)} * as references to T objects are dropped. */ private class RefQueuePoll extends Thread { /** * The thread number associated with this instance. There might briefly be two instances of * this class that exists in a volatile system. If that is the case, this value will * be visible in some of the logging to differentiate the active ones. */ private final int mycnt; /** * Creates an instance of this class. */ public RefQueuePoll() { setDaemon( true ); setName( getClass().getName()+": ReferenceTracker ("+which+")" ); mycnt = tcnt.incrementAndGet(); } /** * This method provides all the activity of performing refqueue.remove() * calls and then calling released(K) to let the application release the * resources needed. */ public void run() { boolean done = false; while( !done ) { try { Reference ref = refqueue.remove(); K ctl; synchronized( refmap ) { ctl = refmap.remove( ref ); done = actCnt.decrementAndGet() == 0; if( log.isLoggable( Level.FINE ) ) log.fine("current act refs="+actCnt.get()); } if( log.isLoggable( Level.FINER ) ) log.finer("reference released for: "+ref+", dep="+ctl ); if( ctl != null ) { try { released( ctl ); if( log.isLoggable( Level.FINE ) ) log.fine("dependant object released: "+ctl ); } catch( Exception ex ) { log.log( Level.SEVERE, ex.toString(), ex ); } } } catch( Exception ex ) { log.log( Level.SEVERE, ex.toString(), ex ); } } if( log.isLoggable( Level.FINE ) ) log.fine("poll thread "+mycnt+" shutdown for "+this ); } } /** * A count of the active references. */ private final AtomicInteger actCnt = new AtomicInteger(); /** * Map from T References to K objects to be used for the released(K) call */ private final ConcurrentHashMap,K>refmap = new ConcurrentHashMap,K>(); /** * Adds a tracked reference. dep should not refer to ref in any way except possibly * a WeakReference. dep is almost always something referred to by ref. * @throw IllegalArgumentException of ref and dep are the same object. * @param dep The dependent object that needs cleanup when ref is no longer referenced. * @param ref the object whose reference is to be tracked */ public void trackReference( T ref, K dep ) { if( ref == dep ) { throw new IllegalArgumentException( "Referenced object and dependent object can not be the same" ); } PhantomReference p = new PhantomReference( ref, refqueue ); synchronized( refmap ) { refmap.put( p, dep ); if( actCnt.getAndIncrement() == 0 ) { poll = new RefQueuePoll(); poll.start(); if( log.isLoggable( Level.FINE ) ) log.fine( "poll thread #"+tcnt.get()+" created for "+this ); } } } /** * This method can be called if the JVM that the tracker is in, is being * shutdown, or someother context is being shutdown and the objects tracked * by the tracker should now be released. This method will result in * {@link #released(K)} being called for each outstanding refernce. */ public void shutdown() { Listrem; // Copy the values and clear the map so that released // is only ever called once, incase GC later evicts references synchronized( refmap ) { rem = new ArrayList( refmap.values() ); refmap.clear(); } for( K dep : rem ) { try { released( dep ); } catch( Exception ex ) { log.log( Level.SEVERE, ex.toString(), ex ); } } } }