Good bye utility classes, here come builders (part 1)

This is Part 1 of a 2 parts series on using a weak variant of the builder pattern with Java instead of using utility classes. This part discusses creating ASCII tables using a builder. You may want to jump to Part 2 being on ASCII art or go on with the article on Base64 encoding with builders.

In this context, a weak builder or a weak variant of the builder pattern is considered to combine the builder pattern with the subject of interest into a single object, e.g. the actual functionality is enriched with builder functionality instead of separating the builder functionality from the functionality of interest.

I don’t like utility classes: Somehow they seem to fill up with functionality soon forgotten and never to be found again. So what is the alternative? An example on ASCII-Art and tables …

In the past article Base! How low can you go? Base64, Base32, Base16, … I already gave some first impression on the usage of weak builders. This article outlines some more usages of builders found in the refcodes-textual (javadoc) artifact. In this part you’ll see on how to get your console output tidied up.

Instead of printing out some information targeted at the human eye as such …

SYSTEM-NAME, ID, LOCATION, LOAD, T, MEM
Atlas, 5459, Harare , 839 KB/s, 54, 5845 MB
Laomedei, 3618, Berlin , 618 KB/s, 19, 470 MB
Hyperion, 6598, Lima , 621 KB/s, 59, 6109 MB
Setebos, 6551, Tokyo , 734 KB/s, 69, 8172 MB
Prometheus, 6102, Detroit, 775 KB/s, 19, 337 MB

… you might prefer the below formatted output? The more when you are trying to identify patterns in your data:

╒══════════════════════════════╤══════╤══════════════╤══════════╤════╤═════════╕
│         SYSTEM-NAME          │  ID  │   LOCATION   │   LOAD   │ T  │   MEM   │
╞══════════════════════════════╪══════╪══════════════╪══════════╪════╪═════════╡
│Atlas                         │ 5459 │Harare        │  839 KB/s│ 54 │  5845 MB│
├------------------------------┼------┼--------------┼----------┼----┼---------┤
│Laomedei                      │ 3618 │Berlin        │  618 KB/s│ 19 │   470 MB│
├------------------------------┼------┼--------------┼----------┼----┼---------┤
│Hyperion                      │ 6598 │Lima          │  621 KB/s│ 59 │  6109 MB│
├------------------------------┼------┼--------------┼----------┼----┼---------┤
│Setebos                       │ 6551 │Tokyo         │  734 KB/s│ 69 │  8172 MB│
├------------------------------┼------┼--------------┼----------┼----┼---------┤
│Prometheus                    │ 6102 │Detroit       │  775 KB/s│ 19 │   337 MB│
└──────────────────────────────┴──────┴──────────────┴──────────┴────┴─────────┘

To get up and running, include the following dependency (without the three dots “…”) in your pom.xml:

1
2
3
4
5
6
7
8
9
<dependencies>
	...
	<dependency>
		<artifactId>refcodes-textual</artifactId>
		<groupId>org.refcodes</groupId>
		<version>3.3.5</version>
	</dependency>
	...
</dependencies>

Part 1: Building ASCII tables

Sometimes I want to print out some data to the terminal in some structured manner and without much hassle.

For example when I was calculating metrics for the BaseMetricsConfig enumeration (see Base! How low can you go? Base64, Base32, Base16, …), I wanted to see an overview of the various settings and the resulting calculations. I got the required overview with just these few lines of code (see the method showCodecMetrics() in the BaseBuilderTest.java class):

TableBuilder theTableBuilder = new TableBuilder().withTableStyle( TableStyle.SINGLE_DOUBLE );
theTableBuilder.printHeader( "ENUM", "BASE", "BYTES/INT", "BITS/DIGIT", "DIGITS/BYTE", "DIGITS/INT" );
for ( BaseMetricsConfig eBaseMetrics : BaseMetricsConfig.values() ) {
	theTableBuilder.printRow( eBaseMetrics.name(), "" + eBaseMetrics.getNumberBase(), "" + eBaseMetrics.getBytesPerInt(), "" + eBaseMetrics.getBitsPerDigit(), "" + eBaseMetrics.getDigitsPerByte(), "" + eBaseMetrics.getDigitsPerInt() );
}
theTableBuilder.printTail();

And voilá, I got this structured output in my console:

╔═════════════╦════════════╦════════════╦════════════╦════════════╦════════════╗
║    ENUM     ║    BASE    ║ BYTES/INT  ║ BITS/DIGIT ║DIGITS/BYTE ║ DIGITS/INT ║
╠═════════════╬════════════╬════════════╬════════════╬════════════╬════════════╣
║BASE2        ║2           ║4           ║1           ║8           ║32          ║
╟─────────────╫────────────╫────────────╫────────────╫────────────╫────────────╢
║BIN          ║2           ║4           ║1           ║8           ║32          ║
╟─────────────╫────────────╫────────────╫────────────╫────────────╫────────────╢
║BASE3        ║3           ║4           ║2           ║6           ║16          ║
╟─────────────╫────────────╫────────────╫────────────╫────────────╫────────────╢
║BASE4        ║4           ║4           ║2           ║4           ║16          ║
╟─────────────╫────────────╫────────────╫────────────╫────────────╫────────────╢
║...          ║...         ║...         ║...         ║...         ║...         ║
╟─────────────╫────────────╫────────────╫────────────╫────────────╫────────────╢
║BASE64       ║64          ║3           ║6           ║2           ║4           ║
╟─────────────╫────────────╫────────────╫────────────╫────────────╫────────────╢
║BASE64_ARABIC║64          ║3           ║6           ║2           ║4           ║
╟─────────────╫────────────╫────────────╫────────────╫────────────╫────────────╢
║BASE64_URL   ║64          ║3           ║6           ║2           ║4           ║
╙─────────────╨────────────╨────────────╨────────────╨────────────╨────────────╜

As the example above illustrates, a basic table builder is set up within a few lines of code. The minimum actually required is to get an instance of the TableBuilder interface:

TableBuilder theTableBuilder = new TableBuilder();
theTableBuilder.printHeader( "SYSTEM-NAME", "ID", "LOCATION", "LOAD", "T", "MEM" );
for ( SysRecord eSysRecord : someSysRecords ) {
	theTableBuilder.printRow( eSysRecord.getName(), eSysRecord.getId(), eSysRecord.getLocation(), eSysRecord.getLoadKbS() + " KB/s", eSysRecord.getThreads(), eSysRecord.getMemoryMb() + " MB" );
}
theTableBuilder.printTail();
  1. Create an instance of the TableBuilder interface by instantiating the TableBuilder class
  2. Print the header of your table (if you want one) with the printHeader method
  3. Print as many rows as you want using the printRow method for each row to be printed
  4. When done, close your table by invoking theprintTailmethod.

The output looks as follows:

╒═════════════╤════════════╤════════════╤════════════╤════════════╤════════════╕
│ SYSTEM-NAME │     ID     │  LOCATION  │    LOAD    │     T      │    MEM     │
╞═════════════╪════════════╪════════════╪════════════╪════════════╪════════════╡
│URDMICKO     │6691        │bbrylyrhrfzj│982 KB/s    │08          │3255 MB     │
├─────────────┼────────────┼────────────┼────────────┼────────────┼────────────┤
│BDHTTTPK     │9532        │yfrrwydtlixt│443 KB/s    │13          │9881 MB     │
├─────────────┼────────────┼────────────┼────────────┼────────────┼────────────┤
│SXIULSQB     │8290        │jocwbqsevxhe│518 KB/s    │35          │0285 MB     │
├─────────────┼────────────┼────────────┼────────────┼────────────┼────────────┤
│XFBQHDSD     │8193        │pvsilxzielai│811 KB/s    │32          │3791 MB     │
├─────────────┼────────────┼────────────┼────────────┼────────────┼────────────┤
│LFUAWZCD     │0953        │kxhwgsgpszsr│388 KB/s    │09          │0697 MB     │
└─────────────┴────────────┴────────────┴────────────┴────────────┴────────────┘

The TableBuilder offers many more capabilities such as setting another OutputStream to which to print. You may also retrieve the Strings of the table instead of printing to a stream e.g. by calling toHeader instead of printHeader (and so on).

Tweaking your table

You may also pimp your table by tweaking your TableBuilder instance’s attributes:

TableBuilder theTableBuilder = new TableBuilder().
	withRowWidth( 70 ).
		withTableStyle( TableStyle.ASCII );

theTableBuilder.printHeader( "SYSTEM-NAME", "ID", "LOCATION", "LOAD", "T", "MEM" );
for ( SysRecord eSysRecord : someSysRecords ) {
	theTableBuilder.printRow( eSysRecord.getName(), eSysRecord.getId(), eSysRecord.getLocation(), eSysRecord.getLoadKbS() + " KB/s", eSysRecord.getThreads(), eSysRecord.getMemoryMb() + " MB" );
}
theTableBuilder.printTail();

In the above example I set the table’s style to be TableStyle.ASCII. I also reduced the table’s width to 70 characters.

By default your terminal window’s width is taken and in case it cannot be determined, a default width of 80 characters is used

This results in the following table now using plain ASCII characters (TableStyle.ASCII) though with some annoying line breaks within the table’s cells:

/-----------+-----------+-----------+----------+----------+----------\
|SYSTEM-NAME|    ID     | LOCATION  |   LOAD   |    T     |   MEM    |
+-----------+-----------+-----------+----------+----------+----------+
|FZDAPLTM   |7529       |wcetijyqzre|825 KB/s  |91        |9389 MB   |
|           |           |x          |          |          |          |

This happens as by default all columns have an equal width calculated from the overall row’s width. In case the content of a cell does not fit into the given width, the content is wrapped to the next line in that cell. To make the table more beautiful, we can choose the width and other properties for our columns in the table:

TableBuilder theTableBuilder = new TableBuilder().withRowWidth( 80 ).withTableStyle( TableStyle.DOUBLE ).
	addColumn(). // SYSTEM-NAME
	addColumn().withRowColumnHorizAlignTextMode( HorizAlignTextMode.CENTER ).withColumnWidth( 6 ). // ID
	addColumn().withColumnWidth( 14 ). // LOCATION
	addColumn().withColumnWidth( 10 ).withRowColumnHorizAlignTextMode( HorizAlignTextMode.RIGHT ). // LOAD
	addColumn().withColumnWidth( 4 ).withRowColumnHorizAlignTextMode( HorizAlignTextMode.CENTER ). // T
	addColumn().withColumnWidth( 9 ).withRowColumnHorizAlignTextMode( HorizAlignTextMode.RIGHT ); // MEM
	
theTableBuilder.printHeader( "SYSTEM-NAME", "ID", "LOCATION", "LOAD", "T", "MEM" );
for ( SysRecord eSysRecord : someSysRecords ) {
	theTableBuilder.printRow( eSysRecord.getName(), eSysRecord.getId(), eSysRecord.getLocation(), eSysRecord.getLoadKbS() + " KB/s", eSysRecord.getThreads(), eSysRecord.getMemoryMb() + " MB" );
}
theTableBuilder.printTail();

In this example, I tweaked the widths and alignments of the various columns as well as the table’s style to TableStyle.DOUBLE. Whenever you start tweaking the next column, invoke addColumn for the TableBuilder to know to which column to assign your settings now.

Now the result looks as I wanted it to look:

╔════════════════════╦══════╦══════════════╦══════════╦════╦═════════╗
║    SYSTEM-NAME     ║  ID  ║   LOCATION   ║   LOAD   ║ T  ║   MEM   ║
╠════════════════════╬══════╬══════════════╬══════════╬════╬═════════╣
║YHJYTHMW            ║ 7966 ║oejsznamnqvm  ║  489 KB/s║ 55 ║  2084 MB║
╠════════════════════╬══════╬══════════════╬══════════╬════╬═════════╣
║YZPVPTVT            ║ 7533 ║wbqtwmuyjrpg  ║  591 KB/s║ 04 ║  3429 MB║
╠════════════════════╬══════╬══════════════╬══════════╬════╬═════════╣
║AYTKTJHI            ║ 3748 ║bofrnrygrekn  ║  586 KB/s║ 64 ║  6951 MB║
╠════════════════════╬══════╬══════════════╬══════════╬════╬═════════╣
║TLSBFYOX            ║ 5229 ║lmkbxumdrdtu  ║  018 KB/s║ 97 ║  7875 MB║
╠════════════════════╬══════╬══════════════╬══════════╬════╬═════════╣
║TQUIQACU            ║ 8374 ║nkelzlaglqeg  ║  321 KB/s║ 16 ║  4098 MB║
╚════════════════════╩══════╩══════════════╩══════════╩════╩═════════╝

The refcodes-logger-alt-console artifact’s console output actually makes heavy use of the TableBuilder. See also the blog post on Logging like the nerds log:

Fancy logger output

With the TableBuilder you can also color your terminal’s output using ANSI escape codes. Or you may prevent wrapping of lines and cutting them off with one of various strategies (at the beginning, at the end, with a “~” replacing the skipped text portions)

For many more examples of the TableBuilder usage see the sources for the TableBuilderTest.

There is another feature the TableBuilder supports which should not stay unmentioned: Joining tables with different table layouts:

Joining tables

Another feature of the TableBuilder is joining of tables represented by two different instances of the TableBuilder. These two instances may represent different table metrics such as row width or number of columns:

╔══════════════════╦═══════════════════╦═══════════════════╦═══════════════════╗
║      AAAAA       ║       BBBBB       ║       CCCCC       ║       DDDDD       ║
╠══════════════════╬═══════════════════╬═══════════════════╬═══════════════════╣
║XX XX XX          ║YY YY YY           ║ZZ ZZ ZZ           ║QQ QQ QQ           ║
╟────────╥─────────╨─────╥─────────────╨────────╥──────────╨───────────────────╢
║XX XX XX║YY YY YY       ║ZZ ZZ ZZ              ║QQ QQ QQ                      ║
╟────────╫───────────────╫──────────────────────╨───────╥──────────────────────╢
║XX XX XX║YY YY YY       ║ZZ ZZ ZZ                      ║QQ QQ QQ              ║
╟────────╨───────╥───────╨───────────────────────╥──────╨──────────────────────╨───────╥────────╥──────────╖
║ATARI           ║XX XX XX                       ║YY YY YY                             ║ZZ ZZ ZZ║ QQ QQ QQ ║
╟────────────────╫───────────────────────────────╫─────────────────────────────────────╫────────╫──────────╢
║ATARI           ║XX XX XX                       ║YY YY YY                             ║ZZ ZZ ZZ║ QQ QQ QQ ║
╟────────╥───────╨───────╥───────────────────────╨──────╥──────────────────────╥───────╨────────╨──────────╜
║XX XX XX║YY YY YY       ║ZZ ZZ ZZ                      ║QQ QQ QQ              ║
╟────────╫───────────────╫──────────────────────────────╫──────────────────────╢
║XX XX XX║YY YY YY       ║ZZ ZZ ZZ                      ║QQ QQ QQ              ║
╟────────╫───────────────╫──────────────────────╥───────╨──────────────────────╫──────────╖
║ATARI   ║XX XX XX       ║YY YY YY              ║ZZ ZZ ZZ                      ║ QQ QQ QQ ║
╟────────╫───────────────╫──────────────────────╫──────────────────────────────╫──────────╢
║ATARI   ║XX XX XX       ║YY YY YY              ║ZZ ZZ ZZ                      ║ QQ QQ QQ ║
╙────────╨───────────────╨──────────────────────╨──────────────────────────────╨──────────╜

You just tell the succeeding TableBuilder to take over and join its output to the output of the first TableBuilder:

TableBuilder theTablePrinterA = new TableBuilder()... // Configure table layout A
... // do some table printing with layout A
TableBuilder theTablePrinterB = new TableBuilder()... // Configure table layout B
theTablePrinterB.printRowEnd( theTablePrinterA ); // Join table B to the end of A 
... // continue some table printing with layout B

The example table above subsequently joins even more than two tables: One after the other, see also the full example source code, especially the testJoinTables() method.

Reflection

For the TableBuilder functionality I did not even try to build a utility class as there are too many parameters (yes, we have state) to be carried around to grant flawless functionality: Table style, table width, column alignments, column width’s, ANSI coloring and so on. Part 2 will contrast the approaches using either the builder pattern or utility classes.

Resources

The source codes of the refcodes-textual artifact is found at bitbucket.org.

Further reading