Skip to content
alexradzin edited this page Jul 1, 2012 · 15 revisions

To understand what does SafeChain do and what was the motivation to implement it please start from reading introduction.

SafeChain: the smart reference implementation for Java

Data model often separated to many simple classes that follow bean naming convention and have references to each other. Often we chain calls of getters as following: person.getCompany().getPresident().getSecretary().getFirstName(). Very often we need the phone of secretary of president of company where the person works or nothing. Obviously that this line will throw NullPointerException if person is unemployed, company does not have president or president does not have secretary. But we do not care. We have to write a series of if statements to be null safe.

This is where SafeChain can help. It wraps object using dynamic proxy that cares about null safety along the chain. If one of the method in chain returns null the proxy remembers this fact and finally returns null when the chain ends:

$(person).getCompany().getPresident().getSecretary().getFirstName()

where $ is a static method defined in class NullSafeUtil that can be imported statically as following:

import static com.safechain.NullSafeUtil.$;

Key features

  • wrap object to prevent NPE during methods chain execution
  • Prevents NoSuchElementException when calling Iterator.next() or Enumeration.nextElement()
  • Two API layers: non-static NullSafeChain and static utility NullSafeUtil
  • NullSafeUtil has 2 types of static methods: long named nullSafe(), nullSafeArray() etc. and short aliases $(), $arr() etc.
  • Uses Javassist for dynamic proxies generation.

Typical usage

It is recommended to use static wrapper NullSafeUtil. First import is statically:

import static com.safechain.NullSafeUtil.$;

Then use it as following:

Person p = new Person();
String spouseFirstName = $(p).getSpouse().getFirstName();

Static wrapper $() is defined in NullSafeUtil. It creates dynamic proxy that extends the original class Person but and wraps the object p. All its method call the original method of class Person and wrap the returned value again unless the execution chain is finished. In our example $(p).getSpouse() will return wrapped object while getFirstName() will return unwrapped object (java.util.String in this case). It means that the existing code should never be broken. Even if you check the class later:

if (String.class.equals(spouseFirstName.getClass())) {
    // do something
}

this will continue working. But the $(p).getSpouse().getFirstName() will not throw NullPointerException even if the person is single. The spouse name will be null in this case. Without using SafeChain library programmer must write code like the following to be null-safe:

Person p = new Person();
Person spouse = p.getSpouse();
String spouseName = spouse == null ? null : spouse.getFirstName();

Well, in this very simple case the "simple" code is not too long. But what if we have to get the phone number of secretary of CTO of company where the person's spouse works?

Here is the optimistic code:

String phone = p.getSpouse().getCompany().getCTO().getSecretary().getPhone();

This code will throw NullPointerException if person is single, if spouse is unemployed, if spouse' company does not have CTO position and if CTO does not have personal secretary. Here is the code that we have write to be null-safe.

String phone = null;
Person spouse = p.getSpouse();
if (spouse != null) {
    Company company = spouse.getCompany();
    if (company != null) {
        Manager cto = company.getCTO();
        if (cto != null) {
            Employee secretary = cto.getSecretary();
            if (secretary != null) {
                phone = secretary.getPhone();
            }
        } 
    }
}

This and other similar code snippets must be written, debugged and maintained.

SafeChain saves your time. Just write

String phone = $(p).getSpouse().getCompany().getCTO().getSecretary().getPhone();

and enjoy null-safe code that just returns null unless all getters in chain return not-null values.

Collections and arrays

Let's say our person has children. We can access the first child using the following call: person.getChildren().iterator().next().getName();

If getChildren() returns empty collection NoSuchElementException will be thrown here. Safe chain helps here too:

$(person).getChildren().iterator().next().getName();

will just return null if person does not have children.

Arrays support is more complicated. Java does not support operators overloading, so we cannot add our functionality to [] operator. We have to call special method to access arrays elements safely. SafeChain provides such method $arr().

Use $arr($(person).getChildren(), 0) instead of person.getChildren()[0] and forget about ArrayIndexOutOfBoundsException.

Unconventional names

One can wonder why the methods named against the usual Java convention. Indeed the name $() is not self explainable and does not start with small letter of English ABC. The reason is that author thinks that SafeChain is a very special library. It actually does not do anything, e.g. does not add any business logic to your code. Therefore it should be almost "invisible" itself. Author liked very much the jQuery's magic $() function and decided to bring this name from JavaScript to Java.

But if you cannot stand this you are welcome to use methods that follow java naming conventions: nullSafe() instead of $(), nullSafeArray() instead of $arr() etc.

Entry point to the API

There are 2 entry points that allow to use this library.

(1) The higher level and the recommended entry point is already mentioned NullSafeUtil that contains a set of static methods. Typically user should import this class' methods statically and use them:

import static com.safechain.NullSafeUtil.$;
.............................................
String spouseFirstName = $(p).getSpouse().getFirstName();
String elderChildFirstName = $(p).getChildren().iterator().next().getFirstName();

People that dislike the $-based short names can use the conventionally named methods:

String spouseFirstName = nullsafe(p).getSpouse().getFirstName();

(2) NullSafeUtil is just a thin wrapper over class NullSafeChain that provides non-static versions of same methods.

import com.safechain.NullSafeChain;
.............................................
NullSafeChain chain = new NullSafeChain();
.............................................
String spouseFirstName = chain.nullsafe(p).getSpouse().getFirstName();
String elderChildFirstName = chain.nullsafe(p).getChildren().iterator().next().getFirstName();

IMHO this just makes the code longer, so first way is recommended.

Custom factory method

One can wish to create wrapped objects using custom factory method:

public Person personFactory() {
    return $(new Person());
}

or just wrap already created object using wrapper method:

public <T> T wrap(T o) {
    return $(o);
}

To make this working correctly with SafeChain framework such methods must be marked using special annotation @SafeChainWrapper:

@SafeChainWrapper 
public <T> T wrap(T o) {
    return $(o);
}

The reasons are described in chapter "How does it work?"

How does it work?

This chapter explains the implementation gory details. It is for curious people only.

Java does not have neither smart pointers (or references) nor operators overloading. Code obj.foo() calls method foo() on object obj. There is no way to intercept the code. To intercept this call we have to either use byte code engineering or wrap object obj. SafeChain creates wrappers over objects. These wrappers make chain null-safe.

There are several ways to create wrapper class.

  • Write wrapper manually. This requires a lot of initial work and maintenance.
  • Using dynamic proxy. Standard dynamic proxies provided by JDK can be created from interfaces only. Since typical data model objects do not implement interfaces this is a serious limitation.
  • Fortunately bytecode engineering libraries like Javassist, CGLIB, ASM allow creating dynamic proxies based on concrete classes. This way was chosen here.

SafeChain's entry methods $() or nullsafe() generate and returns dynamic proxy that wraps given object. All getters of this dynamic proxy return proxy of next object in chain or unwrapped object when chain is finished. By other words the code nullsafe(person).getSpouse().getFirstName(); creates proxy of person that creates yet another proxy for spouse and then returns regular String that contains the first name of the spouse. The proxy cares not to throw NullPointerException if person is single. The fact that the last getter in chain returns unwrapped object guarantees that even in later code checks the value type like:

Person secretary = `$(person).getCompany().getPresident().getSecretary();`
.................
if (Person.class.equals(secretary.getClass())) {
    ........
}

This will not be broken since secretary is a "real" Person but not any kind of dynamically created subclass like $$_javassist_Person. This feature is achieved by code processing implemented by SafeChain using Javassist library. SafeChain "knows" which object should be wrapped and which one should be returned as-is because the call chain is finished.

SafeChain provides API that is used by application programmer and parses code of application itself to detect where the chain ends. The byte code parser must know which part of code to parse, e.g. where the wrapped call chain nullsafe(p).get...() is. This is done as following. Method nullsafe() creates exception and retrieves the line number from its stacktrace. This number is passed to module that parses byte code. The module finds the start of chain into given line and then looks for the end of the chain. This means that the code must be compiled with debug information. Otherwise the line number cannot be retrieved from stack trace.

But how to find the start of chain execution? What special is in method nullsafe() so that SafeChain engine detects it as a chain head? This method is marked with special annotation @SafeChainWrapper. This annotation can be applied on class or on specific method of the class. This allows users creating their custom wrappers. One can write method marked using this annotation:

@SafeChainWrapper 
public <T> T wrap(T o) {
    return $(o);
}

Now this method may be used anywhere in the code and will be interpreted by SafeChain as a start of call chain.

Known limitations

  • Does not support final classes. Actually it is the Javassist's limitation.
  • Code compiled without debug information is not supported.

Quick start instructions can be found here.

Clone this wiki locally