Or find the code on github
Extending the platform:
Resource bundle resolution
Snippetory is an easy-to-use and extensible text generation platform. Here we focus on the extension mechanism. It is based on the service loading mechanism of the jar. Any extension available on class path, in jars, will be loaded whenever Snippetory is initialized.
This has the advantage, that the extensions are available in different environments simply by configuring the class path.
- IDE tooling
- An automatic verifier module for build tools
- ...
Whenever an extension is used without packaging it can be easily loaded by loading the Configurer class.
Service Provider Interface
All the classes, relevant for extending the platform are placed in the package org.jproggy.snippetory.spi. These are the interfaces that have to be implemented for creating new formats, encoding and syntaxes as well as the Configurer interface.
The example
Since information from resource bundles can be resolved statically, it would be nice to resolve them while parsing the template. On the other hand a full build in support would require a complexity, that isn't that handy. However, specialization reduces the information to be denoted. Hence, I decided to provide localization support as a how-to instead of trying to build an all-in-one solution.
Hands on
First we need an VoidFormat class. That's a specialization of the Format It stores the value resolved from the resource bundle.
package org.jproggy.example.msg;
import java.util.Collections;
import java.util.Set;
import org.jproggy.snippetory.spi.*;
public class ResourceFormat extends SimpleFormat implements VoidFormat {
private final Template resolved;
public ResourceFormat(Template resolved) {
this.resolved = resolved;
}
@Override
public Object formatVoid(TemplateNode node) {
return resolved;
}
@Override
public void set(String name, Object value) {
resolved.set(name, value)
}
@Override
public void append(String name, Object value) {
resolved.append(name, value)
}
@Override
public Set<String> names() {
return resolved.names();
}
}
However, to have a resolved value we need to resolve it. This is done within the FormatFactory.
package org.jproggy.example.msg;
import java.util.ResourceBundle;
import org.jproggy.snippetory.TemplateContext;
import org.jproggy.snippetory.spi.*;
public class ResourceFormatFab implements FormatFactory, Configurer {
static
{
// the class is only loaded, so initialization goes to
// static initializer.
Format.register("msg", new ResourceFormatFab());
}
@Override
public FormatConfiguration create(String definition, TemplateContext ctx){
// The resource is resolved in the factory. This mean if resolving
// would fail, the template could not be parsed. I.e. we have a
// strong fail fast behavior.
ResourceBundle msg = ResourceBundle.getBundle(
"org.jproggy.example.msg.Messages", ctx.getLocale());
// parsing it to a template allows providing parameters
Template resolved = Syntaxes.FLUYT.parse(msg.getString(definition))
return new ResourceFormat(resolved);
}
}
Now we have to configure the service loader mechanism. This is a little more complicated
to describe as it needs interaction with you packaging solution. In effect, we to create a
sub folder of the META-INF folder called services. This folder has to contain a file
called org.jproggy.snippetory.spi.Configurer.
I.e. META-INF/services/org.jproggy.snippetory.spi.Configurer
And this file has to contain a single line,
that's simply the binary name of your Configurer class. The binary name differs from the
qualified basically for inner classes. They're separated form their hosts name by '$'
instead of a period.
com.example.tool.ResourceFormatFab
Now it's ready to use. We create a file called HalloWorldApp.html as a resource within our class path. (If you use Maven just put it in src/main/resources)
<html>
<head>
<title>{v:x msg="page.title"}</title>
</head>
<body>
{v:x msg="greeting"} {v:x msg="world"}
</body>
</html>
Of course, we need a properties file. Maybe com/example/tool/Messages_de.properties:
page.title = Hallo Welt Anwendung
greeting = Hallo
world = Welt
With java code like this:
.
.
.
Repo.readResource("HalloWorldApp.html").locale(Locale.GERMANY)
.parse().render(System.out);
.
.
.