package com.xfestudio.xfeservermanager.core.trigger;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/** Declarative .xfelib payload. It intentionally has no class/bytecode/script entry point. */
public record TriggerLibraryPackage(
Manifest manifest,
Map<String, TriggerProgramV2> triggers,
Map<String, Object> variables,
Map<String, Object> functions,
Map<String, Object> templates) {
public TriggerLibraryPackage {
Objects.requireNonNull(manifest, "manifest");
triggers = checkedPrograms(triggers);
variables = immutable(variables);
functions = immutable(functions);
templates = immutable(templates);
if (triggers.size() > 256 || templates.size() > 256) {
throw new IllegalArgumentException("library contains too many triggers or templates");
}
}
public record Manifest(String namespace, String version, String engineRequirement,
Map<String, String> dependencies, String displayName,
String description) {
public Manifest {
namespace = text(namespace, "namespace").toLowerCase(java.util.Locale.ROOT);
if (!namespace.matches("[a-z][a-z0-9_.-]{1,63}")) {
throw new IllegalArgumentException("library namespace is invalid");
}
version = text(version, "version");
if (!version.matches("(?:0|[1-9][0-9]*)\\.(?:0|[1-9][0-9]*)\\.(?:0|[1-9][0-9]*)(?:-[0-9A-Za-z.-]+)?")) {
throw new IllegalArgumentException("library version must be semantic versioning");
}
engineRequirement = engineRequirement == null || engineRequirement.isBlank()
? ">=2.0.0" : engineRequirement.strip();
if (!validRequirement(engineRequirement)) {
throw new IllegalArgumentException("library engine requirement is invalid");
}
dependencies = Map.copyOf(dependencies == null ? Map.of() : dependencies);
if (dependencies.size() > 64 || dependencies.containsKey(namespace)) {
throw new IllegalArgumentException("library dependencies are invalid");
}
dependencies.forEach((name, required) -> {
if (!name.matches("[a-z][a-z0-9_.-]{1,63}") || !validRequirement(required)) {
throw new IllegalArgumentException("invalid library dependency: " + name);
}
});
displayName = bounded(displayName, namespace, 120);
description = bounded(description, "", 500);
}
}
public static boolean satisfies(String actual, String requirement) {
int comparison = compareVersions(actual, requirement.replaceFirst("^(?:>=|<=|>|<|=|\\^|~)\\s*", ""));
String prefix = requirement.strip();
if (prefix.startsWith(">=")) return comparison >= 0;
if (prefix.startsWith("<=")) return comparison <= 0;
if (prefix.startsWith(">")) return comparison > 0;
if (prefix.startsWith("<")) return comparison < 0;
if (prefix.startsWith("^") || prefix.startsWith("~")) {
String[] have = numeric(actual);
String[] need = numeric(prefix.substring(1));
return comparison >= 0 && (prefix.startsWith("^")
? have[0].equals(need[0]) : have[0].equals(need[0]) && have[1].equals(need[1]));
}
return comparison == 0;
}
private static boolean validRequirement(String value) {
return value != null && value.strip().matches("(?:>=|<=|>|<|=|\\^|~)?\\s*"
+ "(?:0|[1-9][0-9]*)\\.(?:0|[1-9][0-9]*)\\.(?:0|[1-9][0-9]*)(?:-[0-9A-Za-z.-]+)?");
}
private static int compareVersions(String left, String right) {
String[] a = numeric(left);
String[] b = numeric(right);
for (int index = 0; index < 3; index++) {
int comparison = Long.compare(Long.parseLong(a[index]), Long.parseLong(b[index]));
if (comparison != 0) return comparison;
}
return 0;
}
private static String[] numeric(String version) {
String normalized = version.strip().replaceFirst("^(?:>=|<=|>|<|=|\\^|~)\\s*", "")
.split("-", 2)[0];
String[] parts = normalized.split("\\.");
if (parts.length != 3) throw new IllegalArgumentException("invalid semantic version: " + version);
return parts;
}
private static Map<String, TriggerProgramV2> checkedPrograms(Map<String, TriggerProgramV2> values) {
Map<String, TriggerProgramV2> copy = new LinkedHashMap<>();
if (values != null) values.forEach((name, program) -> {
if (name == null || !name.matches("[A-Za-z_][A-Za-z0-9_.-]{0,127}") || program == null) {
throw new IllegalArgumentException("library trigger entry is invalid");
}
TriggerProgramTypeChecker.validate(program);
copy.put(name, program);
});
return Map.copyOf(copy);
}
private static Map<String, Object> immutable(Map<String, ?> values) {
return Map.copyOf(values == null ? Map.of() : values);
}
private static String text(String value, String field) {
if (value == null || value.isBlank()) throw new IllegalArgumentException(field + " is required");
return value.strip();
}
private static String bounded(String value, String fallback, int maximum) {
String result = value == null || value.isBlank() ? fallback : value.strip();
if (result.length() > maximum) throw new IllegalArgumentException("library text is too long");
return result;
}
}
package com.xfestudio.xfeservermanager.core.trigger;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/** Declarative .xfelib payload. It intentionally has no class/bytecode/script entry point. */
public record TriggerLibraryPackage(
Manifest manifest,
Map<String, TriggerProgramV2> triggers,
Map<String, Object> variables,
Map<String, Object> functions,
Map<String, Object> templates) {
public TriggerLibraryPackage {
Objects.requireNonNull(manifest, "manifest");
triggers = checkedPrograms(triggers);
variables = immutable(variables);
functions = immutable(functions);
templates = immutable(templates);
if (triggers.size() > 256 || templates.size() > 256) {
throw new IllegalArgumentException("library contains too many triggers or templates");
}
}
public record Manifest(String namespace, String version, String engineRequirement,
Map<String, String> dependencies, String displayName,
String description) {
public Manifest {
namespace = text(namespace, "namespace").toLowerCase(java.util.Locale.ROOT);
if (!namespace.matches("[a-z][a-z0-9_.-]{1,63}")) {
throw new IllegalArgumentException("library namespace is invalid");
}
version = text(version, "version");
if (!version.matches("(?:0|[1-9][0-9]*)\\.(?:0|[1-9][0-9]*)\\.(?:0|[1-9][0-9]*)(?:-[0-9A-Za-z.-]+)?")) {
throw new IllegalArgumentException("library version must be semantic versioning");
}
engineRequirement = engineRequirement == null || engineRequirement.isBlank()
? ">=2.0.0" : engineRequirement.strip();
if (!validRequirement(engineRequirement)) {
throw new IllegalArgumentException("library engine requirement is invalid");
}
dependencies = Map.copyOf(dependencies == null ? Map.of() : dependencies);
if (dependencies.size() > 64 || dependencies.containsKey(namespace)) {
throw new IllegalArgumentException("library dependencies are invalid");
}
dependencies.forEach((name, required) -> {
if (!name.matches("[a-z][a-z0-9_.-]{1,63}") || !validRequirement(required)) {
throw new IllegalArgumentException("invalid library dependency: " + name);
}
});
displayName = bounded(displayName, namespace, 120);
description = bounded(description, "", 500);
}
}
public static boolean satisfies(String actual, String requirement) {
int comparison = compareVersions(actual, requirement.replaceFirst("^(?:>=|<=|>|<|=|\\^|~)\\s*", ""));
String prefix = requirement.strip();
if (prefix.startsWith(">=")) return comparison >= 0;
if (prefix.startsWith("<=")) return comparison <= 0;
if (prefix.startsWith(">")) return comparison > 0;
if (prefix.startsWith("<")) return comparison < 0;
if (prefix.startsWith("^") || prefix.startsWith("~")) {
String[] have = numeric(actual);
String[] need = numeric(prefix.substring(1));
return comparison >= 0 && (prefix.startsWith("^")
? have[0].equals(need[0]) : have[0].equals(need[0]) && have[1].equals(need[1]));
}
return comparison == 0;
}
private static boolean validRequirement(String value) {
return value != null && value.strip().matches("(?:>=|<=|>|<|=|\\^|~)?\\s*"
+ "(?:0|[1-9][0-9]*)\\.(?:0|[1-9][0-9]*)\\.(?:0|[1-9][0-9]*)(?:-[0-9A-Za-z.-]+)?");
}
private static int compareVersions(String left, String right) {
String[] a = numeric(left);
String[] b = numeric(right);
for (int index = 0; index < 3; index++) {
int comparison = Long.compare(Long.parseLong(a[index]), Long.parseLong(b[index]));
if (comparison != 0) return comparison;
}
return 0;
}
private static String[] numeric(String version) {
String normalized = version.strip().replaceFirst("^(?:>=|<=|>|<|=|\\^|~)\\s*", "")
.split("-", 2)[0];
String[] parts = normalized.split("\\.");
if (parts.length != 3) throw new IllegalArgumentException("invalid semantic version: " + version);
return parts;
}
private static Map<String, TriggerProgramV2> checkedPrograms(Map<String, TriggerProgramV2> values) {
Map<String, TriggerProgramV2> copy = new LinkedHashMap<>();
if (values != null) values.forEach((name, program) -> {
if (name == null || !name.matches("[A-Za-z_][A-Za-z0-9_.-]{0,127}") || program == null) {
throw new IllegalArgumentException("library trigger entry is invalid");
}
TriggerProgramTypeChecker.validate(program);
copy.put(name, program);
});
return Map.copyOf(copy);
}
private static Map<String, Object> immutable(Map<String, ?> values) {
return Map.copyOf(values == null ? Map.of() : values);
}
private static String text(String value, String field) {
if (value == null || value.isBlank()) throw new IllegalArgumentException(field + " is required");
return value.strip();
}
private static String bounded(String value, String fallback, int maximum) {
String result = value == null || value.isBlank() ? fallback : value.strip();
if (result.length() > maximum) throw new IllegalArgumentException("library text is too long");
return result;
}
}