diff --git a/src/main/java/dev/spexx/configurationAPI/configuration/yaml/YamlConfig.java b/src/main/java/dev/spexx/configurationAPI/configuration/yaml/YamlConfig.java index 0d3cba5..2b11d15 100644 --- a/src/main/java/dev/spexx/configurationAPI/configuration/yaml/YamlConfig.java +++ b/src/main/java/dev/spexx/configurationAPI/configuration/yaml/YamlConfig.java @@ -28,7 +28,7 @@ public class YamlConfig { private final @NotNull File file; - private @NotNull YamlConfiguration cached = new YamlConfiguration(); + private volatile @NotNull YamlConfiguration cached = new YamlConfiguration(); /** * Cached SHA-256 checksum of the configuration file. @@ -45,7 +45,7 @@ public class YamlConfig { * *

This field is updated internally and should be treated as read-only.

*/ - private String cachedChecksum = null; + private volatile String cachedChecksum = null; /** * Creates a new YAML configuration wrapper. @@ -102,8 +102,9 @@ public YamlConfig(@NotNull File file) throws ConfigException { // try to generate checksum try { - this.cachedChecksum = FileChecksum.getSha256Checksum(file); + this.cachedChecksum = FileChecksum.computeSha256(file); } catch (Exception e) { + e.printStackTrace(); // log the exception this.cachedChecksum = null; } @@ -167,6 +168,10 @@ public void save() throws ConfigFileException, ConfigPermissionException { /** * Returns the cached configuration. * + *

+ * Modifications affect the cached configuration directly. + * Don't forget to save the cached config after you've made changes. + *

*

This method does not perform any file I/O.

* * @return the cached {@link YamlConfiguration} diff --git a/src/main/java/dev/spexx/configurationAPI/configuration/yaml/YamlConfigWatcher.java b/src/main/java/dev/spexx/configurationAPI/configuration/yaml/YamlConfigWatcher.java index de5c1aa..b4e34e9 100644 --- a/src/main/java/dev/spexx/configurationAPI/configuration/yaml/YamlConfigWatcher.java +++ b/src/main/java/dev/spexx/configurationAPI/configuration/yaml/YamlConfigWatcher.java @@ -52,6 +52,19 @@ public class YamlConfigWatcher { */ private final Map lastModified = new ConcurrentHashMap<>(); + /** + * Maps {@link WatchKey} instances to their corresponding directory {@link Path}. + * + *

This reverse mapping allows constant-time (O(1)) resolution of a directory + * from a {@link WatchKey}, avoiding linear scans over registered directories.

+ * + *

The map is populated when directories are registered and cleaned up when + * {@link WatchKey}s become invalid.

+ * + * @since 1.3.0 + */ + private final @NotNull Map watchKeys = new ConcurrentHashMap<>(); + private volatile boolean running = false; /** @@ -95,11 +108,16 @@ public void watch(@NotNull YamlConfig config) throws ConfigException { try { directories.computeIfAbsent(directory, dir -> { try { - return dir.register( + WatchKey key = dir.register( watchService, StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_DELETE ); + + // for reverse mapping + watchKeys.put(key, dir); + + return key; } catch (IOException e) { throw new RuntimeException(e); } @@ -153,6 +171,9 @@ public void stop() { * @since 1.3.0 */ private void run() { + var scheduler = Bukkit.getScheduler(); + var pluginManager = Bukkit.getPluginManager(); + while (running) { WatchKey key; @@ -162,15 +183,7 @@ private void run() { return; } - Path dir = null; - - for (Map.Entry entry : directories.entrySet()) { - if (entry.getValue().equals(key)) { - dir = entry.getKey(); - break; - } - } - + @Nullable Path dir = watchKeys.get(key); if (dir == null) { key.reset(); continue; @@ -196,8 +209,9 @@ private void run() { @Nullable String newChecksum; try { - newChecksum = FileChecksum.getSha256Checksum(config.getFile()); + newChecksum = FileChecksum.computeSha256(config.getFile()); } catch (Exception e) { + e.printStackTrace(); continue; } @@ -218,20 +232,18 @@ private void run() { try { config.reload(); - // get updated checksum String updatedChecksum = config.getCachedChecksum(); - // Fire event on main thread - Bukkit.getScheduler().runTask(javaPlugin, () -> { - Bukkit.getPluginManager().callEvent( - new ConfigReloadEvent( - config.getFile().getName(), - config.get(), - oldChecksum, - updatedChecksum - ) - ); - }); + scheduler.runTask(javaPlugin, () -> + pluginManager.callEvent( + new ConfigReloadEvent( + config.getFile().getName(), + config.get(), + oldChecksum, + updatedChecksum + ) + ) + ); } catch (Exception e) { e.printStackTrace(); @@ -239,7 +251,14 @@ private void run() { } } - key.reset(); + boolean valid = key.reset(); + + if (!valid) { + Path removed = watchKeys.remove(key); + if (removed != null) { + directories.remove(removed); + } + } } } } \ No newline at end of file diff --git a/src/main/java/dev/spexx/configurationAPI/manager/ConfigManager.java b/src/main/java/dev/spexx/configurationAPI/manager/ConfigManager.java index d351fd7..70b82e4 100644 --- a/src/main/java/dev/spexx/configurationAPI/manager/ConfigManager.java +++ b/src/main/java/dev/spexx/configurationAPI/manager/ConfigManager.java @@ -69,10 +69,7 @@ public ConfigManager(@NotNull JavaPlugin javaPlugin) throws ConfigException { throw new ConfigException("Config already registered: " + key); } - YamlConfig config = new YamlConfig(file); - - config.create(); - config.load(); + YamlConfig config = initialize(file); configs.put(key, config); @@ -107,15 +104,33 @@ public void registerFromJar(@NotNull File file, copyResource(plugin, resourcePath, file); } - YamlConfig config = new YamlConfig(file); - config.create(); - config.load(); + YamlConfig config = initialize(file); configs.put(key, config); watcher.watch(config); } + /** + * Initializes a {@link YamlConfig} instance for the given file. + * + *

If the file does not exist, it is created. The configuration is then + * loaded and cached.

+ * + * @param file the configuration file to initialize + * @return the initialized {@link YamlConfig} + * + * @throws ConfigException if file creation fails or the path is invalid + * + * @since 1.3.0 + */ + private @NotNull YamlConfig initialize(File file) { + YamlConfig config = new YamlConfig(file); + config.create(); + config.load(); + return config; + } + /** * Copies a resource from the plugin JAR to the specified file. * diff --git a/src/main/java/dev/spexx/configurationAPI/utils/FileChecksum.java b/src/main/java/dev/spexx/configurationAPI/utils/FileChecksum.java index f6a237c..53089e9 100644 --- a/src/main/java/dev/spexx/configurationAPI/utils/FileChecksum.java +++ b/src/main/java/dev/spexx/configurationAPI/utils/FileChecksum.java @@ -58,7 +58,7 @@ private FileChecksum() { *
  • an I/O error occurs during reading
  • * */ - public static @NotNull String getSha256Checksum(File file) throws Exception { + public static @NotNull String computeSha256(File file) throws Exception { MessageDigest digest = MessageDigest.getInstance("SHA-256"); try (InputStream fis = new FileInputStream(file)) { diff --git a/src/main/resources/paper-plugin.yml b/src/main/resources/paper-plugin.yml index 1536674..997a48a 100644 --- a/src/main/resources/paper-plugin.yml +++ b/src/main/resources/paper-plugin.yml @@ -1,5 +1,5 @@ name: ConfigurationAPI -description: $description +description: "Lightweight YAML config API with automatic reload and event-driven updates." version: '1.3.0' main: dev.spexx.configurationAPI.ConfigurationAPI