Java ====== Overview -------- The Java generator produces java class files from a LinkML model, with optional support for user-supplied jinja2 templates to generate classes with alternate annotations or additional documentation. Docs ---- Command Line ^^^^^^^^^^^^ .. currentmodule:: linkml.generators.javagen .. click:: linkml.generators.javagen:cli :prog: gen-java :nested: short Code ^^^^ .. autoclass:: JavaGenerator :members: serialize Configurable Behaviors ---------------------- Rendering of Enumerations ^^^^^^^^^^^^^^^^^^^^^^^^^ LinkML enumerations can be rendered in two ways: * as plain `String` objects: that is, the enumeration themselves are *not* rendered at all, and slots whose range is set to an enumeration are rendered as `String`-typed fields; * as standard Java ``enum`` objects. For backwards compatibility reasons, the default behavior is to render enumerations as `String` objects. Use the ``--true-enums`` option (from the command line) or the ``true_enums`` named parameter (in the `JavaGenerator` constructor) to render LinkML enumerations as standard Java ``enum`` objects. Of note, this settings applies to all enumerations defined in the LinkML schema – it is not possible to render some enumerations as `String` objects and others as standard ``enum`` objects. Use of Slot Aliases ^^^^^^^^^^^^^^^^^^^ Slots in a LinkML schema can optionally have an `alias `__, which, if present, is intended to be “used instead of the actual slot name”. By default, the Java generator always derives the name of a field in a class from the actual name of the slot, *not* from its alias. Use the ``--use-aliases`` option (from the command line) or the ``use_aliases`` named parameter (in the `JavaGenerator` constructor) to force the generator to honor the presence of a slot alias. For example, given the definition of the ``slot_definitions`` slot in LinkML’s own metamodel: .. code-block:: yaml slot_definitions: domain: schema_definition multivalued: true range: slot_definition inlined: true alias: slots the generator will, by default, render this slot as a field named ``slotDefinitions`` (derived from the actual slot name, ignoring the ``slots`` alias): .. code-block:: java private List slotDefinitions; With ``--use-aliases``, that slot will instead be rendered as: .. code-block:: java private List slots; Of note, when using the ``org.incenp.linkml`` template variant, the slot alias, when present, is always used to determine how the slot is expected to be serialised in the JSON or YAML serialisations; the ``--use-aliases`` option only affects the symbol used to represent the slot in the Java code. Package Configuration --------------------- The generated Java ``package`` statement is driven by the following precedence: 1. ``--package`` command-line option (or ``package=...`` when using ``JavaGenerator`` programmatically) 2. ``generator_args.java.package`` set via ``--config-file``/``-C`` (see :ref:`java-configuration-file` below) 3. Fallback default: ``example`` The package name is validated wherever it comes from. An invalid name (``1bad.pkg``, ``org.class.model``) is reported as an error. NOTE: The package name is *not* configurable via schema-level annotations: LinkML classes, slots, and enums live in a single global namespace regardless of which (sub-)schema declares them - the package is a global configuration for the generator, not a property of the model. The package applies to every generated class and enum: ``gen-java`` emits one file per class into a single flat output directory, and cross-class references rely on all generated types sharing one package. .. _java-configuration-file: Configuration File ------------------- As an alternative to the ``--package`` argument, ``gen-java`` accepts a ``--config-file``/``-C`` YAML file -- the **same format** used by ``gen-project``'s own ``--config-file`` (see :doc:`project-generator`), so a single project-wide ``config.yaml`` can be shared between ``gen-project`` and a standalone ``gen-java`` run. ``package`` lives under ``generator_args.java``, since that file is structured to configure every generator at once: .. code-block:: yaml # config.yaml generator_args: java: package: org.example.model ``gen-java`` reads only the ``generator_args.java`` section of this file (``directory``, ``excludes``, other generators' ``generator_args`` entries, etc. are ignored), so a full multi-generator project ``config.yaml`` can be passed as-is. Any ``gen-java`` option can be set there, keyed by its name with dashes as underscores; command-line options take precedence, and a key that is not an option is reported as a warning and ignored. An explicit ``--package`` command-line option always overrides a value set via ``--config-file``. The same configuration-file mechanism is supported by ``gen-golang`` (see :doc:`golang`), which reads ``generator_args.golang.package`` from the same file. Generating Visitor Patterns --------------------------- The Java generator includes a built-in feature to easily implement a `visitor pattern `__ over a class hierarchy defined in a LinkML schema. Assuming the following schema (simplified excerpt from the `KGCL Schema `__): .. code-block:: yaml classes: Change: description: Any change perform on an ontology or knowledge graph. slots: - id - type SimpleChange: is_a: Change description: A change that is about a single ontology element. slots: - old_value - new_value ComplexChange: is_a: Change description: A change that is a composition of other changes. slots: - change_set # Several dozens of other subclasses (direct or indirect) of Change, # representing various specialized types of change... Calling the Java generator with ``--visitor Change`` (on the command line; ``visitors=["Change"]`` when calling the ``serialize`` method) will cause the generator to (a) create a `IChangeVisitor` interface containing a ``visit`` method for each subclass of `Change` (and for `Change` itself): .. code-block:: java public interface IChangeVisitor { public void visit(Change visited); public void visit(SimpleChange visited); public void visit(ComplexChange visited); /* and so on for all other subclasses... */ } (b) add a ``accept(IChangeVisitor)`` method to the `Change` class and to all its subclasses, e.g. in ``SimpleChange.java``: .. code-block:: java public class SimpleChange extends Change { /* Normal code generated for the SimpleChange class... */ public void accept(IChangeVisitor visitor) { visitor.visit(this); } } Template Variants ----------------- The Java generator offers different templates allowing to generate different “flavors” of Java code to represent the same LinkML schema. A set of template (hereafter called a “template variant”) is selected on the command line by the ``--template-variant`` option, or in Python code by the ``template_variant`` named parameter to the ``serialize`` method. LinkML currently provides three Java template variants: * the default variant; * the `records` variant; * and the `org.incenp.linkml` variant. Default Variant ^^^^^^^^^^^^^^^ The default template variant (which is used when no other variant is explicitly requested) generates Java classes that use Project Lombok’s `@Data `__ annotations to provide getters, setters, equals and hashcode functionality. Records Variant ^^^^^^^^^^^^^^^ The `records` variant represents LinkML classes as Java `Record classes `__, which are intended to hold *immutable data*. Note that Record classes are only available since Java 14 as a feature preview, and as an official feature since Java 16. Also note that a Record class cannot extend another class. If a class `Bar` is defined in a LinkML schema as extending a class `Foo`, the `records` variant will generate a Java `Bar` class that will contain all the slots from the `Foo` class but that will *not* be a subclass of `Foo` (meaning for example that it will not be possible to assign an instance of `Bar` to a `Foo`-typed slot). This makes the `records` variant unlikely to be suitable for schemas that have complex class hierarchies. org.incenp.linkml Variant ^^^^^^^^^^^^^^^^^^^^^^^^^ The `org.incenp.linkml` variant generates Java code that is suitable for use with the `LinkML-Java `__ runtime library – that is, code that meets the requirements set forth in the `runtime documentation `__. This allows to use said runtime to easily load data (conformant to the LinkML schema from which the code was generated) from files into the Java in-memory representation, and conversely to dump data from the Java representation into files. Template Selection Logic ------------------------ The template selection logic used by the Java generator allows to fine-tune which template is used for any given class or enum. When generating the code for a given class *Foo*, and assuming a template variant *V* has been requested (with ``--template-variant=V``), the generator will look up for the following template files, using the first one that it finds: * ``Foo-V.jinja2`` (the *V* variant template specific for the *Foo* class); * ``class-V.jinja2`` (the generic *V* variant template for all classes); * ``Foo.jinja2`` (default template specific for the *Foo* class); * ``class.jinja2`` (generic default template for all classes). When no variant is explicitly requested, the first two lookups are skipped, meaning the generator will look up first for ``Foo.jinja2`` and then for ``class.jinja2``. When the ``--true-enums`` option is enabled, the same logic will also be used to find the template file to render an enumeration *Bar*: * ``Bar-V.jinja2`` (the *V* variant template specific for the *Bar* enumeration); * ``enum-V.jinja2`` (the generic *V* variant template for all enumerations); * ``Bar.jinja2`` (default template specific for the *Bar* enumeration); * ``enum.jinja2`` (generic default template for all enumerations). By default, all templates are looked up in the generator’s internal template directory. Use the ``--template-dir=D`` option to make the generator look up first in the specified directory *D*; any template file found in that directory will take precedence over the templates from the internal directory. Lastly, use the ``--template-file=F`` option to force the generator to always use the specified template. This overrides all the logic described above. Examples -------- Biolink Example ^^^^^^^^^^^^^^^ This example illustrates how to generate a Java package containing a Java representation of the Biolink model, using the default (Lombok-dependent) templates. This assumes a working installation of LinkML. Check the :doc:`Quick Install Guide <../intro/install>` if needed. Begin by downloading the YAML file containing the Biolink schema: .. code-block:: bash curl -OJ https://raw.githubusercontent.com/biolink/biolink-model/master/biolink-model.yaml Now generate the classes using the `generate java` command: .. code-block:: bash linkml generate java --package org.biolink.model \ --output-directory org/biolink/model \ biolink-model.yaml Finally, fetch the Lombok jar, build the java classes and package into a jar file: .. code-block:: bash curl -OJ https://repo1.maven.org/maven2/org/projectlombok/lombok/1.18.20/lombok-1.18.20.jar javac org/biolink/model/*.java -cp lombok-1.18.20.jar jar -cf biolink-model.jar org Alternate Template Example ^^^^^^^^^^^^^^^^^^^^^^^^^^ Here is an alternate template using Hibernate JPA annotations, named ``example_template.java.jinja2``: .. code-block:: package {{ doc.package }}; import java.util.List; import lombok.*; import javax.persistence.*; import org.hibernate.search.engine.backend.types.*; import org.hibernate.envers.Audited; import org.hibernate.search.mapper.pojo.mapping.definition.annotation.*; @Audited @Indexed @Entity @Data @EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = true) public class {{ cls.name }} {% if cls.is_a -%} extends {{ cls.is_a }} {%- endif %} { {% for f in cls.fields %} private {{f.range}} {{ f.name }}; {%- endfor %} } The alternate template for the generator can be specified with the ``--template-file`` option: .. code-block:: linkml generate java --package org.biolink.model \ --output-directory org/biolink/model \ --template-file example_template.java.jinja2 \ biolink-model.yaml