Overview

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.

Reading

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.

Writing

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.