From e5d39bcdfee6c91b4f8b43df5a744eab80e08795 Mon Sep 17 00:00:00 2001
From: devspexx
Date: Mon, 6 Apr 2026 20:54:55 +0200
Subject: [PATCH 1/4] refactor: improve config system performance and
thread-safety
---
.../configuration/yaml/YamlConfig.java | 11 +++++--
.../configuration/yaml/YamlConfigWatcher.java | 2 +-
.../manager/ConfigManager.java | 29 ++++++++++++++-----
.../configurationAPI/utils/FileChecksum.java | 2 +-
4 files changed, 32 insertions(+), 12 deletions(-)
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..2fc7389 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..3678d8f 100644
--- a/src/main/java/dev/spexx/configurationAPI/configuration/yaml/YamlConfigWatcher.java
+++ b/src/main/java/dev/spexx/configurationAPI/configuration/yaml/YamlConfigWatcher.java
@@ -196,7 +196,7 @@ private void run() {
@Nullable String newChecksum;
try {
- newChecksum = FileChecksum.getSha256Checksum(config.getFile());
+ newChecksum = FileChecksum.computeSha256(config.getFile());
} catch (Exception e) {
continue;
}
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)) {
From 9e05af56d388c789efba98bb1a9015c421231b05 Mon Sep 17 00:00:00 2001
From: devspexx
Date: Mon, 6 Apr 2026 21:00:33 +0200
Subject: [PATCH 2/4] refactor: improve config watcher class
---
.../configuration/yaml/YamlConfigWatcher.java | 65 ++++++++++++-------
1 file changed, 42 insertions(+), 23 deletions(-)
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 3678d8f..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;
@@ -198,6 +211,7 @@ private void run() {
try {
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
From 5935273449c235c7991d56af37df6e91da72dcd2 Mon Sep 17 00:00:00 2001
From: devspexx
Date: Mon, 6 Apr 2026 21:02:37 +0200
Subject: [PATCH 3/4] refactor: add description in paper-plugin.yml
---
src/main/resources/paper-plugin.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
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
From 0d2f8855c293c5f16fe903dbd31e5d3aa3e50c76 Mon Sep 17 00:00:00 2001
From: devspexx <46614224+devspexx@users.noreply.github.com>
Date: Mon, 6 Apr 2026 21:08:37 +0200
Subject: [PATCH 4/4] refactor: fixed doclint
---
.../spexx/configurationAPI/configuration/yaml/YamlConfig.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
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 2fc7389..2b11d15 100644
--- a/src/main/java/dev/spexx/configurationAPI/configuration/yaml/YamlConfig.java
+++ b/src/main/java/dev/spexx/configurationAPI/configuration/yaml/YamlConfig.java
@@ -171,7 +171,7 @@ public void save() throws ConfigFileException, ConfigPermissionException {
*
* 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}