FlatTree is a Java library for reading and writing of flat files: CSV, FLR (fixed length record) or mixed structures. It features a tree-style processing API, adapters for SAX, Stax and XStream for transformation, data binding or serialization.
Note: Throughout this documentation we're using the term flat file as a synonym for data in flat file structures, regardless if it is actually contained in a file. Of course FlatTree allows reading from and writing to any stream based data.
Contrary to structured file formats like XML, flat files carry minimal structure information only. A human may be able to guess the meaning of delimiters (commas, semicolons) or whitespace (widths, paddings), but there's no way for a program to reliably derive the complete structure of a flat file.
Let's take a look on the following simple CSV example:
Version;Release date;Codename JDK 1.0;January 23, 1996;OAK JDK 1.1;February 19, 1997; J2SE 1.2;December 8, 1998;Playground J2SE 1.3;May 8, 2000;Kestrel J2SE 1.4;February 6, 2002;Merlin J2SE 5.0;September 30, 2004;Tiger Java SE 6;December 11, 2006;Mustang Java SE 7;;Dolphin
The data is preceeded by a header, rows are delimited by new-lines, columns are delimited by semicolons. To read or write this file, you have to define its implicit structure first:
Node root = new DelimitedNode("Header")
.add(
new DelimitedNode("Row")
.add(
new DelimitedLeaf("Version", ';')
).add(
new DelimitedLeaf("Release date", ';')
).add(
new DelimitedLeaf("Codename", ';')
)
);
We'll learn more about nodes and leaves and how to define such a structure with a configuration file later.
Now we can create a flattree.TreeReader using the defined tree structure:
public TreeReader createTreeReader(Reader reader) {
return new TreeReader(root, new FlatReader(reader));
}
Reading the tree is done similar to other tree-based APIs with flattree.TreeReader#read():
public void read(TreeReader treeReader) {
assertEquals(TreeReader.START, reader.read());
assertEquals("Header", treeReader.getName());
while (treeReader.read() == TreeReader.START) {
assertEquals("Row", treeReader.getName());
String version = treeReader.getValues().get("Version");
String releaseDate = treeReader.getValues().get("Release date");
String codename = treeReader.getValues().get("Codename");
assertEquals(TreeReader.END, treeReader.read()); {
}
}
The root node is read exactly once, all its decendents are read according to the available lines in the flat file. Note that this is a rather 'flat tree' (no pun indented), see Hierarchical structures for an example on how to traverse a deep tree.
To write a flat file with the same structure we use a flattree.TreeWriter:
public TreeWriter createTreeWriter(Writer writer) {
return new TreeWriter(root, new FlatWriter(writer));
}
Writing is done once again in a tree-like fashion with with flattree.TreeWriter#writeStart() and flattree.TreeWriter#writeEnd():
public void write(TreeWriter treeWriter) {
treeWriter.writeStart("Header", Collections.emptyMap());
while (...) {
Map<String,String> values = new HashMap<String,String>();
values.put("Version", ...);
values.put("Release date", ...);
values.put("Codename", ...);
treeWriter.writeStart("Row", values);
treeWriter.writeEnd();
}
treeWriter.writeEnd();
}
The first written start has to match the name of the configured root node, all other nodes have to be valid children of the currently written parent node.