
Listing 7. A more complicated aspect that tracks object allocations
--------------------------------------------------------------------

package aspects.wgrosso.ddjarticle1;

import org.aspectj.lang.*;
import java.lang.ref.*;
import java.rmi.*;
import java.rmi.server.*;
import java.util.*;

/*
	Aspect_ObjectTracking

	A fairly complicated, but very cool, example of how to use aspects to 
	implement additional debugging in your system. This uses a very simple
	RMI server to vend the counts. 
*/

public aspect Aspect_ObjectTracking {

/*
	Note that constructor signatures don't have a return type. 
*/
	pointcut allConstructors(): execution(com.wgrosso..*.new(..));

/*
	Called after constructor is finished, but before we return out of the
	constructor.

	We use the canonical variable thisJoinPoint to figure out what object
	we're in. 
*/

	after(): allConstructors() {
		Object newlyCreatedObject = thisJoinPoint.getThis();
		objectWasCreated(newlyCreatedObject);
	}

/*
	We need to instance variables and state in this aspect, in order
	to implement our memory tracker.

	Aspect definitions are a lot like class definitions (but there
	is only 1 instance of the aspect at runtime). Thus, aspects can
	have state (and constructors which initialize their state).  
*/

	private HashMap _classNamesToWeakHashMapOfInstances = new HashMap();
	private Object DUMMY_ENTRY = new Object();
	private static long TWO_MINUTES = 2 * 60 * 1000;
	private static long MEM_PRINTING_DELAY = TWO_MINUTES;

	public Aspect_ObjectTracking() {
		(new Thread(new BackgroundInstanceCountPrinter())).start();	
	}

	private synchronized void objectWasCreated(Object object) {
		String className = object.getClass().getName();
		/*
			We use weak hashmaps to avoid memory leaks (weak hashmaps are automatically 
			cleaned up when objects are garbage collected). 
		*/
		WeakHashMap instances = (WeakHashMap) _classNamesToWeakHashMapOfInstances.get(className);
		if (null==instances) {
			instances = new WeakHashMap();	
			_classNamesToWeakHashMapOfInstances.put(className, instances);	
			instances.put(object, DUMMY_ENTRY); 
		}
		else {
			/*
				We need to worry about the possibility that one constructor calls another one
				within the same class. That's what this check is for
			*/
			if (!instances.containsKey(object)) {
				instances.put(object, DUMMY_ENTRY); 
			}
		}
	}

	private synchronized int getNumberOfInstances(String className) {
		WeakHashMap instances = (WeakHashMap) _classNamesToWeakHashMapOfInstances.get(className);
		if (null==instances) {
			return 0;
		}
		return instances.size();
	}

	private synchronized void printOutInstanceCountInformation() {
		Iterator i = _classNamesToWeakHashMapOfInstances.keySet().iterator();
		while(i.hasNext()) {
			String nextClassName = (String) i.next();
			int numberOfInstances = getNumberOfInstances(nextClassName);
			if (numberOfInstances > 0) {
				System.out.println(nextClassName + ": " + numberOfInstances);
			}
		}
	}

	private class CountServer extends UnicastRemoteObject implements ClassCounter {
		public CountServer() throws RemoteException {
			super(STANDARD_PORT);
		}
		public int getNumberOfInstances(String className) throws RemoteException{
			return getNumberOfInstances(className);
		}
	}

	private class BackgroundInstanceCountPrinter implements Runnable {
		public void run() {
			while(true) {
				try {
					Thread.sleep(MEM_PRINTING_DELAY);
				}
				catch (Exception ignored) {}
				printOutInstanceCountInformation();	
			}
		}
	}
}