Anyone working with monetary amounts in Java should definitely take a look at the Moneta project. A long time ago there was an article on this subject JSR 354 – Ihre Nummer fürs Geld. Surprisingly, however, I had forgotten about Moneta support for FreshMarker. I’ll make up for that now.
Monetary amounts and currencies are particularly interesting for use in a template, which is why these classes from the Moneta project should be supported.
In order not to unnecessarily increase the dependencies on FreshMarker, Moneta support should be implemented as a separate artifact. If required, the additional dependency can be added to your own project.
We use the FreshMarker Plug-In Mechanism so that Moneta support can easily be used. A PluginProvider is used to extend FreshMarker with new Mappers, BuiltIns, Formatter, etc.
The PluginProvider for Moneta is shown below.
public final class MonetaPluginProvider implements PluginProvider {
@Override
public void registerMapper(Map<Class<?>, Function<Object, TemplateObject>> mapper) {
mapper.put(Money.class, o -> new TemplateMoney((Money) o));
mapper.put(JDKCurrencyAdapter.class, o -> new TemplateConcurrency((JDKCurrencyAdapter) o));
}
@Override
public void registerBuildIn(Map<BuiltInKey, BuiltIn> builtIns) {
BuiltInKeyBuilder<TemplateMoney> builder = new BuiltInKeyBuilder<>(TemplateMoney.class);
builtIns.put(builder.of("currency"), new FunctionalBuiltIn(
(x, y, e) -> new TemplateConcurrency((JDKCurrencyAdapter) ((TemplateMoney) x).getValue().getCurrency())
));
}
}
The first method registerMapper defines the two desired mappers for the classes Money and JDKCurrencyAdapter that map to two extensions of the TemplatePrimitive class. The template engine does not work directly on the model classes Money and JDKCurrencyAdapter but on wrapper classes that inherit from TemplateObject.
The second method registerBuiltIn creates a BuiltIn with the name currency for the TemplateMoney class. This BuiltIn provides the corresponding TemplateCurrency object for the currency of the Money object.
In order for the Moneta support to be integrated automatically, all that is missing is the file org.freshmarker.core.plugin.PluginProvider in the META-INF/service folder. With the entry org.freshmarker.money.MonetaPluginProvider, the Java Service Loader reads this PlugInProvider in addition when FreshMarker is initialized.
The Mapper and the BuildIn can now be used directly from FreshMarker.
CurrencyUnit eur = Monetary.getCurrency("EUR");
MonetaryAmountFactory<?> defaultAmountFactory = Monetary.getDefaultAmountFactory();
MonetaryAmount moneyEur = defaultAmountFactory.setCurrency(eur).setNumber(19.99).create();
MonetaryAmount moneyJpy = defaultAmountFactory.setCurrency("JPY").setNumber(19.99).create();
Template template = configuration.getTemplate("money", """
<#list money as m>
amount: ${m} currencey: ${m?currency}
</#list>""");
String content =template.process(Map.of("money", List.of(moneyJpy, moneyEur))));
If you want to test the Moneta support yourself, you can find it as usual on Maven Central.
<dependency>
<groupId>de.schegge</groupId>
<artifactId>freshmarker-money</artifactId>
<version>1.0.1</version>
</dependency>