From d70ef55096a305d8c250484bb055d2103ebc6091 Mon Sep 17 00:00:00 2001 From: Martin Sulikowski Date: Fri, 27 Feb 2026 20:23:40 +0100 Subject: [PATCH 1/3] Add default command config. --- .../configurator/CommandConfigurator.java | 104 ++++++++++++++++-- 1 file changed, 97 insertions(+), 7 deletions(-) diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/litecommand/configurator/CommandConfigurator.java b/eternalcore-core/src/main/java/com/eternalcode/core/litecommand/configurator/CommandConfigurator.java index 8796e7a55..91bd3f995 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/litecommand/configurator/CommandConfigurator.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/litecommand/configurator/CommandConfigurator.java @@ -1,6 +1,7 @@ package com.eternalcode.core.litecommand.configurator; +import com.eternalcode.core.configuration.ConfigurationManager; import com.eternalcode.core.litecommand.configurator.config.Command; import com.eternalcode.core.litecommand.configurator.config.CommandConfiguration; import com.eternalcode.core.litecommand.configurator.config.SubCommand; @@ -10,7 +11,12 @@ import dev.rollczi.litecommands.editor.Editor; import dev.rollczi.litecommands.meta.Meta; import dev.rollczi.litecommands.permission.PermissionSet; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; import java.util.function.UnaryOperator; import org.bukkit.command.CommandSender; @@ -18,37 +24,121 @@ class CommandConfigurator implements Editor { private final CommandConfiguration commandConfiguration; + private final ConfigurationManager configurationManager; @Inject - CommandConfigurator(CommandConfiguration commandConfiguration) { + CommandConfigurator(CommandConfiguration commandConfiguration, ConfigurationManager configurationManager) { this.commandConfiguration = commandConfiguration; + this.configurationManager = configurationManager; } @Override public CommandBuilder edit(CommandBuilder context) { + if (context.isRoot() || context.name() == null || context.name().isBlank()) { + return context; + } + + boolean changed = this.synchronizeDefaults(context); + if (changed) { + this.configurationManager.save(this.commandConfiguration); + } + Command command = this.commandConfiguration.commands.get(context.name()); if (command == null) { return context; } - for (String child : command.subCommands().keySet()) { - SubCommand subCommand = command.subCommands().get(child); + for (Map.Entry childEntry : command.subCommands().entrySet()) { + String child = childEntry.getKey(); + SubCommand subCommand = childEntry.getValue(); + + if (context.getChild(child).isEmpty()) { + continue; + } context = context.editChild(child, editor -> editor.name(subCommand.name()) - .aliases(subCommand.aliases()) - .applyMeta(editPermissions(command.permissions())) + .aliases(safeList(subCommand.aliases())) + .applyMeta(editPermissions(safeList(subCommand.permissions()))) .enabled(subCommand.isEnabled()) ); } return context .name(command.name()) - .aliases(command.aliases()) - .applyMeta(editPermissions(command.permissions())) + .aliases(safeList(command.aliases())) + .applyMeta(editPermissions(safeList(command.permissions()))) .enabled(command.isEnabled()); } + private boolean synchronizeDefaults(CommandBuilder context) { + this.commandConfiguration.commands = mutableMap(this.commandConfiguration.commands); + boolean changed = this.commandConfiguration.commands.remove("") != null; + + Command current = this.commandConfiguration.commands.get(context.name()); + + if (current == null) { + current = new Command( + context.name(), + new ArrayList<>(context.aliases()), + extractPermissions(context.meta()), + context.isEnabled() + ); + current.subCommands = new LinkedHashMap<>(); + + this.commandConfiguration.commands.put(context.name(), current); + changed = true; + } + + current.subCommands = mutableMap(current.subCommands); + + for (CommandBuilder child : context.children()) { + if (current.subCommands.containsKey(child.name())) { + continue; + } + + current.subCommands.put( + child.name(), + new SubCommand( + child.name(), + child.isEnabled(), + new ArrayList<>(child.aliases()), + extractPermissions(child.meta()) + ) + ); + changed = true; + } + + return changed; + } + + private static List extractPermissions(Meta meta) { + Collection permissionSets = meta.get(Meta.PERMISSIONS); + LinkedHashSet permissions = new LinkedHashSet<>(); + + for (PermissionSet permissionSet : permissionSets) { + permissions.addAll(permissionSet.getPermissions()); + } + + return new ArrayList<>(permissions); + } + + private static Map mutableMap(Map map) { + if (map == null) { + return new LinkedHashMap<>(); + } + + return new LinkedHashMap<>(map); + } + + private static List safeList(List value) { + if (value == null) { + return List.of(); + } + + return value; + } + private static UnaryOperator editPermissions(List permissions) { return meta -> meta.listEditor(Meta.PERMISSIONS) .clear() From b54dbedcf73996b69dc1ba2517570f3899c5add3 Mon Sep 17 00:00:00 2001 From: Martin Sulikowski Date: Fri, 27 Feb 2026 20:41:51 +0100 Subject: [PATCH 2/3] add anti-idiot feature to command configurator. --- .../configurator/CommandConfigurator.java | 105 ++++++++++++++++-- 1 file changed, 98 insertions(+), 7 deletions(-) diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/litecommand/configurator/CommandConfigurator.java b/eternalcore-core/src/main/java/com/eternalcode/core/litecommand/configurator/CommandConfigurator.java index 91bd3f995..ae7eeb807 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/litecommand/configurator/CommandConfigurator.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/litecommand/configurator/CommandConfigurator.java @@ -5,19 +5,25 @@ import com.eternalcode.core.litecommand.configurator.config.Command; import com.eternalcode.core.litecommand.configurator.config.CommandConfiguration; import com.eternalcode.core.litecommand.configurator.config.SubCommand; +import com.eternalcode.commons.scheduler.Scheduler; import com.eternalcode.core.injector.annotations.Inject; import com.eternalcode.core.injector.annotations.lite.LiteCommandEditor; import dev.rollczi.litecommands.command.builder.CommandBuilder; import dev.rollczi.litecommands.editor.Editor; import dev.rollczi.litecommands.meta.Meta; import dev.rollczi.litecommands.permission.PermissionSet; +import java.time.Duration; import java.util.ArrayList; import java.util.Collection; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.UnaryOperator; +import java.util.logging.Logger; import org.bukkit.command.CommandSender; @LiteCommandEditor @@ -25,25 +31,44 @@ class CommandConfigurator implements Editor { private final CommandConfiguration commandConfiguration; private final ConfigurationManager configurationManager; + private final Scheduler scheduler; + private final Logger logger; + private final Set discoveredRuntimeCommands = ConcurrentHashMap.newKeySet(); + private volatile boolean staleCleanupScheduled; @Inject - CommandConfigurator(CommandConfiguration commandConfiguration, ConfigurationManager configurationManager) { + CommandConfigurator( + CommandConfiguration commandConfiguration, + ConfigurationManager configurationManager, + Scheduler scheduler, + Logger logger + ) { this.commandConfiguration = commandConfiguration; this.configurationManager = configurationManager; + this.scheduler = scheduler; + this.logger = logger; } @Override public CommandBuilder edit(CommandBuilder context) { - if (context.isRoot() || context.name() == null || context.name().isBlank()) { + if (context.isRoot()) { + this.scheduleStaleCleanupOnce(); return context; } + if (context.name() == null || context.name().isBlank()) { + return context; + } + + String commandName = context.name(); + this.discoveredRuntimeCommands.add(commandName); + boolean changed = this.synchronizeDefaults(context); if (changed) { this.configurationManager.save(this.commandConfiguration); } - Command command = this.commandConfiguration.commands.get(context.name()); + Command command = this.commandConfiguration.commands.get(commandName); if (command == null) { return context; @@ -71,22 +96,85 @@ public CommandBuilder edit(CommandBuilder context) .enabled(command.isEnabled()); } - private boolean synchronizeDefaults(CommandBuilder context) { + private void scheduleStaleCleanupOnce() { + if (this.staleCleanupScheduled) { + return; + } + + this.staleCleanupScheduled = true; + this.scheduler.runLater(this::cleanupStaleCommands, Duration.ofSeconds(1)); + } + + private void cleanupStaleCommands() { this.commandConfiguration.commands = mutableMap(this.commandConfiguration.commands); boolean changed = this.commandConfiguration.commands.remove("") != null; - Command current = this.commandConfiguration.commands.get(context.name()); + if (changed) { + this.logger.warning("[CommandConfigurator] Removed invalid empty command key ('') from commands.yml"); + } + + Set runtimeNames = new HashSet<>(this.discoveredRuntimeCommands); + int configuredCount = this.commandConfiguration.commands.size(); + int discoveredCount = runtimeNames.size(); + + if (configuredCount > 0 && discoveredCount == 0) { + this.logger.warning("[CommandConfigurator] Skipped stale cleanup because no runtime commands were discovered."); + if (changed) { + this.configurationManager.save(this.commandConfiguration); + } + return; + } + + if (configuredCount > 8 && discoveredCount < (configuredCount / 3)) { + this.logger.warning( + "[CommandConfigurator] Skipped stale cleanup due to suspiciously low discovered command count (discovered=" + + discoveredCount + ", configured=" + configuredCount + ")." + ); + if (changed) { + this.configurationManager.save(this.commandConfiguration); + } + return; + } + + List staleKeys = this.commandConfiguration.commands.entrySet().stream() + .filter(entry -> entry.getValue() == null || !runtimeNames.contains(entry.getKey())) + .map(Map.Entry::getKey) + .toList(); + + for (String staleKey : staleKeys) { + this.commandConfiguration.commands.remove(staleKey); + this.logger.warning("[CommandConfigurator] Removed stale command from commands.yml: '" + staleKey + "'"); + changed = true; + } + + if (changed) { + this.configurationManager.save(this.commandConfiguration); + } + } + + private boolean synchronizeDefaults(CommandBuilder context) { + this.commandConfiguration.commands = mutableMap(this.commandConfiguration.commands); + boolean changed = false; + + if (this.commandConfiguration.commands.remove("") != null) { + this.logger.warning("[CommandConfigurator] Removed invalid empty command key ('') from commands.yml"); + changed = true; + } + + String runtimeName = context.name(); + Command current = this.commandConfiguration.commands.get(runtimeName); if (current == null) { current = new Command( - context.name(), + runtimeName, new ArrayList<>(context.aliases()), extractPermissions(context.meta()), context.isEnabled() ); current.subCommands = new LinkedHashMap<>(); - this.commandConfiguration.commands.put(context.name(), current); + this.commandConfiguration.commands.put(runtimeName, current); + this.logger.info("[CommandConfigurator] Added new command defaults to commands.yml: '" + runtimeName + "'"); changed = true; } @@ -106,6 +194,9 @@ private boolean synchronizeDefaults(CommandBuilder context) { extractPermissions(child.meta()) ) ); + this.logger.info( + "[CommandConfigurator] Added missing subcommand defaults for '" + runtimeName + "': '" + child.name() + "'" + ); changed = true; } From ed573b371d5726f56faf5cfa45d2270ce29b4cda Mon Sep 17 00:00:00 2001 From: Martin Sulikowski Date: Fri, 27 Feb 2026 20:46:29 +0100 Subject: [PATCH 3/3] Fix. --- .../litecommand/configurator/CommandConfigurator.java | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/litecommand/configurator/CommandConfigurator.java b/eternalcore-core/src/main/java/com/eternalcode/core/litecommand/configurator/CommandConfigurator.java index ae7eeb807..a0c7378ba 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/litecommand/configurator/CommandConfigurator.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/litecommand/configurator/CommandConfigurator.java @@ -125,17 +125,6 @@ private void cleanupStaleCommands() { return; } - if (configuredCount > 8 && discoveredCount < (configuredCount / 3)) { - this.logger.warning( - "[CommandConfigurator] Skipped stale cleanup due to suspiciously low discovered command count (discovered=" - + discoveredCount + ", configured=" + configuredCount + ")." - ); - if (changed) { - this.configurationManager.save(this.commandConfiguration); - } - return; - } - List staleKeys = this.commandConfiguration.commands.entrySet().stream() .filter(entry -> entry.getValue() == null || !runtimeNames.contains(entry.getKey())) .map(Map.Entry::getKey)