Tuesday, November 12, 2013

Null safe dereferencing for java?

Nulls blocking your property chain?

Something smells.  You just want a little data, instead you get that nagging NullPointerException.  Urrrgghh!

Let's say you have a hot date.  And you need to get the address of the hot chick your about to go out with.

i.e.
  pickUpDate(contacts.getHotChick().getAddress());

There is only one problem.  You forgot you're a nerd, and you don't know any hot chicks.

NullPointerException!!!

Try this "Null Safe Get Handling" approach.

Contacts contacts = (Contacts) NullSafeGetHandler.getProxy(myContacts);

Such that:
contacts.getHotChick().getAddress()  will return null, if there as no hot chick to be found.

But if you some how do know a hot chick, (probably your cousin), it will return her address.

Everything is expressed cleanly in a nag-free zone, save for the initial proxy wrapping.

How?

Basically the NullSafeGetHandler implements MethodHandler, an api for catching proxy method invocations.

This process goes like this:
  1. Create proxy of a real object
  2. Tell the proxy to handle all methods with names starting with "get"
  3. On "get" method invocation, return a real value if of a specified type, otherwise return a proxy of the return type.
This way the property chain will not be interrupted by nulls until the desired return type is reached.

Yes, it uses reflection.  Yes, you have to specify a return type.  But when all your doing is coding up a simple DTO or query parameters for a stored procedure for an O(1) operation, it'll do.

In most cases, I just want the first type from the "java.lang" package out of my property chain, so I implemented as so.  But you can customize it to your needs.

 public class NullSafeGetHandler implements MethodHandler {  
      private NullSafeGetHandler(Object source) {  
           super();  
           this.source = source;  
      }  
      /**  
       * Creates a proxy factory that produces proxy objects of which intercepts "get" prefixed methods.  
       *   
       * @param obj  
       * @return null safe proxy object  
       */  
      public static Object getProxy(Object obj) {  
           ProxyFactory factory = new ProxyFactory();  
           factory.setSuperclass(obj.getClass());  
           factory.setFilter(  
            new MethodFilter() {  
                          @Override  
                          public boolean isHandled(java.lang.reflect.Method method) {  
                               return Modifier.isPublic(method.getModifiers()) && method.getName().startsWith("get");  
                          }  
            }  
                );  
           Object proxy = null;  
           try {  
                proxy = factory.create(new Class<?>[]{}, new Object[]{}, new NullSafeGetHandler(obj));  
           } catch (Throwable e) {  
                e.printStackTrace();  
           }  
           return proxy;  
      }  
      /**  
       * Checks if return type is from the java system package. If so the object returned from the calling  
       * method is returned.  
       *   
       * Else, a null safe proxy of the return type is returned;  
       *   
       * (non-Javadoc)  
       * @see javassist.util.proxy.MethodHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.reflect.Method, java.lang.Object[])  
       */  
      public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable {  
           Object returnObject = thisMethod.invoke(source, args);  
           if(thisMethod.getReturnType().getPackage().getName().startsWith("java"))  
                return returnObject;  
           if(returnObject == null)  
                returnObject = thisMethod.getReturnType().newInstance();  
           return getProxy(returnObject);  
      }  
 }