Changing Java 8 – handling nulls with the AST

by
Tags: , , ,
Category:

One aspect of Java 8 that would be nice to change is the handling of nulls. Can testName.getName() be changed so that a NullPointerException is never thrown, even if testName is null? Is it possible to modify java to support this directly? Java does provide an interface to read the Abstract Syntax Tree (AST) in javax.annotation.processing.Processor and a base class in javax.annotation.processing.AbstractProcessor. Let’s see what is required to change the AST.

First an annotation is helpful (although not required).

@Retention(RetentionPolicy.CLASS)
@Target({ElementType.LOCAL_VARIABLE})
public @interface Change {
}

The processing is handled in rounds. An environment for the round is passed in as a parameter.

@Override
public boolean process(
    Set<? extends TypeElement> annotations,
    RoundEnvironment roundEnvironment)

Annotations are empty due to a design issue with the annotation processing. The annotation processing can only “see” annotations at the method level or higher. So local variable annotations aren’t accessible this way. Fortunately using com.sun.source.util.Trees these can be accessed.

Set<? extends Element> elements
    = roundEnvironment.getRootElements();
elements.forEach(element -> {
  JCTree tree = (JCTree) trees.getTree(element);
  tree.accept(visitor);
});

The visitor needs to extend com.sun.tools.javac.tree.TreeTranslator

public class ChangeTranslator extends TreeTranslator

Access to variable declarations is needed so

@Override
public void visitVarDef(
    JCTree.JCVariableDecl variableDeclaration) {
  // result is placed into the AST
  // replacing the current variable declaration
  result = createStatement.apply(variableDeclaration);
}

And in the CreateStatement helper class

public JCTree.JCVariableDecl apply(
    JCTree.JCVariableDecl variableDeclaration) {
  // For this simple example, change the type to a String.
  JCTree.JCExpression stringType = treeMaker.Ident(
      getElement.apply(String.class));
  // Use the same variable name.
  Name variableName = variableDeclaration.getName();
  // Use a String literal.
  JCTree.JCLiteral changedValue = treeMaker.Literal("Changed");
  // Remove the modifiers.
  JCTree.JCModifiers modifiers = treeMaker.Modifiers(0);
  // Create the new variable declaration.
  JCTree.JCVariableDecl newVariableDeclaration =
    treeMaker.VarDef(modifiers, variableName,
                     stringType, changedValue);
  return newVariableDeclaration;
}

For the processor to work

<proc>none</proc>

should be specified in the pom
and there needs to be a META-INF/services/javax.annotation.processing.Processor file in the resources directory with one line

com.chariotsolutions.jshepard.annotation.processing.Processor

So there it is, a simple modification to the AST, using lots of com.sun classes.

With more time, this could probably be expanded into a useful framework, but would probably stop working with Java 9. So for now maybe using Optional.ofNullable is a reasonable way to go.
For example:

String name = Optional
    .ofNullable(testName)
    .map(a -> a.getName())
    .orElse(null);

instead of

String name = testName.getName();

Or maybe not.

The full source is available on github.