diff --git a/playtime-api/src/main/java/com/github/imdmk/playtime/PlayTimeApi.java b/playtime-api/src/main/java/com/github/imdmk/playtime/PlayTimeApi.java index 7c239fd..298bec0 100644 --- a/playtime-api/src/main/java/com/github/imdmk/playtime/PlayTimeApi.java +++ b/playtime-api/src/main/java/com/github/imdmk/playtime/PlayTimeApi.java @@ -1,59 +1,10 @@ package com.github.imdmk.playtime; import com.github.imdmk.playtime.user.UserService; -import org.jetbrains.annotations.NotNull; -/** - * Central API contract for interacting with the PlayTime plugin’s core services. - * - *
This interface provides unified access to the main subsystems of the plugin:
- * - *External plugins can use this interface to integrate with PlayTime features - * without depending on internal implementation details. The implementation is provided - * automatically by the PlayTime plugin during runtime initialization.
- * - *Usage Example:
- * - *{@code
- * PlayTimeApi api = PlayTimeApiProvider.get();
- *
- * UserService userService = api.userService();
- * PlaytimeService playtimeService = api.playtimeService();
- *
- * UUID uuid = player.getUniqueId();
- * UserTime time = playtimeService.getTime(uuid);
- * }
- *
- * @see PlaytimeService
- * @see com.github.imdmk.playtime.user.UserService
- * @see com.github.imdmk.playtime.user.UserTime
- */
public interface PlayTimeApi {
- /**
- * Returns the {@link UserService}, which provides access to user-management operations
- * such as creating, saving, and retrieving user data including playtime,
- * ranks, and metadata.
- *
- * @return non-null {@link UserService} instance
- */
- @NotNull UserService userService();
+ UserService getUserService();
- /**
- * Returns the {@link PlaytimeService}, which provides high-level operations for
- * retrieving and modifying player playtime data.
- *
- * This service acts as the bridge between the plugin’s internal user model - * and the underlying storage or platform-specific systems.
- * - * @return non-null {@link PlaytimeService} instance - */ - @NotNull PlaytimeService playtimeService(); + PlayTimeService getPlayTimeService(); } diff --git a/playtime-api/src/main/java/com/github/imdmk/playtime/PlayTimeApiProvider.java b/playtime-api/src/main/java/com/github/imdmk/playtime/PlayTimeApiProvider.java index 0a1ff3c..526a273 100644 --- a/playtime-api/src/main/java/com/github/imdmk/playtime/PlayTimeApiProvider.java +++ b/playtime-api/src/main/java/com/github/imdmk/playtime/PlayTimeApiProvider.java @@ -2,11 +2,6 @@ import org.jetbrains.annotations.NotNull; -/** - * Static access point for the {@link PlayTimeApi}. - *- * Thread-safe: publication via synchronized register/unregister and a volatile reference. - */ public final class PlayTimeApiProvider { private static volatile PlayTimeApi API; // visibility across threads @@ -15,35 +10,18 @@ private PlayTimeApiProvider() { throw new UnsupportedOperationException("This class cannot be instantiated."); } - /** - * Returns the registered {@link PlayTimeApi}. - * - * @return the registered API - * @throws IllegalStateException if the API is not registered - */ - public static @NotNull PlayTimeApi get() { - PlayTimeApi api = API; + public static PlayTimeApi get() { + final PlayTimeApi api = API; if (api == null) { throw new IllegalStateException("PlayTimeAPI is not registered."); } return api; } - /** - * Checks if the API is registered - * - * @return {@code true} if the API is registered. - */ public static boolean isRegistered() { return API != null; } - /** - * Registers the {@link PlayTimeApi} instance. - * - * @param api the API instance to register - * @throws IllegalStateException if already registered - */ static synchronized void register(@NotNull PlayTimeApi api) { if (API != null) { throw new IllegalStateException("PlayTimeAPI is already registered."); @@ -51,20 +29,10 @@ static synchronized void register(@NotNull PlayTimeApi api) { API = api; } - /** - * Forces registration of the {@link PlayTimeApi} instance. - *
- * Intended for tests/bootstrap only; overwrites any existing instance. - */ static synchronized void forceRegister(@NotNull PlayTimeApi api) { API = api; } - /** - * Unregisters the {@link PlayTimeApi}. - * - * @throws IllegalStateException if no API was registered - */ static synchronized void unregister() { if (API == null) { throw new IllegalStateException("PlayTimeAPI is not registered."); diff --git a/playtime-api/src/main/java/com/github/imdmk/playtime/PlayTimeService.java b/playtime-api/src/main/java/com/github/imdmk/playtime/PlayTimeService.java new file mode 100644 index 0000000..1008372 --- /dev/null +++ b/playtime-api/src/main/java/com/github/imdmk/playtime/PlayTimeService.java @@ -0,0 +1,15 @@ +package com.github.imdmk.playtime; + +import com.github.imdmk.playtime.user.UserTime; +import org.jetbrains.annotations.NotNull; + +import java.util.UUID; + +public interface PlayTimeService { + + UserTime getTime(@NotNull UUID uuid); + + void setTime(@NotNull UUID uuid, @NotNull UserTime time); + + void resetTime(@NotNull UUID uuid); +} diff --git a/playtime-api/src/main/java/com/github/imdmk/playtime/PlaytimeService.java b/playtime-api/src/main/java/com/github/imdmk/playtime/PlaytimeService.java deleted file mode 100644 index cb3895d..0000000 --- a/playtime-api/src/main/java/com/github/imdmk/playtime/PlaytimeService.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.github.imdmk.playtime; - -import com.github.imdmk.playtime.user.UserTime; -import org.jetbrains.annotations.NotNull; - -import java.util.UUID; - -/** - * A high-level abstraction for accessing and modifying player playtime data. - *
- * Implementations of this interface are responsible for bridging between - * the plugin domain model ({@link UserTime}) and the underlying platform’s - * data source (e.g., Bukkit statistics API, database, etc.). - *
- * Playtime is typically expressed in Minecraft ticks (20 ticks = 1 second), - * but the {@link UserTime} abstraction handles conversions to and from human-readable units. - * - * @see com.github.imdmk.playtime.user.UserTime - */ -public interface PlaytimeService { - - /** - * Retrieves the total accumulated playtime for the specified player. - * - * @param uuid - * the unique identifier of the player whose playtime should be fetched; - * must not be {@code null} - * @return - * a non-null {@link UserTime} representing the player’s total playtime. - * If no playtime is recorded or the player has never joined, returns {@link UserTime#ZERO}. - * @throws NullPointerException - * if {@code uuid} is {@code null}. - */ - @NotNull UserTime getTime(@NotNull UUID uuid); - - /** - * Sets the total playtime for the specified player to the given value. - * - * @param uuid - * the unique identifier of the player whose playtime should be updated; - * must not be {@code null} - * @param time - * the new total playtime value to assign; must not be {@code null} - * @throws NullPointerException - * if {@code uuid} or {@code time} is {@code null} - */ - void setTime(@NotNull UUID uuid, @NotNull UserTime time); - - /** - * Resets the total recorded playtime of the specified player to zero. - * - * @param uuid - * the unique identifier of the player whose playtime should be reset; - * must not be {@code null} - * @throws NullPointerException - * if {@code uuid} is {@code null} - */ - void resetTime(@NotNull UUID uuid); -} diff --git a/playtime-api/src/main/java/com/github/imdmk/playtime/user/User.java b/playtime-api/src/main/java/com/github/imdmk/playtime/user/User.java index ebc559a..bf83867 100644 --- a/playtime-api/src/main/java/com/github/imdmk/playtime/user/User.java +++ b/playtime-api/src/main/java/com/github/imdmk/playtime/user/User.java @@ -2,123 +2,50 @@ import org.jetbrains.annotations.NotNull; -import java.util.Objects; import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; -/** - * Represents an immutable-identity player aggregate containing all tracked - * playtime-related metadata. - * - *
This class is the main domain model for player statistics and provides: - *
- * Two {@code User} instances are considered equal if and only if their UUIDs match. - */ public final class User { - /** Permanently immutable player UUID. */ private final UUID uuid; - - /** Last known player name. Volatile for safe cross-thread publication. */ private volatile String name; - /** Total accumulated playtime in milliseconds. */ private final AtomicLong playtimeMillis; - /** - * Creates a fully initialized {@code User} instance. - * - * @param uuid unique player identifier (never null) - * @param name last known player name (never null or blank) - * @param playtime initial playtime value (never null) - */ public User(@NotNull UUID uuid, @NotNull String name, @NotNull UserTime playtime) { - Objects.requireNonNull(playtime, "playtime cannot be null"); - - this.uuid = Objects.requireNonNull(uuid, "uuid cannot be null"); - this.name = Objects.requireNonNull(name, "name cannot be null"); + this.uuid = uuid; + this.name = name; this.playtimeMillis = new AtomicLong(playtime.millis()); } - /** - * Convenience constructor for a new player with zero playtime. - * - * @param uuid unique player identifier - * @param name last known player name - */ public User(@NotNull UUID uuid, @NotNull String name) { this(uuid, name, UserTime.ZERO); } - /** - * Returns the unique identifier of this user. - * - * @return player's UUID (never null) - */ @NotNull public UUID getUuid() { return this.uuid; } - /** - * Returns the last known player name. - * - * @return name as a non-null String - */ @NotNull public String getName() { return this.name; } - /** - * Updates the stored player name. - * - * @param name the new name (non-null, non-blank) - * @throws NullPointerException if name is null - * @throws IllegalArgumentException if name is blank - */ public void setName(@NotNull String name) { - Objects.requireNonNull(name, "name cannot be null"); - if (name.trim().isEmpty()) { - throw new IllegalArgumentException("name cannot be blank"); - } this.name = name; } - /** - * Returns the total accumulated playtime as an immutable {@link UserTime} object. - * - * @return playtime value (never null) - */ @NotNull public UserTime getPlaytime() { return UserTime.ofMillis(playtimeMillis.get()); } - /** - * Replaces the stored playtime with a new value. - * - * @param playtime the new playtime (must not be null) - * @throws NullPointerException if playtime is null - */ public void setPlaytime(@NotNull UserTime playtime) { - Objects.requireNonNull(playtime, "playtime cannot be null"); playtimeMillis.set(playtime.millis()); } - /** - * Users are equal if and only if their UUIDs match. - */ @Override public boolean equals(Object o) { if (this == o) return true; @@ -126,17 +53,11 @@ public boolean equals(Object o) { return uuid.equals(other.uuid); } - /** - * Hash code is based solely on UUID. - */ @Override public int hashCode() { return uuid.hashCode(); } - /** - * Returns a concise diagnostic string representation. - */ @Override public String toString() { return "User{" + diff --git a/playtime-api/src/main/java/com/github/imdmk/playtime/user/UserDeleteResult.java b/playtime-api/src/main/java/com/github/imdmk/playtime/user/UserDeleteResult.java index 01e7fa5..7f8ee36 100644 --- a/playtime-api/src/main/java/com/github/imdmk/playtime/user/UserDeleteResult.java +++ b/playtime-api/src/main/java/com/github/imdmk/playtime/user/UserDeleteResult.java @@ -3,32 +3,8 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -/** - * Immutable result container representing the outcome of a user deletion attempt. - * - *
This record provides both contextual information: - * the {@link User} instance (if it existed and was deleted) and a - * {@link UserDeleteStatus} value describing the operation result.
- * - *Usage: Always check {@link #status()} to determine the deletion outcome. - * {@link #user()} may be {@code null} if the user was not found or the operation failed.
- * - * @param user the deleted user instance, or {@code null} if the user did not exist or was not deleted - * @param status non-null result status representing the outcome of the deletion - * - * @see User - * @see UserDeleteStatus - */ public record UserDeleteResult(@Nullable User user, @NotNull UserDeleteStatus status) { - /** - * Indicates whether the deletion succeeded and the user actually existed. - *- * This method is equivalent to checking: - *
{@code user != null && status == UserDeleteStatus.DELETED}
- *
- * @return {@code true} if the user was successfully deleted; {@code false} otherwise
- */
public boolean isSuccess() {
return this.user != null && this.status == UserDeleteStatus.DELETED;
}
diff --git a/playtime-api/src/main/java/com/github/imdmk/playtime/user/UserDeleteStatus.java b/playtime-api/src/main/java/com/github/imdmk/playtime/user/UserDeleteStatus.java
index 8c87147..efb33cc 100644
--- a/playtime-api/src/main/java/com/github/imdmk/playtime/user/UserDeleteStatus.java
+++ b/playtime-api/src/main/java/com/github/imdmk/playtime/user/UserDeleteStatus.java
@@ -1,33 +1,7 @@
package com.github.imdmk.playtime.user;
-/**
- * Enumerates all possible outcomes of a user deletion request.
- *
- * This enum is typically returned as part of a {@link UserDeleteResult} - * to describe whether the deletion succeeded, the user was missing, or - * an internal failure occurred during the operation.
- * - *Usage: Used primarily by {@code UserService} or repository - * implementations to standardize deletion responses.
- * - * @see UserDeleteResult - * @see User - */ public enum UserDeleteStatus { - - /** - * The user existed and was successfully removed from persistent storage. - */ DELETED, - - /** - * The user was not present in the data source at the time of deletion. - */ NOT_FOUND, - - /** - * The deletion operation failed due to an unexpected exception, - * connectivity issue, or database constraint violation. - */ FAILED } diff --git a/playtime-api/src/main/java/com/github/imdmk/playtime/user/UserSaveReason.java b/playtime-api/src/main/java/com/github/imdmk/playtime/user/UserSaveReason.java index 37e44a6..64486e2 100644 --- a/playtime-api/src/main/java/com/github/imdmk/playtime/user/UserSaveReason.java +++ b/playtime-api/src/main/java/com/github/imdmk/playtime/user/UserSaveReason.java @@ -1,47 +1,12 @@ package com.github.imdmk.playtime.user; -/** - * Describes the context in which a {@link User} instance is persisted. - * - *These reasons help services, repositories, logging and auditing systems - * understand why a save operation took place.
- * - *Typical usage: passed to {@code UserService#save(User, UserSaveReason)} - * to provide semantic context for persistence logic.
- * - * @see User - * @see UserService - */ public enum UserSaveReason { - /** - * The player joined the server — user data is loaded or created. - */ PLAYER_JOIN, - - /** - * The player left the server — user data should be persisted. - */ PLAYER_LEAVE, - /** - * An administrator explicitly set the user's playtime via command. - */ SET_COMMAND, - - /** - * An administrator reset the user's playtime via command. - */ RESET_COMMAND, - /** - * The user's data was persisted by a scheduled task - * (e.g., automatic save every 5 minutes). - */ - SCHEDULED_SAVE, - - /** - * The user's playtime was reset by a GUI action (e.g., button click). - */ GUI_RESET_CLICK } diff --git a/playtime-api/src/main/java/com/github/imdmk/playtime/user/UserService.java b/playtime-api/src/main/java/com/github/imdmk/playtime/user/UserService.java index b3ab8e5..ebc38ec 100644 --- a/playtime-api/src/main/java/com/github/imdmk/playtime/user/UserService.java +++ b/playtime-api/src/main/java/com/github/imdmk/playtime/user/UserService.java @@ -8,109 +8,19 @@ import java.util.UUID; import java.util.concurrent.CompletableFuture; -/** - * High-level service for accessing and managing {@link User} data. - *- * Provides both cache-only (synchronous, safe for main-thread use) - * and asynchronous (database-backed) operations. - *
- * Implementations are expected to handle caching, persistence, and - * consistency automatically. - */ public interface UserService { - /** - * Finds a user by their unique UUID from the in-memory cache only. - *
- * This method is non-blocking and safe to call from the main server thread.
- *
- * @param uuid the user's UUID
- * @return an {@link Optional} containing the user if present in cache,
- * or empty if not found
- */
- @NotNull Optional
- * This method is non-blocking and safe to call from the main server thread.
- *
- * @param name the user's name (case-insensitive, depending on implementation)
- * @return an {@link Optional} containing the user if present in cache,
- * or empty if not found
- */
- @NotNull Optional
- * This collection reflects a moment-in-time view and is not updated dynamically.
- * Safe to call from the main thread.
- *
- * @return a collection of cached {@link User} objects
- */
- @NotNull Collection
- * The result may come from the in-memory
- * cached leaderboard or trigger an asynchronous refresh when the cache has expired
- * or does not satisfy the requested limit.
- *
- * If the user already exists, their data is updated.
- *
- * @param user the user to save
- * @param reason the reason of save
- * @return a {@link CompletableFuture} containing the saved user
- */
- @NotNull CompletableFuture This record provides convenient conversions between milliseconds, seconds,
- * Bukkit ticks (1 tick = 50 ms), and {@link Duration}, as well as arithmetic
- * and comparison utilities for working with user playtime or uptime data. Design notes:
- * Carries a {@link UserDeleteResult} with the deleted user snapshot (if any)
- * and the {@link com.github.imdmk.playtime.user.UserDeleteStatus} outcome.
- * Threading: Dispatched synchronously on the main server thread.
- * This method is required by the Bukkit event framework and allows Bukkit
- * to correctly map event handlers to this event class.
- *
- * @return non-null static {@link HandlerList}
- */
public static @NotNull HandlerList getHandlerList() {
return HANDLERS;
}
diff --git a/playtime-bukkit-api/src/main/java/com/github/imdmk/playtime/UserPreSaveEvent.java b/playtime-bukkit-api/src/main/java/com/github/imdmk/playtime/UserPreSaveEvent.java
deleted file mode 100644
index 3b4ebd8..0000000
--- a/playtime-bukkit-api/src/main/java/com/github/imdmk/playtime/UserPreSaveEvent.java
+++ /dev/null
@@ -1,84 +0,0 @@
-package com.github.imdmk.playtime;
-
-import com.github.imdmk.playtime.user.User;
-import com.github.imdmk.playtime.user.UserSaveReason;
-import org.bukkit.event.Event;
-import org.bukkit.event.HandlerList;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.Objects;
-
-/**
- * Event fired immediately before a {@link User} instance is persisted
- * to the database by the plugin.
- *
- * This event serves as a pre-save hook, allowing listeners to
- * inspect or modify the {@link User} object before it is written to storage.
- * Any changes made to the user during this event will be included in the
- * final persisted representation. Note: This event is not cancellable. It is strictly intended
- * for mutation or inspection of the user object before saving. If cancellation
- * behavior is required in the future, a dedicated cancellable event should be introduced. Thread safety: This event is always fired synchronously on the
- * main server thread. Modifying this object will affect the data written to storage. This event is fired after user data has been persisted.
- * It can be canceled to prevent later operations that depend on the save,
- * if applicable within the plugin's logic. Thread safety: This event is always fired synchronously on the main server thread.
- * This method is required by the Bukkit event system and is used
- * to register and manage listeners for this event.
- *
- * @return non-null static {@link HandlerList}
- */
public static @NotNull HandlerList getHandlerList() {
return HANDLERS;
}
diff --git a/playtime-core/build.gradle.kts b/playtime-core/build.gradle.kts
index 918ecdb..2fdc0bb 100644
--- a/playtime-core/build.gradle.kts
+++ b/playtime-core/build.gradle.kts
@@ -13,6 +13,9 @@ dependencies {
// Dynamic dependency loader
implementation("com.alessiodp.libby:libby-bukkit:2.0.0-SNAPSHOT")
+ // Reflections
+ implementation("org.reflections:reflections:0.10.2")
+
// Multification
implementation("com.eternalcode:multification-bukkit:1.2.3")
implementation("com.eternalcode:multification-okaeri:1.2.3")
diff --git a/playtime-core/src/main/java/com/github/imdmk/playtime/PlayTimeApiAdapter.java b/playtime-core/src/main/java/com/github/imdmk/playtime/PlayTimeApiAdapter.java
index c66e1be..ece0e34 100644
--- a/playtime-core/src/main/java/com/github/imdmk/playtime/PlayTimeApiAdapter.java
+++ b/playtime-core/src/main/java/com/github/imdmk/playtime/PlayTimeApiAdapter.java
@@ -1,21 +1,10 @@
package com.github.imdmk.playtime;
import com.github.imdmk.playtime.user.UserService;
-import org.jetbrains.annotations.NotNull;
import org.panda_lang.utilities.inject.annotations.Inject;
@Inject
record PlayTimeApiAdapter(
- @NotNull UserService userService,
- @NotNull PlaytimeService playtimeService) implements PlayTimeApi {
-
- @Override
- public @NotNull UserService userService() {
- return userService;
- }
-
- @Override
- public @NotNull PlaytimeService playtimeService() {
- return playtimeService;
- }
-}
+ UserService getUserService,
+ PlayTimeService getPlayTimeService
+) implements PlayTimeApi {}
diff --git a/playtime-core/src/main/java/com/github/imdmk/playtime/PlayTimeBinder.java b/playtime-core/src/main/java/com/github/imdmk/playtime/PlayTimeBinder.java
deleted file mode 100644
index 1bacd48..0000000
--- a/playtime-core/src/main/java/com/github/imdmk/playtime/PlayTimeBinder.java
+++ /dev/null
@@ -1,77 +0,0 @@
-package com.github.imdmk.playtime;
-
-import com.github.imdmk.playtime.infrastructure.injector.Bind;
-import com.github.imdmk.playtime.shared.validate.Validator;
-import org.jetbrains.annotations.NotNull;
-import org.panda_lang.utilities.inject.Injector;
-import org.panda_lang.utilities.inject.Resources;
-
-import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
-
-/**
- * Discovers fields in {@link PlayTimePlugin} annotated with {@link Bind}
- * and registers their instances into the DI {@link Resources}.
- *
- * This approach keeps {@link PlayTimePlugin} focused on lifecycle/bootstrap logic
- * while delegating dependency wiring to a dedicated, reflection-based binder.
- * Only non-static fields with {@code @Bind} are processed.
- */
-final class PlayTimeBinder {
-
- private final PlayTimePlugin core;
-
- /**
- * Creates a new binder for the given plugin instance.
- *
- * @param core the plugin root object providing core dependencies
- */
- PlayTimeBinder(@NotNull PlayTimePlugin core) {
- this.core = Validator.notNull(core, "core");
- }
-
- /**
- * Scans the {@link PlayTimePlugin} class hierarchy, locates fields annotated with
- * {@link Bind}, reads their values, and registers them into the provided
- * {@link Resources} instance.
- *
- * @param resources DI container resources to bind into
- */
- void bind(@NotNull Resources resources) {
- Validator.notNull(resources, "resources");
-
- Class> type = core.getClass();
-
- while (type != null && type != Object.class) {
- for (Field field : type.getDeclaredFields()) {
- if (!field.isAnnotationPresent(Bind.class)) {
- continue;
- }
-
- if (Modifier.isStatic(field.getModifiers())) {
- continue;
- }
-
- field.setAccessible(true);
-
- final Object value;
- try {
- value = field.get(core);
- } catch (IllegalAccessException e) {
- throw new IllegalStateException("Failed to access @BindCore field: " + field, e);
- }
-
- if (value == null) {
- throw new IllegalStateException("@BindCore field " + field + " is null during binding");
- }
-
- resources.on(field.getType()).assignInstance(value);
- }
-
- type = type.getSuperclass();
- }
-
- // Provide Injector via lazy supplier
- resources.on(Injector.class).assignInstance(() -> core.injector);
- }
-}
diff --git a/playtime-core/src/main/java/com/github/imdmk/playtime/PlayTimePlugin.java b/playtime-core/src/main/java/com/github/imdmk/playtime/PlayTimePlugin.java
index a6311bf..2ff80fc 100644
--- a/playtime-core/src/main/java/com/github/imdmk/playtime/PlayTimePlugin.java
+++ b/playtime-core/src/main/java/com/github/imdmk/playtime/PlayTimePlugin.java
@@ -1,211 +1,48 @@
package com.github.imdmk.playtime;
-import com.eternalcode.multification.notice.Notice;
-import com.github.imdmk.playtime.config.ConfigManager;
-import com.github.imdmk.playtime.config.ConfigSection;
-import com.github.imdmk.playtime.config.InjectorConfigBinder;
-import com.github.imdmk.playtime.config.PluginConfig;
-import com.github.imdmk.playtime.infrastructure.database.DatabaseConfig;
-import com.github.imdmk.playtime.infrastructure.database.DatabaseManager;
-import com.github.imdmk.playtime.infrastructure.database.repository.RepositoryContext;
-import com.github.imdmk.playtime.infrastructure.database.repository.RepositoryManager;
-import com.github.imdmk.playtime.infrastructure.injector.Bind;
-import com.github.imdmk.playtime.infrastructure.module.Module;
-import com.github.imdmk.playtime.infrastructure.module.ModuleContext;
-import com.github.imdmk.playtime.infrastructure.module.ModuleInitializer;
-import com.github.imdmk.playtime.infrastructure.module.ModuleRegistry;
-import com.github.imdmk.playtime.message.MessageConfig;
-import com.github.imdmk.playtime.message.MessageService;
-import com.github.imdmk.playtime.platform.events.BukkitEventCaller;
-import com.github.imdmk.playtime.platform.events.BukkitListenerRegistrar;
-import com.github.imdmk.playtime.platform.gui.GuiRegistry;
-import com.github.imdmk.playtime.platform.litecommands.InvalidUsageHandlerImpl;
-import com.github.imdmk.playtime.platform.litecommands.MissingPermissionsHandlerImpl;
-import com.github.imdmk.playtime.platform.litecommands.NoticeResultHandlerImpl;
+import com.github.imdmk.playtime.injector.ComponentManager;
+import com.github.imdmk.playtime.injector.priority.AnnotationPriorityProvider;
+import com.github.imdmk.playtime.injector.processor.ComponentProcessor;
+import com.github.imdmk.playtime.injector.processor.ComponentProcessors;
+import com.github.imdmk.playtime.injector.subscriber.LocalPublisher;
+import com.github.imdmk.playtime.injector.subscriber.Publisher;
import com.github.imdmk.playtime.platform.logger.BukkitPluginLogger;
import com.github.imdmk.playtime.platform.logger.PluginLogger;
-import com.github.imdmk.playtime.platform.placeholder.adapter.PlaceholderAdapter;
-import com.github.imdmk.playtime.platform.placeholder.adapter.PlaceholderAdapterFactory;
-import com.github.imdmk.playtime.platform.scheduler.BukkitTaskScheduler;
-import com.github.imdmk.playtime.platform.scheduler.TaskScheduler;
-import com.github.imdmk.playtime.shared.time.Durations;
-import com.github.imdmk.playtime.shared.validate.Validator;
-import com.google.common.base.Stopwatch;
-import dev.rollczi.litecommands.LiteCommands;
-import dev.rollczi.litecommands.LiteCommandsBuilder;
-import dev.rollczi.litecommands.bukkit.LiteBukkitFactory;
-import net.kyori.adventure.platform.bukkit.BukkitAudiences;
-import org.bstats.bukkit.Metrics;
import org.bukkit.Server;
-import org.bukkit.command.CommandSender;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.NotNull;
import org.panda_lang.utilities.inject.DependencyInjection;
import org.panda_lang.utilities.inject.Injector;
-import java.sql.SQLException;
-import java.time.Duration;
-import java.util.List;
-import java.util.concurrent.ExecutorService;
+import java.io.File;
-/**
- * Main runtime bootstrap for the PlayTime plugin.
- * Threading note: heavy I/O offloaded to {@link ExecutorService}.
- */
final class PlayTimePlugin {
- private static final String PREFIX = "AdvancedPlayTime";
- private static final int PLUGIN_METRICS_ID = 19362;
+ //private static final String PREFIX = "AdvancedPlayTime";
+ //private static final int PLUGIN_METRICS_ID = 19362;
- @Bind private final ModuleRegistry moduleRegistry = new ModuleRegistry();
+ private final Injector injector;
- @Bind private final Plugin plugin;
- @Bind private final PluginLogger logger;
- @Bind private final Server server;
- @Bind private final ExecutorService executor;
-
- @Bind private ConfigManager configManager;
-
- @Bind private DatabaseManager databaseManager;
- @Bind private RepositoryContext repositoryContext;
- @Bind private RepositoryManager repositoryManager;
-
- @Bind private MessageService messageService;
- @Bind private TaskScheduler taskScheduler;
- @Bind private BukkitEventCaller eventCaller;
- @Bind private BukkitListenerRegistrar listenerRegistrar;
- @Bind private GuiRegistry guiRegistry;
- @Bind private PlaceholderAdapter placeholderAdapter;
-
- @Bind private LiteCommandsBuilder
- * Extends {@link OkaeriConfig} to provide a reusable foundation for plugin
- * configuration sections. Subclasses are required to specify the
- * serialization/deserialization pack and the configuration file name.
- *
- * Supports automatic recursive loading of nested {@link ConfigSection}
- * subclasses declared as fields inside this class.
- *
- * If already connected, this method throws {@link IllegalStateException}.
- * Engine-specific configuration (JDBC URL, file paths, flags) is delegated
- * to the configured {@link DriverConfigurer}.
- *
- * @param dataFolder plugin data folder, used especially for file-based databases (e.g. SQLite/H2)
- * @throws SQLException if JDBC or ORMLite initialization fails
- * @throws IllegalStateException if a connection is already active
- */
- synchronized void connect(@NotNull File dataFolder) throws SQLException {
- Validator.notNull(dataFolder, "dataFolder cannot be null");
-
- if (dataSource != null || connectionSource != null) {
- throw new IllegalStateException("DatabaseConnector is already connected.");
- }
-
- final HikariDataSource ds = createHikariDataSource();
-
- try {
- // Delegated engine-specific configuration (JDBC URL, engine flags, filesystem prep)
- driverConfigurer.configure(ds, config, dataFolder);
-
- final String jdbcUrl = ds.getJdbcUrl();
- if (jdbcUrl == null || jdbcUrl.isBlank()) {
- throw new IllegalStateException("DriverConfigurer did not set JDBC URL for mode " + config.databaseMode);
- }
-
- final ConnectionSource source = new DataSourceConnectionSource(ds, jdbcUrl);
-
- dataSource = ds;
- connectionSource = source;
-
- logger.info("Connected to %s database.", config.databaseMode);
- } catch (SQLException e) {
- logger.error(e, "Failed to connect to database");
- closeQuietly(ds);
- dataSource = null;
- connectionSource = null;
- throw e;
- } catch (Exception e) {
- logger.error(e, "Failed to initialize database");
- closeQuietly(ds);
- dataSource = null;
- connectionSource = null;
- throw new IllegalStateException("Database initialization failed", e);
- }
- }
-
- /**
- * Closes the active database connection and shuts down the underlying HikariCP pool.
- *
- * Safe to call multiple times. Exceptions during close are logged but ignored.
- */
- synchronized void close() {
- if (connectionSource == null && dataSource == null) {
- logger.warn("DatabaseConnector#close() called, but not connected.");
- return;
- }
-
- try {
- if (connectionSource != null) {
- connectionSource.close();
- }
- } catch (Exception e) {
- logger.error(e, "Failed to close ConnectionSource");
- }
-
- closeQuietly(dataSource);
-
- connectionSource = null;
- dataSource = null;
-
- logger.info("Database connection closed successfully.");
- }
-
- /**
- * Returns whether this connector is currently connected.
- *
- * @return {@code true} if both {@link ConnectionSource} and {@link HikariDataSource} are active
- */
- boolean isConnected() {
- final HikariDataSource ds = dataSource;
- return connectionSource != null && ds != null && !ds.isClosed();
- }
-
- /**
- * Returns the current active {@link ConnectionSource}, or {@code null} if not connected.
- *
- * @return active ORMLite connection source, or {@code null} if disconnected
- */
- @Nullable ConnectionSource getConnectionSource() {
- return connectionSource;
- }
-
- /**
- * Creates and configures a new {@link HikariDataSource} with conservative, engine-agnostic defaults.
- *
- * Includes:
- *
- * Each value represents a distinct JDBC provider and is used to:
- *
- * Below each engine is annotated with practical recommendations
- * for typical Minecraft server environments.
- */
-public enum DatabaseMode {
-
- /**
- * MySQL — recommended for most production servers.
- *
- * Stable, well-supported, widely hosted, good performance under sustained load.
- * Best choice for: medium–large servers, networks, Bungee/Velocity setups.
- */
- MYSQL,
-
- /**
- * MariaDB — drop-in MySQL replacement.
- *
- * Often faster for reads, lighter resource usage, very stable on Linux hosts.
- * Best choice for: self-hosted servers (VPS/dedicated), users preferring open-source MySQL alternatives.
- */
- MARIADB,
-
- /**
- * SQLite — file-based embedded database.
- *
- * Zero configuration, no external server needed, safe for smaller datasets.
- * Best choice for: small servers, testing environments, local development.
- * Avoid it for large playtime tables or heavy concurrent write load.
- */
- SQLITE,
-
- /**
- * PostgreSQL — robust, enterprise-grade server engine.
- *
- * Very strong consistency guarantees, excellent indexing, powerful features.
- * Best choice for: large datasets, advanced analytics, servers on modern hosting (e.g., managed PSQL).
- */
- POSTGRESQL,
-
- /**
- * H2 — lightweight embedded or file-based engine.
- *
- * Faster than SQLite in many scenarios, supports MySQL compatibility mode.
- * Best choice for: plugin developers, embedded deployments, users wanting higher performance without external DB.
- * Not recommended for: huge datasets or multi-server networks.
- */
- H2,
-
- /**
- * SQL Server (MSSQL) — enterprise Microsoft database engine.
- *
- * Works well on Windows hosts, strong enterprise tooling.
- * Best choice for: Windows-based servers, corporate networks using MSSQL by default.
- * Rarely needed for typical Minecraft environments.
- */
- SQL
-}
diff --git a/playtime-core/src/main/java/com/github/imdmk/playtime/infrastructure/database/driver/configurer/DriverConfigurer.java b/playtime-core/src/main/java/com/github/imdmk/playtime/infrastructure/database/driver/configurer/DriverConfigurer.java
deleted file mode 100644
index 84621cf..0000000
--- a/playtime-core/src/main/java/com/github/imdmk/playtime/infrastructure/database/driver/configurer/DriverConfigurer.java
+++ /dev/null
@@ -1,41 +0,0 @@
-package com.github.imdmk.playtime.infrastructure.database.driver.configurer;
-
-import com.github.imdmk.playtime.infrastructure.database.DatabaseConfig;
-import com.zaxxer.hikari.HikariDataSource;
-import org.jetbrains.annotations.NotNull;
-
-import java.io.File;
-
-/**
- * Strategy interface defining how to configure a {@link HikariDataSource}
- * for a specific database engine.
- *
- * Implementations are responsible for:
- *
- * Implementations must be deterministic and side-effect-free except for:
- *
- * All supported drivers are registered statically in an immutable lookup table.
- * This ensures fast resolution, avoids reflection, and cleanly separates
- * database-specific logic into dedicated strategy classes.
- *
- * The factory acts as the single entry point for retrieving driver configuration
- * strategies used by {@code DatabaseConnector}.
- */
-public final class DriverConfigurerFactory {
-
- /** Immutable lookup table mapping database modes to their respective configurers. */
- private static final Map
- * Each {@link DatabaseMode} is mapped to a specific third-party JDBC driver
- * defined in {@link DriverLibraries}. This allows the plugin to ship without
- * any embedded JDBC drivers and load only the required one on demand.
- *
- * This component is deliberately isolated from connection logic to keep the
- * database layer modular and compliant with SRP (single responsibility).
- */
-public final class DriverDependencyLoader {
-
- /** Immutable lookup table mapping supported database modes to driver artifacts. */
- private static final Map
- * If the driver is already loaded, Libby will skip re-loading it automatically.
- *
- * @param mode the database mode requesting its driver (never null)
- * @throws IllegalArgumentException if the mode has no registered driver
- */
- public void loadDriverFor(@NotNull DatabaseMode mode) {
- Validator.notNull(mode, "mode cannot be null");
-
- Library library = LIBRARIES_BY_MODE.get(mode);
- if (library == null) {
- throw new IllegalArgumentException("Unsupported database mode: " + mode);
- }
-
- libraryManager.loadLibrary(library);
- }
-}
diff --git a/playtime-core/src/main/java/com/github/imdmk/playtime/infrastructure/database/repository/Repository.java b/playtime-core/src/main/java/com/github/imdmk/playtime/infrastructure/database/repository/Repository.java
deleted file mode 100644
index 97327d7..0000000
--- a/playtime-core/src/main/java/com/github/imdmk/playtime/infrastructure/database/repository/Repository.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package com.github.imdmk.playtime.infrastructure.database.repository;
-
-import com.j256.ormlite.support.ConnectionSource;
-import org.jetbrains.annotations.NotNull;
-
-import java.sql.SQLException;
-
-/**
- * Base contract for all repositories.
- *
- * Provides lifecycle hooks for database initialization and cleanup.
- * Implementations should create their DAO bindings in {@link #start(ConnectionSource)}
- * and release resources in {@link #close()}.
- */
-public interface Repository extends AutoCloseable {
-
- /**
- * Initializes repository to the given connection source.
- *
- * @param source the ORMLite connection source
- * @throws SQLException if database initialization fails
- */
- void start(@NotNull ConnectionSource source) throws SQLException;
-
- /**
- * Closes the repository and releases all resources.
- */
- @Override
- void close();
-}
diff --git a/playtime-core/src/main/java/com/github/imdmk/playtime/infrastructure/database/repository/RepositoryContext.java b/playtime-core/src/main/java/com/github/imdmk/playtime/infrastructure/database/repository/RepositoryContext.java
deleted file mode 100644
index fddcbb3..0000000
--- a/playtime-core/src/main/java/com/github/imdmk/playtime/infrastructure/database/repository/RepositoryContext.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package com.github.imdmk.playtime.infrastructure.database.repository;
-
-import com.github.imdmk.playtime.infrastructure.database.repository.ormlite.BaseDaoRepository;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.concurrent.ExecutorService;
-
-/**
- * Context container providing shared infrastructure resources
- * for all repositories.
- *
- * Currently encapsulates the {@link ExecutorService} responsible for
- * executing asynchronous database operations. This allows repositories
- * to offload blocking I/O work while maintaining a unified execution policy. Usage: Injected into repository instances (see {@link BaseDaoRepository})
- * to provide consistent thread management for database access. Threading: The supplied {@code dbExecutor} should be a dedicated,
- * bounded thread pool optimized for database I/O tasks — typically sized according
- * to connection pool limits or database concurrency capabilities. This class provides thread-safe registration, startup, and shutdown of repositories.
- * It acts as the single entry point for initializing all repositories
- * once the plugin’s database connection has been established. Thread-safety: Repository registration and iteration are backed by
- * {@link CopyOnWriteArrayList}, ensuring safe concurrent reads and registrations. If the repository has already been registered, the operation is skipped
- * and a warning is logged. This method creates required tables and initializes all DAO layers.
- * If any repository fails to start, the exception is logged and rethrown,
- * stopping further startup to prevent inconsistent state. Each repository’s {@link Repository#close()} method is invoked individually.
- * Exceptions during closing are caught and logged as warnings, allowing
- * all repositories to attempt shutdown even if one fails. Responsibilities: Thread-safety: the {@link #dao} reference is {@code volatile} to ensure
- * visibility after initialization. Repository implementations should still avoid compound unsynchronized
- * operations on the DAO. Tables for {@link #entityClass()} and all {@link #entitySubClasses()} are created if absent.
- * Then a new {@link Dao} is obtained via {@link DaoManager#createDao(ConnectionSource, Class)}. This method is idempotent. If no DAO is set, it returns immediately. Exceptions thrown by the supplier are logged and rethrown as {@link CompletionException}.
- * If the task exceeds timeout, the returned future completes exceptionally
- * with a {@link TimeoutException} wrapped in a {@link CompletionException}. Behavior: Exceptions thrown by the runnable are logged and propagated as {@link CompletionException}.
- * On timeout, the future completes exceptionally with a {@link TimeoutException} wrapped in a
- * {@link CompletionException}. Use this to guard synchronous code paths that assume the DAO is ready.
- * This abstraction keeps the repository layer decoupled from the domain layer,
- * allowing storage representations to evolve independently of business logic.
- *
- * @param
- * This is a convenience method for bulk transformations.
- *
- * @param entities list of entities to convert (never null)
- * @return list of mapped domain models
- */
- default @NotNull List
- * This is a convenience method for bulk transformations.
- *
- * @param domains list of domain objects to convert (never null)
- * @return list of mapped persistence entities
- */
- default @NotNull List All metadata interfaces (e.g. {@code UserEntityMeta}) should extend this
- * interface to indicate that they define static constants describing database
- * schema elements such as table and column names. This provides a unified contract for schema metadata used by ORMLite
- * entities, repositories, and migration utilities.
- * Fields annotated with {@code @BindCore} are discovered by
- * PlayTimeCoreBinder and exposed to
- * the Panda DI {@link org.panda_lang.utilities.inject.Resources} as singleton instances.
- *
- * Only non-static fields are eligible. A {@code null} value at binding time
- * results in a bootstrap failure.
- */
-@Injectable
-@Retention(RetentionPolicy.RUNTIME)
-@Target(ElementType.FIELD)
-public @interface Bind {
-}
diff --git a/playtime-core/src/main/java/com/github/imdmk/playtime/infrastructure/module/Module.java b/playtime-core/src/main/java/com/github/imdmk/playtime/infrastructure/module/Module.java
deleted file mode 100644
index 879c498..0000000
--- a/playtime-core/src/main/java/com/github/imdmk/playtime/infrastructure/module/Module.java
+++ /dev/null
@@ -1,85 +0,0 @@
-package com.github.imdmk.playtime.infrastructure.module;
-
-import com.github.imdmk.playtime.infrastructure.module.phase.CommandPhase;
-import com.github.imdmk.playtime.infrastructure.module.phase.GuiPhase;
-import com.github.imdmk.playtime.infrastructure.module.phase.ListenerPhase;
-import com.github.imdmk.playtime.infrastructure.module.phase.PlaceholderPhase;
-import com.github.imdmk.playtime.infrastructure.module.phase.RepositoryPhase;
-import com.github.imdmk.playtime.infrastructure.module.phase.TaskPhase;
-import org.bukkit.Server;
-import org.bukkit.plugin.Plugin;
-import org.jetbrains.annotations.NotNull;
-import org.panda_lang.utilities.inject.Injector;
-import org.panda_lang.utilities.inject.Resources;
-
-/**
- * Base lifecycle contract for all PlayTime modules.
- *
- * Lifecycle phases:
- * Threading: modules are initialized on the server main thread unless documented otherwise.
- * Implementations should avoid long blocking operations in {@code bind()} and {@code init()}. Acts as a central context object passed to all module lifecycle phases, providing access to:
- * This record is created once during plugin bootstrap and reused throughout the
- * module initialization pipeline. The initializer executes modules through a strict, ordered pipeline:
- * Each step is validated against an internal state machine to enforce correct order and avoid
- * partially initialized modules. All operations run exclusively on the Bukkit main thread. Errors thrown by individual modules never abort the lifecycle — they are logged and the
- * pipeline continues for remaining modules. Ordering is used by the module manager to produce a deterministic
- * initialization sequence. When two modules return the same value, the
- * manager should apply a stable tie-breaker (e.g., class name).
- * This registry is responsible for:
- *
- * The registry itself is stateless between runs: every call to
- * {@link #instantiateAndSort(Injector)} rebuilds the internal module list from the current types.
- *
- * Thread-safety: This class is not thread-safe and must be accessed from the main server thread.
- */
-public final class ModuleRegistry {
-
- /** Comparator defining deterministic module ordering: lower {@link Module#order()} first, then by class name. */
- private static final Comparator
- * This method does not instantiate modules; call {@link #instantiateAndSort(Injector)} afterwards
- * to build and sort the instances.
- *
- * @param types the new list of module classes (must not be null)
- * @param
- * This operation is idempotent for the current module types; previous instances are discarded.
- *
- * @param injector the dependency injector used to construct module instances (never null)
- * @throws NullPointerException if {@code injector} is null
- */
- public void instantiateAndSort(@NotNull Injector injector) {
- Validator.notNull(injector, "injector cannot be null");
-
- final List
- * The returned list is guaranteed to be ordered by {@link Module#order()} ascending,
- * with a lexicographic tiebreaker on the class name for consistency across JVM runs.
- *
- * @return an unmodifiable list of module instances (never null, may be empty)
- */
- public List
- * After calling this method, the registry returns to its initial empty state.
- */
- public void clear() {
- moduleTypes = List.of();
- modules = List.of();
- }
-}
diff --git a/playtime-core/src/main/java/com/github/imdmk/playtime/infrastructure/module/phase/CommandPhase.java b/playtime-core/src/main/java/com/github/imdmk/playtime/infrastructure/module/phase/CommandPhase.java
deleted file mode 100644
index 7bb0447..0000000
--- a/playtime-core/src/main/java/com/github/imdmk/playtime/infrastructure/module/phase/CommandPhase.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package com.github.imdmk.playtime.infrastructure.module.phase;
-
-import dev.rollczi.litecommands.LiteCommandsBuilder;
-import org.bukkit.command.CommandSender;
-import org.jetbrains.annotations.NotNull;
-
-/**
- * Functional phase interface responsible for command registration.
- *
- * Implementations should declare and configure commands using the
- * provided {@link LiteCommandsBuilder}.
- */
-@FunctionalInterface
-public interface CommandPhase {
-
- /**
- * Configures and registers commands for this module.
- *
- * @param configurer the command configurer used to register LiteCommands commands (never {@code null})
- */
- void configure(@NotNull LiteCommandsBuilder
- * Implementations should register all inventory or interface GUIs
- * via the provided {@link GuiRegistry}.
- */
-@FunctionalInterface
-public interface GuiPhase {
-
- /**
- * Registers all GUIs provided by this module.
- *
- * @param guiRegistry the GUI registry used for GUI definitions and factories (never {@code null})
- */
- void register(@NotNull GuiRegistry guiRegistry);
-}
diff --git a/playtime-core/src/main/java/com/github/imdmk/playtime/infrastructure/module/phase/ListenerPhase.java b/playtime-core/src/main/java/com/github/imdmk/playtime/infrastructure/module/phase/ListenerPhase.java
deleted file mode 100644
index ba7ed99..0000000
--- a/playtime-core/src/main/java/com/github/imdmk/playtime/infrastructure/module/phase/ListenerPhase.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.github.imdmk.playtime.infrastructure.module.phase;
-
-import com.github.imdmk.playtime.platform.events.BukkitListenerRegistrar;
-import org.jetbrains.annotations.NotNull;
-
-/**
- * Functional phase interface responsible for event listener registration.
- *
- * Implementations should register Bukkit {@link org.bukkit.event.Listener}s
- * using the provided {@link BukkitListenerRegistrar}.
- */
-@FunctionalInterface
-public interface ListenerPhase {
-
- /**
- * Registers all Bukkit listeners for this module.
- *
- * @param registrar the listener registrar used to bind Bukkit event listeners (never {@code null})
- */
- void register(@NotNull BukkitListenerRegistrar registrar);
-}
diff --git a/playtime-core/src/main/java/com/github/imdmk/playtime/infrastructure/module/phase/PlaceholderPhase.java b/playtime-core/src/main/java/com/github/imdmk/playtime/infrastructure/module/phase/PlaceholderPhase.java
deleted file mode 100644
index 15f50e4..0000000
--- a/playtime-core/src/main/java/com/github/imdmk/playtime/infrastructure/module/phase/PlaceholderPhase.java
+++ /dev/null
@@ -1,10 +0,0 @@
-package com.github.imdmk.playtime.infrastructure.module.phase;
-
-import com.github.imdmk.playtime.platform.placeholder.adapter.PlaceholderAdapter;
-import org.jetbrains.annotations.NotNull;
-
-@FunctionalInterface
-public interface PlaceholderPhase {
-
- void register(@NotNull PlaceholderAdapter placeholderAdapter);
-}
diff --git a/playtime-core/src/main/java/com/github/imdmk/playtime/infrastructure/module/phase/RepositoryPhase.java b/playtime-core/src/main/java/com/github/imdmk/playtime/infrastructure/module/phase/RepositoryPhase.java
deleted file mode 100644
index ad9ab8d..0000000
--- a/playtime-core/src/main/java/com/github/imdmk/playtime/infrastructure/module/phase/RepositoryPhase.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.github.imdmk.playtime.infrastructure.module.phase;
-
-import com.github.imdmk.playtime.infrastructure.database.repository.RepositoryManager;
-import org.jetbrains.annotations.NotNull;
-
-/**
- * Functional phase interface responsible for repository registration.
- *
- * Implementations should declare repository descriptors only — no database I/O
- * should occur during this phase.
- */
-@FunctionalInterface
-public interface RepositoryPhase {
-
- /**
- * Registers repository descriptors into the {@link RepositoryManager}.
- *
- * @param manager the repository manager used for descriptor registration (never {@code null})
- */
- void register(@NotNull RepositoryManager manager);
-}
diff --git a/playtime-core/src/main/java/com/github/imdmk/playtime/infrastructure/module/phase/TaskPhase.java b/playtime-core/src/main/java/com/github/imdmk/playtime/infrastructure/module/phase/TaskPhase.java
deleted file mode 100644
index b3da145..0000000
--- a/playtime-core/src/main/java/com/github/imdmk/playtime/infrastructure/module/phase/TaskPhase.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.github.imdmk.playtime.infrastructure.module.phase;
-
-import com.github.imdmk.playtime.platform.scheduler.TaskScheduler;
-import org.jetbrains.annotations.NotNull;
-
-/**
- * Functional phase interface responsible for scheduling asynchronous or repeating tasks.
- *
- * Implementations register all background or periodic tasks needed by a module
- * through the provided {@link TaskScheduler}.
- */
-@FunctionalInterface
-public interface TaskPhase {
-
- /**
- * Registers all scheduled tasks for this module.
- *
- * @param scheduler the task scheduler used to register Bukkit or async tasks (never {@code null})
- */
- void schedule(@NotNull TaskScheduler scheduler);
-}
diff --git a/playtime-core/src/main/java/com/github/imdmk/playtime/injector/Component.java b/playtime-core/src/main/java/com/github/imdmk/playtime/injector/Component.java
new file mode 100644
index 0000000..946c780
--- /dev/null
+++ b/playtime-core/src/main/java/com/github/imdmk/playtime/injector/Component.java
@@ -0,0 +1,13 @@
+package com.github.imdmk.playtime.injector;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.lang.annotation.Annotation;
+
+public record Component(
+ @NotNull Class> type,
+ @NotNull A annotation
+) {}
+
+
+
diff --git a/playtime-core/src/main/java/com/github/imdmk/playtime/injector/ComponentFunctional.java b/playtime-core/src/main/java/com/github/imdmk/playtime/injector/ComponentFunctional.java
new file mode 100644
index 0000000..11ce942
--- /dev/null
+++ b/playtime-core/src/main/java/com/github/imdmk/playtime/injector/ComponentFunctional.java
@@ -0,0 +1,17 @@
+package com.github.imdmk.playtime.injector;
+
+import com.github.imdmk.playtime.injector.processor.ComponentProcessorContext;
+import org.jetbrains.annotations.NotNull;
+
+import java.lang.annotation.Annotation;
+
+@FunctionalInterface
+public interface ComponentFunctional This implementation provides a high-level abstraction for sending messages,
- * notices, and components to Bukkit {@link CommandSender}s, automatically converting
- * them into Adventure audiences via {@link AudienceProvider}. Features: Thread-safety: Message sending is thread-safe and may be performed
- * off the main thread. Underlying Adventure components are immutable and safe for reuse. Players are mapped to player audiences, while other senders
- * (e.g., console or command blocks) are mapped to {@link AudienceProvider#console()}. The notice is resolved through the active {@link MessageConfig}
- * and rendered using {@link MiniMessage} formatting. This should be called during plugin disable to avoid memory leaks or
- * lingering references to the plugin classloader. Notes:
- * Stateless and thread-safe.
- * Instances are created via the {@link Builder}. Once built, the mapping is read-only.
- *
- * Thread-safety: Fully immutable and safe for concurrent use.
- */
public final class AdventurePlaceholders {
private static final AdventurePlaceholders EMPTY = new AdventurePlaceholders(Map.of());
@@ -24,130 +16,59 @@ public final class AdventurePlaceholders {
private final Map
- * This registrar provides two registration modes:
- *
- * All listeners are registered using the plugin's {@link org.bukkit.plugin.PluginManager}.
- */
-public final class BukkitListenerRegistrar {
-
- private final Plugin plugin;
-
- /**
- * Creates a new registrar for the given Bukkit plugin.
- *
- * @param plugin the plugin instance used for listener registration
- * @throws NullPointerException if the plugin is null
- */
- public BukkitListenerRegistrar(@NotNull Plugin plugin) {
- this.plugin = Validator.notNull(plugin, "plugin cannot be null");
- }
-
- /**
- * Registers the provided listener instances with the Bukkit {@link org.bukkit.plugin.PluginManager}.
- *
- * @param listeners the listener instances to register
- * @throws NullPointerException if the listeners array or any listener is null
- */
- public void register(@NotNull Listener... listeners) {
- Validator.notNull(listeners, "listeners cannot be null");
- for (final Listener listener : listeners) {
- plugin.getServer().getPluginManager().registerEvents(listener, plugin);
- }
- }
-
- /**
- * Creates and registers listeners using the provided {@link Injector}.
- *
- * Each listener class is instantiated and its dependencies are injected automatically.
- *
- * @param injector the dependency injector to use for listener instantiation
- * @param listeners the listener classes to create and register
- * @throws NullPointerException if the injector, the listener array, or any class is null
- */
- @SafeVarargs
- public final void register(@NotNull Injector injector, @NotNull Class extends Listener>... listeners) {
- Validator.notNull(injector, "injector cannot be null");
- Validator.notNull(listeners, "listeners cannot be null");
-
- for (final Class extends Listener> listenerClass : listeners) {
- register(injector.newInstance(listenerClass));
- }
- }
-}
diff --git a/playtime-core/src/main/java/com/github/imdmk/playtime/platform/gui/GuiModule.java b/playtime-core/src/main/java/com/github/imdmk/playtime/platform/gui/GuiModule.java
deleted file mode 100644
index c2ebe85..0000000
--- a/playtime-core/src/main/java/com/github/imdmk/playtime/platform/gui/GuiModule.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package com.github.imdmk.playtime.platform.gui;
-
-import com.github.imdmk.playtime.infrastructure.module.Module;
-import com.github.imdmk.playtime.platform.gui.render.GuiRenderer;
-import com.github.imdmk.playtime.platform.gui.render.TriumphGuiRenderer;
-import com.github.imdmk.playtime.platform.gui.view.GuiOpener;
-import org.jetbrains.annotations.NotNull;
-import org.panda_lang.utilities.inject.Injector;
-import org.panda_lang.utilities.inject.Resources;
-
-public final class GuiModule implements Module {
-
- private GuiOpener guiOpener;
- private GuiRenderer guiRenderer;
-
- @Override
- public void bind(@NotNull Resources resources) {
- resources.on(GuiOpener.class).assignInstance(() -> this.guiOpener);
- resources.on(GuiRenderer.class).assignInstance(() -> this.guiRenderer);
- }
-
- @Override
- public void init(@NotNull Injector injector) {
- this.guiOpener = injector.newInstance(GuiOpener.class);
- this.guiRenderer = injector.newInstance(TriumphGuiRenderer.class);
- }
-
- @Override
- public int order() {
- return 10;
- }
-}
diff --git a/playtime-core/src/main/java/com/github/imdmk/playtime/platform/gui/GuiRegistry.java b/playtime-core/src/main/java/com/github/imdmk/playtime/platform/gui/GuiRegistry.java
index c996139..a6a7df3 100644
--- a/playtime-core/src/main/java/com/github/imdmk/playtime/platform/gui/GuiRegistry.java
+++ b/playtime-core/src/main/java/com/github/imdmk/playtime/platform/gui/GuiRegistry.java
@@ -1,6 +1,7 @@
package com.github.imdmk.playtime.platform.gui;
-import com.github.imdmk.playtime.shared.validate.Validator;
+import com.github.imdmk.playtime.injector.annotations.Service;
+import com.github.imdmk.playtime.injector.priority.Priority;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Unmodifiable;
@@ -10,26 +11,14 @@
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
-/**
- * Thread-safe registry of {@link IdentifiableGui}, keyed by normalized id.
- * Invariant: at most one GUI per concrete class (maintained class index).
- */
+@Service(priority = Priority.LOW)
public final class GuiRegistry {
private final Map Each type represents a different interaction model for displaying items.
- * Useful for registering and retrieving GUI instances by their identifier.
- */
@FunctionalInterface
public interface IdentifiableGui {
- /**
- * Returns the unique identifier for this GUI.
- *
- * @return the non-null unique identifier string
- */
@NotNull String getId();
+
}
\ No newline at end of file
diff --git a/playtime-core/src/main/java/com/github/imdmk/playtime/platform/gui/config/ConfigurableGui.java b/playtime-core/src/main/java/com/github/imdmk/playtime/platform/gui/config/ConfigurableGui.java
index a15678f..44a0e4e 100644
--- a/playtime-core/src/main/java/com/github/imdmk/playtime/platform/gui/config/ConfigurableGui.java
+++ b/playtime-core/src/main/java/com/github/imdmk/playtime/platform/gui/config/ConfigurableGui.java
@@ -2,27 +2,13 @@
import com.github.imdmk.playtime.platform.gui.GuiType;
import net.kyori.adventure.text.Component;
-import org.jetbrains.annotations.NotNull;
-/**
- * Represents a configurable GUI loaded from configuration.
- * Implementations should provide all basic GUI metadata and content definitions.
- */
public interface ConfigurableGui {
- /**
- * @return GUI title as Adventure {@link Component}
- */
- @NotNull Component title();
+ Component title();
- /**
- * @return GUI type (e.g. {@link GuiType#STANDARD}, {@link GuiType#PAGINATED})
- */
- @NotNull GuiType type();
+ GuiType type();
- /**
- * @return GUI rows
- */
int rows();
}
diff --git a/playtime-core/src/main/java/com/github/imdmk/playtime/platform/gui/config/GuiConfig.java b/playtime-core/src/main/java/com/github/imdmk/playtime/platform/gui/config/GuiConfig.java
index 4b08026..f1069b1 100644
--- a/playtime-core/src/main/java/com/github/imdmk/playtime/platform/gui/config/GuiConfig.java
+++ b/playtime-core/src/main/java/com/github/imdmk/playtime/platform/gui/config/GuiConfig.java
@@ -2,7 +2,8 @@
import com.github.imdmk.playtime.config.ConfigSection;
import com.github.imdmk.playtime.feature.playtime.gui.PlayTimeTopGuiConfig;
-import com.github.imdmk.playtime.platform.adventure.ComponentSerializer;
+import com.github.imdmk.playtime.injector.annotations.ConfigFile;
+import com.github.imdmk.playtime.platform.adventure.AdventureComponentSerializer;
import com.github.imdmk.playtime.platform.gui.item.ItemGuiSerializer;
import com.github.imdmk.playtime.platform.serdes.EnchantmentSerializer;
import com.github.imdmk.playtime.platform.serdes.SoundSerializer;
@@ -10,6 +11,7 @@
import eu.okaeri.configs.serdes.OkaeriSerdesPack;
import org.jetbrains.annotations.NotNull;
+@ConfigFile
public final class GuiConfig extends ConfigSection {
@Comment({"#", "# Playtime top GUI", "#"})
@@ -19,9 +21,9 @@ public final class GuiConfig extends ConfigSection {
public NavigationBarConfig navigationBar = new NavigationBarConfig();
@Override
- public @NotNull OkaeriSerdesPack getSerdesPack() {
+ public @NotNull OkaeriSerdesPack serdesPack() {
return registry -> {
- registry.register(new ComponentSerializer());
+ registry.register(new AdventureComponentSerializer());
registry.register(new ItemGuiSerializer());
registry.register(new EnchantmentSerializer());
registry.register(new SoundSerializer());
@@ -29,7 +31,7 @@ public final class GuiConfig extends ConfigSection {
}
@Override
- public @NotNull String getFileName() {
+ public @NotNull String fileName() {
return "guiConfig.yml";
}
}
diff --git a/playtime-core/src/main/java/com/github/imdmk/playtime/platform/gui/config/NavigationBarConfig.java b/playtime-core/src/main/java/com/github/imdmk/playtime/platform/gui/config/NavigationBarConfig.java
index 8e828c1..c3755a5 100644
--- a/playtime-core/src/main/java/com/github/imdmk/playtime/platform/gui/config/NavigationBarConfig.java
+++ b/playtime-core/src/main/java/com/github/imdmk/playtime/platform/gui/config/NavigationBarConfig.java
@@ -6,12 +6,6 @@
import eu.okaeri.configs.annotation.Comment;
import org.bukkit.Material;
-/**
- * Configuration for navigation items used in paginated GUIs.
- *
- * Defines visual representation and behavior for navigation controls:
- * next, previous, and exit buttons displayed in inventory-based interfaces.
- */
public final class NavigationBarConfig extends OkaeriConfig {
@Comment({
@@ -100,4 +94,5 @@ public final class NavigationBarConfig extends OkaeriConfig {
" "
))
.build();
+
}
diff --git a/playtime-core/src/main/java/com/github/imdmk/playtime/platform/gui/factory/GuiBuilderFactory.java b/playtime-core/src/main/java/com/github/imdmk/playtime/platform/gui/factory/GuiBuilderFactory.java
index e5cfff8..d901870 100644
--- a/playtime-core/src/main/java/com/github/imdmk/playtime/platform/gui/factory/GuiBuilderFactory.java
+++ b/playtime-core/src/main/java/com/github/imdmk/playtime/platform/gui/factory/GuiBuilderFactory.java
@@ -1,39 +1,20 @@
package com.github.imdmk.playtime.platform.gui.factory;
import com.github.imdmk.playtime.platform.gui.GuiType;
-import com.github.imdmk.playtime.shared.validate.Validator;
import dev.triumphteam.gui.builder.gui.BaseGuiBuilder;
import dev.triumphteam.gui.components.ScrollType;
import dev.triumphteam.gui.guis.Gui;
-import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import java.util.function.Consumer;
-/**
- * Factory for creating TriumphGUI {@link BaseGuiBuilder} instances
- * based on a provided {@link GuiType}.
- *
- * Supports standard, paginated, and scrolling (vertical/horizontal) GUIs.
- */
public final class GuiBuilderFactory {
private GuiBuilderFactory() {
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated.");
}
- /**
- * Returns a TriumphGUI builder matching the given {@link GuiType}.
- *
- * @param type the GUI type
- * @param rows the GUI rows
- * @return a new {@link BaseGuiBuilder} instance for the given type
- * @throws IllegalArgumentException if {@code type} is {@code null}
- */
- @Contract(pure = true)
- public static @NotNull BaseGuiBuilder, ?> forType(@NotNull GuiType type, int rows) {
- Validator.notNull(type, "type cannot be null");
-
+ public static BaseGuiBuilder, ?> forType(@NotNull GuiType type, int rows) {
return switch (type) {
case STANDARD -> Gui.gui().rows(rows);
case PAGINATED -> Gui.paginated().rows(rows);
@@ -42,20 +23,8 @@ private GuiBuilderFactory() {
};
}
- /**
- * Creates and immediately customizes a TriumphGUI builder.
- *
- * @param type the GUI type
- * @param rows the GUI rows
- * @param editConsumer consumer for post-creation customization (e.g., size, disableAllInteractions)
- * @return a modified {@link BaseGuiBuilder} instance
- * @throws IllegalArgumentException if {@code type} or {@code editConsumer} is {@code null}
- */
- public static @NotNull BaseGuiBuilder, ?> forType(@NotNull GuiType type, int rows, @NotNull Consumer
- * Responsibilities:
- *
- * Pure data – no logic, no rendering.
- * Used to describe items in configuration and GUI assembly layers.
- */
public record ItemGui(
@NotNull Material material,
@NotNull Component name,
@@ -34,10 +27,6 @@ public record ItemGui(
) {
public ItemGui {
- Validator.notNull(material, "material cannot be null");
- Validator.notNull(name, "name cannot be null");
- Validator.notNull(lore, "lore cannot be null");
-
lore = List.copyOf(lore);
if (enchantments != null) {
@@ -67,21 +56,20 @@ public static final class Builder {
@CheckReturnValue
@Contract(value = "_ -> this", mutates = "this")
public Builder material(@NotNull Material material) {
- this.material = Validator.notNull(material, "material cannot be null");
+ this.material = material;
return this;
}
@CheckReturnValue
@Contract(value = "_ -> this", mutates = "this")
public Builder name(@NotNull Component name) {
- this.name = Validator.notNull(name, "name cannot be null");
+ this.name = name;
return this;
}
@CheckReturnValue
@Contract(value = "_ -> this", mutates = "this")
public Builder lore(@NotNull List Thread-safety: Pure transformation; prefer main thread for Bukkit objects. This implementation iterates through candidate items in order and returns
- * the first one that either:
- * Usage: Typically used by GUI renderers to determine which
- * item variant to display for users with different roles or permission levels. Thread-safety: This resolver is stateless and thread-safe. Implementations of this interface encapsulate different
- * resolution logics — e.g., by permission, by user state,
- * by contextual conditions, or by custom business rules. The resolver is typically used within GUI frameworks to decide
- * which visual representation of an item (variant) to render for a given player. Thread-safety: Implementations should be stateless and thread-safe.
- * This abstraction allows GUIs and renderers to remain independent
- * from the underlying permission system (e.g. Bukkit, Vault, LuckPerms).
- *
- * Implementations should be thread-safe if evaluated asynchronously.
- */
@FunctionalInterface
public interface PermissionEvaluator {
- /**
- * Checks whether the given human entity possesses the specified permission.
- *
- * @param entity the entity being checked (non-null)
- * @param permission the permission node (non-null)
- * @return {@code true} if the player has the permission; {@code false} otherwise
- */
boolean has(@NotNull HumanEntity entity, @NotNull String permission);
}
diff --git a/playtime-core/src/main/java/com/github/imdmk/playtime/platform/gui/render/RenderContext.java b/playtime-core/src/main/java/com/github/imdmk/playtime/platform/gui/render/RenderContext.java
index 1369aa4..f34a6ef 100644
--- a/playtime-core/src/main/java/com/github/imdmk/playtime/platform/gui/render/RenderContext.java
+++ b/playtime-core/src/main/java/com/github/imdmk/playtime/platform/gui/render/RenderContext.java
@@ -4,29 +4,12 @@
import org.bukkit.permissions.Permissible;
import org.jetbrains.annotations.NotNull;
-/**
- * Immutable context used during GUI item rendering.
- *
- * Encapsulates the viewer and the permission evaluation strategy,
- * ensuring renderers remain stateless and easily testable.
- *
- * Thread-safety: This record is immutable and thread-safe
- * as long as the underlying {@link PermissionEvaluator} implementation is thread-safe.
- *
- * @param viewer the player for whom the GUI is being rendered
- * @param permissionEvaluator the strategy used to check permissions
- */
public record RenderContext(
@NotNull Player viewer,
@NotNull PermissionEvaluator permissionEvaluator
) {
- /**
- * Creates a default context that checks if player has permission.
- *
- * @return the default {@link RenderContext} instance
- */
- public static @NotNull RenderContext defaultContext(@NotNull Player viewer) {
+ public static RenderContext defaultContext(@NotNull Player viewer) {
return new RenderContext(viewer, Permissible::hasPermission);
}
diff --git a/playtime-core/src/main/java/com/github/imdmk/playtime/platform/gui/render/RenderOptions.java b/playtime-core/src/main/java/com/github/imdmk/playtime/platform/gui/render/RenderOptions.java
index 03a8d30..ccfaffa 100644
--- a/playtime-core/src/main/java/com/github/imdmk/playtime/platform/gui/render/RenderOptions.java
+++ b/playtime-core/src/main/java/com/github/imdmk/playtime/platform/gui/render/RenderOptions.java
@@ -5,36 +5,16 @@
import java.util.function.Consumer;
-/**
- * Rendering options that define how permission handling
- * and denied interactions are processed during GUI rendering.
- *
- * @param policy how to handle items when the viewer lacks permission
- * @param onDenied consumer called when a denied item is clicked
- *
- * Thread-safety: This record is immutable and thread-safe,
- * provided that the supplied {@link Consumer} implementation is thread-safe.
- */
public record RenderOptions(
@NotNull NoPermissionPolicy policy,
@NotNull Consumer
- * Responsible for rendering {@link ItemGui} objects into a {@link BaseGui},
- * applying permission policies and wiring click handlers.
- *
- * Behavior:
- * Renderer is stateless and may be safely reused. If the item should be hidden (policy {@code HIDE}), it will not be placed. If the item should be hidden (policy {@code HIDE}), it will not be added.
- * Permission logic:
- *
- * Responsibilities:
- *
- * Each constant represents the index of an inventory slot where
- * navigation buttons should be placed.
- */
final class GridSlots {
private static final int ROW_3_NEXT = 25;
@@ -29,12 +22,6 @@ private GridSlots() {
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated.");
}
- /**
- * Returns the inventory slot index for the "Next Page" button.
- *
- * @param rows number of GUI rows (3–6)
- * @return slot index for the next-page control
- */
static int next(int rows) {
return switch (rows) {
case 3 -> ROW_3_NEXT;
@@ -45,12 +32,6 @@ static int next(int rows) {
};
}
- /**
- * Returns the inventory slot index for the "Previous Page" button.
- *
- * @param rows number of GUI rows (3–6)
- * @return slot index for the previous-page control
- */
static int previous(int rows) {
return switch (rows) {
case 3 -> ROW_3_PREVIOUS;
@@ -61,12 +42,6 @@ static int previous(int rows) {
};
}
- /**
- * Returns the inventory slot index for the "Exit" button.
- *
- * @param rows number of GUI rows (3–6)
- * @return slot index for the exit control
- */
static int exit(int rows) {
return switch (rows) {
case 3 -> ROW_3_EXIT;
diff --git a/playtime-core/src/main/java/com/github/imdmk/playtime/platform/gui/view/GuiOpener.java b/playtime-core/src/main/java/com/github/imdmk/playtime/platform/gui/view/GuiOpener.java
index 4670266..040e6f3 100644
--- a/playtime-core/src/main/java/com/github/imdmk/playtime/platform/gui/view/GuiOpener.java
+++ b/playtime-core/src/main/java/com/github/imdmk/playtime/platform/gui/view/GuiOpener.java
@@ -1,25 +1,15 @@
package com.github.imdmk.playtime.platform.gui.view;
+import com.github.imdmk.playtime.injector.annotations.Service;
import com.github.imdmk.playtime.platform.gui.GuiRegistry;
import com.github.imdmk.playtime.platform.gui.IdentifiableGui;
import com.github.imdmk.playtime.platform.scheduler.TaskScheduler;
-import com.github.imdmk.playtime.shared.validate.Validator;
import dev.triumphteam.gui.guis.BaseGui;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.panda_lang.utilities.inject.annotations.Inject;
-/**
- * Opens GUIs by id or class on the Bukkit main thread.
- *
- * Responsibilities:
- * Responsibilities:
- * This class provides formatted and structured logging methods for common log levels
- * (INFO, WARNING, DEBUG, SEVERE) with support for formatted messages and throwable logging. Design notes: Thread-safety: Delegates to the underlying {@link Logger}, which is thread-safe for concurrent use.
- * This interface is framework-agnostic and does not depend on PlaceholderAPI.
- * Implementations can be adapted to any placeholder platform.
- */
public interface PluginPlaceholder {
- /**
- * Unique identifier of the placeholder set.
- * Example: "playtime"
- */
@NotNull String identifier();
- /**
- * Called for online players, if supported by the underlying platform.
- */
default @Nullable String onRequest(@NotNull Player player, @NotNull String params) {
return null;
}
- /**
- * Called for offline players, if supported by the underlying platform.
- */
default @Nullable String onRequest(@NotNull OfflinePlayer player, @NotNull String params) {
return null;
}
diff --git a/playtime-core/src/main/java/com/github/imdmk/playtime/platform/placeholder/adapter/PlaceholderAdapter.java b/playtime-core/src/main/java/com/github/imdmk/playtime/platform/placeholder/adapter/PlaceholderAdapter.java
index e1fb145..42e8421 100644
--- a/playtime-core/src/main/java/com/github/imdmk/playtime/platform/placeholder/adapter/PlaceholderAdapter.java
+++ b/playtime-core/src/main/java/com/github/imdmk/playtime/platform/placeholder/adapter/PlaceholderAdapter.java
@@ -3,31 +3,14 @@
import com.github.imdmk.playtime.platform.placeholder.PluginPlaceholder;
import org.jetbrains.annotations.NotNull;
-/**
- * Strategy for registering {@link PluginPlaceholder} instances
- * against a concrete placeholder platform (e.g. PlaceholderAPI),
- * or acting as a no-op implementation when the platform is not present.
- */
public interface PlaceholderAdapter {
- /**
- * @return {@code true} if the underlying placeholder platform is available.
- */
boolean isAvailable();
- /**
- * Registers the given placeholder if the platform is available.
- */
void register(@NotNull PluginPlaceholder placeholder);
- /**
- * Unregisters the given placeholder, if it was registered.
- */
void unregister(@NotNull PluginPlaceholder placeholder);
- /**
- * Unregisters all placeholders managed by this registrar.
- */
void unregisterAll();
}
diff --git a/playtime-core/src/main/java/com/github/imdmk/playtime/platform/placeholder/adapter/PlaceholderAdapterFactory.java b/playtime-core/src/main/java/com/github/imdmk/playtime/platform/placeholder/adapter/PlaceholderAdapterFactory.java
index baa0e53..35c230f 100644
--- a/playtime-core/src/main/java/com/github/imdmk/playtime/platform/placeholder/adapter/PlaceholderAdapterFactory.java
+++ b/playtime-core/src/main/java/com/github/imdmk/playtime/platform/placeholder/adapter/PlaceholderAdapterFactory.java
@@ -1,59 +1,23 @@
package com.github.imdmk.playtime.platform.placeholder.adapter;
import com.github.imdmk.playtime.platform.logger.PluginLogger;
-import com.github.imdmk.playtime.shared.validate.Validator;
import org.bukkit.Server;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.NotNull;
-/**
- * Factory responsible for creating the appropriate {@link PlaceholderAdapter}
- * implementation based on runtime plugin availability.
- *
- * This class detects whether PlaceholderAPI is installed and enabled on the server.
- * Depending on its presence, it returns either: This allows the plugin to offer optional PlaceholderAPI integration without requiring it
- * as a hard dependency, while keeping all placeholder logic abstracted behind
- * the {@link PlaceholderAdapter} interface. Thread-safety: The factory contains no mutable state and is fully thread-safe. If PlaceholderAPI is detected and enabled, a {@link PlaceholderAPIAdapter} is returned.
- * Otherwise, a {@link NoopPlaceholderAdapter} is provided, which safely performs no operations. Provides a clean, Duration-based API for scheduling synchronous and asynchronous
- * tasks, including delayed and repeating executions. All time values are expressed using {@link Duration} and internally converted
- * to Minecraft ticks (1 tick = 50 ms). Thread-safety: This class is thread-safe. It holds only immutable
- * references to {@link Plugin} and {@link BukkitScheduler}.
- * Fractions are truncated. Negative durations return {@code 0}.
- *
- * @param duration duration to convert; must not be null
- * @return number of ticks (≥ 0)
- */
- private static int toTicks(@NotNull Duration duration) {
- Validator.notNull(duration, "duration cannot be null");
-
+ private static int toTicks(Duration duration) {
long ticks = duration.toMillis() / MILLIS_PER_TICK;
return ticks <= 0 ? 0 : (int) ticks;
}
diff --git a/playtime-core/src/main/java/com/github/imdmk/playtime/platform/scheduler/PluginTask.java b/playtime-core/src/main/java/com/github/imdmk/playtime/platform/scheduler/PluginTask.java
deleted file mode 100644
index 21ea995..0000000
--- a/playtime-core/src/main/java/com/github/imdmk/playtime/platform/scheduler/PluginTask.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package com.github.imdmk.playtime.platform.scheduler;
-
-import java.time.Duration;
-
-/**
- * Represents a declarative task definition used by the {@link TaskScheduler}.
- *
- * A {@code PluginTask} bundles together: Instances are consumed by scheduler methods that accept {@link PluginTask},
- * allowing tasks to be declared as self-contained objects instead of passing
- * raw parameters into every scheduling call. Repeating vs. non-repeating: Threading: Whether the task runs synchronously or asynchronously
- * depends solely on the {@link TaskScheduler} method used (e.g., {@code runTimerSync}, {@code runTimerAsync}).
- * Called either once (if {@link #period()} is zero) or repeatedly
- * (if {@link #period()} is greater than zero), depending on how
- * the task is scheduled.
- * A zero delay means the task should run immediately. If this returns {@code Duration.ZERO}, the task is treated as
- * a one-shot task and will not repeat after the first execution. If the value is greater than zero, the scheduler executes the
- * task repeatedly with this interval. Threading rules: Delay & period units: All {@code Duration} values are converted to
- * Minecraft ticks (1 tick = 50ms). PluginTask usage: All overloads accepting {@link PluginTask}
- * automatically use the task's declared delay and period. {@link PluginTask#delay()} and {@link PluginTask#period()} are ignored;
- * this method always runs instantly. {@link PluginTask#delay()} and {@link PluginTask#period()} are ignored;
- * this method always runs instantly. Runs once unless {@link PluginTask#period()} is non-zero. Runs once unless {@link PluginTask#period()} is non-zero. Called during plugin shutdown.
- * This keeps the extraction logic in a single place, shared across
- * different formatters.
- */
-public final class DurationSplitter {
-
- private DurationSplitter() {
- throw new UnsupportedOperationException("This is a utility class and cannot be instantiated.");
- }
-
- /**
- * Splits the given duration into ordered units: days, hours, minutes, seconds.
- *
- * @param duration the duration to split (non-null)
- * @return map of {@link DurationUnit} to its value in the given duration
- */
- public static @NotNull Map
- * Supports multiple predefined {@link DurationFormatStyle} strategies.
- * Zero or negative durations are normalized to {@code "<1s"}.
- *
- * This class is stateless apart from the configurable default style.
- */
-public final class Durations {
-
- /** Upper bound for any clamped duration (10 years). */
- private static final Duration MAX_NORMALIZED_DURATION = Duration.ofDays(3650);
-
- /** Returned when the duration is zero or negative. */
- private static final String LESS_THAN_SECOND = "<1s";
-
- /** Default style used when no explicit format style is provided. */
- private static DurationFormatStyle DEFAULT_FORMAT_STYLE = DurationFormatStyle.NATURAL;
-
- private Durations() {
- throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
- }
-
- /**
- * Formats the given duration using {@link #DEFAULT_FORMAT_STYLE}.
- *
- * Zero or negative durations return {@code "<1s"}.
- *
- * @param duration the duration to format (non-null)
- * @return formatted duration string (never {@code null})
- */
- public static @NotNull String format(@NotNull Duration duration) {
- return format(duration, DEFAULT_FORMAT_STYLE);
- }
-
- /**
- * Formats the given duration using the specified {@link DurationFormatStyle}.
- *
- * Zero or negative durations return {@code "<1s"}.
- *
- * @param duration the duration to format (non-null)
- * @param style formatting strategy (non-null)
- * @return human-readable duration string (never {@code null})
- * @throws IllegalArgumentException if duration or style are {@code null}
- */
- public static @NotNull String format(@NotNull Duration duration, @NotNull DurationFormatStyle style) {
- Validator.notNull(duration, "duration");
- Validator.notNull(style, "style");
-
- if (duration.isZero() || duration.isNegative()) {
- return LESS_THAN_SECOND;
- }
-
- return style.format(duration);
- }
-
- /**
- * Sets the global default {@link DurationFormatStyle} used by
- * {@link #format(Duration)}.
- *
- * This modifies process-wide behavior and should be configured during
- * plugin initialization.
- *
- * @param style the new default style (non-null)
- * @throws IllegalArgumentException if the provided style is {@code null}
- */
- public static void setDefaultFormatStyle(@NotNull DurationFormatStyle style) {
- Validator.notNull(style, "durationFormatStyle");
- DEFAULT_FORMAT_STYLE = style;
- }
-
- /**
- * Normalizes (clamps) the given duration so it’s always non-negative
- * and does not exceed {@link #MAX_NORMALIZED_DURATION}.
- *
- * @param input duration to normalize (must not be null)
- * @return clamped, non-negative duration
- */
- public static @NotNull Duration clamp(@NotNull Duration input) {
- Validator.notNull(input, "duration");
-
- if (input.isNegative()) {
- return Duration.ZERO;
- }
-
- return input.compareTo(MAX_NORMALIZED_DURATION) > 0 ? MAX_NORMALIZED_DURATION : input;
- }
-}
diff --git a/playtime-core/src/main/java/com/github/imdmk/playtime/shared/validate/Validator.java b/playtime-core/src/main/java/com/github/imdmk/playtime/shared/validate/Validator.java
deleted file mode 100644
index f356272..0000000
--- a/playtime-core/src/main/java/com/github/imdmk/playtime/shared/validate/Validator.java
+++ /dev/null
@@ -1,65 +0,0 @@
-package com.github.imdmk.playtime.shared.validate;
-
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.util.function.Consumer;
-
-/**
- * Utility class for common validation checks.
- *
- * Provides null-safety guards used throughout the codebase.
- */
-public final class Validator {
-
- private Validator() {
- throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
- }
-
- /**
- * Ensures the given object is not {@code null}.
- *
- * This method is typically used to validate constructor arguments and
- * configuration values. If the supplied object is non-null, it is returned
- * unchanged; otherwise a {@link NullPointerException} is thrown with the
- * provided message.
- *
- * @param obj the value to validate; may be null
- * @param context context of exception used when {@code obj} is null
- * @param
- * This helper is especially useful during shutdown or cleanup phases where
- * optional components may or may not be initialized. The consumer itself
- * must be non-null; however, it will only be invoked when {@code obj} is non-null.
- *
- * Example usage:
- *
- * Each style provides its own implementation of {@link #format(Duration)}.
- * The underlying logic splits the duration into days, hours, minutes and seconds
- * and then renders only non-zero units in a style-specific way.
- */
public enum DurationFormatStyle {
- /**
- * Compact representation using short unit abbreviations.
- *
- * Example: {@code 30d 30m 3s}
- */
COMPACT {
@Override
public String format(@NotNull Duration duration) {
@@ -30,12 +17,6 @@ public String format(@NotNull Duration duration) {
Separator.SPACE);
}
},
-
- /**
- * Long form with full unit names, separated by spaces.
- *
- * Example: {@code 30 days 30 minutes 3 seconds}
- */
LONG {
@Override
public String format(@NotNull Duration duration) {
@@ -44,12 +25,6 @@ public String format(@NotNull Duration duration) {
Separator.SPACE);
}
},
-
- /**
- * Long form with {@code " and "} between units.
- *
- * Example: {@code 30 days and 30 minutes and 3 seconds}
- */
LONG_WITH_AND {
@Override
public String format(@NotNull Duration duration) {
@@ -58,12 +33,6 @@ public String format(@NotNull Duration duration) {
Separator.AND);
}
},
-
- /**
- * Natural language-like form using commas between units.
- *
- * Example: {@code 30 days, 30 minutes, 3 seconds}
- */
NATURAL {
@Override
public String format(@NotNull Duration duration) {
@@ -73,42 +42,20 @@ public String format(@NotNull Duration duration) {
}
};
- /**
- * Formats the given {@link Duration} using this style.
- *
- * The duration is first decomposed into days, hours, minutes and seconds,
- * and only non-zero units are included in the output.
- *
- * @param duration the duration to format; must not be {@code null}
- * @return formatted duration according to this style (never {@code null})
- */
public abstract String format(@NotNull Duration duration);
- /**
- * Joins non-zero units of the given duration using the provided formatter
- * and separator.
- *
- * @param duration duration to format
- * @param valueFormatter function converting (unit, value) → string
- * @param separator separator strategy
- * @return formatted string, or empty string if all units are zero
- */
protected static String formatWith(
@NotNull Duration duration,
@NotNull BiFunction
- * This enum centralizes singular/plural names, abbreviations, and extraction logic.
- */
public enum DurationUnit {
DAY("day", "days", "d") {
@@ -36,8 +31,7 @@ public int extract(@NotNull Duration duration) {
}
};
- /** Ordered for consistent output. */
- public static final DurationUnit[] ORDERED = {
+ protected static final DurationUnit[] ORDERED = {
DAY, HOUR, MINUTE, SECOND
};
@@ -55,12 +49,12 @@ public int extract(@NotNull Duration duration) {
public abstract int extract(@NotNull Duration duration);
- public @NotNull String getAbbreviation() {
+ public String getAbbreviation() {
return abbreviation;
}
- public @NotNull String toDisplayName(int value) {
- String word = (value == 1 ? singular : plural);
+ protected String toDisplayName(int value) {
+ final String word = (value == 1 ? singular : plural);
return DISPLAY_NAME_FORMAT.formatted(value, word);
}
}
diff --git a/playtime-core/src/main/java/com/github/imdmk/playtime/time/Durations.java b/playtime-core/src/main/java/com/github/imdmk/playtime/time/Durations.java
new file mode 100644
index 0000000..d349fcb
--- /dev/null
+++ b/playtime-core/src/main/java/com/github/imdmk/playtime/time/Durations.java
@@ -0,0 +1,40 @@
+package com.github.imdmk.playtime.time;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.time.Duration;
+
+public final class Durations {
+
+ private static final Duration MAX_NORMALIZED_DURATION = Duration.ofDays(3650);
+ private static final String LESS_THAN_SECOND = "<1s";
+ private static DurationFormatStyle DEFAULT_FORMAT_STYLE = DurationFormatStyle.NATURAL;
+
+ private Durations() {
+ throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
+ }
+
+ public static String format(@NotNull Duration duration) {
+ return format(duration, DEFAULT_FORMAT_STYLE);
+ }
+
+ public static String format(@NotNull Duration duration, @NotNull DurationFormatStyle style) {
+ if (duration.isZero() || duration.isNegative()) {
+ return LESS_THAN_SECOND;
+ }
+
+ return style.format(duration);
+ }
+
+ public static void setDefaultFormatStyle(@NotNull DurationFormatStyle style) {
+ DEFAULT_FORMAT_STYLE = style;
+ }
+
+ public static Duration clamp(@NotNull Duration input) {
+ if (input.isNegative()) {
+ return Duration.ZERO;
+ }
+
+ return input.compareTo(MAX_NORMALIZED_DURATION) > 0 ? MAX_NORMALIZED_DURATION : input;
+ }
+}
diff --git a/playtime-core/src/main/java/com/github/imdmk/playtime/user/UserArgument.java b/playtime-core/src/main/java/com/github/imdmk/playtime/user/UserArgument.java
index 549603d..f01bb83 100644
--- a/playtime-core/src/main/java/com/github/imdmk/playtime/user/UserArgument.java
+++ b/playtime-core/src/main/java/com/github/imdmk/playtime/user/UserArgument.java
@@ -2,7 +2,6 @@
import com.github.imdmk.playtime.message.MessageConfig;
import com.github.imdmk.playtime.platform.logger.PluginLogger;
-import com.github.imdmk.playtime.shared.validate.Validator;
import dev.rollczi.litecommands.argument.Argument;
import dev.rollczi.litecommands.argument.parser.ParseResult;
import dev.rollczi.litecommands.argument.resolver.ArgumentResolver;
@@ -17,12 +16,6 @@
import java.time.Duration;
import java.util.concurrent.TimeUnit;
-/**
- * Argument resolver for {@link User} objects.
- *
- * Performs a cache-only lookup on the primary server thread to avoid blocking,
- * and a full asynchronous lookup (cache → database) off the main thread.
- */
final class UserArgument extends ArgumentResolver
- * Implementations are expected to maintain a fast lookup by both UUID and name,
- * keep mappings consistent during updates, and guarantee thread-safety
- * if used in a concurrent environment.
- */
public interface UserCache {
- /**
- * Adds or replaces the given user in the cache.
- *
- * @param user the user instance to store
- */
void cacheUser(@NotNull User user);
- /**
- * Removes the given user from the cache, if present.
- *
- * @param user the user instance to remove
- */
void invalidateUser(@NotNull User user);
- /**
- * Removes a cached user by UUID.
- *
- * @param uuid the UUID to remove
- */
void invalidateByUuid(@NotNull UUID uuid);
- /**
- * Removes a cached user by name (case-insensitive matching recommended).
- *
- * @param name the username to remove
- */
void invalidateByName(@NotNull String name);
- /**
- * Retrieves a user by UUID.
- *
- * @param uuid the UUID to search
- * @return an {@link Optional} containing the cached user, if present
- */
- @NotNull Optional> findTopByPlayTime(int limit);
-
- /**
- * Asynchronously saves a user to the underlying database and updates the cache.
- *
> findTopByPlayTime(int limit);
}
diff --git a/playtime-api/src/main/java/com/github/imdmk/playtime/user/UserTime.java b/playtime-api/src/main/java/com/github/imdmk/playtime/user/UserTime.java
index 2dc8502..117055a 100644
--- a/playtime-api/src/main/java/com/github/imdmk/playtime/user/UserTime.java
+++ b/playtime-api/src/main/java/com/github/imdmk/playtime/user/UserTime.java
@@ -9,219 +9,91 @@
import java.util.Objects;
import java.util.concurrent.TimeUnit;
-/**
- * Immutable value object representing a duration of time measured in milliseconds.
- *
- *
- *
- *
- * @param millis the milliseconds of time
- *
- * @see Duration
- * @see User
- */
public record UserTime(long millis) implements Comparable
- *
- *
- * @return configured Hikari data source (not yet started)
- */
- private @NotNull HikariDataSource createHikariDataSource() {
- final HikariDataSource data = new HikariDataSource();
- data.setPoolName(POOL_NAME);
-
- data.setMaximumPoolSize(Math.max(DEFAULT_MAX_POOL_SIZE, Runtime.getRuntime().availableProcessors()));
- data.setMinimumIdle(DEFAULT_MIN_IDLE);
-
- data.setUsername(config.databaseUserName);
- data.setPassword(config.databasePassword);
-
- // Reduce noisy driver logging if supported
- try {
- data.getParentLogger().setLevel(DATA_SOURCE_LOG_LEVEL);
- } catch (SQLFeatureNotSupportedException ignored) {}
-
- // Prepared statement cache (useful for MySQL-family; harmless for others)
- data.addDataSourceProperty("cachePrepStmts", CACHE_PREP_STMTS);
- data.addDataSourceProperty("prepStmtCacheSize", PREP_STMT_CACHE_SIZE);
- data.addDataSourceProperty("prepStmtCacheSqlLimit", PREP_STMT_CACHE_SQL_LIMIT);
- data.addDataSourceProperty("useServerPrepStmts", USE_SERVER_PREP_STMTS);
-
- // Timeout configuration (milliseconds)
- data.setConnectionTimeout(DEFAULT_CONNECTION_TIMEOUT_MS);
- data.setIdleTimeout(DEFAULT_IDLE_TIMEOUT_MS);
- data.setMaxLifetime(DEFAULT_MAX_LIFETIME_MS);
-
- return data;
- }
-
- /**
- * Closes the given {@link HikariDataSource} without propagating exceptions.
- *
- * @param ds data source to close (nullable)
- */
- private static void closeQuietly(@Nullable HikariDataSource ds) {
- try {
- if (ds != null) {
- ds.close();
- }
- } catch (Exception ignored) {}
- }
-}
diff --git a/playtime-core/src/main/java/com/github/imdmk/playtime/infrastructure/database/DatabaseManager.java b/playtime-core/src/main/java/com/github/imdmk/playtime/infrastructure/database/DatabaseManager.java
deleted file mode 100644
index 46d50e8..0000000
--- a/playtime-core/src/main/java/com/github/imdmk/playtime/infrastructure/database/DatabaseManager.java
+++ /dev/null
@@ -1,51 +0,0 @@
-package com.github.imdmk.playtime.infrastructure.database;
-
-import com.github.imdmk.playtime.infrastructure.database.driver.dependency.DriverDependencyLoader;
-import com.github.imdmk.playtime.platform.logger.PluginLogger;
-import com.github.imdmk.playtime.shared.validate.Validator;
-import com.j256.ormlite.support.ConnectionSource;
-import org.bukkit.plugin.Plugin;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.sql.SQLException;
-
-public final class DatabaseManager {
-
- private final Plugin plugin;
- private final DatabaseConfig config;
-
- private final DriverDependencyLoader driverLoader;
- private final DatabaseConnector connector;
-
- public DatabaseManager(
- @NotNull Plugin plugin,
- @NotNull PluginLogger logger,
- @NotNull DatabaseConfig config
- ) {
- this.plugin = Validator.notNull(plugin, "plugin");
- this.config = Validator.notNull(config, "config");
-
- this.driverLoader = new DriverDependencyLoader(plugin);
- this.connector = new DatabaseConnector(logger, config);
- }
-
- public void loadDriver() {
- driverLoader.loadDriverFor(config.databaseMode);
- }
-
- public void connect() throws SQLException {
- connector.connect(plugin.getDataFolder());
- }
-
- @Nullable
- public ConnectionSource getConnection() {
- return connector.getConnectionSource();
- }
-
- public void shutdown() {
- if (connector.isConnected()) {
- connector.close();
- }
- }
-}
diff --git a/playtime-core/src/main/java/com/github/imdmk/playtime/infrastructure/database/DatabaseMode.java b/playtime-core/src/main/java/com/github/imdmk/playtime/infrastructure/database/DatabaseMode.java
deleted file mode 100644
index 940faa7..0000000
--- a/playtime-core/src/main/java/com/github/imdmk/playtime/infrastructure/database/DatabaseMode.java
+++ /dev/null
@@ -1,68 +0,0 @@
-package com.github.imdmk.playtime.infrastructure.database;
-
-/**
- * Enumerates all database engines supported by the plugin.
- *
- *
- *
- *
- * This abstraction allows {@link com.github.imdmk.playtime.infrastructure.database.DatabaseConnector}
- * to remain engine-agnostic while still supporting multiple database types.
- */
-public interface DriverConfigurer {
-
- /**
- * Configures the provided {@link HikariDataSource} instance using the database
- * settings supplied in {@link DatabaseConfig} and the plugin data folder.
- *
- *
- *
- * @param dataSource the HikariCP data source to configure (never null)
- * @param config the database configuration containing connection details (never null)
- * @param dataFolder the plugin data folder, used especially for file-based engines like SQLite/H2 (never null)
- */
- void configure(@NotNull HikariDataSource dataSource,
- @NotNull DatabaseConfig config,
- @NotNull File dataFolder);
-}
diff --git a/playtime-core/src/main/java/com/github/imdmk/playtime/infrastructure/database/driver/configurer/DriverConfigurerFactory.java b/playtime-core/src/main/java/com/github/imdmk/playtime/infrastructure/database/driver/configurer/DriverConfigurerFactory.java
deleted file mode 100644
index 4085841..0000000
--- a/playtime-core/src/main/java/com/github/imdmk/playtime/infrastructure/database/driver/configurer/DriverConfigurerFactory.java
+++ /dev/null
@@ -1,53 +0,0 @@
-package com.github.imdmk.playtime.infrastructure.database.driver.configurer;
-
-import com.github.imdmk.playtime.infrastructure.database.DatabaseMode;
-import com.github.imdmk.playtime.shared.validate.Validator;
-import org.jetbrains.annotations.NotNull;
-
-import java.util.Map;
-
-/**
- * Factory responsible for selecting the correct {@link DriverConfigurer}
- * implementation for a given {@link DatabaseMode}.
- *
- *
- *
- *
- *
- *
- * @param supplier unit of work to execute (non-null)
- * @param timeout maximum execution time (non-null)
- * @param
- *
- * The manager guarantees {@code bind()} is called before {@code init()}.
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- * Component c = AdventureComponents.text("<red>Hello");
- * Component plain = AdventureComponents.withoutItalics(c);
- *
- */
public final class AdventureComponents {
private static final MiniMessage MINI_MESSAGE = MiniMessage.miniMessage();
@@ -34,26 +18,11 @@ private AdventureComponents() {
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated.");
}
- /**
- * Deserializes a MiniMessage-formatted text into a {@link Component}.
- *
- * @param text the MiniMessage-formatted text
- * @return the deserialized component
- */
- public static @NotNull Component text(@NotNull CharSequence text) {
- Validator.notNull(text, "text");
+ public static Component text(@NotNull CharSequence text) {
return MINI_MESSAGE.deserialize(text.toString());
}
- /**
- * Deserializes multiple MiniMessage-formatted texts into a list of {@link Component}s.
- *
- * @param texts array of MiniMessage-formatted texts
- * @return an unmodifiable list of deserialized components
- */
- public static @NotNull List
- *
- */
- public
- *
- *
- *
- */
public final class GuiFactory {
private GuiFactory() {
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated.");
}
- /**
- * Builds a GUI instance from the provided configuration.
- *
- * @param config the GUI configuration
- * @return a new {@link BaseGui} instance
- * @throws IllegalArgumentException if the GUI type is unsupported
- */
- public static @NotNull BaseGui build(@NotNull ConfigurableGui config) {
- Validator.notNull(config, "config cannot be null");
+ public static BaseGui build(@NotNull ConfigurableGui config) {
return GuiBuilderFactory.forType(config.type(), config.rows())
.title(config.title())
.create();
}
- /**
- * Builds and immediately customizes a GUI using the provided consumer.
- *
- * @param config the GUI configuration
- * @param editConsumer consumer to modify the GUI instance before returning
- * @return the configured {@link BaseGui}
- */
- public static @NotNull BaseGui build(@NotNull ConfigurableGui config, @NotNull Consumer
- *
- * If no candidate matches, a predefined fallback item is returned.
- *
- */
public final class TriumphGuiRenderer implements GuiRenderer {
- /**
- * Creates a new renderer instance.
- *
- *
- *
- * @return a built {@link GuiItem}, or {@code null} if hidden
- */
- private @Nullable GuiItem buildGuiItem(
- @NotNull final ItemGui item,
- @NotNull final RenderContext context,
- @NotNull final RenderOptions options,
- @NotNull final Consumer
- *
- *
- * Threading: All methods are expected to be called on the Bukkit main thread.
- */
public abstract class AbstractGui {
protected final NavigationBarConfig config;
@@ -35,22 +20,16 @@ public abstract class AbstractGui {
private final NavigationBar navigationBar;
- /**
- * @param config GUI config (visual defaults, nav items, etc.)
- * @param taskScheduler scheduler for short, sync GUI updates
- * @param renderer renderer that places items and enforces permission policy
- * @param renderOptions render options (no-permission policy, onDenied)
- */
protected AbstractGui(
@NotNull NavigationBarConfig config,
@NotNull TaskScheduler taskScheduler,
@NotNull GuiRenderer renderer,
@NotNull RenderOptions renderOptions
) {
- this.config = Validator.notNull(config, "config cannot be null");
- this.scheduler = Validator.notNull(taskScheduler, "taskScheduler cannot be null");
- this.renderer = Validator.notNull(renderer, "renderer cannot be null");
- this.renderOptions = Validator.notNull(renderOptions, "renderOptions cannot be null");
+ this.config = config;
+ this.scheduler = taskScheduler;
+ this.renderer = renderer;
+ this.renderOptions = renderOptions;
this.navigationBar = new NavigationBar(
this.config,
@@ -60,44 +39,15 @@ protected AbstractGui(
);
}
- /**
- * Places the "Next" control if the GUI is paginated.
- *
- * @param gui target GUI
- * @param viewer target viewer
- */
protected void placeNext(@NotNull BaseGui gui, @NotNull Player viewer) {
- Validator.notNull(gui, "gui cannot be null");
- Validator.notNull(viewer, "viewer cannot be null");
navigationBar.setNext(gui, viewer);
}
- /**
- * Places the "Previous" control if the GUI is paginated.
- *
- * @param gui target GUI
- * @param viewer target viewer
- */
protected void placePrevious(@NotNull BaseGui gui, @NotNull Player viewer) {
- Validator.notNull(gui, "gui cannot be null");
- Validator.notNull(viewer, "viewer cannot be null");
navigationBar.setPrevious(gui, viewer);
}
- /**
- * Places the "Exit" control.
- *
- * @param gui target GUI
- * @param viewer target viewer
- * @param exit action to run on click
- */
- protected void placeExit(
- @NotNull BaseGui gui,
- @NotNull Player viewer,
- @NotNull Consumer
- *
- *
- * Thread-safety: Safe to call from any thread. Actual GUI operations are marshalled to the main thread.
- */
+@Service
public final class GuiOpener {
private final GuiRegistry registry;
@@ -27,110 +17,85 @@ public final class GuiOpener {
@Inject
public GuiOpener(@NotNull GuiRegistry registry, @NotNull TaskScheduler taskScheduler) {
- this.registry = Validator.notNull(registry, "registry cannot be null");
- this.taskScheduler = Validator.notNull(taskScheduler, "taskScheduler cannot be null");
+ this.registry = registry;
+ this.taskScheduler = taskScheduler;
}
- /**
- * Opens a non-parameterized GUI by its concrete class.
- *
- * @throws IllegalArgumentException if GUI is not registered or not a {@link SimpleGui}
- */
public void open(
@NotNull Class extends SimpleGui> type,
- @NotNull Player viewer) {
- Validator.notNull(type, "type cannot be null");
- Validator.notNull(viewer, "viewer cannot be null");
-
- IdentifiableGui gui = require(type);
+ @NotNull Player viewer
+ ) {
+ final IdentifiableGui gui = require(type);
if (!(gui instanceof SimpleGui simpleGui)) {
throw wrongType(type.getName(), gui, "SimpleGui");
}
- BaseGui baseGui = simpleGui.createGui();
+ final BaseGui baseGui = simpleGui.createGui();
simpleGui.prepareItems(baseGui, viewer);
taskScheduler.runSync(() -> baseGui.open(viewer));
}
- /**
- * Opens a parameterized GUI by its concrete class.
- *
- * @throws IllegalArgumentException if GUI is not registered or not a {@link ParameterizedGui}
- */
@SuppressWarnings("unchecked")
- public
- *
- *
- * Threading: All methods are expected to be called on the Bukkit main thread.
- * The class is stateless w.r.t. rendering; it holds only injected collaborators.
- */
final class NavigationBar {
- private static final Duration RESTORE_DELAY = Duration.ofSeconds(1);
+ private static final Duration DELAY = Duration.ofSeconds(1);
private final NavigationBarConfig config;
private final TaskScheduler scheduler;
private final GuiRenderer renderer;
private final RenderOptions renderOptions;
- /**
- * @param config navigation bar config (items, etc.)
- * @param scheduler scheduler for short delayed updates
- * @param renderer GUI renderer enforcing permission policy
- * @param renderOptions render options (no-permission policy, onDenied)
- */
NavigationBar(
@NotNull NavigationBarConfig config,
@NotNull TaskScheduler scheduler,
@NotNull GuiRenderer renderer,
@NotNull RenderOptions renderOptions
) {
- this.config = Validator.notNull(config, "config cannot be null");
- this.renderer = Validator.notNull(renderer, "renderer cannot be null");
- this.scheduler = Validator.notNull(scheduler, "scheduler cannot be null");
- this.renderOptions = Validator.notNull(renderOptions, "renderOptions cannot be null");
+ this.config = config;
+ this.renderer = renderer;
+ this.scheduler = scheduler;
+ this.renderOptions = renderOptions;
}
- /**
- * Places the "Next page" button if {@code gui} is paginated.
- */
void setNext(@NotNull BaseGui gui, @NotNull Player viewer) {
- Validator.notNull(gui, "gui cannot be null");
- Validator.notNull(viewer, "viewer cannot be null");
-
if (!(gui instanceof PaginatedGui paginated)) {
return;
}
- final var context = RenderContext.defaultContext(viewer);
- final var slot = GridSlots.next(gui.getRows());
+ final RenderContext context = RenderContext.defaultContext(viewer);
+ final int slot = GridSlots.next(gui.getRows());
final Consumer
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- * Validator.ifNotNull(taskScheduler, TaskScheduler::shutdown);
- * Validator.ifNotNull(messageService, MessageService::shutdown);
- *
- *
- * @param obj the object to check before executing the consumer; may be null
- * @param consumer operation to execute when {@code obj} is non-null (never null)
- * @param > findTopByPlayTime(int limit) {
+ public CompletableFuture
> findTopByPlayTime(int limit) {
return topUsersCache.getTopByPlayTime(limit);
}
@Override
- public @NotNull CompletableFuture