IIIA-Example for Explicit Announcement from Berkeley DB

The following three examples show the application of Explicit Announcement, and how this improves over original AspectJ implementations we used in Berkeley DB. All excerpts are taken from Berkeley DB and simplified to illustrate the concept. All three examples can be downloaded as minimal, but compilable code snippets: compileable Examples

Exposing Local Variables

This example is based on a code excerpt from Berkeley DB, on a real problem we faced when extracting the Logging-Feature with AspectJ. In the original source code Tree.java, we wanted to extract the tracing call "traceInsert(...)". Note, that the Logging Feature in Berkeley DB implements several logging calls in specific well defined position in the source code and publishes several context parameters, again different in each position.

Original Code from Berkeley DB (Excerpt, Simplified)

public class Tree {
	public long insert(LeafNode ln, byte[] key, ...) { 
		BottomNode bin = findBINForInsert(key, ...);
		long position = ln.log(key, ...);
		bin.updateEntry(ln, position, key);
		bin.clearKnownDeleted();
		traceInsert(Level.FINER, bin, ln, position);
		//...
	}
}

AspectJ Implementation

The difficulty of the AspectJ implementation lies in accessing the local variables that should be exposed at this position in the source code. In the original implementation, we added an additional empty method "hook" and a call to this method. The hook method exposes all required local variables and is subsequently used as target for the aspect.
public class Tree {
	public long insert(LeafNode ln, byte[] key, ...) { 
		BottomNode bin = findBINForInsert(key, ...);
		long position = ln.log(key, ...);
		bin.updateEntry(ln, position, key);
		bin.clearKnownDeleted();
		hook(Level.FINER, bin, ln, position);
		//...
	}
	void hook(Level l, BottomNode b, LeafNode l, long p) {
	}
}
public aspect TreeLogging {
	before(Level l, BottomNode bin, LeafNode ln, long pos): 
		execution(void Tree.hook(...)) && args(l,bin,ln,pos) {
		traceInsert(l, bin, ln, pos)
	}
}

IIIA Implementation

In the IIIA Implementation we created a join point type for all parameters and created an explicit join point to expose the local variables.
joinpointtype Trace {
	Level logLevel;
}
joinpointtype TraceInsert extends Trace {
	BottomNode bin;
	LeafNode ln;
	long position;
}
public class Tree exhibits TraceInsert {
	public long insert(LeafNode ln, byte[] key , ... ) { //...
		bin.clearKnownDeleted();
		exhibit new TraceInsert(Level.FINER, bin, ln, position) {};
		// ...
	}
}
public aspect TreeLogging advises TraceInsert {
	after(TraceInsert t){
		traceInsert(t.logLevel, t.bin, t.ln, t.position);
	}
	//...
}

Note the empty block: there is no statement to be advised, only a point between two statements. before, after, and around all have the same effect.

Statement Extension Emulation

Employing AspectJ's fine grained join point model, it is possible to advise join points that occur in the middle of a method (call, get, and set join points).

Consider the following Java example from Berkeley DB, where the updateMemorySize call belonging to the MemoryBudget Feature should be extracted into an aspect.

public class IN {
	public int insertEntry1(CR entry) {	//...
		if (nEntries < entryTargets.length) { //...
			<strong>updateMemorySize(0, getInMemorySize(index));
			adjustCursorsForInsert(index); //...
		} 
	}
}

AspectJ Implementation

There are two reasonable AspectJ implementations possible. First we could use a hook method and advise this hook method as in the first example:
public class IN {
	public int insertEntry1(CR entry) {	//...
		if (nEntries < entryTargets.length) { //...
			hook(index);
			adjustCursorsForInsert(index); //...
		} 
	}
	void hook(int index) {}
}
public aspect MemoryBudget {
	before(IN in, int index):
		execution(void IN.hook(int)) && this(in) && args(index)  {
		in.updateMemorySize(0, in.getInMemorySize(index));
	}
}
Second, we can use AspectJ's fine grained join point model to "emulate" a statement extension. Note how this hides the semantics of an aspect and is extremely sensitive to change.
public aspect MemoryBudget {
	before(IN in, int index):
		call(void IN.adjustCursorsForInsert(int)) && this(in) && args(index) && withincode(int IN.insertEntry1(CR)) {
		in.updateMemorySize(0, in.getInMemorySize(index));
	}
}

IIIA Implementation

In the IIIA implementation, we use Explicit Announcement to exhibit a new join point at the exact position.
joinpointtype INMemoryUpdate {
	IN internalNode;
	int index;
}
public class IN exhibits INMemoryUpdate {
	public int insertEntry1(CR entry) { // ...
		if (nEntries < entryTargets.length) { // ...
			exhibit new INMemoryUpdate(this, index) {
				adjustCursorsForInsert(index); 	
			}; // ...
		}
	}
aspect MemoryBudget advises INMemoryUpdate {
	before(INMemoryUpdate m) {
		m.internalNode.updateMemorySize(0, m.internalNode.getInMemorySize(m.index));
	}
}

Extending a Sequence of Statements

In examples as the one before, also annotations can be useful to explicitly announce a hook for a piece of advise. However, this approach is limited in that it cannot advise a sequence of statements as needed in the following example. In this example, we again want to extract the MemoryBudget Feature. Note, how this wraps around a sequence of statements.
public class IN {
	public boolean deleteEntry(int index, boolean maybeValidate)
			throws DatabaseException {

		if (nEntries == 0) {
			return false;
		}

		/* Check the subtree validation only if maybeValidate is true. */
		assert maybeValidate ? validateSubtreeBeforeDelete(index) : true;

		if (index < nEntries) {
			int oldLSNArraySize = computeLsnOverhead();
			/* LSNArray.setElement can mutate to an array of longs. */
			for (int i = index; i < nEntries - 1; i++) {
				setEntryInternal(i + 1, i);
			}
			clearEntry(nEntries - 1);
			updateMemorySize(oldLSNArraySize, computeLsnOverhead());
			nEntries--;
			setDirty(true);
			setProhibitNextDelta();

			/*
			 * Note that we don't have to adjust cursors for delete, since there
			 * should be nothing pointing at this record.
			 */
			traceDelete(Level.FINEST, index);
			return true;
		} else {
			return false;
		}
	}
	//...
}

AspectJ Implementation

In the AspectJ implementation, we again used a hook method and moved the sequence of statements that should be advised into this hook method. This can be performed as an Extract Method refactoring.
public class IN {
	public boolean deleteEntry(int index) {
		if (nEntries == 0 || index >= nEntries)
			return false;

		hookr_deleteEntryInternal(index);
		nEntries--;
		setDirty(true);
		return true;
	public class IN {}

	private void hookr_deleteEntryInternal(int index) {
		for (int i = index; i < nEntries - 1; i++) 
			setEntryInternal(i + 1, i);
		clearEntry(nEntries - 1);
	}
	//...
}
aspect MemoryBudget {
	void around(IN in, int index) :  execution(void IN.hookr_deleteEntryInternal(int)) && this(in) && args(index) {
		in.updateMemorySize(in.getEntryInMemorySize(index), 0);
		int oldSize = in.computeLsnOverhead();
		proceed(in);
		in.changeMemorySize(in.computeLsnOverhead() - oldSize);
	}
}

IIIA Implementation

Using Explicit Announcement, we have a more elegant version, in that we just announce a sequence of statements as new join point.
joinpointtype OverheadSizeChanged {
	IN in;
	int index;
}
public class IN exhibits OverheadSizeChanged {

	public boolean deleteEntry(int index) {
		if (nEntries == 0 || index >= nEntries)
			return false;

		exhibit new OverheadSizeChanged(this, index) {
			for (int i = index; i < nEntries - 1; i++) 
				setEntryInternal(i + 1, i);
			clearEntry(nEntries - 1);
		};
		nEntries--;
		setDirty(true);
		return true;
	}
}
aspect MemoryBudget advises OverheadSizeChanged{
	void around(OverheadSizeChanged o){
		o.in.updateMemorySize(in.getEntryInMemorySize(o.index), 0);
		int oldSize = o.in.computeLsnOverhead();
		proceed();
		o.in.changeMemorySize(o.in.computeLsnOverhead() - oldSize);
	}
}

Back to main page