Skip to content
2 changes: 1 addition & 1 deletion playtime-api/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
dependencies {
compileOnly("org.jetbrains:annotations:26.0.2-1")
compileOnlyApi("org.jetbrains:annotations:26.0.2-1")

testImplementation("org.junit.jupiter:junit-jupiter:6.0.1")
testImplementation("org.assertj:assertj-core:3.25.2")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ public record UserTime(long millis) implements Comparable<UserTime>, Serializabl
*/
@Contract("_ -> new")
public static @NotNull UserTime from(@NotNull Duration duration) {
Objects.requireNonNull(duration, "duration is null");
Objects.requireNonNull(duration, "duration cannot be null");
return new UserTime(duration.toMillis());
}

Expand Down
6 changes: 2 additions & 4 deletions playtime-bukkit-api/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
dependencies {
api(project(":playtime-api"))

compileOnly("org.jetbrains:annotations:26.0.2-1")
compileOnly("org.spigotmc:spigot-api:1.21.10-R0.1-SNAPSHOT")
}
compileOnlyApi("org.spigotmc:spigot-api:1.21.10-R0.1-SNAPSHOT")
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public final class UserDeleteEvent extends Event {
*/
public UserDeleteEvent(@NotNull UserDeleteResult result) {
super(ASYNC);
this.result = Objects.requireNonNull(result, "result cannot be null");;
this.result = Objects.requireNonNull(result, "result cannot be null");
}

/**
Expand Down
5 changes: 2 additions & 3 deletions playtime-core/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
dependencies {
compileOnly("org.spigotmc:spigot-api:1.21.10-R0.1-SNAPSHOT")
compileOnly("me.clip:placeholderapi:2.11.7")

implementation(project(":playtime-bukkit-api"))
api(project(":playtime-bukkit-api"))

// DI
implementation("org.panda-lang.utilities:di:1.8.0")
Expand All @@ -25,7 +24,7 @@ dependencies {

// Okaeri configs
api("eu.okaeri:okaeri-configs-yaml-snakeyaml:5.0.9")
api("eu.okaeri:okaeri-configs-serdes-commons:5.0.5")
implementation("eu.okaeri:okaeri-configs-serdes-commons:5.0.5")

// GUI, metrics, commands
implementation("dev.triumphteam:triumph-gui:3.1.13")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.github.imdmk.playtime;

import com.github.imdmk.playtime.infrastructure.di.BindCore;
import com.github.imdmk.playtime.shared.Validator;
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;
Expand All @@ -10,14 +10,14 @@
import java.lang.reflect.Modifier;

/**
* Discovers fields in {@link PlayTimePlugin} annotated with {@link BindCore}
* Discovers fields in {@link PlayTimePlugin} annotated with {@link Bind}
* and registers their instances into the DI {@link Resources}.
* <p>
* 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 @BindCore} are processed.
* Only non-static fields with {@code @Bind} are processed.
*/
final class PlayTimeCoreBinder {
final class PlayTimeBinder {

private final PlayTimePlugin core;

Expand All @@ -26,25 +26,25 @@ final class PlayTimeCoreBinder {
*
* @param core the plugin root object providing core dependencies
*/
PlayTimeCoreBinder(@NotNull PlayTimePlugin core) {
this.core = Validator.notNull(core, "core cannot be null");
PlayTimeBinder(@NotNull PlayTimePlugin core) {
this.core = Validator.notNull(core, "core");
}

/**
* Scans the {@link PlayTimePlugin} class hierarchy, locates fields annotated with
* {@link BindCore}, reads their values, and registers them into the provided
* {@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 cannot be null");
Validator.notNull(resources, "resources");

Class<?> type = core.getClass();

while (type != null && type != Object.class) {
for (Field field : type.getDeclaredFields()) {
if (!field.isAnnotationPresent(BindCore.class)) {
if (!field.isAnnotationPresent(Bind.class)) {
continue;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,40 +1,39 @@
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.DatabaseConnector;
import com.github.imdmk.playtime.infrastructure.database.driver.dependency.DriverDependencyLoader;
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.di.BindCore;
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.platform.litecommands.configurer.BukkitLiteCommandsRegistrar;
import com.github.imdmk.playtime.platform.litecommands.configurer.LiteCommandsRegistrar;
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.Validator;
import com.github.imdmk.playtime.shared.config.ConfigBinder;
import com.github.imdmk.playtime.shared.config.ConfigManager;
import com.github.imdmk.playtime.shared.config.ConfigSection;
import com.github.imdmk.playtime.shared.config.PluginConfig;
import com.github.imdmk.playtime.shared.message.MessageConfig;
import com.github.imdmk.playtime.shared.message.MessageService;
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;
Expand All @@ -58,28 +57,28 @@ final class PlayTimePlugin {
private static final String PREFIX = "AdvancedPlayTime";
private static final int PLUGIN_METRICS_ID = 19362;

@BindCore private final ModuleRegistry moduleRegistry = new ModuleRegistry();
@Bind private final ModuleRegistry moduleRegistry = new ModuleRegistry();

@BindCore private final Plugin plugin;
@BindCore private final PluginLogger logger;
@BindCore private final Server server;
@BindCore private final ExecutorService executor;
@Bind private final Plugin plugin;
@Bind private final PluginLogger logger;
@Bind private final Server server;
@Bind private final ExecutorService executor;

@BindCore private ConfigManager configManager;
@Bind private ConfigManager configManager;

@BindCore private DatabaseConnector databaseConnector;
@BindCore private RepositoryContext repositoryContext;
@BindCore private RepositoryManager repositoryManager;
@Bind private DatabaseManager databaseManager;
@Bind private RepositoryContext repositoryContext;
@Bind private RepositoryManager repositoryManager;

@BindCore private MessageService messageService;
@BindCore private TaskScheduler taskScheduler;
@BindCore private BukkitEventCaller eventCaller;
@BindCore private BukkitListenerRegistrar listenerRegistrar;
@BindCore private GuiRegistry guiRegistry;
@BindCore private PlaceholderAdapter placeholderAdapter;
@Bind private MessageService messageService;
@Bind private TaskScheduler taskScheduler;
@Bind private BukkitEventCaller eventCaller;
@Bind private BukkitListenerRegistrar listenerRegistrar;
@Bind private GuiRegistry guiRegistry;
@Bind private PlaceholderAdapter placeholderAdapter;

@BindCore private LiteCommandsRegistrar LiteCommandsRegistrar;
private LiteCommands<CommandSender> liteCommands;
@Bind private LiteCommandsBuilder<CommandSender, ?, ?> liteCommandsBuilder;
@Bind private LiteCommands<?> liteCommands;

private Metrics metrics;

Expand All @@ -89,11 +88,12 @@ final class PlayTimePlugin {
@NotNull Plugin plugin,
@NotNull Server server,
@NotNull PluginLogger logger,
@NotNull ExecutorService executor) {
this.plugin = Validator.notNull(plugin, "plugin cannot be null");
this.server = Validator.notNull(server, "server cannot be null");
this.logger = Validator.notNull(logger, "logger cannot be null");
this.executor = Validator.notNull(executor, "executorService cannot be null");
@NotNull ExecutorService executor
) {
this.plugin = Validator.notNull(plugin, "plugin");
this.server = Validator.notNull(server, "server");
this.logger = Validator.notNull(logger, "logger");
this.executor = Validator.notNull(executor, "executorService");
}

PlayTimePlugin(@NotNull Plugin plugin, @NotNull ExecutorService executor) {
Expand All @@ -102,14 +102,15 @@ final class PlayTimePlugin {

void enable(
@NotNull List<Class<? extends ConfigSection>> enabledConfigs,
@NotNull List<Class<? extends Module>> enabledModules) {
Validator.notNull(enabledConfigs, "enabledConfigs cannot be null");
Validator.notNull(enabledModules, "enabled modules cannot be null");
@NotNull List<Class<? extends Module>> enabledModules
) {
Validator.notNull(enabledConfigs, "enabledConfigs");
Validator.notNull(enabledModules, "enabled modules");

final Stopwatch stopwatch = Stopwatch.createStarted();

// Configuration
configManager = new ConfigManager(logger, executor, plugin.getDataFolder());
configManager = new ConfigManager(logger, plugin.getDataFolder());
configManager.createAll(enabledConfigs);

// Duration format style
Expand All @@ -118,18 +119,15 @@ void enable(

// Database
final DatabaseConfig databaseConfig = configManager.require(DatabaseConfig.class);
databaseManager = new DatabaseManager(plugin, logger, databaseConfig);

logger.info("Resolving database driver...");
final DriverDependencyLoader databaseDependencyLoader = new DriverDependencyLoader(plugin);
databaseDependencyLoader.loadDriverFor(databaseConfig.databaseMode);

logger.info("Connecting to database...");
databaseConnector = new DatabaseConnector(logger, databaseConfig);
databaseManager.loadDriver();
try {
databaseConnector.connect(plugin.getDataFolder());
databaseManager.connect();
} catch (SQLException e) {
logger.error(e, "Failed to connect database.");
throw new IllegalStateException("Database initialization failed", e);
logger.error(e, "An error occurred while trying to start all repositories. Disabling plugin...");
plugin.getPluginLoader().disablePlugin(plugin);
throw new IllegalStateException("Repository startup failed", e);
}

// Infrastructure services
Expand All @@ -145,17 +143,16 @@ void enable(
guiRegistry = new GuiRegistry();
placeholderAdapter = PlaceholderAdapterFactory.createFor(plugin, server, logger);

LiteCommandsRegistrar = new BukkitLiteCommandsRegistrar();
LiteCommandsRegistrar.configure(builder -> {
builder.invalidUsage(new InvalidUsageHandlerImpl(messageService));
builder.missingPermission(new MissingPermissionsHandlerImpl(messageService));
builder.result(Notice.class, new NoticeResultHandlerImpl(messageService));
});
liteCommandsBuilder = LiteBukkitFactory.builder(PREFIX, plugin, server);
liteCommandsBuilder
.invalidUsage(new InvalidUsageHandlerImpl(messageService))
.missingPermission(new MissingPermissionsHandlerImpl(messageService))
.result(Notice.class, new NoticeResultHandlerImpl(messageService));

// Dependency Injection
injector = DependencyInjection.createInjector(resources -> {
new PlayTimeCoreBinder(this).bind(resources);
ConfigBinder.bind(resources, configManager.getConfigs());
new PlayTimeBinder(this).bind(resources);
InjectorConfigBinder.bind(resources, configManager.getConfigs());
});

// Module initialization
Expand All @@ -168,7 +165,7 @@ void enable(
initializer.registerRepositories();

// Start repositories
Validator.ifNotNull(databaseConnector.getConnectionSource(), connection -> {
Validator.ifNotNull(databaseManager.getConnection(), connection -> {
try {
repositoryManager.startAll(connection);
} catch (SQLException e) {
Expand All @@ -181,8 +178,8 @@ void enable(
// Activate all feature modules
initializer.activateFeatures();

// Build and register commands
liteCommands = LiteCommandsRegistrar.create(PREFIX, plugin, server);
// Build commands
liteCommands = liteCommandsBuilder.build();

// Metrics
metrics = new Metrics(plugin, PLUGIN_METRICS_ID);
Expand All @@ -197,11 +194,11 @@ void enable(

void disable() {
Validator.ifNotNull(configManager, (manager) -> {
manager.saveAllSync();
manager.shutdown();
manager.saveAll();
manager.clearAll();
});
Validator.ifNotNull(repositoryManager, RepositoryManager::close);
Validator.ifNotNull(databaseConnector, DatabaseConnector::close);
Validator.ifNotNull(databaseManager, DatabaseManager::shutdown);
Validator.ifNotNull(messageService, MessageService::shutdown);
Validator.ifNotNull(taskScheduler, TaskScheduler::shutdown);
Validator.ifNotNull(liteCommands, LiteCommands::unregister);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.github.imdmk.playtime.config;

public class ConfigAccessException extends RuntimeException {

public ConfigAccessException(String message) {
super(message);
}

public ConfigAccessException(String message, Throwable cause) {
super(message, cause);
}

public ConfigAccessException(Throwable cause) {
super(cause);
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.github.imdmk.playtime.config;

import com.github.imdmk.playtime.shared.validate.Validator;
import eu.okaeri.configs.serdes.OkaeriSerdesPack;
import eu.okaeri.configs.serdes.commons.SerdesCommons;
import eu.okaeri.configs.yaml.snakeyaml.YamlSnakeYamlConfigurer;
import org.jetbrains.annotations.NotNull;

import java.io.File;

final class ConfigBinder {

void bind(@NotNull ConfigSection config, @NotNull File file) {
Validator.notNull(config, "config");
Validator.notNull(file, "file");

final OkaeriSerdesPack serdesPack = config.getSerdesPack();
final YamlSnakeYamlConfigurer yamlConfigurer = YamlConfigurerFactory.create();

config.withConfigurer(yamlConfigurer)
.withSerdesPack(serdesPack)
.withSerdesPack(new SerdesCommons())
.withBindFile(file)
.withRemoveOrphans(true);
}
}
Loading
Loading