An embedded Java DSL for manipulating hierarchical JavaBeansApril 26th, 2008 | by Jevgeni Kabanov | |
Just yesterday I was thinking on how to improve the DSLs I’m working on at the moment and got an idea, which wasn’t too useful in the context of SQL, but could be used for something else.
Imagine we have three JavaBeans (I’m omitting getters/setters for readability):
It’s a straightforward task to build a DSL for manipulating this data. It could look something like that:
-
Company c = new CompanyBuilder()
-
.name(“ZeroTurnaround”)
-
.address()
-
.country(“Estonia”)
-
.finish()
-
.addEmployee()
-
.firstName(“Jevgeni”)
-
.lastName(“Kabanov”)
-
.address()
-
.country(“Estonia”)
-
.finish()
-
.finish()
-
.toCompany();
The trick is that the AddressBuilder hiding behind the address() call is used in two contexts — as an employee address and as a company address. In a large domain such situations are very common and we would like the corresponding builders to be reused.
What we can do is make every builder keep in mind it’s parent context like this:
-
public class EmployeeBuilder<PARENT extends Builder>
-
implements Builder {
-
PARENT parent;
-
-
public EmployeeBuilder(Employee e, PARENT parent) {
-
this.parent = parent;
-
}
-
-
//Field setters …
-
-
public AddressBuilder<EmployeeBuilder<PARENT>>
-
addAddress() {
-
return
-
new AddressBuilder<EmployeeBuilder<PARENT>>(this);
-
}
-
-
public PARENT finish() {
-
return parent;
-
}
-
}
-
-
public class AddressBuilder<PARENT extends Builder>
-
implements Builder {
-
PARENT parent;
-
-
public AddressBuilder(Address a, PARENT parent) {
-
this.parent = parent;
-
}
-
-
//Field setters …
-
-
public PARENT finish() {
-
return parent;
-
}
-
}
This way the generic type becomes nested and holds the whole path to the root of the hierarchy in a typesafe way. Of course we also need to return the actual value independently of the parent, but it’s easy to do by passing a corresponding closure from the parent and calling it in the finish:
-
public class AddressBuilder<PARENT extends Builder>
-
implements Builder {
-
//…
-
-
interface Closure {
-
void address(Address address);
-
}
-
-
public PARENT finish() {
-
addressClosure.address(address);
-
return parent;
-
}
-
}
This trick can be reused for any kind of hierarchical composition — JavaBeans, XML and so on. For XML you could generate the DSL from the Schema, which is a relatively painful process. However for JavaBeans you can generate a builder per bean in a very straightforward way from the reflection information. Such a builder could be used for modifying JavaBeans as well as for constructing them.
It would most probably be simplest to output the bytecode directly and generate .class files or JAR for this DSL, which makes it an ideal test case for the typesafe bytecode engineering DSL library I’m working on :) Most likely I’ll implement this language as an example of using typesafe bytecode engineering, so if you’re interested keep an eye on the blog.