CUSTOMER_ID = TYPE.integerColumn("customer_id");
ForeignKey CUSTOMER_FK = TYPE.foreignKey("customer_fk", CUSTOMER_ID, Customer.ID);
```
View Suffix
Databases often suffix view names with `_VW`, `_VIEW`, or similar. Setting this suffix removes it from the caption before "View" is appended to the interface name.
Important
|
The EntityType identifier always matches the actual database view name. "View" is ALWAYS appended to view interface names to avoid collisions with table names. The suffix configuration only affects whether the suffix is removed from the caption first. |
Example: View suffix "\_v"
``` java
CREATE VIEW ORDERS_V AS SELECT ...
```
Without suffix configured
- EntityType: `DOMAIN.entityType("orders_v")`
- Interface: `interface OrdersVView` ("Orders v" → PascalCase + "View")
- Caption: `"Orders v"`
With suffix "\_v" configured
- EntityType: `DOMAIN.entityType("orders_v")` (unchanged - always matches database)
- Interface: `interface OrdersView` (suffix removed → "Orders" → PascalCase + "View")
- Caption: `"Orders"` (clean caption without suffix)
View Prefix
Databases sometimes prefix view names with `VW_`, `V_`, or similar. Setting this prefix removes it from the caption before "View" is appended to the interface name.
Important
|
The EntityType identifier always matches the actual database view name. "View" is ALWAYS appended to view interface names to avoid collisions with table names. The prefix configuration only affects whether the prefix is removed from the caption first. |
Example: View prefix "vw\_"
``` java
CREATE VIEW VW_ORDERS AS SELECT ...
```
Without prefix configured
- EntityType: `DOMAIN.entityType("vw_orders")`
- Interface: `interface VwOrdersView` ("Vw orders" → PascalCase + "View")
- Caption: `"Vw orders"`
With prefix "vw\_" configured
- EntityType: `DOMAIN.entityType("vw_orders")` (unchanged - always matches database)
- Interface: `interface OrdersView` (prefix removed → "Orders" → PascalCase + "View")
- Caption: `"Orders"` (clean caption without prefix)
Note
|
Both view prefix and view suffix can be used simultaneously. The prefix is removed first, then the suffix. This prevents collisions when both a table and view exist with similar names (e.g., orders table and orders_v view). |
Audit Columns
Many schemas include audit columns like `INSERT_USER`, `INSERT_TIME`, `UPDATE_USER`, `UPDATE_TIME`. Specifying these column names (comma-separated, case-insensitive) marks them as read-only in generated definitions.
Example: Audit columns "insert_user,insert_time,update_user,update_time"
``` java
Customer.INSERT_USER.as()
.column()
.readOnly(true)
.hidden(true) // If "Hide Audit Columns" enabled
```
##### User Workflow
1. **Launch the application**
``` rouge
./gradlew :your-generator-module:run
```
2. **Authenticate** (if required)
Enter database credentials in the login dialog. Set `codion.tools.generator.user` to pre-fill the username field.
3. **Select a schema**
The schema table lists all available database schemas with their catalog names. Click to select.
4. **Populate the schema**
Double-click the schema row or press Cmd+Enter (macOS) / Ctrl+Enter (other) to load entity definitions. This introspects database metadata and populates the entity table.
5. **Configure schema settings** (optional)
Right-click the schema row and select **Settings…** to customize naming conventions and audit column handling. Settings are persisted in user preferences.
6. **Review entities**
The entity table shows all tables and views discovered in the schema, including their type (TABLE/VIEW) and metadata.
7. **Select entities for DTO generation** (optional)
Check the **DTO** column for entities that should generate DTO record classes. Foreign key attributes are only included in the DTO if the referenced entity also has a DTO.
8. **Enable generation options**
- **DTOs** checkbox - Generate DTO records for selected entities
- **i18n** checkbox - Generate resource bundle properties files
- **Test** checkbox - Generate a JUnit test class for domain validation
9. **Preview generated code**
Select a tab to view generated code:
- **API / Impl** - Split view showing separate API interface and implementation files
- **API Source Directory** - Where to write the API interface (default: current directory)
- **Implementation Source Directory** - Where to write the implementation class (default: current directory)
- **Combined** - Single-file domain model
- **Combined Source Directory** - Where to write the combined file (default: current directory)
- **i18n** - Resource bundle properties (if enabled)
Use the search field to highlight occurrences of text in the code view. Click the **…** button next to any directory field to select a different output directory.
10. **Configure output directories** (optional)
Each tab has directory fields showing where files will be written. Click **…** to select a directory. The generator automatically converts absolute paths to relative paths from your working directory for portability.
11. **Save to filesystem**
Click **Save** on the appropriate tab to write files. Existing files trigger an overwrite confirmation dialog. Generated files can be copied to actual module directories as needed.
Tip
|
Use keyboard shortcuts Alt+1 through Alt+5 to navigate between major UI sections (schema table, entity table, tabs, etc.). |
##### Command Line Interface
The [codion-tools-generator-cli](https://codion.is/doc/0.18.88/technical/technical.html#_codion_tools_generator_cli) module generates the domain source code for a whole schema without the UI, for scripted use, such as regenerating the domain model after a schema migration.
Without an output directory the combined source is printed to standard output, with diagnostics on standard error, so it can be piped or redirected.
``` java
java -m is.codion.tools.generator.cli \
--url jdbc:h2:mem:h2db \
--init-scripts /path/to/create_schema.sql \
--user sa \
--schema PETCLINIC \
--package is.codion.demos.petclinic.domain > Petclinic.java
```
The dbms module and the JDBC driver for the database in question must be available at runtime, as must the domain generator itself, the following writes the api and implementation to a source directory, along with the i18n resource bundles and the domain unit test.
*build.gradle.kts*
``` java
plugins {
application
}
dependencies {
runtimeOnly("is.codion:codion-tools-generator-cli:{codion-version}")
// Add your database JDBC driver
runtimeOnly("is.codion:codion-dbms-postgresql:{codion-version}")
runtimeOnly("org.postgresql:postgresql:{postgresql-driver-version}")
}
application {
mainModule = "is.codion.tools.generator.cli"
mainClass = "is.codion.tools.generator.cli.DomainGeneratorCli"
}
```
``` java
./gradlew :your-generator-module:installDist
./build/install/your-generator-module/bin/your-generator-module \
--url jdbc:postgresql://localhost:5432/mydb \
--user scott:tiger \
--schema store \
--package com.example.domain \
--output-dir src/main/java \
--resource-dir src/main/resources \
--test-dir src/test/java \
--split-api-impl --i18n --test --overwrite
```
| Option | Description |
|--------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `--url ` | The database url, falls back to `codion.db.url` |
| `--init-scripts ` | Database init scripts, comma separated, falls back to `codion.db.initScripts` |
| `--user ` | The database user, on the form `username` or `username:password` |
| `--schema ` | The schema to generate a domain model for (required) |
| `--package ` | The domain package (required) |
| `--output-dir ` | The source directory to write the domain source to, the package directories are created below it, when not specified the source is printed to standard output |
| `--resource-dir ` | The resource directory to write the i18n properties to, required by `--i18n` |
| `--test-dir ` | The test source directory to write the domain unit test to, required by `--test` |
| `--split-api-impl` | Write the domain api and implementation to separate source files |
| `--i18n` | Generate i18n resource bundles for the entity and attribute captions |
| `--test` | Generate a domain unit test |
| `--dtos` | Generate a record based dto for each entity |
| `--overwrite` | Overwrite existing source files |
| `--help` | Print the available options |
Exit codes: 0 success, 1 failure, 2 usage error.
Note
|
The CLI generates the domain model for the whole schema, using the default schema settings, entity selection and schema settings require the UI. |
##### Generated Output
###### File Structure
The generator writes files to the configured output directories. By default, files are written to simple directories in your working directory (e.g., `api/`, `impl/`, `combined/`), which can later be copied to actual module source directories as needed.
API/Implementation Mode
Generates separate API and implementation files. When configured with directories `"api"` and `"impl"`:
``` java
api/src/main/java//api/
└── .java # Public API interface
api/src/main/resources//api/
└── $.properties # i18n resources (if enabled)
impl/src/main/java//
└── Impl.java # Implementation class
impl/src/test/java//
└── Test.java # JUnit test (if enabled)
```
Note
|
These files can be copied to separate Gradle/Maven modules (e.g., domain-api and domain-impl) for applications using RMI or HTTP connections where clients only need the API on their classpath. |
API interface (simplified)
``` java
package is.codion.manual.tools.generator.apiimpl.api;
import is.codion.framework.domain.DomainType;
import is.codion.framework.domain.entity.EntityType;
import is.codion.framework.domain.entity.attribute.Column;
import is.codion.framework.domain.entity.attribute.ForeignKey;
import static is.codion.framework.domain.DomainType.domainType;
public interface Store {
DomainType DOMAIN = domainType(Store.class);
interface Customer {
EntityType TYPE = DOMAIN.entityType("customer");
Column ID = TYPE.integerColumn("id");
Column NAME = TYPE.stringColumn("name");
Column EMAIL = TYPE.stringColumn("email");
}
interface Order {
EntityType TYPE = DOMAIN.entityType("order");
Column ID = TYPE.integerColumn("id");
Column CUSTOMER_ID = TYPE.integerColumn("customer_id");
ForeignKey CUSTOMER_FK = TYPE.foreignKey("customer_fk", CUSTOMER_ID, Customer.ID);
}
}
```
Implementation class (simplified)
``` java
package is.codion.manual.tools.generator.apiimpl;
import is.codion.framework.domain.DomainModel;
import is.codion.framework.domain.entity.EntityDefinition;
import is.codion.manual.tools.generator.apiimpl.api.Store.Customer;
import is.codion.manual.tools.generator.apiimpl.api.Store.Order;
import static is.codion.framework.domain.entity.attribute.Column.Generator.identity;
import static is.codion.manual.tools.generator.apiimpl.api.Store.DOMAIN;
public final class StoreImpl extends DomainModel {
public StoreImpl() {
super(DOMAIN);
add(customer(), order());
}
EntityDefinition customer() {
return Customer.TYPE.as()
.attributes(
Customer.ID.as()
.primaryKey()
.generator(identity()),
Customer.NAME.as()
.column()
.caption("Name")
.nullable(false)
.maximumLength(100),
Customer.EMAIL.as()
.column()
.caption("Email")
.maximumLength(255))
.caption("Customer")
.build();
}
EntityDefinition order() {
return Order.TYPE.as()
.attributes(
Order.ID.as()
.primaryKey()
.generator(identity()),
Order.CUSTOMER_ID.as()
.column(),
Order.CUSTOMER_FK.as()
.foreignKey()
.caption("Customer"))
.caption("Order")
.build();
}
}
```
Combined Mode
Generates a single class containing both API and implementation. When configured with directory `"combined"`:
``` java
combined/src/main/java//
└── .java # Combined API + implementation
combined/src/main/resources//
└── $.properties # i18n resources (if enabled)
combined/src/test/java//
└── Test.java # JUnit test (if enabled)
```
Note
|
This mode is suitable for simpler projects using only local JDBC connections. The generated directory can be copied directly to your application’s source tree. |
Combined class (simplified)
``` java
package is.codion.manual.tools.generator;
import is.codion.framework.domain.DomainModel;
import is.codion.framework.domain.DomainType;
import is.codion.framework.domain.entity.EntityDefinition;
import is.codion.framework.domain.entity.EntityType;
import is.codion.framework.domain.entity.attribute.Column;
import is.codion.framework.domain.entity.attribute.ForeignKey;
import static is.codion.framework.domain.DomainType.domainType;
import static java.util.Collections.emptyList;
public final class Store extends DomainModel {
public static final DomainType DOMAIN = domainType(Store.class);
public Store() {
super(DOMAIN);
add(customer(), order());
}
public interface Customer {
EntityType TYPE = DOMAIN.entityType("customer");
Column ID = TYPE.integerColumn("id");
Column NAME = TYPE.stringColumn("name");
// ...
}
public interface Order {
EntityType TYPE = DOMAIN.entityType("order");
Column ID = TYPE.integerColumn("id");
Column CUSTOMER_ID = TYPE.integerColumn("customer_id");
ForeignKey CUSTOMER_FK = TYPE.foreignKey("customer_fk", CUSTOMER_ID, Customer.ID);
// ...
}
EntityDefinition customer() {
return Customer.TYPE.as().attributes(emptyList()).build();
}
EntityDefinition order() {
return Order.TYPE.as().attributes(emptyList()).build();
}
}
```
##### Schema Introspection
The [codion-framework-domain-db](https://codion.is/doc/0.18.88/technical/technical.html#_codion_framework_domain_db) module deals with introspecting database metadata using JDBC `DatabaseMetaData` and applies schema settings to generate appropriate domain model configurations.
###### Column Mapping
Database column metadata is transformed into Codion column definitions:
| Database Metadata | Generated Configuration |
|-----------------------|-----------------------------------------------------------------------|
| Primary key column | `.primaryKey()` with optional `.primaryKey(index)` for composite keys |
| Auto-increment column | `.generator(identity())` |
| NOT NULL constraint | `.nullable(false)` (except for primary keys) |
| VARCHAR(n) size | `.maximumLength(n)` |
| DECIMAL(p,s) scale | `.fractionDigits(s)` |
| Column default value | `.withDefault(true)` |
| Column comment | `.description("comment")` |
###### Foreign Key Detection
Foreign key constraints in the database schema are automatically detected and transformed into ForeignKey constants and definitions:
``` java
CREATE TABLE orders (
id INTEGER PRIMARY KEY,
customer_id INTEGER REFERENCES customers(id)
);
```
``` java
interface Order {
EntityType TYPE = DOMAIN.entityType("orders");
Column ID = TYPE.integerColumn("id");
Column CUSTOMER_ID = TYPE.integerColumn("customer_id");
ForeignKey CUSTOMER_FK = TYPE.foreignKey("customer_fk", CUSTOMER_ID, Customer.ID);
}
Order.CUSTOMER_FK.as()
.foreignKey()
.caption("Customer")
```
Composite foreign keys are supported - the generator detects multi-column foreign key constraints and generates appropriate multi-reference ForeignKey definitions.
###### View Handling
Database views are automatically marked as read-only entities:
``` java
EntityDefinition customerSummary() {
return CustomerSummary.TYPE.as(/* ... */)
.caption("Customer summary")
.readOnly(true) // Automatically added for views
.build();
}
```
###### Naming Conventions
The generator applies consistent naming transformations:
| Database Name | Generated Name |
|----------------------------|------------------------------------------------------------------------------------|
| Table: `CUSTOMER_ORDER` | EntityType: `customer_order` (or `CUSTOMER_ORDER` if `lowerCaseIdentifiers=false`) |
| Column: `order_date` | Column constant: `ORDER_DATE` |
| Column: `customer_id` (FK) | ForeignKey constant: `CUSTOMER_FK` (with `primaryKeyColumnSuffix="ID"`) |
| View: `ACTIVE_ORDERS_VW` | EntityType: `active_orders` (with `viewSuffix="VW"`) |
##### DTO Generation
Data Transfer Object (DTO) generation creates Java record classes for entities, providing a lightweight alternative to the full Entity framework for data transfer scenarios.
###### When to Generate DTOs
Enable DTO generation for entities that:
- Need to be serialized for REST APIs or messaging systems
- Represent simple data structures without complex Entity behaviors
- Are frequently transferred between application layers
See [Chinook demo](https://codion.is/doc/0.18.88/tutorials/chinook/chinook.html#_chinook_tutorial)
###### DTO Structure
For each entity with DTO generation enabled, the generator creates a nested `Dto` record within the entity interface:
``` java
interface Customer {
EntityType TYPE = DOMAIN.entityType("customer");
Column ID = TYPE.integerColumn("id");
Column NAME = TYPE.stringColumn("name");
Column EMAIL = TYPE.stringColumn("email");
// Generated DTO record
public static record Dto(
Integer id,
String name,
String email) {
public Entity entity(Entities entities) {
return entities.entity(TYPE)
.with(ID, id)
.with(NAME, name)
.with(EMAIL, email)
.build();
}
}
// Conversion method
public static Dto dto(Customer customer) {
return customer == null ? null :
new Dto(
customer.get(ID),
customer.get(NAME),
customer.get(EMAIL));
}
}
```
###### Foreign Key DTOs
When an entity with a DTO references another entity via foreign key, the foreign key attribute is included in the DTO only if the referenced entity also has DTO generation enabled. The generator creates nested DTOs for included foreign keys:
``` java
interface Order {
Column CUSTOMER_ID = TYPE.integerColumn("customer_id");
ForeignKey CUSTOMER_FK = TYPE.foreignKey("customer_fk",
CUSTOMER_ID, Customer.ID);
public static record Dto(
Integer id,
Customer.Dto customer) { // Nested DTO reference (only if Customer has DTO)
public Entity entity(Entities entities) {
return entities.entity(TYPE)
.with(ID, id)
.with(CUSTOMER_FK, customer.entity(entities))
.build();
}
}
}
```
Note
|
Foreign keys are selectively included - if Order references Customer and Customer is marked for DTO generation, Order’s DTO will include Customer.Dto. If Customer is not marked for DTOs, the CUSTOMER_FK attribute is simply excluded from Order’s DTO. |
###### Usage Example
``` java
// Entity to DTO
Entity customer = connection.selectSingle(Customer.ID.equalTo(42));
Customer.Dto dto = Customer.dto(customer);
// DTO to Entity
Entities entities = connection.entities();
Entity newCustomer = dto.entity(entities);
connection.insert(newCustomer);
```
##### Internationalization
When i18n generation is enabled, the generator creates resource bundle property files for entity and attribute captions and descriptions.
###### Generated Properties Format
*Store\$Customer.properties*
``` java
customer=Customer
customer.description=Customer master data
id=Id
name=Name
email=Email
email.description=Customer email address
```
The generator creates one properties file per entity, using the naming convention `$.properties`.
###### i18n Mode vs Literal Mode
The generator supports two caption strategies:
**Literal Mode** (i18n disabled)
Captions and descriptions are embedded directly in the generated code:
``` java
Customer.NAME.as()
.column()
.caption("Name")
.description("Customer full name")
```
**i18n Mode** (i18n enabled)
Captions and descriptions are loaded from resource bundles:
``` java
// EntityType with resource bundle reference
EntityType TYPE = DOMAIN.entityType("customer", Customer.class.getName());
// No caption() or description() calls - loaded from properties
Customer.NAME.as()
.column()
.nullable(false)
```
The framework automatically loads captions and descriptions from the properties file matching the fully qualified class name of the entity interface.
Note
|
When using i18n mode, create additional properties files with locale suffixes (e.g., Store$Customer_de.properties, Store$Customer_fr.properties) for internationalization support. |
##### Test Generation
When test generation is enabled, the generator creates a JUnit test class that extends [DomainTest](https://codion.is/doc/0.18.88/api/is.codion.framework.domain.test/is/codion/framework/domain/test/DomainTest.html) to verify domain model integrity. The test class includes a test method per entity that exercises full CRUD operations and validates constraints. The test may need further configuration to run successfully.
For further information see [Domain model testing](https://codion.is/doc/0.18.88/manual/manual.html#_domain_unit_testing).
##### Best Practices
###### Choosing Output Mode
**Use API/Implementation separation when:**
- Building applications with RMI or HTTP connections
- Multiple client applications share the same domain API
- You want lighter client classpaths (API only, no implementation)
- Following strict architectural separation
**Use Combined mode when:**
- Building simple local-JDBC applications
- The domain model is small (\<20 entities)
- You prefer fewer files to maintain
- Deployment simplicity outweighs architectural purity
###### Output Directory Strategy
The generator can write to any directory (relative or absolute paths). A simple workflow:
1. **Generate to local directories** - Use simple relative paths like `"api"`, `"impl"`, or `"combined"` in your working directory
2. **Review and customize** - Examine the generated code, make any immediate adjustments
3. **Copy to modules** - If using separate modules, copy the generated directories to your module source trees
This approach keeps the generator configuration simple while supporting any project structure. There’s no need to configure the generator to write directly into complex module hierarchies.
Tip
|
Domain generation is typically a one-shot operation. Generating to simple local directories and copying files manually provides more control and flexibility than trying to configure direct module paths. |
###### DTO Selection Strategy
- Enable DTOs for entities that cross architectural boundaries (e.g., REST APIs, messaging systems)
- If you want nested foreign key references in DTOs, enable DTOs for those referenced entities as well
- Foreign keys to entities without DTOs are simply excluded from the DTO
- Not every entity needs a DTO - be selective based on your application’s needs
###### Schema Settings Workflow
1. Connect to database
2. Select schema but **don’t populate yet**
3. Right-click → Settings to configure naming conventions
4. Now populate schema with Cmd+Enter or double-click
5. Settings are saved in user preferences and reused next time
###### Version Control
- **Commit generated code** - It’s source code, not build artifacts
- **Customize after generation** - The generator output is a starting point
- **Don’t regenerate blindly** - Manual customizations will be lost
- **Use version control to track changes** - Diff generated vs customized code
###### Customization Pattern
The generator produces standard Codion domain models. After generation, customize as needed:
- Add [EntityValidator](https://codion.is/doc/0.18.88/api/is.codion.framework.domain/is/codion/framework/domain/entity/EntityValidator.html) implementations for business rules
- Define [derived attributes](https://codion.is/doc/0.18.88/api/is.codion.framework.domain/is/codion/framework/domain/entity/attribute/DerivedValue.html) for calculated values
- Add denormalized attributes for performance optimization
- Configure foreign key fetch depth with `referenceDepth()`
- Implement custom [toString()](https://codion.is/doc/0.18.88/api/is.codion.framework.domain/is/codion/framework/domain/entity/EntityFormatter.html) formatters
- Add [custom condition types](https://codion.is/doc/0.18.88/api/is.codion.framework.domain/is/codion/framework/domain/entity/condition/ConditionType.html) for complex queries
Tip
|
Generate once, customize as needed, and use version control to preserve your customizations. Don’t treat the generator as a round-trip tool. |
###### Database Support
The generator works with any JDBC-compliant database.
Each database requires its appropriate JDBC driver on the runtime classpath. See [Chinook demo](https://codion.is/doc/0.18.88/tutorials/chinook/chinook.html#_chinook_tutorial).
Important
|
When using H2 with init scripts, H2 does not allow path traversal. Use absolute paths for codion.db.initScripts. |
##### Keyboard Navigation
The generator UI supports keyboard-driven workflows:
| Shortcut | Action |
|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------|
| Alt+1 - Alt+5 | Navigate between UI sections (schema table, entity table, tabs, etc.) |
| Cmd+Enter / Ctrl+Enter | Populate selected schema |
| Ctrl+F | Focus search field in code view |
| Cmd+C / Ctrl+C | Copy code to clipboard (when code view focused) |
Tip
|
The search field in code tabs highlights all occurrences of the search term, making it easy to locate specific entities or attributes in generated code. |
##### Examples
For complete working examples:
- **Configuration**: [Chinook demo](https://codion.is/doc/0.18.88/tutorials/chinook/chinook.html#_chinook_tutorial)
- **Generated Code**: `tools/generator/domain/src/test/resources/` (Chinook, World, Petstore)
- **Hand-crafted Domains**: See domain model sections in demo tutorials:
- [Chinook domain model](https://codion.is/doc/0.18.88/tutorials/chinook/chinook.html#_chinook_tutorial)
- [World domain model](https://codion.is/doc/0.18.88/tutorials/world/world.html#_domain_model)
- [Petstore domain model](https://codion.is/doc/0.18.88/tutorials/petstore/petstore.html#_domain_model)
The generator produces code that follows the same patterns as these hand-crafted examples, although the structure may differ, making it easy to compare generated vs manually-written domain models.
## 2. Common
### 2.1. Common Reactive
#### 2.1.1. Reactive classes
Three common classes used throughout the framework are [Event](https://codion.is/doc/0.18.88/api/is.codion.common.reactive/is/codion/common/reactive/event/Event.html), [State](https://codion.is/doc/0.18.88/api/is.codion.common.reactive/is/codion/common/reactive/state/State.html) and [Value](https://codion.is/doc/0.18.88/api/is.codion.common.reactive/is/codion/common/reactive/value/Value.html) and their respective observers [Observer](https://codion.is/doc/0.18.88/api/is.codion.common.reactive/is/codion/common/reactive/observer/Observer.html) and [ObservableState](https://codion.is/doc/0.18.88/api/is.codion.common.reactive/is/codion/common/reactive/state/ObservableState.html).
Note
|
Not all available methods are included in the diagrams below, see javadocs for details. |
##### Event
The [Event](https://codion.is/doc/0.18.88/api/is.codion.common.reactive/is/codion/common/reactive/event/Event.html) class is a synchronous event implementation used throughout the framework. Classes typically expose observers for their events via public accessors. Events are triggered by calling the **run** method in case no data is associated with the event or **accept** in case data should be propogated to consumers.
The associated [Observer](https://codion.is/doc/0.18.88/api/is.codion.common.reactive/is/codion/common/reactive/observer/Observer.html) instance can not trigger the event and can be safely passed around.
Event listeners must implement either [Runnable](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Runnable.html) or [Consumer](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/function/Consumer.html), depending on whether they are interested in the data associated with the event.
Note
|
Both listeners and consumer are notified each time the event is triggered, regardless of whether run or accept is used, listeners and consumers are notified in the order they were added. |
Events are instantiated via factory methods in the [Event](https://codion.is/doc/0.18.88/api/is.codion.common.reactive/is/codion/common/reactive/event/Event.html) class.
``` java
// specify an event propagating
// a String as the event data
Event event = Event.event();
// an observer manages the listeners
// for an Event but can not trigger it
Observer observer = event.observer();
// add a listener if you're not
// interested in the event data
observer.addListener(() -> System.out.println("Event occurred"));
event.run();//output: 'Event occurred'
// or a consumer if you're
// interested in the event data
observer.addConsumer(data -> System.out.println("Event: " + data));
event.accept("info");//output: 'Event: info'
// Event implements Observer so
// listeneres can be added directly without
// referring to the Observer
event.addConsumer(System.out::println);
```
##### Observer
The **Observer** class provides a way to add conditional listeners via [Observer.when()](https://codion.is/doc/0.18.88/api/is.codion.common.reactive/is/codion/common/reactive/observer/Observer.html#when(java.lang.Object)).
``` java
private void observer() {
// React to a specific value or predicate
Value value = Value.nullable();
value.when(1)
.addListener(() -> System.out.println("Value is one"));
value.when(2)
.addConsumer(System.out::println);
value.when(Objects::isNull)
.addListener(() -> System.out.println("Value is null"));
value.when(1)
.addListener(() -> System.out.println("one"));
value.when(2)
.addListener(() -> System.out.println("two"));
value.when(v -> v > 10)
.addConsumer(v -> System.out.println("Large value: " + v));
// React to boolean states
State enabled = State.builder()
.when(true, () -> System.out.println("Enabled"))
.when(false, () -> System.out.println("Disabled"))
.build();
}
```
##### Value
A [Value](https://codion.is/doc/0.18.88/api/is.codion.common.reactive/is/codion/common/reactive/value/Value.html) wraps a value and provides a change observer.
Values are instantiated via factory methods in the [Value](https://codion.is/doc/0.18.88/api/is.codion.common.reactive/is/codion/common/reactive/value/Value.html) class.
Values can be linked so that changes in one are reflected in the other.
``` java
// a nullable value with 2 as the initial value
Value value =
Value.nullable(2);
value.set(4);
// a non-null value using 0 as null substitute
Value otherValue =
Value.nonNull(0);
// linked to the value above
value.link(otherValue);
System.out.println(otherValue.get());// output: 0
otherValue.set(3);
System.out.println(value.get());// output: 3
System.out.println(value.is(3));// output: true
value.set(null);
System.out.println(otherValue.get());// output: 0
value.addConsumer(System.out::println);
otherValue.addListener(() ->
System.out.println("Value changed: " + otherValue.get()));
```
Values can be non-nullable if a *nullValue* is specified when the value is initialized. Null is then translated to the *nullValue* when set.
``` java
Integer initialValue = 42;
Integer nullValue = 0;
Value value =
Value.builder()
.nonNull(nullValue)
.value(initialValue)
.build();
System.out.println(value.isNullable());//output: false
System.out.println(value.get());// output: 42
value.set(null); //or value.clear();
value.isNull(); //output: false;
System.out.println(value.get());//output: 0
```
###### Linking
Values of the same type can be linked, instead of synchronizing them manually with listeners. Linking is bidirectional and the current value of the original propagates to the linked value when the link is established. This is the mechanism binding input components to values throughout the framework.
``` java
Value value = Value.nullable();
Value linked = Value.nullable();
// linking propagates the current value
// of the original value to the linked one
linked.link(value);
value.set(2);
System.out.println(linked.get());// output: 2
// linking is bidirectional, so a change
// in either value propagates to the other
linked.set(3);
System.out.println(value.get());// output: 3
```
######