Over a million developers have joined DZone.

Dynamic visitor builder with closures

· Java Zone

Learn more about Kotlin, a new programming language designed to solve problems that software developers face every day brought to you in partnership with JetBrains.

In my previous post I was fumbling with how to leverage closures to improve the visitor pattern. Neal suggested that I could leverage closures in building the visitor rather than executing it.

So, here's a hack at doing that. The Visitor part is all standard. [I'm taking the easy route of encoding navigation in the data structure and yes I know there are better ways but this is easier to show.]

public interface Visitable {  void accept(Visitor visitor);  }
public interface Node extends Visitable {}

public class ConcreteNode implements Node {
public void accept(Visitor visitor) { visitor.visit(this); }
}

public class CompositeNode implements Node {
public void accept(Visitor visitor) {
visitor.visit(this);
for(Node node : nodes) {
node.accept(visitor);
}
}
}

public interface Visitor {
void visit(ConcreteNode node);
void visit(CompositeNode node);
}

We then can create a VisitorBuilder which is just a builder to create DynamicVisitor instances:

public class VisitorBuilder {
public static VisitorBuilder visitor() {
return new VisitorBuilder();
}

private DynamicVisitor visitor = new DynamicVisitor();

public VisitorBuilder handleConcreteNode({ConcreteNode=>void} block) {
visitor.addConcreteNode(block);
return this;
}

public VisitorBuilder handleCompositeNode({CompositeNode=>void} block) {
visitor.addCompositeNode(block);
return this;
}

public Visitor build() {
return this.visitor;
}
}

public class DynamicVisitor implements Visitor {
private {ConcreteNode=>void} concreteNodeBlock;
private {CompositeNode=>void} compositeNodeBlock;

public void addConcreteNode({ConcreteNode=>void} block) {
this.concreteNodeBlock = block;
}

public void addCompositeNode({CompositeNode=>void} block) {
this.compositeNodeBlock = block;
}

public void visit(ConcreteNode node) {
if(concreteNodeBlock != null) {
concreteNodeBlock.invoke(node);
}
}

public void visit(CompositeNode node) {
if(compositeNodeBlock != null) {
compositeNodeBlock.invoke(node);
}
}
}

You then can dynamically assemble a visitor from blocks by doing something like:

Visitor visitor =
VisitorBuilder.visitor()
.handleConcreteNode({ConcreteNode node=>
System.out.println("Visiting ConcreteNode");
})
.handleCompositeNode({CompositeNode node=>
System.out.println("Visiting CompositeNode");
})
.build();

Node root = ...
root.acceptVisitor(visitor);

You'll note that this kind of sucks in the naming of the handle and add methods. Due to type erasure, all of the blocks passed to these methods have the same type signature so I can't overload the handle() and add() methods. I also don't know of any reflective interface on the block that would let me use reflection to discover the parameter type of the closure (also missing due to erasure I would think).

One alternative would be to have a single handle() method on the builder that took a class as an extra parameter and then encoded a big gross if/else for all possible types within the handle method. In the fluent style you could then have something like:

Visitor visitor =
VisitorBuilder.visitor()
.on(ConcreteNode.class).do({ConcreteNode node=>
System.out.println("Visiting ConcreteNode");
})
.on(CompositeNode.class).do({CompositeNode node=>
System.out.println("Visiting CompositeNode");
})
.build();

There are probably some interesting extensions to this that are model-specific. If you had many nodes that shared some common attribute, you could have methods on the builder that could apply the closure to a set of node types in one call. That sort of thing could let you build some kinds of visitors in a lot less code.

Another nice consequence is that since the closures can reach the local state during build time, it is easy to have the closures update local variables, such as by adding to a collection. I believe there are also some nice improvements for the cases of return values and exception handling. I'll play with those next.

The Java Zone is brought to you in partnership with JetBrains.  Discover how powerful static code analysis and ergonomic design make development not only productive but also an enjoyable experience.

Topics:

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}