Composite pattern: Printing in Java

Posted on 10 November 2012 in Patterns applied

At school we were building a system in Java that involved invoices. One of my tasks was to design and build a invoice viewer and printer. I applied the composite pattern here because i thought it was fitting and because i wanted to implement it in a "real" application.

In this post i will not focus on the actual printing part. In short i implemented the Printable interface for the drawing part, and implemented Runnable as to not block the application with the long running printing process.

The problem

To print something with Java you use the Graphics2D class to draw what ever it is you want to be printed. When doing so you specify exact 2D coordinates. So lets say you have 100 shapes: text blocks, lines, images, etc, all positioned with hardcoded coordinates it will be verry hard to change anything.

The solution

The composite pattern can help here to define a hierarchy of shape components. This hierarchy then gets the responsibility to draw itself recursively. The hierarchy will consist of composite components like tables, and leaf components like text blocks. If each leaf knows its size as a rectangle than all other coordinates and sizes can be calculated when traversing the composite structure. Figure 1 displays the UML diagram.

UML diagram
Figure 1 - UML Diagram (displays only most important properties and methods)

Drawable This is the component class. It defines a origin property which is the top left position and a size property. The abstract draw method must be implemented by concrete Drawables.

Table Composite component. The Table class contains a 2D array of Drawables. The draw method will delegate the request to each Drawable in the table. A Table object knows the dimensions of its rows and columns and will modify the Drawables by setting its size property so it fills the whole cell. Also responsible for calculating the origin property of each Drawable in the table.

Ruler and TextBlock Leaf components. Responsible for drawing a horizontal line and text.

Below is the code that creates the component hierarchy. Its still alot of code but it is far more maintainable than hardcoding all the coordnates. The system also allows you to add new Drawables when they are needed.

// Create demotable
Table demoTable = new Table(2, 3);
demoTable.setBorderStroke(new BasicStroke(1));
demoTable.setCellPadding(2);
demoTable.setColumnSize(new int[]{100, 200});
demoTable.setRowHeight(new int[]{40, 40, 100});

// Table inside the demotable
Table subTable = new Table(2, 2);
subTable.setBorderStroke(new BasicStroke(1));
subTable.setCellPadding(2);
subTable.setColumnSize(new int[]{40, 40});
subTable.setRowHeight(40);
subTable.setCell(0, 0, new TextBlock("A"));
subTable.setCell(1, 0, new TextBlock("B"));
subTable.setCell(0, 1, new TextBlock("C"));
subTable.setCell(1, 1, new TextBlock("D"));

demoTable.setCell(0, 0, new TextBlock("1"));
demoTable.setCell(1, 0, new TextBlock("2"));
demoTable.setCell(0, 1, new TextBlock("3"));
demoTable.setCell(1, 1, new TextBlock("Below is a table inside a table, nice huh?"));
demoTable.setCell(0, 2, new TextBlock("5"));
demoTable.setCell(1, 2, subTable);

// Title
AttributedString titleString = new AttributedString("Composite pattern demo");
titleString.addAttribute(TextAttribute.FONT, new Font("Dialog", Font.PLAIN, 20));
TextBlock title = new TextBlock(titleString);

// Layout table, put all components together
layoutTable = new Table(new Point(10, 10), 1, 3);
Table layoutTableTemp = (Table)layoutTable;
layoutTableTemp.setRowHeight(new int[]{50, 40, 200});
layoutTableTemp.setColumnSize(0, 300);

layoutTableTemp.setCell(0, 0, title);
layoutTableTemp.setCell(0, 1, new TextBlock("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla non nunc vulputate mauris pulvinar molestie. Curabitur in leo eget lorem fermentum facilisis et a eros."));
layoutTableTemp.setCell(0, 2, demoTable);
Figure 2 - Building the composite

Figure 3 shows what happens when calling the layoutTable.draw and passing in the Graphics object from a JPanel.

Demo
Figure 3 - Demo in JPanel

Downloads