Java-Binding
Ein Modell wird normalerweise in-app erstellt und kann direkt ohne Code-Generierung für die Definition von Sichten in der Anwendung verwendet werden. Hierfür werden TL-Script Ausdrücke verwendet, um auf das Modell zuzugreifen. Wenn allerdings komplexere Algorithmen implementiert werden müssen, die Zugriff auf das Fachmodell haben müssen, kann es sinnvoll sein, diese in Java zu implementieren und die Engine entsprechend zu erweitern. In einem solchen Fall ist es nützlich, wenn der Java-Code typisierte Modell-Interfaces hat, über die der Zugriff stattfinden kann. Ohne diese Schicht kann nur über die generische API von TLObject auf die Eigenschaften von Modellelementen zugegriffen werden. Hierbei erfolgt beispielsweise der Zugriff auf Attribute über deren Namen, was schnell zu schlecht wartbarem Code führt.
Um wartbaren Code für den Zugriff auf das TopLogic-Modell zu schreiben, steht der sog. "Wrapper-Generator" zur Verfügung. Dieser generiert aus einer TopLogic-Modellbeschreibung Java-Interfaces, über die aus Java-Code typisiert auf das Modell zugegriffen werden kann.
Java-Paket-Definition
Um Java-Interfaces für die Klassen in einem Modul generieren zu können, muss definiert werden, in welchem Java-Paket der generierte Code erzeugt werden soll. Hierfür muss die Annotation package-binding
an dem Modul gesetzt werden, für welches Interfaces generiert werden sollen. Diese Annotation kann nicht in-app im Modelleditor vorgenommen werden, weil sie für die normale In-App-Entwicklung nicht sinnvoll/notwendig ist. Stattdessen muss das Modell in die Entwicklungsumgebung exportiert werden, um dort in der XML-Definition des Modells die Annotation zu setzten:
<model xmlns="http://www.top-logic.com/ns/dynamic-types/6.0">
<module name="my.app">
<annotations>
<package-binding
implementation-package="my.company.my.app.model.impl"
interface-package="my.company.my.app.model"
/>
</annotations>
<class name="A">
...
</class>
</module>
</model>
Obiges Beispiel setzt die Annotation package-binding
für das Modul my.app
und definert, dass die generierten Schnittstellen in das Java-Paket my.company.my.app.model
abgelegt werden sollen. Die von der Engine verwendeten Implementierungsklassen, welche dabei ebenfalls generiert werden, werden im Paket my.company.my.app.model.impl
erzeugt.
Build-Anpassung
Wenn ein Modul Java-Pakete für Schnittstellen und Implementierungsklassen definiert, kann die Generierung gestartet werden. Hierfür kann entweder die Klasse com.top_logic.element.model.generate.WrapperGenerator
über eine Launch-Konfiguration aus der Entwicklungsumgebung aufgerufen werden, oder man ergänzt die Build-Definition (d.h. die pom.xml
) der Applikation wie folgt:
<build>
<plugins>
<plugin>
<groupId>com.top-logic</groupId>
<artifactId>tl-maven-plugin</artifactId>
<version>${tl.version}</version>
<executions>
<execution>
<id>generate-binding</id>
<goals>
<goal>generate-java</goal>
</goals>
<configuration>
<modules>my.app</modules>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
Obige Build-Plugin-Definition verwendet das Standard-Maven-Plugin exec
, um den Wrapper-Generator mit den zwei verpflichtenden Argumenten -out
und -modules
aufzurufen. Das Argument -out
legt fest, wohin der generierte Code geschrieben werden soll. Mit -modules
werden die Module aufgezählt, für die Code generiert werden soll. Wenn für mehr als ein Modul Code generiert werden soll, müssen die Modul-Namen kommasepariert in einem Argument übergeben werden.
Wrapper-Generierung
Ist die Build-Definition wie oben beschrieben angepasst, kann die Generierung über den folgenden Aufruf angestoßen werden:
mvn install tl:generate-java@generate-binding
Der generierte Code wird in den am Modell definierten Java-Paketen abgelegt. Nachdem dieser entsprechend übersetzt wurde, kann die Anwendung neu gestartet werden. Dabei werden die so generierten Klassen automatisch geladen und für Modellelemente in dem entsprechenden Modul verwendet. Java-Implementierungen, welche auf das Modell zugreifen, können dann mit regulären Java-Instanceof-Abfragen testen, welchen Typ ein Modellelement hat und über die in den generierten Interfaces definierten Methoden auf die Eigenschaften von Modellelementen zugreifen.
Codierung von Fachfunktionalität
Funktionen, welche auf das Modell zugreifen, können entweder extern z.B. in statischen Methoden definiert werden, oder aber direkt als Methodenimplementierungen in den generierten Schnittstellen implementiert werden. Die Generierung produziert hierfür für eine Modell-Klasse A
zwei Java-Interfaces (A
und ABase
) und eine Java-Klasse (AImpl
). Von diesen drei Typen enthalten zwei tatsächlich generierten Code (ABase
und AImpl
) und dürfen daher nicht modifiziert werden. Das Interface A
hingegen ist lediglich ein leeres Template, das mit eigenen Methoden angereichert werden kann. Bei einer Neu-Generierung wird nur ABase
und AImpl
überschrieben. Das angepasste Template A
bleibt hingegen erhalten.
Achtung: Selbstdefinierte Methoden in Modell-Interfaces müssen immer Default-Methoden mit einer Implementierung sein. Keinesfalls darf die ebenfalls generierte Implementierungsklasse angepasst werden. Default-Implementierungen von Modell-Methoden dürfen sich ausschließlich auf im Modell definierte Eigenschaften beziehen (auf welche sie über die generierten Get-Methoden zugreifen können). Auf keinen Fall darf die Impementierungsklasse mit zusätlichen Feldern ergänzt werden.
Ein Beispiel einer Fachmethode in dem generierten Template für einen Typ A
könnte wie folgt aussehen:
public interface A extends ABase {
/**
* The {@link #getName() name} enclosed in stars.
*/
default String enhancedName() {
return "*** " + getName() + " ***";
}
}
Die Methode enhancedName()
verwendet die generierte Zugriffsmethode getName()
für die Eigenschaft name
, um einen neuen Wert zu berechnen.
Fachfunktionalität kann auch Polymorphie verwenden. In einem Interface für einen abstrakten Modell-Typ kann die Default-Implementierung fehlen. Dann müssen in allen Interfaces von Untertypen Default-Methoden für die abstrakte Interface-Methode des Obertyps angeben.
Achtung: Insbesondere gilt auch in diesem Fall, dass die Implementierungen für eine abstrakte Fachmethode eines abstrakten Modell-Typs nicht in die generierte Implementierungsklasse geschrieben werden dürfen (auch wenn die Entwicklungsungebung hier einen Fehler anzeigt), sondern als Default-Methode in das generierte Interface für den entsprechenden Modell-Typ.