The Artima Developer Community
Sponsored Link

Indeterminate Heuristics
Implementing the Null Object Pattern using AOP
by Dale Asberry
April 12, 2004
Summary
The Handy-Dandy Null Object Pattern Looked at from the AOP Perspective

Advertisement

Overview

In January 2003, I started writing a newsletter article titled "Aspect Oriented Programming: Using Aspects to Implement Design Patterns". When I found that many patterns had already been implemented and written up, I decided to scrap the article. However, I didn't find a Null Object pattern (NOPat) and decided that implementing it would be good practice.

While writing an earlier article Aspect Oriented Programming (AOP): Using AspectJ to implement and enforce coding standards, I was thinking about an anti-pattern that was annoying me at the time: null method return values and parameters. I was feeling some pain tracking down NPE's in untested code. I felt the NOPat could be leveraged to help me to at least find where the null assignments were being made. Since I didn't own, or in some cases, have access to the source, I felt that AOP could help me implement the NOPat.

The Implementation

The first thing I realized (partly through trial-and-error) was that I needed pointcuts that matched both null assignments to class member variables and null return value assignments to any variables. Both types of pointcuts are needed because matching on assignments only works for class member variables and not method variables. On the other hand, matching on calls to methods with return values misses direct variable assignments such as "a = b" or "a = null".

Let's take a look at the pointcut to match assignments:

  pointcut nullMemberAssignments(Object pSetValue):
      set(* *.*) && args(pSetValue) &&
      !within(test.NullObjectAspect) && !within(test.NullProxy);
The set joinpoint matches all assignment statements in the code that is to be aspected. The args joinpoint matches, and binds, the value of the object being assigned. The !within joinpoint is following a typical AOP idiom -- all aspected(1) code but not the aspect (and related instrumentation).

Next, we'll take a look at the pointcut to match assignments occurring due to method calls:

  pointcut callsThatReturnAnObject(Object pCalledObject):
      call(* *.*(..)) && target(pCalledObject) &&
      !within(test.NullObjectAspect) && !within(test.NullProxy);
The call joinpoint matches all method calls that return a value(2). The target joinpoint matches and binds the object of the method being invoked.

Now, let's look at the "handler" code:

As I was thinking about how to implement the handler, I realized that I needed some way to differentiate Null Object instances(3). Each instance reflects the stack trace of where the null assignment was originally made. HashMap immediately popped in my head -- using a HashMap is another useful idiom for maintaining aspect state between advice executions. Since each Null Object instance would have a unique hashcode, I realized, "problem solved!"

  Object around(Object pCalledObject): callsThatReturnAnObject(pCalledObject)
  {
         ...
       //Is the program trying to invoke a method call on a Null Object instance?
       if(cNullObjectMap.containsKey(pCalledObject))
       {
            NullPointerException lNpe = new NullPointerException();
              ... 
            lNpe.setStackTrace(lNewStackTrace);
            lNpe.initCause((NullPointerException)cNullObjectMap.get(pCalledObject));
            cNullObjectMap.remove(pCalledObject);
            throw lNpe;
       }
       lRetVal = proceed(pCalledObject);
       //Did the method return null?
       if(lRetVal == null)
       {
            NullPointerException lE = new NullPointerException();
              ...
            lRetVal = lErrorMethod.getReturnType().newInstance();
              ...
            cNullObjectMap.put(lRetVal, lE);
       }
         ...
  }

  Object around(Object pSetValue): nullMemberAssignments(pSetValue)
  {
         ...
       if(pSetValue == null)
       {
            NullPointerException lE = new NullPointerException();
              ...
            lRetVal = lErrorField.getFieldType().newInstance();
              ...
            cNullObjectMap.put(lRetVal, lE);
       }
       return proceed(lRetVal);
}
The first problem I ran into was an unexpected and unhandled NPE happening in the aspect's advice. Creating a new object using Class.newInstance() was returning a null. But why?... because the "class" was an interface! Ouch - no apparent solution. I put the project away for a couple days and was happily distracted by fatherhood. When I came back, I realized almost immediately that Java 1.3's dynamic proxy fits the problem perfectly.

I didn't really have any problems at this stage, however, I didn't like the aspectj instrumented code showing up in the stack traces since it could be confusing to a non-aspect programmer or end-user. After a little code refactoring, everything was in place(4)!

Here's the sample output from test.Main:
java.lang.NullPointerException
	at test.Main.main(Main.java:33)
Caused by: java.lang.NullPointerException: Null return value from test.Main.nullStringTest(Main.java:32)
	at test.Main.main(Main.java:32)

java.lang.NullPointerException
	at test.Main.main(Main.java:43)
Caused by: java.lang.NullPointerException: Null return value from test.Main.nullArrayListTest(Main.java:42)
	at test.Main.main(Main.java:42)

java.lang.NullPointerException
	at test.Main.main(Main.java:53)
Caused by: java.lang.NullPointerException: Null value assigned to field test.Main.cList(Main.java:22)
	at test.Main.(Main.java:22)
	at test.Main.main(Main.java:28)
java.lang.NullPointerException
	at test.Main.main(Main.java:63)
Caused by: java.lang.NullPointerException: Null return value from test.Main.nullArrayListTest(Main.java:62)
	at test.Main.main(Main.java:62)
Exception in thread "main" 

Future Enhancements

I have a couple of theories about some potential bugs that may need to be fixed. Write test and code to verify that:

Notes

1. compiled by the aspectj compiler ajc.
2. those methods that return "void" have a different signature and is irrelevant since the compiler won't allow assigning void to an object.
3. I worked the problem backwards... I knew what the solution would sort of look like, but the direction of where to start was too fuzzy.
4. although I didn't mention it directly, I maintained a simple testing class rather than full-on JUnit for simple TDD.

Resources

Source Code

test.Main:
http://daleasberry.com/code_library/aspectj/project_nullobject/test/Main.java

test.NullObjectAspect:
http://daleasberry.com/code_library/aspectj/project_nullobject/test/NullObjectAspect.java

test.NullProxy:
http://daleasberry.com/code_library/aspectj/project_nullobject/test/NullProxy.java

Convenient JAR of all the above with everything compiled:
http://daleasberry.com/code_library/aspectj/project_nullobject/nullobject.jar

Definitions

Joinpoint:
http://dev.eclipse.org/viewcvs/indextech.cgi/~checkout~/aspectj-home/doc/progguide/apb.html#joinPoints

Pointcut:
http://dev.eclipse.org/viewcvs/indextech.cgi/~checkout~/aspectj-home/doc/progguide/apbs02.html

Talk Back!

Have an opinion? Be the first to post a comment about this weblog entry.

RSS Feed

If you'd like to be notified whenever Dale Asberry adds a new entry to his weblog, subscribe to his RSS feed.

About the Blogger

R. Dale Asberry been hacking since 1978, professionally since 1990. He's certified in Java 1.1 and has a four digit MCP number. He discovered Jini at the 2000 JavaOne and has been building incredibly cool, dynamic, distributed architectures ever since! Over time, he's discovered several principles that have contributed to his success - they are the Princples of: Enabling Others, Simplicity, No Complaining, Least Work, Least Surprise, Least Damage, and "It Just Works".

This weblog entry is Copyright © 2004 Dale Asberry. All rights reserved.

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use