Clone
8
Programming Guide
Leo Galambos edited this page 2025-08-28 18:55:19 +02:00

Conflux Programming Guide

The Ctx class in the conflux package is a lightweight, type-safe, generic, application-wide context. It provides key-based, strongly typed storage with thread-safe put/get, type consistency for each key, and listener support to observe changes. Listeners are weakly referenced to avoid memory leaks. Ctx is ideal for coordinating shared data among otherwise decoupled classes.

Quick Start

First, import the required classes:

import conflux.Ctx;
import conflux.Key;
import conflux.Listener;

Creating Keys

Define typed keys for the data you want to store:

Key<Integer> counterKey = Key.of("counter", Integer.class);
Key<String> nameKey = Key.of("name", String.class);
Key<int[]> arrayKey = Key.of("numbers", int[].class);
Key<MyPojo> pojoKey = Key.of("myPojo", MyPojo.class);

Storing and Retrieving Values

Storing

Ctx.INSTANCE.put(counterKey, 42);
Ctx.INSTANCE.put(nameKey, "Alice");
Ctx.INSTANCE.put(arrayKey, new int[]{1, 2, 3});
Ctx.INSTANCE.put(pojoKey, new MyPojo("Widget", 99));

Retrieving

Integer counter = Ctx.INSTANCE.get(counterKey);
System.out.println("Counter: " + counter);

String name = Ctx.INSTANCE.get(nameKey);
System.out.println("Name: " + name);

int[] numbers = Ctx.INSTANCE.get(arrayKey);
System.out.println("Numbers: " + Arrays.toString(numbers));

MyPojo pojo = Ctx.INSTANCE.get(pojoKey);
System.out.println("POJO: " + pojo);

Listening for Changes

You can attach listeners to keys so other classes can react when a value changes. Listeners are weakly referenced to avoid memory leaks.

Listener<Integer> counterListener = newValue ->
    System.out.println("Counter changed to " + newValue);

Ctx.INSTANCE.addListener(counterKey, counterListener);

Ctx.INSTANCE.put(counterKey, 100); // triggers listener
Ctx.INSTANCE.put(counterKey, 200); // triggers listener again

Ctx.INSTANCE.removeListener(counterKey, counterListener);

Working with Arrays

int[] oldArray = new int[]{1, 2, 3};
int[] newArray = new int[]{4, 5, 6};

Ctx.INSTANCE.put(arrayKey, oldArray);

Ctx.INSTANCE.addListener(arrayKey, newValue ->
    System.out.println("Array changed to " + Arrays.toString(newValue))
);

Ctx.INSTANCE.put(arrayKey, newArray); // triggers listener

Using with Objects

Define a simple POJO:

public class MyPojo {
    private final String name;
    private final int price;

    public MyPojo(String name, int price) {
        this.name = name;
        this.price = price;
    }

    @Override
    public String toString() {
        return name + " ($" + price + ")";
    }
}

And store it:

MyPojo initialPojo = new MyPojo("Widget", 50);

Ctx.INSTANCE.put(pojoKey, initialPojo);

Ctx.INSTANCE.addListener(pojoKey, newValue ->
    System.out.println("POJO changed to " + newValue)
);

Ctx.INSTANCE.put(pojoKey, new MyPojo("Widget Pro", 99)); // triggers listener

Type Safety

Once a key is registered with a type, you cannot use the same name with a different type:

Key<Integer> myKey = Key.of("sharedKey", Integer.class);
Ctx.INSTANCE.put(myKey, 5);

// later:
Key<String> badKey = Key.of("sharedKey", String.class);
Ctx.INSTANCE.put(badKey, "wrong type"); // throws IllegalStateException

Removing and Clearing

Remove a single key and its listeners:

Ctx.INSTANCE.remove(nameKey);

Or clear everything:

Ctx.INSTANCE.clear();

Ephemeral Named Contexts

You can create multiple independent, ephemeral contexts, each identified by a unique name. These contexts behave like isolated key-value stores:

  • They are created lazily on first access.
  • They live only as long as you keep a reference to them.
  • They are automatically deregistered once no strong references remain (via the garbage collector), or can be removed explicitly.
// Obtain or create named contexts
CtxInterface orderCtx = Ctx.INSTANCE.getContext("order");
CtxInterface userCtx = Ctx.INSTANCE.getContext("user");

// Define keys for each context
Key<String> orderIdKey = Key.of("orderId", String.class);
Key<String> userIdKey = Key.of("userId", String.class);

// Put values into separate contexts
orderCtx.put(orderIdKey, "ORD123");
userCtx.put(userIdKey, "USR456");

// Retrieve independently
String orderId = orderCtx.get(orderIdKey);
String userId = userCtx.get(userIdKey);

You can list all named contexts:

Set<String> contexts = Ctx.INSTANCE.contextNames();
System.out.println("Active contexts: " + contexts);

Each context is fully isolated—changes in one do not affect any others—making them suitable for modular scoping (e.g., per-request, per-user, per-transaction).

Best Practices

  • Define keys as constants - centralize Key definitions to avoid typos and ensure consistency across the codebase.
  • Scope listeners carefully - register listeners only where reactive updates are needed; remove them when the owning component is disposed. (The framework uses weak references to help, but explicit cleanup is still good practice.)
  • Use descriptive, unique key names - this prevents accidental collisions within a context and improves readability.
  • Separate concerns with multiple contexts - create distinct contexts (e.g., "order", "user", "session") to isolate unrelated data and reduce coupling.
  • Treat named contexts as ephemeral - hold onto a CtxInterface only for as long as you need it; once all strong references are gone, the context is automatically reclaimed by the garbage collector.

Complete Example

Key<Integer> temperatureKey = Key.of("temperature", Integer.class);

Listener<Integer> display = t ->
    System.out.println("Temperature changed to " + t + "°C");

Ctx.INSTANCE.addListener(temperatureKey, display);

Ctx.INSTANCE.put(temperatureKey, 20);
Ctx.INSTANCE.put(temperatureKey, 25);

Ctx.INSTANCE.removeListener(temperatureKey, display);

Ctx.INSTANCE.put(temperatureKey, 30); // no notification after listener removed

Home | API Reference | Design and Architecture | FAQ | Contributing