The Artima Developer Community
Sponsored Link

Weblogs Forum
Implementing the Null Object Pattern using AOP

0 replies on 1 page.

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 0 replies on 1 page
Dale Asberry

Posts: 161
Nickname: bozomind
Registered: Mar, 2004

Implementing the Null Object Pattern using AOP (View in Weblogs)
Posted: Apr 12, 2004 4:00 AM
Reply to this message Reply
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:
  • ... Null Object references are not being GC'd if the variable originally containing it is assigned a real object.
  • ... if a variable has multiple interfaces in it's class definition and if it is assigned a Null Proxy and is dereferenced as one of the other interfaces then it will throw a ClassCastException.

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

Topic: Stunting a Framework Previous Topic   Next Topic Topic: Jini and Web Services: Judy Project Overview

Sponsored Links



Google
  Web Artima.com   

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