From 2bbd2d0983f50eb96004fa822e28b9528e44159e Mon Sep 17 00:00:00 2001
From: devspexx <46614224+devspexx@users.noreply.github.com>
Date: Sat, 11 Apr 2026 11:22:15 +0200
Subject: [PATCH 1/5] refactor(api): reorganize package structure and improve
API access
---
pom.xml | 2 +-
.../configurationAPI/ConfigurationAPI.java | 18 ++-
.../api/ConfigurationAPI.java | 48 ++++++
.../api/config/yaml/YamlConfig.java | 2 +-
.../api/config/yaml/YamlConfigWatcher.java | 143 +++++++++---------
.../api/event/ConfigDeletedEvent.java | 63 ++++++++
.../api/event/ConfigRegisteredEvent.java | 78 ++++++++++
...oadEvent.java => ConfigReloadedEvent.java} | 10 +-
.../api/manager/ConfigManager.java | 75 +++++++--
src/main/resources/paper-plugin.yml | 2 +-
10 files changed, 338 insertions(+), 103 deletions(-)
create mode 100644 src/main/java/dev/spexx/configurationAPI/api/ConfigurationAPI.java
create mode 100644 src/main/java/dev/spexx/configurationAPI/api/event/ConfigDeletedEvent.java
create mode 100644 src/main/java/dev/spexx/configurationAPI/api/event/ConfigRegisteredEvent.java
rename src/main/java/dev/spexx/configurationAPI/api/event/{ConfigReloadEvent.java => ConfigReloadedEvent.java} (90%)
diff --git a/pom.xml b/pom.xml
index e125ec2..7509333 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
This class is managed by the Bukkit/Paper plugin system and is responsible + * for initializing the plugin lifecycle.
* - *Initializes and demonstrates usage of the configuration system.
+ *It does not expose the API directly. Use the API classes from the + * {@code dev.spexx.configurationAPI.api} package instead.
* - * @since 1.0.0 + * @since 1.3.1 */ public final class ConfigurationAPI extends JavaPlugin { /** - * Creates a new plugin instance. + * Creates the plugin instance. * - * @since 1.0.0 + *This constructor is called by the server and should not be used manually.
*/ - public ConfigurationAPI() { - } + public ConfigurationAPI() { } } \ No newline at end of file diff --git a/src/main/java/dev/spexx/configurationAPI/api/ConfigurationAPI.java b/src/main/java/dev/spexx/configurationAPI/api/ConfigurationAPI.java new file mode 100644 index 0000000..2d87055 --- /dev/null +++ b/src/main/java/dev/spexx/configurationAPI/api/ConfigurationAPI.java @@ -0,0 +1,48 @@ +package dev.spexx.configurationAPI.api; + +import dev.spexx.configurationAPI.api.manager.ConfigManager; +import org.bukkit.plugin.java.JavaPlugin; +import org.jetbrains.annotations.NotNull; + +/** + * Facade for accessing configuration API components. + * + * @since 1.3.2 + */ +public class ConfigurationAPI { + + /** + * The internal configuration manager instance. + * + * @since 1.3.2 + */ + private final @NotNull ConfigManager configManager; + + /** + * Creates a new {@link dev.spexx.configurationAPI.ConfigurationAPI} instance. + * + *The provided {@link JavaPlugin} is used internally for scheduling tasks + * and interacting with the Bukkit API.
+ * + * @param javaPlugin the owning plugin instance + * + * @since 1.3.2 + */ + public ConfigurationAPI(@NotNull JavaPlugin javaPlugin) { + this.configManager = new ConfigManager(javaPlugin); + } + + /** + * Returns the core {@link ConfigManager} API. + * + *This provides access to configuration registration, retrieval, + * and file watching functionality.
+ * + * @return the {@link ConfigManager} instance + * + * @since 1.3.2 + */ + public @NotNull ConfigManager api() { + return this.configManager; + } +} diff --git a/src/main/java/dev/spexx/configurationAPI/api/config/yaml/YamlConfig.java b/src/main/java/dev/spexx/configurationAPI/api/config/yaml/YamlConfig.java index 87ea766..0787085 100644 --- a/src/main/java/dev/spexx/configurationAPI/api/config/yaml/YamlConfig.java +++ b/src/main/java/dev/spexx/configurationAPI/api/config/yaml/YamlConfig.java @@ -102,7 +102,7 @@ public YamlConfig(@NotNull File file) throws ConfigException { try { this.cachedChecksum = FileChecksum.computeSha256(file); } catch (Exception e) { - e.printStackTrace(); // log the exception + e.printStackTrace(); this.cachedChecksum = null; } diff --git a/src/main/java/dev/spexx/configurationAPI/api/config/yaml/YamlConfigWatcher.java b/src/main/java/dev/spexx/configurationAPI/api/config/yaml/YamlConfigWatcher.java index ec587d4..4308b5b 100644 --- a/src/main/java/dev/spexx/configurationAPI/api/config/yaml/YamlConfigWatcher.java +++ b/src/main/java/dev/spexx/configurationAPI/api/config/yaml/YamlConfigWatcher.java @@ -1,6 +1,8 @@ package dev.spexx.configurationAPI.api.config.yaml; -import dev.spexx.configurationAPI.api.event.ConfigReloadEvent; +import dev.spexx.configurationAPI.api.event.ConfigDeletedEvent; +import dev.spexx.configurationAPI.api.event.ConfigRegisteredEvent; +import dev.spexx.configurationAPI.api.event.ConfigReloadedEvent; import dev.spexx.configurationAPI.api.exceptions.ConfigException; import dev.spexx.configurationAPI.api.utils.FileChecksum; import org.bukkit.Bukkit; @@ -12,57 +14,30 @@ import java.nio.file.*; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; /** - * Watches {@link YamlConfig} files for changes. + * Watches registered {@link YamlConfig} files for changes. * - *Registered configurations are monitored for file system modifications. - * Files located in the same directory share a single {@link WatchKey}.
+ *Only files registered through this watcher are monitored. + * File system events for unrelated files are ignored.
* - *Modification events are debounced to prevent duplicate reloads.
+ *Events are dispatched on the main server thread.
* * @since 1.3.0 */ public class YamlConfigWatcher { private final @NotNull JavaPlugin javaPlugin; - private final @NotNull WatchService watchService; - /** - * Maps directories to their {@link WatchKey}. - * - *Each directory is registered only once, even if multiple files within it are watched.
- * - * @since 1.3.0 - */ - private final @NotNull MapThis 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 MapIf multiple configurations are located in the same directory, - * the directory is registered only once.
+ *Only registered configurations will produce events.
* * @param config the configuration to watch * @throws ConfigException if already registered or invalid - * @since 1.3.0 */ public void watch(@NotNull YamlConfig config) throws ConfigException { - Path path = config.getFile().toPath().toAbsolutePath(); + Path path = config.getFile().toPath().toAbsolutePath().normalize(); if (watchedFiles.containsKey(path)) { throw new ConfigException("File is already being watched: " + path); @@ -111,13 +82,13 @@ public void watch(@NotNull YamlConfig config) throws ConfigException { WatchKey key = dir.register( watchService, StandardWatchEventKinds.ENTRY_MODIFY, - StandardWatchEventKinds.ENTRY_DELETE + StandardWatchEventKinds.ENTRY_DELETE, + StandardWatchEventKinds.ENTRY_CREATE ); - // for reverse mapping watchKeys.put(key, dir); - return key; + } catch (IOException e) { throw new RuntimeException(e); } @@ -125,6 +96,17 @@ public void watch(@NotNull YamlConfig config) throws ConfigException { watchedFiles.put(path, config); + // REGISTER EVENT + Bukkit.getScheduler().runTask(javaPlugin, () -> + Bukkit.getPluginManager().callEvent( + new ConfigRegisteredEvent( + config.getFile().getName(), + config.get(), + config.getCachedChecksum() + ) + ) + ); + } catch (RuntimeException e) { throw new ConfigException("Failed to register file watcher: " + path, e); } @@ -134,7 +116,6 @@ public void watch(@NotNull YamlConfig config) throws ConfigException { * Starts the watcher thread. * * @throws ConfigException if already running - * @since 1.3.0 */ public void start() throws ConfigException { if (running) { @@ -143,33 +124,34 @@ public void start() throws ConfigException { running = true; - Thread thread = new Thread(this::run, "YamlConfigWatcher"); - thread.setDaemon(true); - thread.start(); + watcherThread = new Thread(this::run, "YamlConfigWatcher"); + watcherThread.setDaemon(true); + watcherThread.start(); } /** - * Stops the watcher thread. - * - * @since 1.3.0 + * Stops the watcher and releases resources. */ public void stop() { running = false; try { watchService.close(); - } catch (IOException ignored) { + } catch (IOException ignored) {} + + if (watcherThread != null) { + watcherThread.interrupt(); } } - /** - * Internal watcher loop. + * Returns whether the watcher is running. * - *Resolves {@link WatchKey} instances back to their corresponding directory - * and processes events for registered files only.
- * - * @since 1.3.0 + * @return true if running */ + public boolean isRunning() { + return running; + } + private void run() { var scheduler = Bukkit.getScheduler(); var pluginManager = Bukkit.getPluginManager(); @@ -190,20 +172,35 @@ private void run() { } for (WatchEvent> event : key.pollEvents()) { - Path changed = dir.resolve((Path) event.context()).toAbsolutePath(); + Path changed = dir.resolve((Path) event.context()) + .toAbsolutePath() + .normalize(); - // null safety YamlConfig config = watchedFiles.get(changed); if (config == null) { continue; } + // DELETE if (event.kind() == StandardWatchEventKinds.ENTRY_DELETE) { + + String checksum = config.getCachedChecksum(); + String name = config.getFile().getName(); + watchedFiles.remove(changed); + + scheduler.runTask(javaPlugin, () -> + pluginManager.callEvent( + new ConfigDeletedEvent(name, checksum) + ) + ); + continue; } - if (event.kind() == StandardWatchEventKinds.ENTRY_MODIFY) { + // MODIFY / CREATE + if (event.kind() == StandardWatchEventKinds.ENTRY_MODIFY + || event.kind() == StandardWatchEventKinds.ENTRY_CREATE) { @Nullable String oldChecksum = config.getCachedChecksum(); @Nullable String newChecksum; @@ -211,19 +208,18 @@ private void run() { try { newChecksum = FileChecksum.computeSha256(config.getFile()); } catch (Exception e) { - e.printStackTrace(); + javaPlugin.getLogger().log(Level.SEVERE, + "Failed to compute checksum for " + config.getFile(), e); continue; } - // skip if nothing actually changed if (oldChecksum != null && oldChecksum.equals(newChecksum)) { continue; } - // debounce long now = System.currentTimeMillis(); long last = lastModified.getOrDefault(changed, 0L); - if (now - last < 200) { + if (now - last < DEBOUNCE_MS) { continue; } @@ -236,7 +232,7 @@ private void run() { scheduler.runTask(javaPlugin, () -> pluginManager.callEvent( - new ConfigReloadEvent( + new ConfigReloadedEvent( config.getFile().getName(), config.get(), oldChecksum, @@ -246,7 +242,8 @@ private void run() { ); } catch (Exception e) { - e.printStackTrace(); + javaPlugin.getLogger().log(Level.SEVERE, + "Failed to reload config: " + config.getFile(), e); } } } diff --git a/src/main/java/dev/spexx/configurationAPI/api/event/ConfigDeletedEvent.java b/src/main/java/dev/spexx/configurationAPI/api/event/ConfigDeletedEvent.java new file mode 100644 index 0000000..1c74ab9 --- /dev/null +++ b/src/main/java/dev/spexx/configurationAPI/api/event/ConfigDeletedEvent.java @@ -0,0 +1,63 @@ +package dev.spexx.configurationAPI.api.event; + +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Fired when a watched configuration file is deleted. + * + *The event contains the last known checksum before deletion.
+ * + * @since 1.3.0 + */ +public class ConfigDeletedEvent extends Event { + + private static final HandlerList HANDLERS = new HandlerList(); + + private final @NotNull String configName; + private final String checksum; + + /** + * Creates a new deletion event. + * + * @param configName the name of the deleted configuration + * @param checksum the last known checksum before deletion + */ + public ConfigDeletedEvent(@NotNull String configName, String checksum) { + this.configName = configName; + this.checksum = checksum; + } + + /** + * Required handler list for Bukkit events. + * + * @return the handler list + */ + public static @NotNull HandlerList getHandlerList() { + return HANDLERS; + } + + /** + * Returns the configuration name. + * + * @return the configuration file name + */ + public @NotNull String getConfigName() { + return configName; + } + + /** + * Returns the last known checksum. + * + * @return checksum or null if unavailable + */ + public String getChecksum() { + return checksum; + } + + @Override + public @NotNull HandlerList getHandlers() { + return HANDLERS; + } +} \ No newline at end of file diff --git a/src/main/java/dev/spexx/configurationAPI/api/event/ConfigRegisteredEvent.java b/src/main/java/dev/spexx/configurationAPI/api/event/ConfigRegisteredEvent.java new file mode 100644 index 0000000..c813e53 --- /dev/null +++ b/src/main/java/dev/spexx/configurationAPI/api/event/ConfigRegisteredEvent.java @@ -0,0 +1,78 @@ +package dev.spexx.configurationAPI.api.event; + +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +/** + * Fired when a configuration is registered. + * + *This event is triggered after the file is loaded and tracked by the watcher.
+ * + * @since 1.3.0 + */ +public class ConfigRegisteredEvent extends Event { + + private static final HandlerList HANDLERS = new HandlerList(); + + private final @NotNull String configName; + private final @NotNull FileConfiguration config; + private final String checksum; + + /** + * Creates a new registration event. + * + * @param configName the configuration file name + * @param config the loaded configuration + * @param checksum the current checksum + */ + public ConfigRegisteredEvent(@NotNull String configName, + @NotNull FileConfiguration config, + String checksum) { + this.configName = configName; + this.config = config; + this.checksum = checksum; + } + + /** + * Required handler list for Bukkit events. + * + * @return the handler list + */ + public static @NotNull HandlerList getHandlerList() { + return HANDLERS; + } + + /** + * Returns the configuration name. + * + * @return the configuration file name + */ + public @NotNull String getConfigName() { + return configName; + } + + /** + * Returns the loaded configuration. + * + * @return the configuration instance + */ + public @NotNull FileConfiguration getConfig() { + return config; + } + + /** + * Returns the checksum. + * + * @return checksum or null if unavailable + */ + public String getChecksum() { + return checksum; + } + + @Override + public @NotNull HandlerList getHandlers() { + return HANDLERS; + } +} \ No newline at end of file diff --git a/src/main/java/dev/spexx/configurationAPI/api/event/ConfigReloadEvent.java b/src/main/java/dev/spexx/configurationAPI/api/event/ConfigReloadedEvent.java similarity index 90% rename from src/main/java/dev/spexx/configurationAPI/api/event/ConfigReloadEvent.java rename to src/main/java/dev/spexx/configurationAPI/api/event/ConfigReloadedEvent.java index 78cd22b..b603eba 100644 --- a/src/main/java/dev/spexx/configurationAPI/api/event/ConfigReloadEvent.java +++ b/src/main/java/dev/spexx/configurationAPI/api/event/ConfigReloadedEvent.java @@ -13,7 +13,7 @@ * * @since 1.3.0 */ -public class ConfigReloadEvent extends Event { +public class ConfigReloadedEvent extends Event { private static final HandlerList HANDLERS = new HandlerList(); @@ -31,10 +31,10 @@ public class ConfigReloadEvent extends Event { * @param newChecksum the new checksum, or {@code null} if generation failed * @since 1.3.0 */ - public ConfigReloadEvent(@NotNull String configName, - @NotNull FileConfiguration newConfig, - String oldChecksum, - String newChecksum) { + public ConfigReloadedEvent(@NotNull String configName, + @NotNull FileConfiguration newConfig, + String oldChecksum, + String newChecksum) { this.configName = configName; this.newConfig = newConfig; this.oldChecksum = oldChecksum; diff --git a/src/main/java/dev/spexx/configurationAPI/api/manager/ConfigManager.java b/src/main/java/dev/spexx/configurationAPI/api/manager/ConfigManager.java index 46516d6..3c1e8d5 100644 --- a/src/main/java/dev/spexx/configurationAPI/api/manager/ConfigManager.java +++ b/src/main/java/dev/spexx/configurationAPI/api/manager/ConfigManager.java @@ -64,9 +64,9 @@ public ConfigManager(@NotNull JavaPlugin javaPlugin) throws ConfigException { */ public @NotNull YamlConfig register(@NotNull File file) throws ConfigException { - String key = file.getAbsolutePath(); + String key = getNormalizedPath(file); - if (configs.containsKey(key)) { + if (isRegistered(file)) { throw new ConfigException("Config already registered: " + key); } @@ -80,24 +80,37 @@ public ConfigManager(@NotNull JavaPlugin javaPlugin) throws ConfigException { } /** - * Registers a configuration file using a resource from the plugin JAR. + * Registers a configuration file backed by a resource inside the plugin JAR. * - *If the file does not exist, it is copied from the specified resource path - * before being loaded.
+ *If the target file does not exist, it is first created by copying the specified + * resource from the plugin JAR. The file is then initialized, loaded, and registered + * within this manager.
* - * @param file the target configuration file - * @param resourcePath the path inside the plugin JAR + *Once registered, the configuration is tracked and automatically monitored for + * changes if the internal watcher is running.
+ * + * @param file the target configuration file on disk + * @param resourcePath the path to the resource inside the plugin JAR * @param plugin the plugin used to access the resource - * @throws ConfigException if already registered or copy/load fails + * + * @return the managed {@link YamlConfig} instance + * + * @throws ConfigException if: + *Lookup is performed using the file's absolute path.
+ * + * @param file the configuration file + * @return {@code true} if registered, {@code false} otherwise + * + * @since 1.3.2 + */ + public boolean isRegistered(@NotNull File file) { + return configs.containsKey(getNormalizedPath(file)); + } + /** * Starts the internal watcher. * * @throws ConfigException if already running or fails * @since 1.3.0 */ - public void start() throws ConfigException { + public void startFileWatcher() throws ConfigException { watcher.start(); } @@ -228,7 +258,22 @@ public void start() throws ConfigException { * * @since 1.3.0 */ - public void stop() { + public void stopFileWatcher() { watcher.stop(); } + + /** + * Resolves a normalized key for the given file. + * + *This ensures consistent lookup regardless of relative paths, + * redundant segments, or platform differences.
+ * + * @param file the file + * @return normalized absolute path string + * + * @since 1.3.2 + */ + private @NotNull String getNormalizedPath(@NotNull File file) { + return file.toPath().toAbsolutePath().normalize().toString(); + } } diff --git a/src/main/resources/paper-plugin.yml b/src/main/resources/paper-plugin.yml index c2f7a85..413e690 100644 --- a/src/main/resources/paper-plugin.yml +++ b/src/main/resources/paper-plugin.yml @@ -1,6 +1,6 @@ name: ConfigurationAPI description: "Lightweight YAML config API with automatic reload and event-driven updates." -version: '1.3.1' +version: '1.3.2' main: dev.spexx.configurationAPI.ConfigurationAPI bootstrapper: dev.spexx.configurationAPI.bootstrap.ConfigurationBootstrap From e54fe603a2345a040b951b09e08518f9b19386ef Mon Sep 17 00:00:00 2001 From: devspexx <46614224+devspexx@users.noreply.github.com> Date: Sat, 11 Apr 2026 11:28:44 +0200 Subject: [PATCH 2/5] refactor(api): rename API entry class and clarify plugin/API separation --- .github/workflows/maven.yml | 2 +- .../java/dev/spexx/configurationAPI/ConfigurationAPI.java | 2 +- .../api/{ConfigurationAPI.java => ConfigurationProvider.java} | 4 ++-- .../configurationAPI/bootstrap/ConfigurationBootstrap.java | 2 +- .../spexx/configurationAPI/loader/ConfigurationLoader.java | 2 +- src/main/resources/paper-plugin.yml | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) rename src/main/java/dev/spexx/configurationAPI/api/{ConfigurationAPI.java => ConfigurationProvider.java} (91%) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index b6d56a9..0dbda5e 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -43,7 +43,7 @@ jobs: - name: Upload JAR artifact uses: actions/upload-artifact@v4 with: - name: ConfigurationAPI + name: ConfigurationProvider path: target/*.jar # Optional: GitHub security / dependency tracking diff --git a/src/main/java/dev/spexx/configurationAPI/ConfigurationAPI.java b/src/main/java/dev/spexx/configurationAPI/ConfigurationAPI.java index 54da6eb..7b2cb90 100644 --- a/src/main/java/dev/spexx/configurationAPI/ConfigurationAPI.java +++ b/src/main/java/dev/spexx/configurationAPI/ConfigurationAPI.java @@ -3,7 +3,7 @@ import org.bukkit.plugin.java.JavaPlugin; /** - * Main plugin entry point for ConfigurationAPI. + * Main plugin entry point for ConfigurationProvider. * *This class is managed by the Bukkit/Paper plugin system and is responsible * for initializing the plugin lifecycle.
diff --git a/src/main/java/dev/spexx/configurationAPI/api/ConfigurationAPI.java b/src/main/java/dev/spexx/configurationAPI/api/ConfigurationProvider.java similarity index 91% rename from src/main/java/dev/spexx/configurationAPI/api/ConfigurationAPI.java rename to src/main/java/dev/spexx/configurationAPI/api/ConfigurationProvider.java index 2d87055..4a769ca 100644 --- a/src/main/java/dev/spexx/configurationAPI/api/ConfigurationAPI.java +++ b/src/main/java/dev/spexx/configurationAPI/api/ConfigurationProvider.java @@ -9,7 +9,7 @@ * * @since 1.3.2 */ -public class ConfigurationAPI { +public class ConfigurationProvider { /** * The internal configuration manager instance. @@ -28,7 +28,7 @@ public class ConfigurationAPI { * * @since 1.3.2 */ - public ConfigurationAPI(@NotNull JavaPlugin javaPlugin) { + public ConfigurationProvider(@NotNull JavaPlugin javaPlugin) { this.configManager = new ConfigManager(javaPlugin); } diff --git a/src/main/java/dev/spexx/configurationAPI/bootstrap/ConfigurationBootstrap.java b/src/main/java/dev/spexx/configurationAPI/bootstrap/ConfigurationBootstrap.java index 0806160..348d5f0 100644 --- a/src/main/java/dev/spexx/configurationAPI/bootstrap/ConfigurationBootstrap.java +++ b/src/main/java/dev/spexx/configurationAPI/bootstrap/ConfigurationBootstrap.java @@ -5,7 +5,7 @@ import org.jetbrains.annotations.NotNull; /** - * Bootstrap entry point for the ConfigurationAPI plugin. + * Bootstrap entry point for the ConfigurationProvider plugin. * *This class is invoked during the early plugin bootstrap phase, * before the plugin is fully loaded and enabled.
diff --git a/src/main/java/dev/spexx/configurationAPI/loader/ConfigurationLoader.java b/src/main/java/dev/spexx/configurationAPI/loader/ConfigurationLoader.java index 1505580..d9586cb 100644 --- a/src/main/java/dev/spexx/configurationAPI/loader/ConfigurationLoader.java +++ b/src/main/java/dev/spexx/configurationAPI/loader/ConfigurationLoader.java @@ -5,7 +5,7 @@ import org.jetbrains.annotations.NotNull; /** - * Classpath loader for the ConfigurationAPI plugin. + * Classpath loader for the ConfigurationProvider plugin. * *This class is responsible for modifying the plugin's classpath * during the loading phase.
diff --git a/src/main/resources/paper-plugin.yml b/src/main/resources/paper-plugin.yml index 413e690..eef7776 100644 --- a/src/main/resources/paper-plugin.yml +++ b/src/main/resources/paper-plugin.yml @@ -1,4 +1,4 @@ -name: ConfigurationAPI +name: ConfigurationProvider description: "Lightweight YAML config API with automatic reload and event-driven updates." version: '1.3.2' From 49b27900835039b139aca516ac171bdfe56e8426 Mon Sep 17 00:00:00 2001 From: devspexx <46614224+devspexx@users.noreply.github.com> Date: Sat, 11 Apr 2026 11:38:33 +0200 Subject: [PATCH 3/5] naming mismatch --- .../bootstrap/ConfigurationBootstrap.java | 40 ------------------- .../loader/ConfigurationLoader.java | 39 ------------------ src/main/resources/paper-plugin.yml | 4 +- 3 files changed, 1 insertion(+), 82 deletions(-) delete mode 100644 src/main/java/dev/spexx/configurationAPI/bootstrap/ConfigurationBootstrap.java delete mode 100644 src/main/java/dev/spexx/configurationAPI/loader/ConfigurationLoader.java diff --git a/src/main/java/dev/spexx/configurationAPI/bootstrap/ConfigurationBootstrap.java b/src/main/java/dev/spexx/configurationAPI/bootstrap/ConfigurationBootstrap.java deleted file mode 100644 index 348d5f0..0000000 --- a/src/main/java/dev/spexx/configurationAPI/bootstrap/ConfigurationBootstrap.java +++ /dev/null @@ -1,40 +0,0 @@ -package dev.spexx.configurationAPI.bootstrap; - -import io.papermc.paper.plugin.bootstrap.BootstrapContext; -import io.papermc.paper.plugin.bootstrap.PluginBootstrap; -import org.jetbrains.annotations.NotNull; - -/** - * Bootstrap entry point for the ConfigurationProvider plugin. - * - *This class is invoked during the early plugin bootstrap phase, - * before the plugin is fully loaded and enabled.
- * - *It can be used to perform early initialization logic such as - * preparing resources, validating environment state, or influencing - * plugin loading behavior.
- * - *Note that the Bukkit API is not fully available at this stage.
- * - * @since 1.3.0 - */ -public class ConfigurationBootstrap implements PluginBootstrap { - - /** - * Creates a new ConfigurationBootstrap instance. - * - * @since 1.3.0 - */ - public ConfigurationBootstrap() { - } - - /** - * Called during the bootstrap phase of plugin initialization. - * - * @param context the bootstrap context providing access to plugin metadata and logging - */ - @Override - public void bootstrap(@NotNull BootstrapContext context) { - // no-op (reserved for future use) - } -} \ No newline at end of file diff --git a/src/main/java/dev/spexx/configurationAPI/loader/ConfigurationLoader.java b/src/main/java/dev/spexx/configurationAPI/loader/ConfigurationLoader.java deleted file mode 100644 index d9586cb..0000000 --- a/src/main/java/dev/spexx/configurationAPI/loader/ConfigurationLoader.java +++ /dev/null @@ -1,39 +0,0 @@ -package dev.spexx.configurationAPI.loader; - -import io.papermc.paper.plugin.loader.PluginClasspathBuilder; -import io.papermc.paper.plugin.loader.PluginLoader; -import org.jetbrains.annotations.NotNull; - -/** - * Classpath loader for the ConfigurationProvider plugin. - * - *This class is responsible for modifying the plugin's classpath - * during the loading phase.
- * - *It can be used to add external libraries or dependencies to the - * plugin classloader before the plugin is initialized.
- * - *Currently, no additional libraries are injected.
- * - * @since 1.3.0 - */ -public class ConfigurationLoader implements PluginLoader { - - /** - * Creates a new ConfigurationLoader instance. - * - * @since 1.3.0 - */ - public ConfigurationLoader() { - } - - /** - * Called when the plugin classloader is being constructed. - * - * @param builder the classpath builder used to modify the plugin classpath - */ - @Override - public void classloader(@NotNull PluginClasspathBuilder builder) { - // no-op (reserved for future use) - } -} \ No newline at end of file diff --git a/src/main/resources/paper-plugin.yml b/src/main/resources/paper-plugin.yml index eef7776..0b5f5fb 100644 --- a/src/main/resources/paper-plugin.yml +++ b/src/main/resources/paper-plugin.yml @@ -1,10 +1,8 @@ -name: ConfigurationProvider +name: ConfigurationAPI description: "Lightweight YAML config API with automatic reload and event-driven updates." version: '1.3.2' main: dev.spexx.configurationAPI.ConfigurationAPI -bootstrapper: dev.spexx.configurationAPI.bootstrap.ConfigurationBootstrap -loader: dev.spexx.configurationAPI.bootstrap.ConfigurationLoader api-version: '21.1.1' load: POSTWORLD From 825b4a05c573e90a7f30467be20d6b68ec756bec Mon Sep 17 00:00:00 2001 From: devspexx <46614224+devspexx@users.noreply.github.com> Date: Sat, 11 Apr 2026 12:58:58 +0200 Subject: [PATCH 4/5] fix(watcher): handle file deletion correctly and prevent checksum errors --- .../api/config/yaml/YamlConfigWatcher.java | 10 +++++-- .../api/event/ConfigDeletedEvent.java | 30 +++++++++++-------- src/main/resources/paper-plugin.yml | 4 +-- 3 files changed, 27 insertions(+), 17 deletions(-) diff --git a/src/main/java/dev/spexx/configurationAPI/api/config/yaml/YamlConfigWatcher.java b/src/main/java/dev/spexx/configurationAPI/api/config/yaml/YamlConfigWatcher.java index 4308b5b..4f06145 100644 --- a/src/main/java/dev/spexx/configurationAPI/api/config/yaml/YamlConfigWatcher.java +++ b/src/main/java/dev/spexx/configurationAPI/api/config/yaml/YamlConfigWatcher.java @@ -184,14 +184,13 @@ private void run() { // DELETE if (event.kind() == StandardWatchEventKinds.ENTRY_DELETE) { - String checksum = config.getCachedChecksum(); String name = config.getFile().getName(); watchedFiles.remove(changed); scheduler.runTask(javaPlugin, () -> pluginManager.callEvent( - new ConfigDeletedEvent(name, checksum) + new ConfigDeletedEvent(name, changed) ) ); @@ -202,6 +201,13 @@ private void run() { if (event.kind() == StandardWatchEventKinds.ENTRY_MODIFY || event.kind() == StandardWatchEventKinds.ENTRY_CREATE) { + Path filePath = config.getFile().toPath(); + + // skip if file was deleted + if (!Files.exists(filePath)) { + continue; + } + @Nullable String oldChecksum = config.getCachedChecksum(); @Nullable String newChecksum; diff --git a/src/main/java/dev/spexx/configurationAPI/api/event/ConfigDeletedEvent.java b/src/main/java/dev/spexx/configurationAPI/api/event/ConfigDeletedEvent.java index 1c74ab9..9e1283e 100644 --- a/src/main/java/dev/spexx/configurationAPI/api/event/ConfigDeletedEvent.java +++ b/src/main/java/dev/spexx/configurationAPI/api/event/ConfigDeletedEvent.java @@ -4,29 +4,33 @@ import org.bukkit.event.HandlerList; import org.jetbrains.annotations.NotNull; +import java.nio.file.Path; + /** * Fired when a watched configuration file is deleted. * - *The event contains the last known checksum before deletion.
+ *This event is triggered when the underlying file is removed + * from the file system.
* - * @since 1.3.0 + * @since 1.3.2 */ public class ConfigDeletedEvent extends Event { private static final HandlerList HANDLERS = new HandlerList(); private final @NotNull String configName; - private final String checksum; + private final @NotNull Path path; /** * Creates a new deletion event. * - * @param configName the name of the deleted configuration - * @param checksum the last known checksum before deletion + * @param configName the configuration file name + * @param path the absolute path of the deleted file */ - public ConfigDeletedEvent(@NotNull String configName, String checksum) { + public ConfigDeletedEvent(@NotNull String configName, + @NotNull Path path) { this.configName = configName; - this.checksum = checksum; + this.path = path; } /** @@ -39,21 +43,21 @@ public ConfigDeletedEvent(@NotNull String configName, String checksum) { } /** - * Returns the configuration name. + * Returns the configuration file name. * - * @return the configuration file name + * @return the file name */ public @NotNull String getConfigName() { return configName; } /** - * Returns the last known checksum. + * Returns the absolute path of the deleted file. * - * @return checksum or null if unavailable + * @return the file path */ - public String getChecksum() { - return checksum; + public @NotNull Path getPath() { + return path; } @Override diff --git a/src/main/resources/paper-plugin.yml b/src/main/resources/paper-plugin.yml index 0b5f5fb..fd86284 100644 --- a/src/main/resources/paper-plugin.yml +++ b/src/main/resources/paper-plugin.yml @@ -4,8 +4,8 @@ version: '1.3.2' main: dev.spexx.configurationAPI.ConfigurationAPI -api-version: '21.1.1' -load: POSTWORLD +api-version: '1.21.11' +load: STARTUP authors: [ Spexx ] website: www.spexx.dev \ No newline at end of file From 46dc85d12d41c6cb2d0262e5838f70018b65f66c Mon Sep 17 00:00:00 2001 From: devspexx <46614224+devspexx@users.noreply.github.com> Date: Sat, 11 Apr 2026 14:27:42 +0200 Subject: [PATCH 5/5] refactor(docs + api): updated code structure and javadocs as well as README.md --- README.md | 335 +++++++++++++----- .../configurationAPI/ConfigurationAPI.java | 3 +- .../api/ConfigurationProvider.java | 2 - .../api/config/yaml/YamlConfigWatcher.java | 13 +- .../api/manager/ConfigManager.java | 75 +++- 5 files changed, 312 insertions(+), 116 deletions(-) diff --git a/README.md b/README.md index 3a26c7e..2cd0e39 100644 --- a/README.md +++ b/README.md @@ -1,40 +1,56 @@ -[](https://github.com/devspexx/CentralDatabase/actions/workflows/maven.yml) +[](https://github.com/devspexx/ConfigurationAPI/actions/workflows/maven.yml) [](https://www.codefactor.io/repository/github/devspexx/ConfigurationAPI)  +## 1. Overview -## ConfigurationAPI +ConfigurationAPI is a Paper-based plugin that handles configuration files for you. -This is a simple yet somewhat robust plugin I wrote in my free time. -It allows developers to not worry about config changes and implementing /reload commands in their java plugins. -It is a Paper based API, well, a Paper based plugin. It will only work on Paper servers. Don't try to install it on spigot -servers, it won't work. I might make a fork of it in the near future, to also support spigot. -This constructor is called by the server and should not be used manually.
*/ - public ConfigurationAPI() { } + public ConfigurationAPI() { + } } \ No newline at end of file diff --git a/src/main/java/dev/spexx/configurationAPI/api/ConfigurationProvider.java b/src/main/java/dev/spexx/configurationAPI/api/ConfigurationProvider.java index 4a769ca..3fadd59 100644 --- a/src/main/java/dev/spexx/configurationAPI/api/ConfigurationProvider.java +++ b/src/main/java/dev/spexx/configurationAPI/api/ConfigurationProvider.java @@ -25,7 +25,6 @@ public class ConfigurationProvider { * and interacting with the Bukkit API. * * @param javaPlugin the owning plugin instance - * * @since 1.3.2 */ public ConfigurationProvider(@NotNull JavaPlugin javaPlugin) { @@ -39,7 +38,6 @@ public ConfigurationProvider(@NotNull JavaPlugin javaPlugin) { * and file watching functionality. * * @return the {@link ConfigManager} instance - * * @since 1.3.2 */ public @NotNull ConfigManager api() { diff --git a/src/main/java/dev/spexx/configurationAPI/api/config/yaml/YamlConfigWatcher.java b/src/main/java/dev/spexx/configurationAPI/api/config/yaml/YamlConfigWatcher.java index 4f06145..bd6bbd3 100644 --- a/src/main/java/dev/spexx/configurationAPI/api/config/yaml/YamlConfigWatcher.java +++ b/src/main/java/dev/spexx/configurationAPI/api/config/yaml/YamlConfigWatcher.java @@ -28,18 +28,14 @@ */ public class YamlConfigWatcher { + private static final long DEBOUNCE_MS = 200; private final @NotNull JavaPlugin javaPlugin; private final @NotNull WatchService watchService; - - private Thread watcherThread; - - private static final long DEBOUNCE_MS = 200; - private final @NotNull MapIf the file does not exist, it is created and loaded. The provided default + * values are then applied only to keys that are not already present in the file.
+ * + *Existing values are never overwritten.
+ * + * @param file the configuration file + * @param defaults a map of default key-value pairs to apply if missing + * @return the managed {@link YamlConfig} instance + * + * @throws ConfigException if: + *