From 050969ed16b19bf510076e38dbfbd4ff21e01c6ba8c8108b4002179ad7e4fe6f Mon Sep 17 00:00:00 2001 From: Minimons Date: Tue, 23 Jun 2026 13:35:49 +0200 Subject: [PATCH] 12: Add grace period support for persistent alarms --- .../impl/ref/AlarmConfigurationParser.tjava | 60 +++++++++++++++++-- .../impl/ref/PriceAlarm.tjava | 44 ++++++++++++-- .../impl/ref/PriceAlarmDefinition.tjava | 7 +++ .../impl/ref/TriggerConfiguration.java | 2 + 4 files changed, 104 insertions(+), 9 deletions(-) create mode 100644 src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/TriggerConfiguration.java diff --git a/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/AlarmConfigurationParser.tjava b/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/AlarmConfigurationParser.tjava index d67dacc..abc7916 100644 --- a/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/AlarmConfigurationParser.tjava +++ b/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/AlarmConfigurationParser.tjava @@ -13,6 +13,7 @@ public final class AlarmConfigurationParser { public static List parse(Path path) throws IOException { List lines = Files.readAllLines(path); List alarms = new ArrayList<>(); + Map constants = new LinkedHashMap<>(); for (int lineNumber = 1; lineNumber <= lines.size(); lineNumber++) { String line = lines.get(lineNumber - 1); @@ -59,8 +60,8 @@ public final class AlarmConfigurationParser { BigDecimal target = parseTarget(cursor.nextToken("target")); - AlarmTrigger trigger = AlarmTrigger.valueOf( - cursor.nextToken("trigger").toUpperCase(Locale.ROOT) + TriggerConfiguration triggerConfiguration = parseTrigger( + cursor.nextToken("trigger") ); AlarmSeverity severity = AlarmSeverity.valueOf( @@ -81,7 +82,8 @@ public final class AlarmConfigurationParser { asset, direction, target, - trigger, + triggerConfiguration.trigger(), + triggerConfiguration.gracePeriod(), severity, note ); @@ -109,6 +111,50 @@ public final class AlarmConfigurationParser { return target; } + private static TriggerConfiguration parseTrigger(String triggerText) { + String normalized = triggerText.toUpperCase(Locale.ROOT); + + if (normalized.equals("ONETIME")) { + return new TriggerConfiguration(AlarmTrigger.ONETIME, 0); + } + + if (normalized.equals("PERSISTENT")) { + return new TriggerConfiguration(AlarmTrigger.PERSISTENT, 0); + } + + if (normalized.startsWith("PERSISTENT:")) { + String graceText = normalized.substring("PERSISTENT:".length()); + + if (graceText.isEmpty()) { + throw new IllegalArgumentException("Missing persistent grace period: " + triggerText); + } + + ΩsecondsΩ gracePeriodSeconds; + try { + gracePeriodSeconds = Integer.parseInt(graceText); + } catch (NumberFormatException exception) { + throw new IllegalArgumentException( + "Invalid persistent grace period: " + triggerText, + exception + ); + } + + if (gracePeriodSeconds < 0) { + throw new IllegalArgumentException( + "Persistent grace period cannot be negative: " + triggerText + ); + } + + return new TriggerConfiguration(AlarmTrigger.PERSISTENT, gracePeriodSeconds); + } + + if (normalized.startsWith("ONETIME:")) { + throw new IllegalArgumentException("ONETIME cannot have a grace period: " + triggerText); + } + + throw new IllegalArgumentException("Unknown trigger: " + triggerText); + } + private static void validateTarget(BigDecimal target, String originalTargetStr) { if (target.compareTo(BigDecimal.ZERO) < 0) { throw new IllegalArgumentException( @@ -274,8 +320,12 @@ public final class AlarmConfigurationParser { } } - private AlarmConfigurationParser() { + private record TriggerConfiguration( + AlarmTrigger trigger, + ΩsecondsΩ gracePeriod + ) { } - private static Map constants = new LinkedHashMap<>(); + private AlarmConfigurationParser() { + } } diff --git a/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/PriceAlarm.tjava b/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/PriceAlarm.tjava index 13ad000..0022445 100644 --- a/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/PriceAlarm.tjava +++ b/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/PriceAlarm.tjava @@ -1,5 +1,7 @@ package com.r35157.jupiterperpsalarm.impl.ref; +import java.time.Instant; + public final class PriceAlarm { public PriceAlarm(PriceAlarmDefinition definition, AlarmAction action) { @@ -25,16 +27,27 @@ public final class PriceAlarm { previousReached = reached; - if (!enteredTriggeredSide) { + if (!reached) { return; } - if (definition.trigger() == AlarmTrigger.ONETIME && triggerCount > 0) { + if (definition.trigger() == AlarmTrigger.ONETIME) { + if (!enteredTriggeredSide || triggerCount > 0) { + return; + } + + trigger(price); return; } - triggerCount++; - action.trigger(price, definition); + if (definition.trigger() == AlarmTrigger.PERSISTENT) { + if (enteredTriggeredSide || persistentGracePeriodHasPassed()) { + trigger(price); + } + return; + } + + throw new IllegalStateException("Unsupported alarm trigger: " + definition.trigger()); } public PriceAlarmDefinition definition() { @@ -45,9 +58,32 @@ public final class PriceAlarm { return triggerCount; } + private boolean persistentGracePeriodHasPassed() { + if (lastTriggeredAt == null) { + return true; + } + + ΩsecondsΩ gracePeriod = definition.triggerGracePeriod(); + + if (gracePeriod == 0) { + return true; + } + + return !Instant.now().isBefore( + lastTriggeredAt.plusSeconds(gracePeriod) + ); + } + + private void trigger(OraclePrice price) { + triggerCount++; + lastTriggeredAt = Instant.now(); + action.trigger(price, definition); + } + private final PriceAlarmDefinition definition; private final AlarmAction action; + private Instant lastTriggeredAt; private Boolean previousReached; private long triggerCount; } diff --git a/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/PriceAlarmDefinition.tjava b/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/PriceAlarmDefinition.tjava index edcd9b0..e7085a6 100644 --- a/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/PriceAlarmDefinition.tjava +++ b/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/PriceAlarmDefinition.tjava @@ -11,6 +11,7 @@ public record PriceAlarmDefinition( PriceDirection direction, BigDecimal target, AlarmTrigger trigger, + ΩsecondsΩ triggerGracePeriod, AlarmSeverity severity, String note ) { @@ -24,5 +25,11 @@ public record PriceAlarmDefinition( if (target.signum() < 0) { throw new IllegalArgumentException("Target price cannot be negative (was: " + target.signum() + ")!"); } + + if (triggerGracePeriod < 0) { + throw new IllegalArgumentException( + "Trigger grace period cannot be negative: " + triggerGracePeriod + ); + } } } diff --git a/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/TriggerConfiguration.java b/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/TriggerConfiguration.java new file mode 100644 index 0000000..45716fb --- /dev/null +++ b/src/main/tjava/com/r35157/jupiterperpsalarm/impl/ref/TriggerConfiguration.java @@ -0,0 +1,2 @@ +package com.r35157.jupiterperpsalarm.impl.ref; +