diff --git a/pom.xml b/pom.xml
index 0b25067..d48aa7f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
This class encapsulates:
+ *This allows safe, non-throwing APIs for configuration handling.
+ * + * @since 1.1.0 + */ +public final class ConfigLoadResult { + + private final ConfigLoadStatus status; + private final YamlConfig config; + private final Exception error; + + /** + * Creates a new result instance. + * + * @param status operation status, must not be {@code null} + * @param config resulting configuration, may be {@code null} + * @param error exception if an error occurred, may be {@code null} + */ + public ConfigLoadResult( + @NotNull ConfigLoadStatus status, + YamlConfig config, + Exception error + ) { + this.status = status; + this.config = config; + this.error = error; + } + + /** + * Returns the operation status. + * + * @return status value, never {@code null} + */ + public @NotNull ConfigLoadStatus status() { + return status; + } + + /** + * Returns the loaded configuration if present. + * + * @return optional configuration + */ + public @NotNull OptionalThis enum is used in conjunction with {@link ConfigLoadResult} to provide + * structured feedback about configuration handling outcomes.
+ * + * @since 1.1.0 + */ +public enum ConfigLoadStatus { + + /** + * Configuration was successfully loaded from disk. + */ + LOADED, + + /** + * Configuration file was newly created and loaded. + */ + CREATED, + + /** + * Configuration already existed and was not created again. + */ + ALREADY_EXISTS, + + /** + * An I/O error occurred during read/write operations. + */ + IO_ERROR +} \ No newline at end of file diff --git a/src/main/java/dev/spexx/configurationAPI/config/YamlConfig.java b/src/main/java/dev/spexx/configurationAPI/config/YamlConfig.java index 1987621..0142344 100644 --- a/src/main/java/dev/spexx/configurationAPI/config/YamlConfig.java +++ b/src/main/java/dev/spexx/configurationAPI/config/YamlConfig.java @@ -1,161 +1,329 @@ -package dev.spexx.configurationAPI.config; +package dev.spexx.configurationAPI.manager; -import org.bukkit.configuration.file.FileConfiguration; +import dev.spexx.configurationAPI.config.ConfigLoadResult; +import dev.spexx.configurationAPI.config.ConfigLoadStatus; +import dev.spexx.configurationAPI.config.YamlConfig; +import dev.spexx.configurationAPI.watcher.GlobalConfigWatcher; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.io.File; -import java.util.List; -import java.util.Optional; +import java.io.IOException; +import java.nio.file.Path; +import java.util.Collection; +import java.util.Objects; /** - * Immutable snapshot of a YAML configuration file. + * Central access point for configuration files managed by the system. * - *This class encapsulates the following components:
+ *This class is responsible for:
*Each instance represents a point-in-time view of a configuration file. - * When the file is reloaded, a new {@code YamlConfig} instance is created - * and atomically replaces the previous instance.
+ *The {@link GlobalConfigWatcher} serves as the single source of truth for all + * configuration state. This class delegates all state management to the watcher + * and does not maintain its own cache.
* - *This class is immutable. All fields are {@code final} and no mutator - * methods are provided. This ensures thread-safe access without requiring - * synchronization.
+ *This class provides multiple levels of control over configuration handling:
+ *Instances should be treated as read-only snapshots. Consumers should not - * cache instances long-term if they require access to the most up-to-date - * configuration state.
+ *This class is thread-safe. The underlying watcher uses concurrent data structures + * and atomic replacement of {@link YamlConfig} instances.
+ * + * @apiNote {@link YamlConfig} instances are immutable snapshots. Consumers should + * retrieve them on demand instead of caching long-term references. * * @since 1.1.0 */ -public record YamlConfig(@NotNull File file, @NotNull FileConfiguration config) { +public final class ConfigManager { + + private final @NotNull JavaPlugin plugin; + private final @NotNull GlobalConfigWatcher watcher; /** - * Returns the underlying configuration file. + * Creates a new {@code ConfigManager}. + * + *This initializes and starts the {@link GlobalConfigWatcher}, which begins + * monitoring registered configuration files for changes.
+ * + * @param plugin owning plugin instance, must not be {@code null} + * + * @throws NullPointerException if {@code plugin} is {@code null} + * @throws RuntimeException if watcher initialization fails */ - @Override - public @NotNull File file() { - return file; + public ConfigManager(@NotNull JavaPlugin plugin) { + this.plugin = Objects.requireNonNull(plugin, "plugin"); + + try { + this.watcher = new GlobalConfigWatcher(plugin); + this.watcher.start(); + } catch (IOException e) { + throw new RuntimeException("Failed to initialize GlobalConfigWatcher", e); + } } /** - * Returns the parsed configuration snapshot. + * Returns the latest configuration snapshot for the given file. * - *This object should be treated as read-only.
+ *The configuration must have been previously loaded using one of the + * loading methods. This method does not attempt to load the file.
+ * + * @param file configuration file, must not be {@code null} + * @return latest {@link YamlConfig} snapshot, never {@code null} + * + * @throws NullPointerException if {@code file} is {@code null} + * @throws IllegalStateException if the configuration is not loaded */ - @Override - public @NotNull FileConfiguration config() { + public @NotNull YamlConfig get(@NotNull File file) { + Path path = normalize(file.toPath()); + + YamlConfig config = watcher.get(path); + if (config == null) { + throw new IllegalStateException("Config not loaded: " + path); + } + return config; } - // ========================================================= - // Optional-based getters (safe access) - // ========================================================= + /** + * Returns an existing configuration or loads it if not already tracked. + * + *If the configuration is not registered, it will be loaded from disk + * and registered with the watcher.
+ * + * @param file configuration file, must not be {@code null} + * @return latest {@link YamlConfig} snapshot, never {@code null} + * + * @throws NullPointerException if {@code file} is {@code null} + */ + public @NotNull YamlConfig getOrLoad(@NotNull File file) { + Path path = normalize(file.toPath()); + + YamlConfig existing = watcher.get(path); + if (existing != null) { + return existing; + } + + return loadInternal(file); + } /** - * Returns a string value at the given path. + * Loads a configuration file intended for internal plugin usage. + * + *This method guarantees that the configuration file exists by copying it + * from the plugin JAR if it is not already present in the plugin's data folder.
+ * + *If the file already exists, it is simply loaded and returned.
* - * @param path config path - * @return Optional containing the value, or empty if not present + *Behavior:
+ *Failure Conditions:
+ *This method will throw an exception if the resource does not exist inside + * the plugin JAR. It is intended strictly for internal configuration files that + * are guaranteed to be packaged with the plugin.
+ * + *Example usage:
+ *
+ * YamlConfig config = manager.getInternal("config.yml");
+ *
+ *
+ * @param resourceName name of the resource inside the plugin JAR (e.g. {@code "config.yml"}), must not be {@code null}
+ * @return loaded {@link YamlConfig} snapshot, never {@code null}
+ *
+ * @throws NullPointerException if {@code resourceName} is {@code null}
+ * @throws IllegalArgumentException if the resource does not exist in the plugin JAR
+ * @throws RuntimeException if loading or registration fails
*/
- public @NotNull OptionalThis method always attempts to load the file regardless of its current + * registration state.
+ * + * @param file configuration file, must not be {@code null} + * @return loaded {@link YamlConfig} snapshot, never {@code null} * - *This method avoids Bukkit's default fallback (0) by checking presence.
+ * @throws NullPointerException if {@code file} is {@code null} + * @throws RuntimeException if loading or registration fails */ - public @NotNull OptionalThis method does not throw exceptions. Instead, it returns a structured + * {@link ConfigLoadResult} describing the outcome.
+ * + * @param file configuration file, must not be {@code null} + * @return result object containing status, configuration, or error */ - public @NotNull OptionalIf the file already exists, the existing configuration is returned.
+ * + * @param file configuration file, must not be {@code null} + * @return result object describing the outcome */ - public @NotNull Optional