package com.xfestudio.xfeservermanager.api.trigger;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
/**
* Stable, declarative extension point for trigger catalog contributions.
*
* <p>Implementations describe capabilities only. They do not expose host classes or executable
* bytecode to trigger documents. Platform code is responsible for binding a descriptor id to a
* separately registered, isolated implementation.</p>
*/
public interface TriggerExtension {
String namespace();
String version();
default Collection<Descriptor> descriptors() {
return List.of();
}
/** Pure/snapshot handlers keyed by their namespaced VALUE_FUNCTION or CONDITION_FUNCTION id. */
default Map<String, FunctionHandler> functionHandlers() {
return Map.of();
}
/** Server-thread handlers keyed by their namespaced ACTION descriptor id. */
default Map<String, ActionHandler> actionHandlers() {
return Map.of();
}
@FunctionalInterface
interface FunctionHandler {
Object evaluate(FunctionInvocation invocation) throws Exception;
}
@FunctionalInterface
interface ActionHandler {
void execute(ActionInvocation invocation) throws Exception;
}
/** Contains transport-safe snapshots only; Minecraft/JVM host objects are never supplied. */
record FunctionInvocation(
String functionId,
List<Object> arguments,
Map<String, Object> event,
Map<String, Object> variables,
long executionSeed,
String executionInstant) {
public FunctionInvocation {
functionId = requireId(functionId, "functionId");
arguments = java.util.Collections.unmodifiableList(new java.util.ArrayList<>(
arguments == null ? List.of() : arguments));
event = immutableMap(event);
variables = immutableMap(variables);
executionInstant = requireText(executionInstant, "executionInstant");
}
}
/** Stable action boundary carrying the VM's retry/idempotency identity. */
record ActionInvocation(
String actionId,
Map<String, Object> parameters,
Map<String, Object> event,
String executionId,
String nodeId,
String idempotencyKey) {
public ActionInvocation {
actionId = requireId(actionId, "actionId");
parameters = immutableMap(parameters);
event = immutableMap(event);
executionId = requireText(executionId, "executionId");
nodeId = requireText(nodeId, "nodeId");
idempotencyKey = requireText(idempotencyKey, "idempotencyKey");
}
}
enum Kind { EVENT, EVENT_RESPONSE, VALUE_FUNCTION, CONDITION_FUNCTION, ACTION, TYPE }
enum Purity { PURE, SNAPSHOT_QUERY, WORLD_QUERY, SIDE_EFFECT }
enum ThreadAffinity { BACKGROUND, SERVER }
enum Risk {
SAFE,
ADMINISTRATOR,
OWNER;
/** Extensions may raise an existing risk, but can never lower it. */
public static Risk atLeast(Risk builtIn, Risk proposed) {
Objects.requireNonNull(builtIn, "builtIn");
Objects.requireNonNull(proposed, "proposed");
return values()[Math.max(builtIn.ordinal(), proposed.ordinal())];
}
}
record Parameter(
String name,
String valueType,
boolean required,
Object defaultValue,
String description) {
public Parameter {
name = requireId(name, "parameter name");
valueType = requireText(valueType, "parameter valueType");
description = description == null ? "" : description.strip();
}
}
record Descriptor(
String id,
Kind kind,
String displayName,
String description,
List<Parameter> parameters,
String returnType,
Purity purity,
ThreadAffinity threadAffinity,
Risk risk,
Set<String> supportedPlatforms,
Set<String> applicableEvents,
Map<String, Object> metadata) {
public Descriptor {
id = requireId(id, "descriptor id");
Objects.requireNonNull(kind, "kind");
displayName = requireText(displayName, "displayName");
description = description == null ? "" : description.strip();
parameters = List.copyOf(parameters == null ? List.of() : parameters);
returnType = returnType == null ? "void" : requireText(returnType, "returnType");
Objects.requireNonNull(purity, "purity");
Objects.requireNonNull(threadAffinity, "threadAffinity");
Objects.requireNonNull(risk, "risk");
supportedPlatforms = Set.copyOf(supportedPlatforms == null ? Set.of() : supportedPlatforms);
applicableEvents = Set.copyOf(applicableEvents == null ? Set.of() : applicableEvents);
metadata = Map.copyOf(metadata == null ? Map.of() : metadata);
}
}
private static String requireId(String value, String field) {
String normalized = requireText(value, field).toLowerCase(java.util.Locale.ROOT);
if (!normalized.matches("[a-z][a-z0-9_.:_-]{0,127}")) {
throw new IllegalArgumentException(field + " contains unsupported characters");
}
return normalized;
}
private static String requireText(String value, String field) {
if (value == null || value.isBlank()) throw new IllegalArgumentException(field + " is required");
return value.strip();
}
private static Map<String, Object> immutableMap(Map<String, Object> values) {
return java.util.Collections.unmodifiableMap(new java.util.LinkedHashMap<>(
values == null ? Map.of() : values));
}
}
package com.xfestudio.xfeservermanager.api.trigger;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
/**
* Stable, declarative extension point for trigger catalog contributions.
*
* <p>Implementations describe capabilities only. They do not expose host classes or executable
* bytecode to trigger documents. Platform code is responsible for binding a descriptor id to a
* separately registered, isolated implementation.</p>
*/
public interface TriggerExtension {
String namespace();
String version();
default Collection<Descriptor> descriptors() {
return List.of();
}
/** Pure/snapshot handlers keyed by their namespaced VALUE_FUNCTION or CONDITION_FUNCTION id. */
default Map<String, FunctionHandler> functionHandlers() {
return Map.of();
}
/** Server-thread handlers keyed by their namespaced ACTION descriptor id. */
default Map<String, ActionHandler> actionHandlers() {
return Map.of();
}
@FunctionalInterface
interface FunctionHandler {
Object evaluate(FunctionInvocation invocation) throws Exception;
}
@FunctionalInterface
interface ActionHandler {
void execute(ActionInvocation invocation) throws Exception;
}
/** Contains transport-safe snapshots only; Minecraft/JVM host objects are never supplied. */
record FunctionInvocation(
String functionId,
List<Object> arguments,
Map<String, Object> event,
Map<String, Object> variables,
long executionSeed,
String executionInstant) {
public FunctionInvocation {
functionId = requireId(functionId, "functionId");
arguments = java.util.Collections.unmodifiableList(new java.util.ArrayList<>(
arguments == null ? List.of() : arguments));
event = immutableMap(event);
variables = immutableMap(variables);
executionInstant = requireText(executionInstant, "executionInstant");
}
}
/** Stable action boundary carrying the VM's retry/idempotency identity. */
record ActionInvocation(
String actionId,
Map<String, Object> parameters,
Map<String, Object> event,
String executionId,
String nodeId,
String idempotencyKey) {
public ActionInvocation {
actionId = requireId(actionId, "actionId");
parameters = immutableMap(parameters);
event = immutableMap(event);
executionId = requireText(executionId, "executionId");
nodeId = requireText(nodeId, "nodeId");
idempotencyKey = requireText(idempotencyKey, "idempotencyKey");
}
}
enum Kind { EVENT, EVENT_RESPONSE, VALUE_FUNCTION, CONDITION_FUNCTION, ACTION, TYPE }
enum Purity { PURE, SNAPSHOT_QUERY, WORLD_QUERY, SIDE_EFFECT }
enum ThreadAffinity { BACKGROUND, SERVER }
enum Risk {
SAFE,
ADMINISTRATOR,
OWNER;
/** Extensions may raise an existing risk, but can never lower it. */
public static Risk atLeast(Risk builtIn, Risk proposed) {
Objects.requireNonNull(builtIn, "builtIn");
Objects.requireNonNull(proposed, "proposed");
return values()[Math.max(builtIn.ordinal(), proposed.ordinal())];
}
}
record Parameter(
String name,
String valueType,
boolean required,
Object defaultValue,
String description) {
public Parameter {
name = requireId(name, "parameter name");
valueType = requireText(valueType, "parameter valueType");
description = description == null ? "" : description.strip();
}
}
record Descriptor(
String id,
Kind kind,
String displayName,
String description,
List<Parameter> parameters,
String returnType,
Purity purity,
ThreadAffinity threadAffinity,
Risk risk,
Set<String> supportedPlatforms,
Set<String> applicableEvents,
Map<String, Object> metadata) {
public Descriptor {
id = requireId(id, "descriptor id");
Objects.requireNonNull(kind, "kind");
displayName = requireText(displayName, "displayName");
description = description == null ? "" : description.strip();
parameters = List.copyOf(parameters == null ? List.of() : parameters);
returnType = returnType == null ? "void" : requireText(returnType, "returnType");
Objects.requireNonNull(purity, "purity");
Objects.requireNonNull(threadAffinity, "threadAffinity");
Objects.requireNonNull(risk, "risk");
supportedPlatforms = Set.copyOf(supportedPlatforms == null ? Set.of() : supportedPlatforms);
applicableEvents = Set.copyOf(applicableEvents == null ? Set.of() : applicableEvents);
metadata = Map.copyOf(metadata == null ? Map.of() : metadata);
}
}
private static String requireId(String value, String field) {
String normalized = requireText(value, field).toLowerCase(java.util.Locale.ROOT);
if (!normalized.matches("[a-z][a-z0-9_.:_-]{0,127}")) {
throw new IllegalArgumentException(field + " contains unsupported characters");
}
return normalized;
}
private static String requireText(String value, String field) {
if (value == null || value.isBlank()) throw new IllegalArgumentException(field + " is required");
return value.strip();
}
private static Map<String, Object> immutableMap(Map<String, Object> values) {
return java.util.Collections.unmodifiableMap(new java.util.LinkedHashMap<>(
values == null ? Map.of() : values));
}
}