I’m trying to write a small internal DSL in Java which generates an tree of objects. The DSL code looks like this:
RuleBuilder builder = new RuleBuilder(new Syntax());
Syntax s =
builder.rule("rule1")
.identifier("foo")
.choice()
.terminal("bar")
.end() // 1) Here it works.
.end() // 2) Here complains the compiler.
.rule("rule2")
.identifier("bar")
.end()
.build();
The compiler complains (at 2) that the object java.lang.Object returned by end() does not have a method rule(). It’s obvious to me that Object does not have this method. The builder code is as follows (The code assembling the tree is left for simplicity):
class RuleBuilder {
private final Syntax syntax;
public RuleBuilder(Syntax syntax) {
this.syntax = syntax;
}
public GenericBuilder<RuleBuilder> rule(String name) {
return new GenericBuilder<RuleBuilder>(this);
}
public Syntax build() { return syntax; }
}
class GenericBuilder<P> {
private final P parentBuilder;
public GenericBuilder(P parentBuilder) {
this.parentBuilder = parentBuilder;
}
public <P> P end() {
return (P)parentBuilder;
}
public GenericBuilder<P> identifier(String value) { return this; }
public GenericBuilder<P> terminal(String value) { return this; }
public GenericBuilder<GenericBuilder> choice() { return new GenericBuilder<GenericBuilder>(this); }
// ... other sub node types
}
The principal idea behind my implementation: The generated syntax tree consists of a Syntax root node which has some Rule nodes. This rule nodes can have some sub nodes. The sub nodes are of two types: Leafs and nodes (in the example code only choice for simplicity). I will provide a fluent interface where I can add nodes and leaves to the tree. To ‘end’ a tree branch there is the end() method which returns the parent builder.
The issue I try to solve is, that the method end() should return the parent builder object which may be of type RuleBuilder or GenericBuilder. I do not understand why it works at 1) and not at 2) in the example above.
I’ve read through a lot of resources about generics and I understand how List<T> and Map<K,V> and such works. And I’m aware of the “Erasure” thing, that the type information is lost at runtime. So I can understand that at runtime end() returns java.lang.Object, when type information is erased. But I got a compile time error. I’ve also read through the blog of Neal Gafter about Super Type Tokens and Typesafe Heterogenous Containers. But I’m not sure if this solves my problem. I tried several different approaches (during my search in the web), but now I’m stuck.
There are two problems. First there’s this:
That’s not saying what kind of
GenericBuilderthe “inner” one is. You should be able to change it to:At that point, the inner
end()call will still know it’s got aGenericBuilder<RuleBuilder>instead of a rawGenericBuilder.The second problem is here:
That’s a generic method when it shouldn’t be – you don’t want to introduce a new type parameter
Phere; you just want the existing one: