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.