Collin Fagan is a Sr. Software Engineer at NAVTEQ working on next generation map building software. He holds a BS in Computer Science from the Rochester Institute of Technology. Collin has worked in the Map, Medical, Produce Warehousing and Telecommunications industries on a wide assortment of projects ranging from customized Linux thin clients to highly concurrent telecommunications control systems. Collin has posted 13 posts at DZone. View Full User Profile

StringTemplate Part 3: Complex Data Types and Renderers

06.08.2010
| 16807 views |
  • submit to reddit

In Part 1 and Part 2 we dealt with simple data types who's string representation is well known. Complex data types may not have a string representation that meets the needs of a particular template.

// Example 1: Some objects don't have a useful toString()

Object objectParamater = new Object();

StringTemplate template = new StringTemplate("Hello $param$ !");

template.setAttribute("param", objectParamater);

System.out.println(template);

Output:

Hello java.lang.Object@18eb9e6 !

Your output will very but I think it's safe to say that in most cases this output is undesirable. Here is another example of a well intentioned toString() that just won't do for our purposes.

//Example 2: Even a well intentioned toString() methods may not be useful.

Planet mars = createMars();
template = new StringTemplate("Hello $param$ !");
template.setAttribute("param", mars);

System.out.println("Example 2:");
System.out.println(template);

Output:

Example 2:

Hello Planet [atmosphere=CO2 N2, diameter=0.532, inclinationToSunsEquator=5.65, mass=0.11,
moons=[Phobos, Deimos], name=Mars, orbitalEccentricity=0.093, orbitalPeriod=1.88,
orbitalRadius=1.52, rings=false, rotationPeriod=1.03] !

For object that follow the JavaBean specification (getters and setters) StringTemplate provides property access via the “.” notation. This allows you to access properties from within your template.

//Example 3: Dot notation for property access

template = new StringTemplate("Hello $planet.name$ !");
template.setAttribute("planet", createMars());

System.out.println("Example 3:");
System.out.println(template);

Output:

Example 3:

Hello Mars !

Now we are getting somewhere. The statement planet.name was translated by StringTemplate into a call to getName(). The object we pushed into the template has the name “Mars” so our output became “Hello Mars !”. If we take what we learned about collections (multi-valued attributes) in Part 2 and apply it to a collection of beans we can build more powerful templates.

/**
* Template to convert a collection of planets into a CSV file.
*/
toCSV(planets) ::= <<
Example 4:
Planet Listing
Name,Mass,Diameter,Orbital Period,Orbital Radius,Orbital Eccentricity,
Rotation Period,Inclination To Suns Equator,Rings,Atmosphere,

$planets:planetToCSVRow()$
>>

/**
* Creates a single row of comma separated values
*/
planetToCSVRow(planet) ::= <<

$planet.name$,$planet.mass$,$planet.diameter$,$planet.orbitalPeriod$,$planet.orbitalRadius$,
$planet.orbitalEccentricity$,$planet.rotationPeriod$,$planet.inclinationToSunsEquator$,$planet.rings$,
$planet.atmosphere$

>>

This generates CSV data with a header and named columns. I think it's important to note that we pushed data into this template that same way we always have .

template.setAttribute("planets",createPlanets());

We have shifted complexity from the Java code to the template where we can deal with it more effectively. Here is the output from example 4.

Example 4:
Planet Listing

Name,Mass,Diameter,Orbital Period,Orbital Radius,Orbital Eccentricity,Rotation Period,Inclination To Suns Equator,Rings,Atmosphere,
Mercury,0.06,0.382,0.24,0.39,0.206,58.64,3.38,false,Minimal
Venus,0.82,0.949,0.62,0.72,0.0070,-243.02,3.86,false,CO2 N2
Jupiter,317.8,11.209,11.86,5.2,0.048,0.41,6.09,true,H2 HE
Saturn,95.2,9.449,29.46,9.54,0.054,0.43,5.51,true,H2 HE
Neptune,17.2,3.883,164.8,30.06,0.0090,0.67,6.43,true,H2 HE
Earth,1.0,1.0,1.0,1.0,0.017,1.0,7.25,false,N2 O2
Mars,0.11,0.532,1.88,1.52,0.093,1.03,5.65,false,CO2 N2

Here we have nice CSV output. When a column is added or removed all we need to do it make the appropriate change in the template. The source code does not need to change. Static header or footer information can be added with no impact on Java code.

All planetary data was taken from: http://en.wikipedia.org/wiki/Planet

Renderers:

In all our previous examples we could, at some point, obtain an adequate string representation of our data without fiddling around too much. This will not always be the case. As an example of how to deal with this I will use a data type notorious for needing special attention, java.util.Date. Lets start simply with the following Java code.

// Example 5: Unformatted Date
template = new StringTemplate("The current date is: $now$ !");
template.setAttribute("now", new Date());

System.out.println("Example 5:");
System.out.println(template);<span style="text-decoration: none;"> </span>

Output:

Example 5:
The current time is: Fri Jun 04 08:01:48 CDT 2010 !

 

While this does accurately report the current time it may not be in a form familiar to your users. In this case there is no property that we can use as our preferred display value. StringTemplate provides a system for intercepting an object before it gets turned into a string. This is called a “Renderer”.

Here is the skeleton of a simple Renderer. ( AttributeRenderer has a second method but lets ignore that for now.)

public class DateRenderer implements AttributeRenderer {

@Override
public String toString(Object attribute) {
return null;
}
}

The original attribute is passed as a parameter to the toString() method. The return value from the toString() method becomes the new value placed in the template. For Dates we can leverage the SimpleDateFormat (http://java.sun.com/javase/6/docs/api/java/text/SimpleDateFormat.html) class to do the actual formatting.

public class StaticDateRenderer implements AttributeRenderer{

private SimpleDateFormat format;

public StaticDateRenderer(String pattern){
format = new SimpleDateFormat(pattern);
}

@Override
public String toString(Object attribute) {
return format.format((Date) attribute);
}
...
}

As you can see we are just delegating to an instance of SimpleDateFormat. The cast to a Date is always safe because we register a rendered by the class literal.

template.registerRenderer(Date.class,new StaticDateRenderer("h:m:ss a"));

When we add the above line to our previous example we see that the date now appears in a more familiar from.

Example 5: Using a Date Renderer

The current time is: 7:51:08 AM !

Renders are specified per class. This means that if we had two dates in the above example they would both use the “h:m:ss a” format. This would be undesirable if for instance we had two column in a table, one was a date and one was a time. Luckily AttributeRenderer provides a second method to help deal with this.

public String toString(Object attribute, String format)

This method takes the attribute to be rendered and a format parameter. This format parameter is passed from the template to the render.

$now; format="SHORT"$

In this case the toString method is passed the value 'SHORT'. It's up to the render to decide what to do with any particular parameter. DynamicDateRenderer is an example of a render that utilizes the format parameter to effect the formatting of a particular date. It supports the formatting styles from DateFormat and the SimpleDateFormat pattern syntax.

Example 6: Formatting Dates

DateFromat Styles:

DEFAULT $now$
SHORT $now; format="SHORT"$
MEDIUM $now; format="MEDIUM"$
LONG $now; format="LONG"$
FULL $now; format="FULL"$

Using SimpleDateFormat Patterns:

yyyy.MM.dd G 'at' HH:mm:ss z $now; format="yyyy.MM.dd G 'at' HH:mm:ss z"$
EEE, MMM d, ''yy $now; format="EEE, MMM d, ''yy"$
h:mm a $now; format="h:mm a"$
hh 'o''clock' a, zzzz $now; format="hh 'o''clock' a, zzzz"$
K:mm a, z $now; format="K:mm a, z"$
yyyyy.MMMMM.dd GGG hh:mm aaa $now; format="yyyyy.MMMMM.dd GGG hh:mm aaa"$
EEE, d MMM yyyy HH:mm:ss Z $now; format="EEE, d MMM yyyy HH:mm:ss Z"$
yyMMddHHmmssZ $now; format="yyMMddHHmmssZ"$
yyyy-MM-dd'T'HH:mm:ss.SSSZ $now; format="yyyy-MM-dd'T'HH:mm:ss.SSSZ"$
dd.MM.yy $now; format="dd.MM.yy"$
h:mm a $now; format="h:mm a"$
H:mm $now; format="H:mm"$
H:mm:ss:SSS $now; format="H:mm:ss:SSS"$

(The syntax hilighter is having troubles with the spacing in this block. Please click "view source" to see the actual template.)

The template uses the same variable in every line, only the formatting parameter changes. On one side I have put the description of the date format. On the other side I have applied the format to the date value. Below is the final output from the above template.

Example 6: Formatting Dates

DateFromat Styles:

DEFAULT Jun 7, 2010
SHORT 6/7/10
MEDIUM Jun 7, 2010
LONG June 7, 2010
FULL Monday, June 7, 2010

Using SimpleDateFormat Patterns:

yyyy.MM.dd G 'at' HH:mm:ss z 2010.06.07 AD at 21:00:18 CDT
EEE, MMM d, ''yy Mon, Jun 7, '10
h:mm a 9:00 PM
hh 'o''clock' a, zzzz 09 o'clock PM, Central Daylight Time
K:mm a, z 9:00 PM, CDT
yyyyy.MMMMM.dd GGG hh:mm aaa 02010.June.07 AD 09:00 PM
EEE, d MMM yyyy HH:mm:ss Z Mon, 7 Jun 2010 21:00:18 -0500
yyMMddHHmmssZ 100607210018-0500
yyyy-MM-dd'T'HH:mm:ss.SSSZ 2010-06-07T21:00:18.668-0500
dd.MM.yy 07.06.10
h:mm a 9:00 PM
H:mm 21:00
H:mm:ss:SSS 21:00:18:668

(The syntax hilighter is having troubles with the spacing in this block. Please click "view source" to see the actual output.)

Renders are a powerful system for converting data into template friendly forms. As a rule of thumb when you are combining data you should use a template. When the data itself needs to take on a different form it's time to consider a renderer.

This concludes part 3. I hope you give StringTemplate a try. As always, if you want more information please see the official StringTemplate documentation.

Example source code (eclipse project)

From http://weblogs.java.net/blog/aberrant/archive/2010/06/07/stringtemplate-part-3-complex-data-types-and-renderers

Published at DZone with permission of its author, Collin Fagan.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Tags: