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