Templating mechanism simplifies scripting work
In a recent project that I was working on, I found myself having to generate the following String0 programatically.
AFlamer = function(c) {
list = new
LinkedList();
var idx;
if ((arguments.length <=
0) || (typeof c != 'number'))
idx = 1;
else
idx = c;
for (var i = 0; i < idx;
i++) {
list.add(
}
return (list);
}
The parts in red was the name of the object. The lines in greenvaries depending on what the events and properties that were defined on the object.
The "brute force" way
My first cut of this solution was a combination of String concatenation and clever, or so I thought, pattern replacement with regular expression.
Here is how it looks...I first used a
String intName = "___0" + name;
...
strBuilder.append("var ").append(intName).append(" = new terminator();\n");
strBuilder.append(intName).append(".type = ").append(type).append(";\n");
...
while (!stop) {
...
switch (k) {
case property:
str = head(shift(currLine, 1).trim());
strBuilder.append(intName).append(".property[\"").append(str)
.append("\"] = ").append("\"").append(shift(currLine, 2)).append("\";\n");
break;
...
}
}
Once I've established the top portion, the remainder of the String is fairly static. So I resorted to defining a String constant with a series of markers,
Here is how it looks:
+ "var idx;\nif ((arguments.length <= 0) || (typeof c != 'number'))\nidx = 1;\n"
+ "else\nidx = c;\nfor (var i = 0; i < idx; i++) {\nXXXX.makeName('YYYY');\nlist.add(XXXX.clone());\n}\nreturn (list);\n}\n";
...
String prot = PROTOTYPE_TERMINATOR.replaceAll("
prot = prot.replaceAll("
strBuilder.append(name).append(prot);
I was quite proud of what I've done until I ran it for the first time. The String that was generated was incorrect. There were missing quotes, commas, line breaks, etc. I found it really difficult to visualize what was generated.
The second problem was when I had to change what was generated. This meant I had to change my code which resulted in another round of hunting down missing quotes, commas and line breaks.
String template--first try
The StringTemplate templating engine is a tool for generating formatted text. Here is my first attempt with StringTemplate:
+ "var idx;\nif ((arguments.length <= 0) || (typeof c != 'number'))\nidx = 1;\n"
+ "else\nidx = c;\nfor (var i = 0; i < idx; i++) {\n$prototype$.makeName('$name$');\nlist.add(
StringTemplate t = new StringTemplate(PROTOTYPE_TERMINATOR);
t.setAttribute("prototype", "___0AFlamer");
t.setAttribute("name", "AFlamer");
System.out.println(t.toString());
As you can see
This approach of using StringTemplate is no different than using my previous method where I use regular expression for String replacement. Furthermore it has not solve the 2 problems that I faced in.
String template--first attempt
One feature of StringTemplate is to allow you to define a template outside your code. You can then subsequently load this template into your application.
This solves two problems; first your template is now independent of your code. And if you did not introduce any new attributes into your template, you do not have to recompile your code1. The second problem that this resolves is that it removes all that nasty escapes (backslashs).
A template is just a text file that ends with the '
Here is how
$prototype$.type = $type$;
$name$ = function(c) {
list = new LinkedList();
var idx;
if ((arguments.length <= 0) || (typeof c != 'number'))
idx = 1;
else
idx = c;
for (var i = 0; i < idx; i++) {
$prototype$.makeName('$name$');
list.add($prototype$.clone());
}
return (list);
}
Now to access
The relevant parts of the code are shown in
StringTemplateGroup templates = new StringTemplateGroup("terminator", "./templates");
// Look for the prototype.st template
StringTemplate t = templates.getInstanceOf("prototype");
// As before
t.setAttribute("prototype", "___0AFlamer");
t.setAttribute("name", "AFlamer");
t.setAttribute("type", "FLAMER");
System.out.println(t.toString());
Conditions, Loops and Includes
One of the requirement of String that I am generating is that there is a part which specifies events. The number of events varies according a user's configuration file.
Every event has a name and some JavaScript codes associated with that event. These events are held in an object called
private String name;
private String code;
public EventBean(String n, String c) {
name = n;
code = c;
}
public String getName() {
return (name);
}
public String getCode() {
return (code);
}
}
An array of
$if (hasEvents)$
$events:{ e | $event(ev=e)$ }$
$endif$
$name$ = function(c) {
var idx;
if ((arguments.length <= 0) || (typeof c != 'number'))
idx = 1;
else
idx = c;
for (var i = 0; i < idx; i++) {
$prototype$.makeName('$name$');
list.add($prototype$.clone());
}
return (list);
}
In the above StringTemplate we see conditional templates, loops and template includes. These allow us to control dynamically how a template is generated.
A conditional template provides an if-like statement. This allows you to dynamically determine if certain parts of the template should be applied.
The boolean expression,
If an attribute in a template is an array, we can create a sub-template and iteratively apply the value to the sub-template.
The-sub template in the above example is shown in
Finally, the
The following shows an
On every iteration of the loop we invoke
After we have completed evaluation of
What is really nice about template includes is that every template that we write can potentially be reused in another template.
>The following piece of Java code sets up the various StringTemplate attributes.
// Populate
eventList
...
StringTemplateGroup templates = new
StringTemplateGroup("terminator", "./templates");
StringTemplate t =
templates.getInstanceOf("prototype");
t.setAttribute("prototype",
"___0AFlamer");
t.setAttribute("name", "AFlamer");
t.setAttribute("type",
"FLAMER");
t.setAttribute("hasEvents", true);
t.setAttribute("events", eventList);
System.out.println(t.toString());
There are other templating libraries like Apache
Velocity and Freemarker which have JSR-223 (Scripting) integration.
Both of these provide about the same capabilities as StringTemplate.
So if you find yourself performing a lot of String concatenation and contorting your String with cleverly placed backslashes, maybe its time to look at a templating mechanism.
Lee Chuk-Munn is a staff engineer at Sun Microsystems.
0 What I am attempting to do here is to generate a JavaScript object based on some input from a configuration file
1 If you use a property file to store your attribute-value pair, you can avoid recompiling