Tables Tutorial
In this tutorial you'll learn how to create simple tables for your reports.
    Each table must be declared inside document's body (a table isn't allowed inner footers or headers), and is defined by a beginTable - endTable block. The number of columns of the table is a priori value, passed to the beginTable call. Optionally, at this call, you can define each column's width. If not defined, for most ReportGenerators, the table will have full page width (the exception to this is TeXReportGenerator, where the table will be with the needed width to have its content, even if greater than the page's width).
    Let's declare a 4 column's table:

reportGenerator.beginTable(4);
reportGenerator.endTable();         
         
    A table is created with the definition of its rows. Rows can be of two row types: one for headers, defined by beginTableHeaderRow() - endTableHeaderRow() blocks, and the other for normal table rows (usually colored with even/odd row colors), defined by beginTableRow() - endTableRow blocks.
    As said above, a header row is declared with a beginTableHeaderRow - endTableHeaderRow. There's no limit of how many header rows a table can have, and you can declare a table header row at any desired table line, not only at the first ones (it's useful, for example, to generate subtitle separators between table rows).
    A header column could be optionally expanded within multiple columns. Obviously, when colspan is used, the next column will be the one relative to the previous plus its colspan value, if any. Let's create our table header lines:

reportGenerator.beginTable(4);

reportGenerator.beginTableHeaderRow();
reportGenerator.addTableHeaderCell("Solar system", 4);
reportGenerator.endTableHeaderRow();

reportGenerator.beginTableHeaderRow();
reportGenerator.addTableHeaderCell("Type");
reportGenerator.addTableHeaderCell("Name");
reportGenerator.addTableHeaderCell("Temperature (K)");
reportGenerator.addTableHeaderCell("Orbital Period (~days)");
reportGenerator.endTableHeaderRow();

reportGenerator.endTable();
         
    As can be seen in the above code, we created two header lines, the first one with the table title, and the second one with each column title. If you think the columns title declaration is too much verbose (and it really is), you can use the function addTableHeaderRow which receives the list of columns as parameters (and, of course, do the same code as above internally). The main disadvantage of addTableHeaderRow is the memory and processor to create and use the list passed as parameter. I usually prefer the verbose way used above, but it's to you consider the tradeoffs that bother you must.
    Let's go back to our table, we must create its content lines. For simplicity, the values used at the current tutorial are hardcoded in the bellow variable, but in real world cases they'll be, of course, got from files or databases. You should add them to the report at the time you read them from its source (file or result set, for example).

private static final String[][] solarSystem = {{"Star", "Sun", "5778", "-"},
    {"Planet", "Mercury", "100-700", "88"}, 
    {"Planet", "Venus", "737", "225"}, 
    {"Planet", "Earth", "184-330", "365"},
    {"Planet", "Mars", "130-308", "687"},
    {"Planet", "Jupiter", "165", "4333"},
    {"Planet", "Saturn", "134", "10759"}, 
    {"Planet", "Uranus", "76", "30687"},
    {"Planet", "Neptune", "72", "60190"}, 
    {"Dwarf Planet", "Ceres", "168-235", "1681"},
    {"Dwarf Planet", "Pluto", "~33-55", "90570"}, 
    {"Dwarf Planet", "Haumea", "lesser 50", "103774"}, 
    {"Dwarf Planet", "MakeMake", "~32-44", "112897"}, 
    {"Dwarf Planet", "Eris", "~30-55", "203830"}};
         
    Let's iterate through it and create the row lines:

for (String[] corpse : solarSystem) {
   reportGenerator.beginTableRow();
   reportGenerator.addTableCell(corpse[0]);
   reportGenerator.addTableCell(corpse[1]);
   reportGenerator.addTableCell(corpse[2]);
   reportGenerator.addTableCell(corpse[3]);
   reportGenerator.endTableRow();
}
         
as you can see, we added the each column element with addTableCell. You can also add table elements by defining a beginTableCell - endTableCell block. But, as in our current case it's just a single string, it's easer to just use a addTableCell call.
    With it we have the full table created. But the repetition of types maybe aren't gently for some purposes, so we should group them using row spans. You can span a column through as many rows as you need, the only limitation is that the greater spanned column must be the left most one. When a spanned through row column is declared, the respective column on the next row should not be declared (ie: for the present case, the next row first beginTableCell() call will be relative to the second column, not to the first one).
    Let's change the generation code a bit:

String lastType = null;
for (String[] corpse : solarSystem) {
   reportGenerator.beginTableRow();
   /* Let's check if it's a new type */
   if (!corpse[0].equals(lastType)) {
      /* New type, must group its elements with a rowspan */
      lastType = corpse[0];
      int rowspan = 1;
      if (useRowSpan) {
          if (corpse[0].equals("Star")) {
              rowspan = totalStars;
          } else if (corpse[0].equals("Planet")) {
              rowspan = totalPlanets;
          } else if (corpse[0].equals("Dwarf Planet")) {
              rowspan = totalDwarfPlanets;
          }
      }
      reportGenerator.addTableCell(corpse[0]);
   }
   reportGenerator.addTableCell(corpse[1]);
   reportGenerator.addTableCell(corpse[2]);
   reportGenerator.addTableCell(corpse[3]);
   reportGenerator.endTableRow();
}
         
    There are yet some things your need to known about tables. You can define the table's border width and color by a

reportGenerator.setTableBorderStyle(2.0f, ReportColors.BLACK);
         
call before the beginTable - endTable block (remember that the ReportGenerator is a state machine, so, once defined, the table style will affect all tables defined after that, until redefined again with a new setTableBorderStyle call).
    The same apply to table colors. Usually row colors are alternated between even and odd row colors, which can be defined by setEvenRowColor and setOddRowColor. You can change this behaviour by either setting both to the same color or by setting setRowColor which, if defined, will make to ignore even and odd colors. You can also use setRowColor to highlight a single row line. Let's, for example, highlight the line relative to Earth:

(...)
for (String[] corpse : solarSystem) {
   if (corpse[1].equals("Earth")) {
       /* Highlight earth line color. */
       reportGenerator.setRowColor(ReportColors.ORANGE);
   } else {
       /* Use Even/Odd colors */
       reportGenerator.setRowColor(null);
   }

   reportGenerator.beginTableRow();
(...)
}
         
    That's it for now.
Here's the pdf generated by NervalReports' PDFReportGenerator:

And here you can download the full code of this tutorial.