Bean Builder is intended to be a simple utility for creating JavaBeans and collections of JavaBeans from text. In particular, the original use was for building collections of JavaBeans from XML and CSV files.
Bean Builder's use mimics that of SAX in that it supports simple events:
While Bean Builder can be used programmatically, its intended use is via a file parser such as those in the
net.sealisland.beanbuilder.csv
and
net.sealisland.beanbuilder.sax
packages.
This library arose from the need to load JavaBeans into memory for a mock DAO implementation (used in unit testing). For homogeneous data like this CSV was the preferred format and I just wanted a few lines of code in my mock DAO to load the file - minimal configuration.
While there are several excellent XML and JavaBean frameworks such as XStream and Commons Digester, I struggled to find one that would load CSV files. No doubt I could have written a CSV parser that generated SAX events to adapt to an XML parser but that would mean more configuration - I just wanted to get the unit testing over with! I also got annoyed by those that used direct field manipulation (ie, bypassing setter methods); aside from the blatent violation of encapsulation this prevents any logic or validation built into these methods being applied during loading.
This library isn't intended to replace anything, cover all bases or be particularly fast. It's simply meant to be a useful way of loading beans from files - if it provides utility elsewhere then that's a bonus!
Bean Builder uses the following concepts:
Collection
interface. Note that
Map
is not a collection and is therefore not supported.
java.lang.Stringjava.lang.Stringjava.text.Format
which converts from a text value to a type.
Bean Builder works by building a tree of nodes (JavaBean/Collection/Type instances) using push, pop and apply events.
A push event creates a new node on the tree:
beanBuilder.push("name");
. The node that is created is based on the name used for the push event according to the following rules:
To create a new
JButton
we need to do the following:
The following code implements these steps:
BeanBuilder beanBuilder = new BeanBuilder();
beanBuilder.registerType("button", JButton.class);
beanBuilder.push("button");
beanBuilder.pop("button");
JButton button = (JButton) beanBuilder.root();
This may seem like a lot of work just to create a new JButton. Things get a little smoother when we start setting properties and chaining methods:
BeanBuilder beanBuilder = new BeanBuilder();
beanBuilder.registerType("button", JButton.class);
beanBuilder.push("button");
// Set the text property of the button
beanBuilder.push("text").apply("This is the button text").pop("text");
// Set other properties of the button
beanBuilder.push("borderPainted").apply("false").pop("borderPainted");
beanBuilder.push("contentAreaFilled").apply("false").pop("contentAreaFilled");
beanBuilder.pop("button");
JButton button = (JButton) beanBuilder.root();
If a JavaBean property is another JavaBean there are several ways of accessing it. For example, consider the following beans:
public class User {
private Status status;
public void setStatus(Status status) {
this.status = status;
}
public Status getStatus() {
return status;
}
}
public class Status {
private String code;
private Date modificationDate;
// setters and getters...
}
If we wanted to set the status for a user we could do the following:
beanBuilder.registerType("user", User.class");
beanBuilder.push("user");
beanBuilder.push("status");
beanBuilder.push("code").apply("active").pop("code");
beanBuilder.push("modificationDate").apply("01-01-1970").pop("modificationDate");
beanBuilder.pop("status");
beanBuilder.pop("user");
Note that we don't need to specify an alias for status. It is the name of a property of User - aliases are only needed when no matching property exists.
It is important to note here that Bean Builder will only create a new instance for a JavaBean property if it needs to. Therefore we can also push a new status property as follows - a Status instance will only be created on the first push/pop of that property:
beanBuilder.registerType("user", User.class");
beanBuilder.push("user");
beanBuilder.push("status").push("code").apply("active").pop("code").pop("status");
beanBuilder.push("status").push("modificationDate").apply("01-01-1970").pop("modificationDate").pop("status");
beanBuilder.pop("user");
This may not seem very useful (or logical). It relates to the desire to load beans from CSV files where column names might be "status.code" and "status.modificationDate" - we don't want to create a new Status for each column, only when necessary.
Collection types can also be registered and created. Pushing onto a collection adds an entry rather than setting a property (as it would for a JavaBean node):
BeanBuilder beanBuilder = new BeanBuilder();
beanBuilder.registerType("button", JButton.class);
// Start a list
beanBuilder.push("list");
// Add two buttons to the list
beanBuilder.push("button").pop("button");
beanBuilder.push("button").pop("button");
// Finish the list
beanBuilder.pop("list");
List list = (List) beanBuilder.root();
If a JavaBean property isn't of String type a
java.text.Format
can be specified to handle the conversion from the applied text to the appropriate datatype:
public class MyBean {
...
public void setCreationDate(Date creationDate) {
this.creationDate = creationDate;
}
...
}
BeanBuilder beanBuilder = new BeanBuilder();
beanBuilder.registerType("mybean", MyBean.class);
beanBuilder.registerFormat(Date.class, new SimpleDateFormat("dd-MM-yyyy"));
beanBuilder.push("list");
// Create a new MyBean and set its creation date property
beanBuilder.push("mybean").push("creationDate").apply("01-02-1980").pop("creationDate").pop("mybean");
beanBuilder.pop("list");
List list = (List)beanBuilder.root();
The following formats are registered by default:
new Integer(String)
)
new Long(String)
)
new Float(String)
)
new Double(String)
)
new BigDecimal(String)
)
Even non-JavaBean values can be created by registering an alias for the type. If a format is registered for that type then it will be used to create the node when a value is applied:
BeanBuilder beanBuilder = new BeanBuilder();
beanBuilder.registerType("date", Date.class):
beanBuilder.registerFormat(Date.class, new
SimpleDateFormat("dd-MM-yyyy");
// Create a list
beanBuilder.push("list");
// Push two dates onto the list
beanBuilder.push("date").apply("31-12-2004").pop("date");
beanBuilder.push("date").apply("01-01-1970").pop("date");
// Finish a list beanBuilder.pop("list");
List list = (List)beanBuilder.root();