Im letzten Beitrag wurden die Built-Ins vorgestellt. Dies sind Funktionen, die auf den Instanzen von TemplateObject
ausgeführt werden. Das TemplateObject
Interface und seine Implementierungen bilden das Datenmodel der Template-Engine. Das BuiltIn
Interface seinerseits definiert eine apply
Methode, die auf einem TemplateObject
ausgeführt wird. Die Methode besitzt als zusätzliche Parameter das aktuelle Environment
und zusätzliche Built-In Parametern in Form einer Liste von TemplateObject
Instanzen.
public interface BuiltIn { void validate(List<TemplateObject> parameters); TemplateObject apply(TemplateObject value, List<TemplateObject> parameter, Environment environment); }
Bei einer Variableninterpolation ${variable?upper_case}
wird im ersten Schritt die Variable ausgewertet und dann abhängig von ihrem Typ nach einem Built-In upper_case
gesucht. Da dieses Built-In nur für den Typ TemplateString
definiert ist, wird bei einem anderem Typen ein entsprechende Exception
geworfen.
Um die Verwaltung der Built-Ins einfach zu gestalten, werden alle in einer Map<BuiltInKey, BuiltIn>
gespeichert. Der BuildInKey
setzt sich aus dem Namen des Built-Ins und seinem Operationstyp zusammen. Der Operationstyp ist dabei der Typ der Eingangsvariable des Built-Ins. Bei der Initialisierung der FreshMarker
Konfiguration wird diese Map
mit allen vorhandenen BuiltIn
Implementierungen gefüllt.
Das BuiltIn
Interface kann auf verschiedene Weisen implementiert werden. FreshMarker liefert die beiden konkurrierenden Implementierungen MethodBuitIn
und FunctionalBuiltIn
. Erstere verwendet Reflections um das Built-In auf eine Java Methode abzubilden und Letztere verwendet ein funktionales Interface.
Im folgenden Beispiel sind drei Java Methoden für die Built-Ins trim
, upper_case
und then
dargestellt. Es handelt sich jeweils um öffentliche, statische Methoden, mit einem Rückgabewert der von TemplateObject
erbt. Der erste Parameter entspricht dem Typ des Built-Ins und die folgenden Parameter sind die weiteren Built-Ins Parameter. Als zweiter Parameter kann optional das aktuelle Environment
übergeben werden. Die Methoden sind zusätzlich mit der Annotation @BuiltInMethod
versehen um das Auswählen der Methoden zu vereinfachen. Zusätzlich kann die Annotation genutzt werden um den BuiltIn
einen alternativen Namen zu geben. Die Methode stringBuiltIn
definiert das Built-In stringhen
und die Methode trim
das BuiltIn trim
. Für die Methode upperCase
erhält das BuiltIn die beiden Namen upperCase
und upper_case
.
@BuiltInMethod public static TemplateString trim(TemplateString value) { return new TemplateString(value.getValue().trim()); } @BuiltInMethod public static TemplateString upperCase(TemplateString value, Environment environment) { return new TemplateString(value.getValue().toUpperCase(environment.getLocale())); } @BuiltInMethod("string") public static TemplateString stringBuiltIn(TemplateBoolean value, TemplateString trueValue, TemplateString falseValue) { return value == TemplateBoolean.TRUE ? trueValue : falseValue; }
Statt die Built-Ins hart zu verdrahten, wurde zusätzlich das Konzept des Plug-Ins eingeführt. Jedes Plug-In implementiert das Interface PluginProvider
und besitzt damit eine registerBuiltIn
Methode. Diese Methode dient zum Einfügen, der vom Plug-In bereitgestellten Built-Ins
in die als Parameter übergebene Map
. Die Plug-Ins können noch andere Objekte in der Konfiguration registrieren, dies wird aber in einem anderen Beitrag erörtert.
Interessant werden die Plug-Ins durch den Java ServiceLoader, mit denen FreshMarker alle verfügbaren PluginProvider
sucht ihre registerBuiltIn
Methoden aufruft. Die von FreeMarker mitgelieferten Plug-Ins sind in der Datei META-INF/services/org.freshmarker.core.plugin.PluginProvider
zu finden.
org.freshmarker.core.plugin.StringPluginProvider org.freshmarker.core.plugin.BooleanPluginProvider org.freshmarker.core.plugin.NumberPluginProvider org.freshmarker.core.plugin.TemporalPluginProvider org.freshmarker.core.plugin.DatePluginProvider org.freshmarker.core.plugin.FileAndPathPluginProvider org.freshmarker.core.plugin.LooperPluginProvider
Bei den Plug-Ins StringPluginProvider
, BooleanPluginProvider
, NumberPluginProvider
handelt es sich um Plug-Ins mit den Built-Ins für die Basistypen TemplateString
, TemplateBoolean
und TemplateNumber
. Das Plug-In LooperPlugInProvider
stellt die Built-Ins für Schleifenvariablen zur Verfügung. Dazu mehr im Beitrag über FreeMarker Direktiven.
Die Plug-Ins DatePlugInProvider
und TemporalPluginProvider
kümmern sich um die Typen TemplateDateTime
, TemplateDate
, TemplateTime
mit Date
bzw. mit einer LocalDateTime
Implementierung. Zusätzlich bietet der TemporalPluginProvider
noch die Typen TemplateDuration
und TemplatePeriod
für Zeiträume an.
Zuletzt stellt das Plug-In FileAndPathPluginProvider
die Built-Ins für die Typen TemplatePath
und TemplateFile
zur Verfügung. Über diese kann auf die Namen und Attribute von Dateien zugegriffen werden.
Der Einsatz des Java ServiceLoader bietet an dieser Stelle nicht nur einen einfachen Mechanismus um die Implementierungen der Built-Ins zu modularisieren, sondern eröffnet auch die Möglichkeit eigene Built-Ins einfach zu integrieren.