From 5c63785f092f72c17c2433ba05316dd07157372a Mon Sep 17 00:00:00 2001 From: imDMK Date: Tue, 30 Sep 2025 11:17:53 +0200 Subject: [PATCH 1/5] delay API: rework delay system with per-entry Instant TTL --- .../eternalcode/core/delay/DefaultDelay.java | 63 +++++++ .../com/eternalcode/core/delay/Delay.java | 83 +++++---- .../eternalcode/core/delay/ExplicitDelay.java | 62 +++++++ .../core/delay/GuavaDefaultDelay.java | 42 +++++ .../eternalcode/core/delay/GuavaDelay.java | 174 ++++++++++++++++++ .../core/delay/GuavaExplicitDelay.java | 34 ++++ .../core/feature/afk/AfkCommand.java | 16 +- .../core/feature/helpop/HelpOpCommand.java | 14 +- .../randomteleport/RandomTeleportCommand.java | 24 +-- .../core/feature/repair/RepairCommand.java | 18 +- 10 files changed, 462 insertions(+), 68 deletions(-) create mode 100644 eternalcore-api/src/main/java/com/eternalcode/core/delay/DefaultDelay.java create mode 100644 eternalcore-api/src/main/java/com/eternalcode/core/delay/ExplicitDelay.java create mode 100644 eternalcore-api/src/main/java/com/eternalcode/core/delay/GuavaDefaultDelay.java create mode 100644 eternalcore-api/src/main/java/com/eternalcode/core/delay/GuavaDelay.java create mode 100644 eternalcore-api/src/main/java/com/eternalcode/core/delay/GuavaExplicitDelay.java diff --git a/eternalcore-api/src/main/java/com/eternalcode/core/delay/DefaultDelay.java b/eternalcore-api/src/main/java/com/eternalcode/core/delay/DefaultDelay.java new file mode 100644 index 000000000..1e217d552 --- /dev/null +++ b/eternalcore-api/src/main/java/com/eternalcode/core/delay/DefaultDelay.java @@ -0,0 +1,63 @@ +package com.eternalcode.core.delay; + +import java.time.Duration; +import java.time.Instant; + +/** + * Represents a delay mechanism with a predefined default duration. + *

+ * A key can be marked either with the default duration or with a custom one. + * + * @param the type of key used to identify the delay + */ +public interface DefaultDelay { + + /** + * Marks a delay for the given key using the predefined default duration. + * + * @param key the key to mark + */ + void markDelay(T key); + + /** + * Removes any existing delay for the given key. + * + * @param key the key to unmark + */ + void unmarkDelay(T key); + + /** + * Checks if the given key currently has an active delay. + * + * @param key the key to check + * @return true if the key has a delay, false otherwise + */ + boolean hasDelay(T key); + + /** + * Returns the remaining duration of the delay for the given key. + * Returns {@code Duration.ZERO} if no active delay exists. + * + * @param key the key to check + * @return remaining duration, or {@code Duration.ZERO} if none + */ + Duration getRemaining(T key); + + /** + * Returns the expiration time of the delay for the given key. + * Returns {@code null} if no active delay exists. + * + * @param key the key to check + * @return expiration instant, or {@code null} if none + */ + Instant getExpireAt(T key); + + /** + * Extends the delay for the given key by the specified extra duration. + * If no active delay exists, a new one is created starting now. + * + * @param key the key to extend + * @param extra the duration to add + */ + void extendDelay(T key, Duration extra); +} diff --git a/eternalcore-api/src/main/java/com/eternalcode/core/delay/Delay.java b/eternalcore-api/src/main/java/com/eternalcode/core/delay/Delay.java index 8d30ad07c..c2ad17bd2 100644 --- a/eternalcore-api/src/main/java/com/eternalcode/core/delay/Delay.java +++ b/eternalcore-api/src/main/java/com/eternalcode/core/delay/Delay.java @@ -1,50 +1,61 @@ package com.eternalcode.core.delay; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; - import java.time.Duration; -import java.time.Instant; -import java.util.function.Supplier; - -public class Delay { - - private final Cache delays; - private final Supplier delaySettings; - - public Delay(Supplier delayProvider) { - this.delaySettings = delayProvider; - - this.delays = CacheBuilder.newBuilder() - .expireAfterWrite(delayProvider.get()) - .build(); +/** + * Factory class for creating delay instances. + *

+ * Naming convention: + * - withDefault(...) -> DefaultDelay (uses predefined Duration) + * - explicit(...) -> ExplicitDelay (requires explicit Duration each time) + */ +public final class Delay { + + private Delay() { + throw new UnsupportedOperationException("Utility class - do not instantiate"); } - public void markDelay(T key, Duration delay) { - this.delays.put(key, Instant.now().plus(delay)); + /** + * Creates a DefaultDelay with the default cache size. + * + * @param defaultDelay the default duration used for delays + * @param the key type + * @return DefaultDelay instance + */ + public static DefaultDelay withDefault(Duration defaultDelay) { + return new GuavaDefaultDelay<>(defaultDelay); } - public void markDelay(T key) { - this.markDelay(key, this.delaySettings.get()); + /** + * Creates a DefaultDelay with a custom maximum cache size. + * + * @param defaultDelay the default duration used for delays + * @param maximumSize maximum number of entries in the cache + * @param the key type + * @return DefaultDelay instance + */ + public static DefaultDelay withDefault(Duration defaultDelay, long maximumSize) { + return new GuavaDefaultDelay<>(defaultDelay, maximumSize); } - public void unmarkDelay(T key) { - this.delays.invalidate(key); + /** + * Creates an ExplicitDelay with the default cache size. + * + * @param the key type + * @return ExplicitDelay instance + */ + public static ExplicitDelay explicit() { + return new GuavaExplicitDelay<>(); } - public boolean hasDelay(T key) { - Instant delayExpireMoment = this.getDelayExpireMoment(key); - - return Instant.now().isBefore(delayExpireMoment); + /** + * Creates an ExplicitDelay with a custom maximum cache size. + * + * @param maximumSize maximum number of entries in the cache + * @param the key type + * @return ExplicitDelay instance + */ + public static ExplicitDelay explicit(long maximumSize) { + return new GuavaExplicitDelay<>(maximumSize); } - - public Duration getDurationToExpire(T key) { - return Duration.between(Instant.now(), this.getDelayExpireMoment(key)); - } - - private Instant getDelayExpireMoment(T key) { - return this.delays.asMap().getOrDefault(key, Instant.MIN); - } - } diff --git a/eternalcore-api/src/main/java/com/eternalcode/core/delay/ExplicitDelay.java b/eternalcore-api/src/main/java/com/eternalcode/core/delay/ExplicitDelay.java new file mode 100644 index 000000000..4ccdf2b84 --- /dev/null +++ b/eternalcore-api/src/main/java/com/eternalcode/core/delay/ExplicitDelay.java @@ -0,0 +1,62 @@ +package com.eternalcode.core.delay; + +import java.time.Duration; +import java.time.Instant; + +/** + * Represents a delay mechanism where each key must be marked with an explicit duration. + * + * @param the type of key used to identify the delay + */ +public interface ExplicitDelay { + + /** + * Marks a delay for the given key with the specified duration. + * + * @param key the key to mark + * @param duration the duration of the delay + */ + void markDelay(T key, Duration duration); + + /** + * Removes any existing delay for the given key. + * + * @param key the key to unmark + */ + void unmarkDelay(T key); + + /** + * Checks if the given key currently has an active delay. + * + * @param key the key to check + * @return true if the key has a delay, false otherwise + */ + boolean hasDelay(T key); + + /** + * Returns the remaining duration of the delay for the given key. + * Returns {@code Duration.ZERO} if no active delay exists. + * + * @param key the key to check + * @return remaining duration, or {@code Duration.ZERO} if none + */ + Duration getRemaining(T key); + + /** + * Returns the expiration time of the delay for the given key. + * Returns {@code null} if no active delay exists. + * + * @param key the key to check + * @return expiration instant, or {@code null} if none + */ + Instant getExpireAt(T key); + + /** + * Extends the delay for the given key by the specified extra duration. + * If no active delay exists, a new one is created starting now. + * + * @param key the key to extend + * @param extra the duration to add + */ + void extendDelay(T key, Duration extra); +} diff --git a/eternalcore-api/src/main/java/com/eternalcode/core/delay/GuavaDefaultDelay.java b/eternalcore-api/src/main/java/com/eternalcode/core/delay/GuavaDefaultDelay.java new file mode 100644 index 000000000..b78658141 --- /dev/null +++ b/eternalcore-api/src/main/java/com/eternalcode/core/delay/GuavaDefaultDelay.java @@ -0,0 +1,42 @@ +package com.eternalcode.core.delay; + +import java.time.Duration; + +/** + * DefaultDelay implementation backed by {@link GuavaDelay} using Guava cache. + *

+ * Each key marked without explicit duration uses a predefined default delay. + * + * @param the type of key used to identify the delay + */ +final class GuavaDefaultDelay extends GuavaDelay implements DefaultDelay { + + private final Duration defaultDelay; + + /** + * Creates a new DefaultDelay with the specified default duration + * and the default maximum cache size. + * + * @param defaultDelay the default delay duration, must be positive + */ + GuavaDefaultDelay(Duration defaultDelay) { + this(defaultDelay, DEFAULT_MAXIMUM_SIZE); + } + + /** + * Creates a new DefaultDelay with the specified default duration + * and a custom maximum cache size. + * + * @param defaultDelay the default delay duration, must be positive + * @param maximumSize maximum number of entries allowed in the cache + */ + GuavaDefaultDelay(Duration defaultDelay, long maximumSize) { + super(defaultDelay, maximumSize); + this.defaultDelay = defaultDelay; + } + + @Override + public void markDelay(T key) { + putDelay(key, this.defaultDelay); + } +} diff --git a/eternalcore-api/src/main/java/com/eternalcode/core/delay/GuavaDelay.java b/eternalcore-api/src/main/java/com/eternalcode/core/delay/GuavaDelay.java new file mode 100644 index 000000000..147048cc3 --- /dev/null +++ b/eternalcore-api/src/main/java/com/eternalcode/core/delay/GuavaDelay.java @@ -0,0 +1,174 @@ +package com.eternalcode.core.delay; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; + +import java.time.Duration; +import java.time.Instant; + +/** + * Base class providing shared logic for delay implementations using a Guava cache. + *

+ * Each key is associated with an {@link Instant} representing the expiration time. + * Expired entries are cleaned up eagerly on read operations. + *

+ * Contract: + *

+ *

+ * Thread-safe as guaranteed by Guava's {@link Cache}. + * + * @param the type of key used to identify delays + */ +abstract class GuavaDelay { + + /** Default maximum number of cache entries. */ + protected static final long DEFAULT_MAXIMUM_SIZE = 50_000L; + + /** Underlying Guava cache mapping keys to expiration instants. */ + protected final Cache cache; + + /** + * Creates a new BaseDelay with a custom maximum cache size. + * + * @param maximumSize the maximum number of entries in the cache must be greater than 0 + */ + protected GuavaDelay(long maximumSize) { + this.cache = CacheBuilder.newBuilder() + .maximumSize(maximumSize) + .build(); + } + + /** + * Creates a new BaseDelay with a custom expireAfterWrite and maximum cache size. + * + * @param maximumSize the maximum number of entries in the cache must be greater than 0 + */ + protected GuavaDelay(Duration expireAfter, long maximumSize) { + this.cache = CacheBuilder.newBuilder() + .maximumSize(maximumSize) + .expireAfterWrite(expireAfter) + .build(); + } + + /** + * Stores a delay until the given expiration instant. + * + * @param key the key to mark + * @param expireAt the expiration instant + */ + protected void putDelay(T key, Instant expireAt) { + this.cache.put(key, expireAt); + } + + /** + * Stores a delay for the given duration starting from now. + * Durations <= 0 are treated as no delay and will remove the entry. + * + * @param key the key to mark + * @param duration the delay duration + */ + protected void putDelay(T key, Duration duration) { + if (duration.isZero() || duration.isNegative()) { + this.cache.invalidate(key); + return; + } + + this.cache.put(key, Instant.now().plus(duration)); + } + + /** + * Removes any existing delay for the given key. + * + * @param key the key to unmark + */ + public void unmarkDelay(T key) { + this.cache.invalidate(key); + } + + /** + * Checks if the given key currently has an active delay. + * Expired entries are removed on read. + * + * @param key the key to check + * @return true if an active delay exists, false otherwise + */ + public boolean hasDelay(T key) { + Instant until = this.cache.getIfPresent(key); + if (until == null) { + return false; + } + + if (Instant.now().isAfter(until)) { + this.cache.invalidate(key); + return false; + } + + return true; + } + + /** + * Returns the remaining duration of the delay for the given key. + * Returns {@code Duration.ZERO} if no active delay exists. + * + * @param key the key to check + * @return remaining duration, or {@code Duration.ZERO} if none + */ + public Duration getRemaining(T key) { + Instant until = this.cache.getIfPresent(key); + if (until == null) { + return Duration.ZERO; + } + + Duration left = Duration.between(Instant.now(), until); + if (left.isNegative() || left.isZero()) { + this.cache.invalidate(key); + return Duration.ZERO; + } + + return left; + } + + /** + * Returns the expiration instant of the delay for the given key. + * Returns {@code null} if no active delay exists. + * + * @param key the key to check + * @return expiration instant, or {@code null} if none + */ + public Instant getExpireAt(T key) { + Instant until = this.cache.getIfPresent(key); + if (until == null) { + return null; + } + + if (Instant.now().isAfter(until)) { + this.cache.invalidate(key); + return null; + } + + return until; + } + + /** + * Extends the delay for the given key by the specified duration. + * If no active delay exists, a new one is created starting now. + * Durations <= 0 are ignored. + * + * @param key the key to extend + * @param extra the duration to add + */ + public void extendDelay(T key, Duration extra) { + if (extra.isZero() || extra.isNegative()) { + return; + } + + Instant base = this.cache.getIfPresent(key); + Instant now = Instant.now(); + Instant start = (base == null || now.isAfter(base)) ? now : base; + this.cache.put(key, start.plus(extra)); + } +} diff --git a/eternalcore-api/src/main/java/com/eternalcode/core/delay/GuavaExplicitDelay.java b/eternalcore-api/src/main/java/com/eternalcode/core/delay/GuavaExplicitDelay.java new file mode 100644 index 000000000..d53cc5104 --- /dev/null +++ b/eternalcore-api/src/main/java/com/eternalcode/core/delay/GuavaExplicitDelay.java @@ -0,0 +1,34 @@ +package com.eternalcode.core.delay; + +import java.time.Duration; + +/** + * ExplicitDelay implementation backed by {@link GuavaDelay} using Guava cache. + *

+ * Each key must be marked with an explicit duration when creating a delay. + * + * @param the type of key used to identify the delay + */ +final class GuavaExplicitDelay extends GuavaDelay implements ExplicitDelay { + + /** + * Creates a new ExplicitDelay with the default maximum cache size. + */ + GuavaExplicitDelay() { + this(DEFAULT_MAXIMUM_SIZE); + } + + /** + * Creates a new ExplicitDelay with a custom maximum cache size. + * + * @param maximumSize maximum number of entries allowed in the cache + */ + GuavaExplicitDelay(long maximumSize) { + super(maximumSize); + } + + @Override + public void markDelay(T key, Duration duration) { + putDelay(key, duration); + } +} diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/afk/AfkCommand.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/afk/AfkCommand.java index d37aa2270..c61292ed5 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/afk/AfkCommand.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/afk/AfkCommand.java @@ -1,9 +1,8 @@ package com.eternalcode.core.feature.afk; -import static com.eternalcode.core.feature.afk.AfkCommand.AFK_BYPASS_PERMISSION; - import com.eternalcode.annotations.scan.command.DescriptionDocs; import com.eternalcode.annotations.scan.permission.PermissionDocs; +import com.eternalcode.core.delay.DefaultDelay; import com.eternalcode.core.delay.Delay; import com.eternalcode.core.injector.annotations.Inject; import com.eternalcode.core.notice.NoticeService; @@ -12,9 +11,12 @@ import dev.rollczi.litecommands.annotations.context.Sender; import dev.rollczi.litecommands.annotations.execute.Execute; import dev.rollczi.litecommands.annotations.permission.Permission; +import org.bukkit.entity.Player; + import java.time.Duration; import java.util.UUID; -import org.bukkit.entity.Player; + +import static com.eternalcode.core.feature.afk.AfkCommand.AFK_BYPASS_PERMISSION; @Command(name = "afk") @Permission("eternalcore.afk") @@ -30,14 +32,14 @@ class AfkCommand { private final NoticeService noticeService; private final AfkSettings afkSettings; private final AfkService afkService; - private final Delay delay; + private final DefaultDelay delay; @Inject AfkCommand(NoticeService noticeService, AfkSettings afkSettings, AfkService afkService) { this.noticeService = noticeService; this.afkSettings = afkSettings; this.afkService = afkService; - this.delay = new Delay<>(() -> this.afkSettings.afkCommandDelay()); + this.delay = Delay.withDefault(this.afkSettings.afkCommandDelay()); } @Execute @@ -51,7 +53,7 @@ void execute(@Sender Player player) { } if (this.delay.hasDelay(uuid)) { - Duration time = this.delay.getDurationToExpire(uuid); + Duration time = this.delay.getRemaining(uuid); this.noticeService .create() @@ -64,6 +66,6 @@ void execute(@Sender Player player) { } this.afkService.switchAfk(uuid, AfkReason.COMMAND); - this.delay.markDelay(uuid, this.afkSettings.afkCommandDelay()); + this.delay.markDelay(uuid); } } diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/helpop/HelpOpCommand.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/helpop/HelpOpCommand.java index ad77e0d31..0a277d5cc 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/helpop/HelpOpCommand.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/helpop/HelpOpCommand.java @@ -2,6 +2,7 @@ import com.eternalcode.annotations.scan.command.DescriptionDocs; import com.eternalcode.annotations.scan.permission.PermissionDocs; +import com.eternalcode.core.delay.DefaultDelay; import com.eternalcode.core.delay.Delay; import com.eternalcode.core.event.EventCaller; import com.eternalcode.core.feature.helpop.event.HelpOpEvent; @@ -14,11 +15,12 @@ import dev.rollczi.litecommands.annotations.execute.Execute; import dev.rollczi.litecommands.annotations.join.Join; import dev.rollczi.litecommands.annotations.permission.Permission; -import java.time.Duration; -import java.util.UUID; import org.bukkit.Server; import org.bukkit.entity.Player; +import java.time.Duration; +import java.util.UUID; + @Command(name = "helpop", aliases = { "report" }) @Permission("eternalcore.helpop") @PermissionDocs( @@ -34,7 +36,7 @@ class HelpOpCommand { private final HelpOpSettings helpOpSettings; private final EventCaller eventCaller; private final Server server; - private final Delay delay; + private final DefaultDelay delay; @Inject HelpOpCommand(NoticeService noticeService, HelpOpSettings helpOpSettings, EventCaller eventCaller, Server server) { @@ -42,7 +44,7 @@ class HelpOpCommand { this.helpOpSettings = helpOpSettings; this.eventCaller = eventCaller; this.server = server; - this.delay = new Delay<>(() -> this.helpOpSettings.helpOpDelay()); + this.delay = Delay.withDefault(this.helpOpSettings.helpOpDelay()); } @Execute @@ -58,7 +60,7 @@ void execute(@Sender Player player, @Join String message) { } if (this.delay.hasDelay(uuid)) { - Duration time = this.delay.getDurationToExpire(uuid); + Duration time = this.delay.getRemaining(uuid); this.noticeService.create() .notice(translation -> translation.helpOp().helpOpDelay()) @@ -91,7 +93,7 @@ void execute(@Sender Player player, @Join String message) { .notice(translation -> translation.helpOp().send()) .send(); - this.delay.markDelay(uuid, this.helpOpSettings.helpOpDelay()); + this.delay.markDelay(uuid); } } diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/randomteleport/RandomTeleportCommand.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/randomteleport/RandomTeleportCommand.java index 201a96bc1..2aa863902 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/randomteleport/RandomTeleportCommand.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/randomteleport/RandomTeleportCommand.java @@ -1,11 +1,7 @@ package com.eternalcode.core.feature.randomteleport; -import static com.eternalcode.core.feature.randomteleport.RandomTeleportPermissionConstant.RTP_BYPASS_PERMISSION; -import static com.eternalcode.core.feature.randomteleport.RandomTeleportPermissionConstant.RTP_COMMAND_OTHER; -import static com.eternalcode.core.feature.randomteleport.RandomTeleportPermissionConstant.RTP_COMMAND_SELF; -import static com.eternalcode.core.feature.randomteleport.RandomTeleportPlaceholders.PLACEHOLDERS; - import com.eternalcode.annotations.scan.command.DescriptionDocs; +import com.eternalcode.core.delay.DefaultDelay; import com.eternalcode.core.delay.Delay; import com.eternalcode.core.injector.annotations.Inject; import com.eternalcode.core.notice.NoticeService; @@ -16,9 +12,15 @@ import dev.rollczi.litecommands.annotations.context.Sender; import dev.rollczi.litecommands.annotations.execute.Execute; import dev.rollczi.litecommands.annotations.permission.Permission; +import org.bukkit.entity.Player; + import java.time.Duration; import java.util.UUID; -import org.bukkit.entity.Player; + +import static com.eternalcode.core.feature.randomteleport.RandomTeleportPermissionConstant.RTP_BYPASS_PERMISSION; +import static com.eternalcode.core.feature.randomteleport.RandomTeleportPermissionConstant.RTP_COMMAND_OTHER; +import static com.eternalcode.core.feature.randomteleport.RandomTeleportPermissionConstant.RTP_COMMAND_SELF; +import static com.eternalcode.core.feature.randomteleport.RandomTeleportPlaceholders.PLACEHOLDERS; @Command(name = "rtp", aliases = "randomteleport") class RandomTeleportCommand { @@ -27,7 +29,7 @@ class RandomTeleportCommand { private final RandomTeleportService randomTeleportService; private final RandomTeleportTaskService randomTeleportTaskService; private final RandomTeleportSettings randomTeleportSettings; - private final Delay cooldown; + private final DefaultDelay cooldown; @Inject RandomTeleportCommand( @@ -40,7 +42,7 @@ class RandomTeleportCommand { this.randomTeleportService = randomTeleportService; this.randomTeleportTaskService = randomTeleportTaskService; this.randomTeleportSettings = randomTeleportSettings; - this.cooldown = new Delay<>(() -> this.randomTeleportSettings.cooldown()); + this.cooldown = Delay.withDefault(this.randomTeleportSettings.cooldown()); } @Execute @@ -68,7 +70,7 @@ void executeSelf(@Sender Player player) { this.handleTeleportSuccess(player); }); - this.cooldown.markDelay(uuid, this.randomTeleportSettings.cooldown()); + this.cooldown.markDelay(uuid); } @Execute @@ -96,7 +98,7 @@ void executeOther(@Sender Viewer sender, @Arg Player player) { this.handleAdminTeleport(sender, player); }); - this.cooldown.markDelay(uuid, this.randomTeleportSettings.cooldown()); + this.cooldown.markDelay(uuid); } private void handleTeleportSuccess(Player player) { @@ -129,7 +131,7 @@ private boolean hasRandomTeleportDelay(Player player) { } if (this.cooldown.hasDelay(uniqueId)) { - Duration time = this.cooldown.getDurationToExpire(uniqueId); + Duration time = this.cooldown.getRemaining(uniqueId); this.noticeService.create() .notice(translation -> translation.randomTeleport().randomTeleportDelay()) diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/repair/RepairCommand.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/repair/RepairCommand.java index 3fa32d1c1..cc3024f7f 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/repair/RepairCommand.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/repair/RepairCommand.java @@ -1,6 +1,7 @@ package com.eternalcode.core.feature.repair; import com.eternalcode.annotations.scan.command.DescriptionDocs; +import com.eternalcode.core.delay.DefaultDelay; import com.eternalcode.core.delay.Delay; import com.eternalcode.core.injector.annotations.Inject; import com.eternalcode.core.notice.NoticeService; @@ -9,8 +10,6 @@ import dev.rollczi.litecommands.annotations.context.Sender; import dev.rollczi.litecommands.annotations.execute.Execute; import dev.rollczi.litecommands.annotations.permission.Permission; -import java.time.Duration; -import java.util.UUID; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.PlayerInventory; @@ -18,18 +17,21 @@ import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.Repairable; +import java.time.Duration; +import java.util.UUID; + @Command(name = "repair") class RepairCommand { private final NoticeService noticeService; - private final Delay delay; + private final DefaultDelay delay; private final RepairSettings repairSettings; @Inject RepairCommand(NoticeService noticeService, RepairSettings repairSettings) { this.noticeService = noticeService; this.repairSettings = repairSettings; - this.delay = new Delay<>(() -> this.repairSettings.repairDelay()); + this.delay = Delay.withDefault(this.repairSettings.repairDelay()); } @Execute @@ -73,7 +75,7 @@ void repair(@Sender Player player) { .player(player.getUniqueId()) .send(); - this.delay.markDelay(uuid, this.repairSettings.repairDelay()); + this.delay.markDelay(uuid); } @Execute(name = "all") @@ -117,7 +119,7 @@ void repairAll(@Sender Player player) { .player(player.getUniqueId()) .send(); - this.delay.markDelay(uuid, this.repairSettings.repairDelay()); + this.delay.markDelay(uuid); } @Execute(name = "armor") @@ -161,12 +163,12 @@ void repairArmor(@Sender Player player) { .player(player.getUniqueId()) .send(); - this.delay.markDelay(uuid, this.repairSettings.repairDelay()); + this.delay.markDelay(uuid); } private boolean hasRepairDelay(UUID uuid) { if (this.delay.hasDelay(uuid)) { - Duration time = this.delay.getDurationToExpire(uuid); + Duration time = this.delay.getRemaining(uuid); this.noticeService .create() From 7de9479ea281005b9044ea374d2cdeebb1e42000 Mon Sep 17 00:00:00 2001 From: imDMK Date: Tue, 30 Sep 2025 11:37:38 +0200 Subject: [PATCH 2/5] Follow GEMINI code review --- .../eternalcode/core/delay/DefaultDelay.java | 57 ++----------------- .../eternalcode/core/delay/DelayActions.java | 39 +++++++++++++ .../eternalcode/core/delay/ExplicitDelay.java | 55 ++---------------- .../core/delay/GuavaDefaultDelay.java | 36 ++++++++---- .../eternalcode/core/delay/GuavaDelay.java | 16 +----- 5 files changed, 76 insertions(+), 127 deletions(-) create mode 100644 eternalcore-api/src/main/java/com/eternalcode/core/delay/DelayActions.java diff --git a/eternalcore-api/src/main/java/com/eternalcode/core/delay/DefaultDelay.java b/eternalcore-api/src/main/java/com/eternalcode/core/delay/DefaultDelay.java index 1e217d552..93c1afd3b 100644 --- a/eternalcore-api/src/main/java/com/eternalcode/core/delay/DefaultDelay.java +++ b/eternalcore-api/src/main/java/com/eternalcode/core/delay/DefaultDelay.java @@ -1,63 +1,14 @@ package com.eternalcode.core.delay; -import java.time.Duration; -import java.time.Instant; - /** - * Represents a delay mechanism with a predefined default duration. - *

- * A key can be marked either with the default duration or with a custom one. + * Delay with a predefined default duration, with an option to override per call. * - * @param the type of key used to identify the delay + * @param key type */ -public interface DefaultDelay { +public interface DefaultDelay extends ExplicitDelay { /** - * Marks a delay for the given key using the predefined default duration. - * - * @param key the key to mark + * Marks the key using the configured default duration. */ void markDelay(T key); - - /** - * Removes any existing delay for the given key. - * - * @param key the key to unmark - */ - void unmarkDelay(T key); - - /** - * Checks if the given key currently has an active delay. - * - * @param key the key to check - * @return true if the key has a delay, false otherwise - */ - boolean hasDelay(T key); - - /** - * Returns the remaining duration of the delay for the given key. - * Returns {@code Duration.ZERO} if no active delay exists. - * - * @param key the key to check - * @return remaining duration, or {@code Duration.ZERO} if none - */ - Duration getRemaining(T key); - - /** - * Returns the expiration time of the delay for the given key. - * Returns {@code null} if no active delay exists. - * - * @param key the key to check - * @return expiration instant, or {@code null} if none - */ - Instant getExpireAt(T key); - - /** - * Extends the delay for the given key by the specified extra duration. - * If no active delay exists, a new one is created starting now. - * - * @param key the key to extend - * @param extra the duration to add - */ - void extendDelay(T key, Duration extra); } diff --git a/eternalcore-api/src/main/java/com/eternalcode/core/delay/DelayActions.java b/eternalcore-api/src/main/java/com/eternalcode/core/delay/DelayActions.java new file mode 100644 index 000000000..07abc0c30 --- /dev/null +++ b/eternalcore-api/src/main/java/com/eternalcode/core/delay/DelayActions.java @@ -0,0 +1,39 @@ +package com.eternalcode.core.delay; + +import java.time.Duration; +import java.time.Instant; + +/** + * Common delay operations shared by all delay types. + * + * @param key type + */ +interface DelayActions { + + /** + * Removes any existing delay for the key. + * */ + void unmarkDelay(T key); + + /** + * @return true, if the key has an active delay; expired entries are cleaned on read. + */ + boolean hasDelay(T key); + + /** + * @return remaining duration or {@code Duration.ZERO} if none/expired. + */ + Duration getRemaining(T key); + + /** + * @return expiration instant or {@code null} if none/expired. + */ + Instant getExpireAt(T key); + + /** + * Extends current delay by {@code extra}; if none/expired, starts now. + * Non-positive durations are ignored. + */ + void extendDelay(T key, Duration extra); +} + diff --git a/eternalcore-api/src/main/java/com/eternalcode/core/delay/ExplicitDelay.java b/eternalcore-api/src/main/java/com/eternalcode/core/delay/ExplicitDelay.java index 4ccdf2b84..651cf643a 100644 --- a/eternalcore-api/src/main/java/com/eternalcode/core/delay/ExplicitDelay.java +++ b/eternalcore-api/src/main/java/com/eternalcode/core/delay/ExplicitDelay.java @@ -1,62 +1,17 @@ package com.eternalcode.core.delay; import java.time.Duration; -import java.time.Instant; /** - * Represents a delay mechanism where each key must be marked with an explicit duration. + * Delay that requires an explicit duration on marking. * - * @param the type of key used to identify the delay + * @param key type */ -public interface ExplicitDelay { +public interface ExplicitDelay extends DelayActions { /** - * Marks a delay for the given key with the specified duration. - * - * @param key the key to mark - * @param duration the duration of the delay + * Marks the key with the given duration. + * Non-positive durations remove the entry. */ void markDelay(T key, Duration duration); - - /** - * Removes any existing delay for the given key. - * - * @param key the key to unmark - */ - void unmarkDelay(T key); - - /** - * Checks if the given key currently has an active delay. - * - * @param key the key to check - * @return true if the key has a delay, false otherwise - */ - boolean hasDelay(T key); - - /** - * Returns the remaining duration of the delay for the given key. - * Returns {@code Duration.ZERO} if no active delay exists. - * - * @param key the key to check - * @return remaining duration, or {@code Duration.ZERO} if none - */ - Duration getRemaining(T key); - - /** - * Returns the expiration time of the delay for the given key. - * Returns {@code null} if no active delay exists. - * - * @param key the key to check - * @return expiration instant, or {@code null} if none - */ - Instant getExpireAt(T key); - - /** - * Extends the delay for the given key by the specified extra duration. - * If no active delay exists, a new one is created starting now. - * - * @param key the key to extend - * @param extra the duration to add - */ - void extendDelay(T key, Duration extra); } diff --git a/eternalcore-api/src/main/java/com/eternalcode/core/delay/GuavaDefaultDelay.java b/eternalcore-api/src/main/java/com/eternalcode/core/delay/GuavaDefaultDelay.java index b78658141..b3cb6b3c7 100644 --- a/eternalcore-api/src/main/java/com/eternalcode/core/delay/GuavaDefaultDelay.java +++ b/eternalcore-api/src/main/java/com/eternalcode/core/delay/GuavaDefaultDelay.java @@ -3,40 +3,56 @@ import java.time.Duration; /** - * DefaultDelay implementation backed by {@link GuavaDelay} using Guava cache. + * {@link DefaultDelay} implementation backed by {@link GuavaDelay}. *

- * Each key marked without explicit duration uses a predefined default delay. + * Stores a per-entry expiration instant in the cache. Unlike using + * {@code expireAfterWrite}, each entry is managed individually by its + * {@link java.time.Instant}. + *

+ * Calling {@link #markDelay(Object)} uses the predefined {@link #defaultDelay}. * - * @param the type of key used to identify the delay + * @param the type of key used to identify delays */ final class GuavaDefaultDelay extends GuavaDelay implements DefaultDelay { private final Duration defaultDelay; /** - * Creates a new DefaultDelay with the specified default duration - * and the default maximum cache size. + * Creates a new delay manager with the given default delay and + * the default maximum cache size. * - * @param defaultDelay the default delay duration, must be positive + * @param defaultDelay the default duration applied when marking a key, + * must be positive */ GuavaDefaultDelay(Duration defaultDelay) { this(defaultDelay, DEFAULT_MAXIMUM_SIZE); } /** - * Creates a new DefaultDelay with the specified default duration - * and a custom maximum cache size. + * Creates a new delay manager with the given default delay and a + * custom maximum cache size. * - * @param defaultDelay the default delay duration, must be positive + * @param defaultDelay the default duration applied when marking a key, + * must be positive * @param maximumSize maximum number of entries allowed in the cache */ GuavaDefaultDelay(Duration defaultDelay, long maximumSize) { - super(defaultDelay, maximumSize); + super(maximumSize); this.defaultDelay = defaultDelay; } + /** + * Marks the specified key with the configured {@link #defaultDelay}. + * + * @param key the key to mark + */ @Override public void markDelay(T key) { putDelay(key, this.defaultDelay); } + + @Override + public void markDelay(T key, Duration duration) { + putDelay(key, duration); + } } diff --git a/eternalcore-api/src/main/java/com/eternalcode/core/delay/GuavaDelay.java b/eternalcore-api/src/main/java/com/eternalcode/core/delay/GuavaDelay.java index 147048cc3..8653b46a6 100644 --- a/eternalcore-api/src/main/java/com/eternalcode/core/delay/GuavaDelay.java +++ b/eternalcore-api/src/main/java/com/eternalcode/core/delay/GuavaDelay.java @@ -32,9 +32,9 @@ abstract class GuavaDelay { protected final Cache cache; /** - * Creates a new BaseDelay with a custom maximum cache size. + * Creates a new GuavaDelay with a custom maximum cache size. * - * @param maximumSize the maximum number of entries in the cache must be greater than 0 + * @param maximumSize the maximum number of entries in the cache */ protected GuavaDelay(long maximumSize) { this.cache = CacheBuilder.newBuilder() @@ -42,18 +42,6 @@ protected GuavaDelay(long maximumSize) { .build(); } - /** - * Creates a new BaseDelay with a custom expireAfterWrite and maximum cache size. - * - * @param maximumSize the maximum number of entries in the cache must be greater than 0 - */ - protected GuavaDelay(Duration expireAfter, long maximumSize) { - this.cache = CacheBuilder.newBuilder() - .maximumSize(maximumSize) - .expireAfterWrite(expireAfter) - .build(); - } - /** * Stores a delay until the given expiration instant. * From 4d0f3be41f4d959c52faef5e0c39828a006be077 Mon Sep 17 00:00:00 2001 From: DMK <81445555+imDMK@users.noreply.github.com> Date: Tue, 30 Sep 2025 14:56:02 +0200 Subject: [PATCH 3/5] Update eternalcore-api/src/main/java/com/eternalcode/core/delay/GuavaDelay.java Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../src/main/java/com/eternalcode/core/delay/GuavaDelay.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eternalcore-api/src/main/java/com/eternalcode/core/delay/GuavaDelay.java b/eternalcore-api/src/main/java/com/eternalcode/core/delay/GuavaDelay.java index 8653b46a6..0a46db3cc 100644 --- a/eternalcore-api/src/main/java/com/eternalcode/core/delay/GuavaDelay.java +++ b/eternalcore-api/src/main/java/com/eternalcode/core/delay/GuavaDelay.java @@ -156,7 +156,7 @@ public void extendDelay(T key, Duration extra) { Instant base = this.cache.getIfPresent(key); Instant now = Instant.now(); - Instant start = (base == null || now.isAfter(base)) ? now : base; + Instant start = (base == null || !now.isBefore(base)) ? now : base; this.cache.put(key, start.plus(extra)); } } From c9c0cae13cb6317f396312ed689dcc56a338567e Mon Sep 17 00:00:00 2001 From: DMK <81445555+imDMK@users.noreply.github.com> Date: Tue, 30 Sep 2025 14:56:09 +0200 Subject: [PATCH 4/5] Update eternalcore-api/src/main/java/com/eternalcode/core/delay/GuavaDelay.java Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../src/main/java/com/eternalcode/core/delay/GuavaDelay.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eternalcore-api/src/main/java/com/eternalcode/core/delay/GuavaDelay.java b/eternalcore-api/src/main/java/com/eternalcode/core/delay/GuavaDelay.java index 0a46db3cc..85db812ab 100644 --- a/eternalcore-api/src/main/java/com/eternalcode/core/delay/GuavaDelay.java +++ b/eternalcore-api/src/main/java/com/eternalcode/core/delay/GuavaDelay.java @@ -90,7 +90,7 @@ public boolean hasDelay(T key) { return false; } - if (Instant.now().isAfter(until)) { + if (!Instant.now().isBefore(until)) { this.cache.invalidate(key); return false; } From b67ef4372303d5c5a6977e07669554698bd744db Mon Sep 17 00:00:00 2001 From: DMK <81445555+imDMK@users.noreply.github.com> Date: Wed, 1 Oct 2025 10:50:55 +0200 Subject: [PATCH 5/5] Update eternalcore-api/src/main/java/com/eternalcode/core/delay/Delay.java Co-authored-by: Igor Michalski --- .../src/main/java/com/eternalcode/core/delay/Delay.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eternalcore-api/src/main/java/com/eternalcode/core/delay/Delay.java b/eternalcore-api/src/main/java/com/eternalcode/core/delay/Delay.java index c2ad17bd2..22a8c789d 100644 --- a/eternalcore-api/src/main/java/com/eternalcode/core/delay/Delay.java +++ b/eternalcore-api/src/main/java/com/eternalcode/core/delay/Delay.java @@ -12,7 +12,7 @@ public final class Delay { private Delay() { - throw new UnsupportedOperationException("Utility class - do not instantiate"); + throw new UnsupportedOperationException("This is a utility class and cannot be instantiated"); } /**