TL-Script kann ganz leicht mit eigenen Funktionen erweitert werden. Es gibt zwei Mechanismen – der einfachste passende sollte gewählt werden:
com.top_logic.model.search.expr.config.operations.TLScriptFunctions hinzufügen. Eine Registrierung in der Anwendungskonfiguration ist nicht nötig.GenericMethod mit einem MethodBuilder implementieren und in der Anwendungskonfiguration registrieren.Der einfachste Weg, Funktionen hinzuzufügen, ist eine Ableitung von com.top_logic.model.search.expr.config.operations.TLScriptFunctions zu erstellen und public static-Methoden hinzuzufügen. Jede public-static-Methode wird automatisch zu einer TL-Script-Funktion – es gibt keine Registrierung in der Anwendungskonfiguration, da alle Ableitungen beim Start automatisch gefunden werden. Eine Ableitung darf in jedem Modul liegen, das von com.top_logic.model.search abhängt.
@ScriptPrefix("math")
public class MathFunctions extends TLScriptFunctions {
/**
* Returns the absolute value of a number.
*
* @param a
* The value whose absolute value is to be determined.
* @return The absolute value of the argument.
*/
@Label("Absolute value of a number")
@SideEffectFree
public static double abs(@Mandatory double a) {
return Math.abs(a);
}
}
Der Funktionsname ist der Präfix gefolgt vom groß geschriebenen Methodennamen, ohne Trennzeichen (z. B. math + abs → mathAbs()). Die Klasse sollte stets mit @ScriptPrefix annotiert werden, um diesen Präfix festzulegen: Er fasst die Funktionen unter einem gemeinsamen Namensraum zusammen, verhindert Namenskonflikte mit eingebauten oder zukünftigen Funktionen und lässt Nutzer die Funktionen über den Präfix in der Autovervollständigung des Editors finden. Ohne die Annotation wird als Präfix der einfache Klassenname verwendet, was nicht empfohlen wird. Der Suffix einer einzelnen Funktion kann mit @Name an der Methode überschrieben werden.
Jeder Methodenparameter wird zu einem TL-Script-Parameter (positionsbezogen). Pflichtparameter werden mit @Mandatory markiert; ein Standardwert für einen primitiven Parameter wird über die passende Annotation angegeben (@StringDefault, @LongDefault, …); für einen Typ, den TL-Script nicht nativ konvertieren kann, wird der Parameter mit @ScriptConversion unter Angabe eines ValueConverter annotiert. Das JavaDoc der Methode wird zur Funktionsbeschreibung und jeder Parameter wird über sein @param-Tag beschrieben; @Label überschreibt die generierte UI-Beschriftung und @SideEffectFree deklariert eine nebenwirkungsfreie Funktion. Funktionsnamen müssen über alle Ableitungen hinweg eindeutig sein; ein Konflikt wird beim Start als Konfigurationsfehler gemeldet. So definierte Funktionen benötigen keine eigene Dokumentationsseite – ihre Beschreibung wird aus dem JavaDoc generiert.
Die TL-Script-Funktion wird in einer Ableitung von com.top_logic.model.search.expr.GenericMethod implementiert. Die eigentliche Funktion wird in der überschriebenen Methode com.top_logic.model.search.expr.Info.eval(Object, Object[], EvalContext) implementiert. Die Methode erhält das Self-Argument als ersten Parameter und alle weiteren Argumente in dem Arguments-Array. Als Ergebnis muss die Methode das Funktionsergebnis der TL-Script-Funktion liefern.
Zusätzlich muss ein Builder für die Funktionsklasse erstellt werden. Dieser wird als Ableitung von com.top_logic.model.search.expr.config.operations.AbstractSimpleMethodBuilder<I> erstellt. Dieser Builder erzeugt eine Instanz der Funktionsimplementierung von oben. Die Builder-Implementierung wird in der Anwendungskonfiguration registriert.
Eine minimale Implementierung einer TL-Script-Funktion, welche eine Zahl abrundet, könnte folgendermaßen aussehen:
public class Floor extends SimpleGenericMethod {
protected Floor(String name, SearchExpression self, SearchExpression[] arguments) {
super(name, self, arguments);
}
@Override
public GenericMethod copy(SearchExpression self, SearchExpression[] arguments) {
return new Floor(getName(), self, arguments);
}
@Override
public TLType getType(TLType selfType, List<TLType> argumentTypes) {
return selfType;
}
@Override
public Object eval(Object self, Object[] arguments) {
return Math.floor(asDouble(self));
}
public static final class Builder extends AbstractSimpleMethodBuilder<Floor> {
public Builder(InstantiationContext context, Config<?> config) {
super(context, config);
}
@Override
public Floor build(Expr expr, SearchExpression self, SearchExpression[] args)
throws ConfigurationException {
return new Floor(getConfig().getName(), self, args);
}
}
}
Nimmt die Funktion Argumente entgegen, sollten diese über einen konstanten ArgumentDescriptor beschrieben werden, indem im Builder descriptor() überschrieben wird. Das ermöglicht den Aufruf mit benannten Argumenten (z. B. my_fun(value: 42)), dokumentiert die Signatur und prüft die Anzahl der Argumente.
private static final ArgumentDescriptor DESCRIPTOR = ArgumentDescriptor.builder()
.mandatory("value")
// .optional("digits", 0)
.build();
@Override
public ArgumentDescriptor descriptor() {
return DESCRIPTOR;
}
Hinweis: Wird eine einzelne Ausdrucksklasse für mehrere Registrierungen wiederverwendet (parametrisiert über die Builder-Konfiguration), muss getId() sowohl im Builder als auch im Ausdruck überschrieben werden und einen über alle Builder eindeutigen Wert liefern (z. B. das Paar aus Ausdrucksklasse und einem konfigurierten Unterscheidungsmerkmal). Andernfalls wird die kompilierte Form des Ausdrucks möglicherweise vom falschen Builder neu erzeugt.
Der Builder für die Funktionsimplementierung wird in der Anwendungskonfiguration im Abschnitt com.top_logic.model.search.expr.config.SearchBuilder registriert. Die obige Funktion könnte wie folgt unter dem Namen my_floor registriert werden:
<config service-class="com.top_logic.model.search.expr.config.SearchBuilder">
<instance>
<methods>
<method name="my_floor" class="my.package.Floor$Builder"/>
</methods>
</instance>
</config>
Eigene Funktionen können wie Built-In-Funktionen über den in der Konfiguration vergebenen Namen aufgerufen werden. Um Namenskonflikte mit zukünftigen Updates zu verhindern, empfiehlt es sich, einen Namenspräfix wie my_ in obigem Konfigurationsbeispiel zu verwenden.
Die oben registrierte Funktion kann dann wie folgt aufgerufen werden:
my_floor(4.2)
Das erwartete Ergebnis wäre dann 4.
Dies gilt für Funktionen, die über einen MethodBuilder registriert werden; Funktionen, die als statische Methoden einer TLScriptFunctions-Ableitung definiert sind, werden wie oben beschrieben über ihr JavaDoc dokumentiert.
Eine MethodBuilder-basierte TL-Script-Funktion ist erst mit ihrer Dokumentation vollständig – ohne sie können Nutzer die Funktion weder finden noch verstehen. Zu jeder solchen Funktion gehört daher eine Dokumentationsseite. Eingebaute Engine-Funktionen werden unter src/main/webapp/doc/{en,de}/DeveloperGuide/TLScript/<Section>/<functionName>/ dokumentiert, mit einem Verzeichnis pro Funktion (benannt genau wie der registrierte <method name>), das enthält:
index.html – ein HTML-Fragment mit den Abschnitten Syntax, Beschreibung, Parameter (eine tlDocTable), Rückgabewert und Beispiele.page.properties – mit position (Reihenfolge innerhalb des Abschnitts), source, title und einer uuid (identisch für die en- und de-Variante).Seiten werden anhand des Verzeichnisses erkannt; es gibt keinen zentralen Index zu pflegen. Eine bestehende Seite kann als Vorlage kopiert werden.