Java binding
A model is usually created in-app and can be used directly without code generation for the definition of views in the application. For this purpose, TL script expressions are used to access the model. However, if more complex algorithms need to be implemented that require access to the domain model, it may make sense to implement these in Java and extend the engine accordingly. In such a case, it is useful if the Java code has typed model interfaces that can be used for access. Without this layer, the properties of model elements can only be accessed via the generic API of TLObject. Attributes are accessed via their names, for example, which quickly leads to poorly maintainable code.
To write maintainable code for accessing the TopLogic model, the so-called "wrapper generator" is available. This generates Java interfaces from a TopLogic model description, which can be used to access the model from Java code in a typed manner.
Java package definition
In order to be able to generate Java interfaces for the classes in a module, the Java package in which the generated code is to be created must be defined. To do this, the annotation package-binding must be set on the module for which interfaces are to be generated. This annotation cannot be made in-app in the model editor because it is not useful/necessary for normal in-app development. Instead, the model must be exported to the development environment in order to set the annotation in the XML definition of the model:
<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>
The example above sets the annotation package-binding for the module my.app and defines that the generated interfaces are to be stored in the Java package my.company.my.app.model. The implementation classes used by the engine, which are also generated in the process, are created in the package my.company.my.app.model.impl.
Build customization
If a module defines Java packages for interfaces and implementation classes, generation can be started. To do this, either the class com.top_logic.element.model.generate.WrapperGenerator can be called from the development environment via a launch configuration, or the build definition (i.e. the pom.xml) of the application can be supplemented as follows:
<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>
The above build plugin definition uses the standard Maven plugin exec to call the wrapper generator with the two mandatory arguments -out and -modules. The argument -out specifies where the generated code should be written. The modules for which code is to be generated are enumerated with -modules. If code is to be generated for more than one module, the module names must be passed comma-separated in one argument.
Wrapper generation
If the build definition has been adapted as described above, the generation can be triggered via the following call:
mvn install tl:generate-java@generate-binding
The generated code is stored in the Java packages defined on the model. Once this has been compiled accordingly, the application can be restarted. The classes generated in this way are automatically loaded and used for model elements in the corresponding module. Java implementations that access the model can then use regular Java instance-of queries to test the type of a model element and access the properties of model elements via the methods defined in the generated interfaces.
Coding of specialist functionality
Functions that access the model can either be defined externally, e.g. in static methods, or implemented directly as method implementations in the generated interfaces. For this purpose, the generation produces two Java interfaces (A and ABase) and one Java class (AImpl) for a model class A. Of these three types, two actually contain generated code (ABase and AImpl) and must therefore not be modified. The interface A, on the other hand, is merely an empty template that can be enriched with your own methods. Only ABase and AImpl are overwritten during a new generation. However, the customized template A is retained.
Caution: Self-defined methods in model interfaces must always be default methods with an implementation. Under no circumstances may the generated implementation class be adapted. Default implementations of model methods may only refer to properties defined in the model (which they can access via the generated get methods). Under no circumstances may the implementation class be supplemented with additional fields.
An example of a specialized method in the generated template for a type A could look as follows:
public interface A extends ABase {
/**
* The {@link #getName() name} enclosed in stars.
*/
default String enhancedName() {
return "*** " + getName() + " ***";
}
}
The method enhancedName() uses the generated access method getName() for the property name to calculate a new value.
Specialized functionality can also use polymorphism. The default implementation can be missing in an interface for an abstract model type. In this case, default methods for the abstract interface method of the superordinate type must be specified in all interfaces of subtypes.
Caution: In this case in particular, the implementations for an abstract specialized method of an abstract model type must not be written to the generated implementation class (even if the development environment displays an error here), but rather as a default method in the generated interface for the corresponding model type.