From 7b10101f9f40931816fa117260b7b0416e626792 Mon Sep 17 00:00:00 2001 From: Jakubk15 <77227023+Jakubk15@users.noreply.github.com> Date: Sat, 13 Dec 2025 13:55:20 +0100 Subject: [PATCH 1/9] Extract ParcelDispatchService in order to de-load responsibilities from GuiManager --- .../parcellockers/ParcelLockers.java | 19 +++-- .../parcellockers/gui/GuiManager.java | 64 ++------------ .../parcel/ParcelDispatchService.java | 83 +++++++++++++++++++ 3 files changed, 101 insertions(+), 65 deletions(-) create mode 100644 src/main/java/com/eternalcode/parcellockers/parcel/ParcelDispatchService.java diff --git a/src/main/java/com/eternalcode/parcellockers/ParcelLockers.java b/src/main/java/com/eternalcode/parcellockers/ParcelLockers.java index 3bec8b3c..9139be36 100644 --- a/src/main/java/com/eternalcode/parcellockers/ParcelLockers.java +++ b/src/main/java/com/eternalcode/parcellockers/ParcelLockers.java @@ -30,6 +30,7 @@ import com.eternalcode.parcellockers.locker.validation.LockerValidationService; import com.eternalcode.parcellockers.locker.validation.LockerValidator; import com.eternalcode.parcellockers.notification.NoticeService; +import com.eternalcode.parcellockers.parcel.ParcelDispatchService; import com.eternalcode.parcellockers.parcel.ParcelService; import com.eternalcode.parcellockers.parcel.ParcelServiceImpl; import com.eternalcode.parcellockers.parcel.ParcelStatus; @@ -137,18 +138,24 @@ public void onEnable() { ItemStorageManager itemStorageManager = new ItemStorageManager(itemStorageRepository); DeliveryManager deliveryManager = new DeliveryManager(deliveryRepository); + ParcelDispatchService parcelDispatchService = new ParcelDispatchService( + lockerManager, + parcelService, + deliveryManager, + itemStorageManager, + scheduler, + config, + noticeService + ); + // guis TriumphGui.init(this); GuiManager guiManager = new GuiManager( - config, - scheduler, - noticeService, parcelService, lockerManager, userManager, itemStorageManager, - parcelContentManager, - deliveryManager + parcelDispatchService ); MainGui mainGUI = new MainGui( @@ -231,5 +238,3 @@ private boolean setupEconomy() { return this.economy != null; } } - - diff --git a/src/main/java/com/eternalcode/parcellockers/gui/GuiManager.java b/src/main/java/com/eternalcode/parcellockers/gui/GuiManager.java index d43caf28..bf1dfd98 100644 --- a/src/main/java/com/eternalcode/parcellockers/gui/GuiManager.java +++ b/src/main/java/com/eternalcode/parcellockers/gui/GuiManager.java @@ -1,23 +1,16 @@ package com.eternalcode.parcellockers.gui; -import com.eternalcode.commons.scheduler.Scheduler; -import com.eternalcode.parcellockers.configuration.implementation.PluginConfig; -import com.eternalcode.parcellockers.content.ParcelContentManager; -import com.eternalcode.parcellockers.delivery.DeliveryManager; import com.eternalcode.parcellockers.itemstorage.ItemStorage; import com.eternalcode.parcellockers.itemstorage.ItemStorageManager; import com.eternalcode.parcellockers.locker.Locker; import com.eternalcode.parcellockers.locker.LockerManager; -import com.eternalcode.parcellockers.notification.NoticeService; import com.eternalcode.parcellockers.parcel.Parcel; +import com.eternalcode.parcellockers.parcel.ParcelDispatchService; import com.eternalcode.parcellockers.parcel.ParcelService; -import com.eternalcode.parcellockers.parcel.task.ParcelSendTask; import com.eternalcode.parcellockers.shared.Page; import com.eternalcode.parcellockers.shared.PageResult; import com.eternalcode.parcellockers.user.User; import com.eternalcode.parcellockers.user.UserManager; -import java.time.Duration; -import java.time.Instant; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -27,73 +20,28 @@ public class GuiManager { - private final PluginConfig config; - private final Scheduler scheduler; - private final NoticeService noticeService; private final ParcelService parcelService; private final LockerManager lockerManager; private final UserManager userManager; private final ItemStorageManager itemStorageManager; - private final ParcelContentManager parcelContentManager; - private final DeliveryManager deliveryManager; + private final ParcelDispatchService parcelDispatchService; public GuiManager( - PluginConfig config, Scheduler scheduler, NoticeService noticeService, ParcelService parcelService, + ParcelService parcelService, LockerManager lockerManager, UserManager userManager, ItemStorageManager itemStorageManager, - ParcelContentManager parcelContentManager, - DeliveryManager deliveryManager + ParcelDispatchService parcelDispatchService ) { - this.config = config; - this.scheduler = scheduler; - this.noticeService = noticeService; this.parcelService = parcelService; this.lockerManager = lockerManager; this.userManager = userManager; this.itemStorageManager = itemStorageManager; - this.parcelContentManager = parcelContentManager; - this.deliveryManager = deliveryManager; + this.parcelDispatchService = parcelDispatchService; } public void sendParcel(Player sender, Parcel parcel, List items) { - this.lockerManager.isLockerFull(parcel.destinationLocker()).thenAccept(isFull -> { - if (isFull) { - this.noticeService.create() - .notice(messages -> messages.parcel.lockerFull) - .player(sender.getUniqueId()) - .send(); - return; - } - - Duration delay = parcel.priority() - ? this.config.settings.priorityParcelSendDuration - : this.config.settings.parcelSendDuration; - - this.parcelService.send(sender, parcel, items) - .thenAccept(success -> { - if (!success) { - return; - } - - this.deliveryManager.create(parcel.uuid(), Instant.now().plus(delay)); - - ParcelSendTask task = new ParcelSendTask( - parcel, - this.parcelService, - this.deliveryManager - ); - this.itemStorageManager.delete(sender.getUniqueId()); - - this.scheduler.runLaterAsync(task, delay); - }); - }).exceptionally(throwable -> { - this.noticeService.create() - .notice(messages -> messages.parcel.cannotSend) - .player(sender.getUniqueId()) - .send(); - return null; - }); + this.parcelDispatchService.dispatch(sender, parcel, items); } public void collectParcel(Player player, Parcel parcel) { diff --git a/src/main/java/com/eternalcode/parcellockers/parcel/ParcelDispatchService.java b/src/main/java/com/eternalcode/parcellockers/parcel/ParcelDispatchService.java new file mode 100644 index 00000000..39027ea2 --- /dev/null +++ b/src/main/java/com/eternalcode/parcellockers/parcel/ParcelDispatchService.java @@ -0,0 +1,83 @@ +package com.eternalcode.parcellockers.parcel; + +import com.eternalcode.commons.scheduler.Scheduler; +import com.eternalcode.parcellockers.configuration.implementation.PluginConfig; +import com.eternalcode.parcellockers.delivery.DeliveryManager; +import com.eternalcode.parcellockers.itemstorage.ItemStorageManager; +import com.eternalcode.parcellockers.locker.LockerManager; +import com.eternalcode.parcellockers.notification.NoticeService; +import com.eternalcode.parcellockers.parcel.task.ParcelSendTask; +import java.time.Duration; +import java.time.Instant; +import java.util.List; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +public class ParcelDispatchService { + + private final LockerManager lockerManager; + private final ParcelService parcelService; + private final DeliveryManager deliveryManager; + private final ItemStorageManager itemStorageManager; + private final Scheduler scheduler; + private final PluginConfig config; + private final NoticeService noticeService; + + public ParcelDispatchService( + LockerManager lockerManager, + ParcelService parcelService, + DeliveryManager deliveryManager, + ItemStorageManager itemStorageManager, + Scheduler scheduler, + PluginConfig config, + NoticeService noticeService + ) { + this.lockerManager = lockerManager; + this.parcelService = parcelService; + this.deliveryManager = deliveryManager; + this.itemStorageManager = itemStorageManager; + this.scheduler = scheduler; + this.config = config; + this.noticeService = noticeService; + } + + public void dispatch(Player sender, Parcel parcel, List items) { + this.lockerManager.isLockerFull(parcel.destinationLocker()).thenAccept(isFull -> { + if (isFull) { + this.noticeService.create() + .notice(messages -> messages.parcel.lockerFull) + .player(sender.getUniqueId()) + .send(); + return; + } + + Duration delay = parcel.priority() + ? this.config.settings.priorityParcelSendDuration + : this.config.settings.parcelSendDuration; + + this.parcelService.send(sender, parcel, items) + .thenAccept(success -> { + if (!Boolean.TRUE.equals(success)) { + return; + } + + this.deliveryManager.create(parcel.uuid(), Instant.now().plus(delay)); + + ParcelSendTask task = new ParcelSendTask( + parcel, + this.parcelService, + this.deliveryManager + ); + this.itemStorageManager.delete(sender.getUniqueId()); + + this.scheduler.runLaterAsync(task, delay); + }); + }).exceptionally(throwable -> { + this.noticeService.create() + .notice(messages -> messages.parcel.cannotSend) + .player(sender.getUniqueId()) + .send(); + return null; + }); + } +} From dadbd5b5c1afe055100693d9cd04b6729b62b965 Mon Sep 17 00:00:00 2001 From: Jakubk15 <77227023+Jakubk15@users.noreply.github.com> Date: Sat, 13 Dec 2025 14:01:37 +0100 Subject: [PATCH 2/9] Refactor delete methods in ParcelContentManager, ItemStorageManager, and DeliveryManager to return boolean indicating success --- .../parcellockers/content/ParcelContentManager.java | 4 ++-- .../eternalcode/parcellockers/delivery/DeliveryManager.java | 4 ++-- .../parcellockers/itemstorage/ItemStorageManager.java | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/eternalcode/parcellockers/content/ParcelContentManager.java b/src/main/java/com/eternalcode/parcellockers/content/ParcelContentManager.java index 2a4573dd..133d42c4 100644 --- a/src/main/java/com/eternalcode/parcellockers/content/ParcelContentManager.java +++ b/src/main/java/com/eternalcode/parcellockers/content/ParcelContentManager.java @@ -51,10 +51,10 @@ public ParcelContent create(UUID parcel, List items) { return content; } - public CompletableFuture delete(UUID parcel) { + public CompletableFuture delete(UUID parcel) { return this.contentRepository.delete(parcel).thenApply(i -> { this.cache.invalidate(parcel); - return null; + return i > 0; }); } diff --git a/src/main/java/com/eternalcode/parcellockers/delivery/DeliveryManager.java b/src/main/java/com/eternalcode/parcellockers/delivery/DeliveryManager.java index 251137a7..e31412fe 100644 --- a/src/main/java/com/eternalcode/parcellockers/delivery/DeliveryManager.java +++ b/src/main/java/com/eternalcode/parcellockers/delivery/DeliveryManager.java @@ -39,10 +39,10 @@ public Delivery create(UUID parcel, Instant deliveryTimestamp) { return delivery; } - public CompletableFuture delete(UUID parcel) { + public CompletableFuture delete(UUID parcel) { return this.deliveryRepository.delete(parcel).thenApply(i -> { this.deliveryCache.invalidate(parcel); - return null; + return i > 0; }); } diff --git a/src/main/java/com/eternalcode/parcellockers/itemstorage/ItemStorageManager.java b/src/main/java/com/eternalcode/parcellockers/itemstorage/ItemStorageManager.java index 2129b3c5..3a7cc849 100644 --- a/src/main/java/com/eternalcode/parcellockers/itemstorage/ItemStorageManager.java +++ b/src/main/java/com/eternalcode/parcellockers/itemstorage/ItemStorageManager.java @@ -61,9 +61,9 @@ private void cacheAll() { itemStorage)))); } - public CompletableFuture delete(UUID parcel) { + public CompletableFuture delete(UUID parcel) { this.cache.invalidate(parcel); - return this.itemStorageRepository.delete(parcel).thenApply(i -> null); + return this.itemStorageRepository.delete(parcel).thenApply(i -> i > 0); } public CompletableFuture deleteAll(CommandSender sender, NoticeService noticeService) { From 0d407e83d798d739734596aaae483df703ff230f Mon Sep 17 00:00:00 2001 From: Jakubk15 <77227023+Jakubk15@users.noreply.github.com> Date: Sat, 13 Dec 2025 14:04:44 +0100 Subject: [PATCH 3/9] Fix build --- src/main/java/com/eternalcode/parcellockers/gui/GuiManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/eternalcode/parcellockers/gui/GuiManager.java b/src/main/java/com/eternalcode/parcellockers/gui/GuiManager.java index bf1dfd98..44b790b5 100644 --- a/src/main/java/com/eternalcode/parcellockers/gui/GuiManager.java +++ b/src/main/java/com/eternalcode/parcellockers/gui/GuiManager.java @@ -81,7 +81,7 @@ public void saveItemStorage(UUID player, List items) { this.itemStorageManager.create(player, items); } - public CompletableFuture deleteItemStorage(UUID owner) { + public CompletableFuture deleteItemStorage(UUID owner) { return this.itemStorageManager.delete(owner); } } From 47435ebbfe8adb83118a384ec3dc659fa7010436 Mon Sep 17 00:00:00 2001 From: Jakubk15 <77227023+Jakubk15@users.noreply.github.com> Date: Sat, 13 Dec 2025 14:16:17 +0100 Subject: [PATCH 4/9] Adjust to Gemini's suggestions --- .../itemstorage/ItemStorageManager.java | 8 ++- .../parcel/ParcelDispatchService.java | 71 ++++++++++--------- 2 files changed, 44 insertions(+), 35 deletions(-) diff --git a/src/main/java/com/eternalcode/parcellockers/itemstorage/ItemStorageManager.java b/src/main/java/com/eternalcode/parcellockers/itemstorage/ItemStorageManager.java index 3a7cc849..3ad125e1 100644 --- a/src/main/java/com/eternalcode/parcellockers/itemstorage/ItemStorageManager.java +++ b/src/main/java/com/eternalcode/parcellockers/itemstorage/ItemStorageManager.java @@ -61,9 +61,11 @@ private void cacheAll() { itemStorage)))); } - public CompletableFuture delete(UUID parcel) { - this.cache.invalidate(parcel); - return this.itemStorageRepository.delete(parcel).thenApply(i -> i > 0); + public CompletableFuture delete(UUID owner) { + return this.itemStorageRepository.delete(owner).thenApply(i -> { + this.cache.invalidate(owner); + return i > 0; + }); } public CompletableFuture deleteAll(CommandSender sender, NoticeService noticeService) { diff --git a/src/main/java/com/eternalcode/parcellockers/parcel/ParcelDispatchService.java b/src/main/java/com/eternalcode/parcellockers/parcel/ParcelDispatchService.java index 39027ea2..d6ac7a21 100644 --- a/src/main/java/com/eternalcode/parcellockers/parcel/ParcelDispatchService.java +++ b/src/main/java/com/eternalcode/parcellockers/parcel/ParcelDispatchService.java @@ -10,6 +10,7 @@ import java.time.Duration; import java.time.Instant; import java.util.List; +import java.util.concurrent.CompletableFuture; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; @@ -42,42 +43,48 @@ public ParcelDispatchService( } public void dispatch(Player sender, Parcel parcel, List items) { - this.lockerManager.isLockerFull(parcel.destinationLocker()).thenAccept(isFull -> { - if (isFull) { - this.noticeService.create() - .notice(messages -> messages.parcel.lockerFull) - .player(sender.getUniqueId()) - .send(); - return; - } + this.lockerManager.isLockerFull(parcel.destinationLocker()) + .thenCompose(isFull -> { + if (isFull) { + this.noticeService.create() + .notice(messages -> messages.parcel.lockerFull) + .player(sender.getUniqueId()) + .send(); + return CompletableFuture.completedFuture(null); + } + + Duration delay = parcel.priority() + ? this.config.settings.priorityParcelSendDuration + : this.config.settings.parcelSendDuration; - Duration delay = parcel.priority() - ? this.config.settings.priorityParcelSendDuration - : this.config.settings.parcelSendDuration; + return this.parcelService.send(sender, parcel, items) + .thenAccept(success -> { + if (!Boolean.TRUE.equals(success)) { + return; + } - this.parcelService.send(sender, parcel, items) - .thenAccept(success -> { - if (!Boolean.TRUE.equals(success)) { - return; - } + this.deliveryManager.create(parcel.uuid(), Instant.now().plus(delay)); - this.deliveryManager.create(parcel.uuid(), Instant.now().plus(delay)); + ParcelSendTask task = new ParcelSendTask( + parcel, + this.parcelService, + this.deliveryManager + ); - ParcelSendTask task = new ParcelSendTask( - parcel, - this.parcelService, - this.deliveryManager - ); - this.itemStorageManager.delete(sender.getUniqueId()); + this.itemStorageManager.delete(sender.getUniqueId()).exceptionally(throwable -> { + throwable.printStackTrace(); + return false; + }); - this.scheduler.runLaterAsync(task, delay); - }); - }).exceptionally(throwable -> { - this.noticeService.create() - .notice(messages -> messages.parcel.cannotSend) - .player(sender.getUniqueId()) - .send(); - return null; - }); + this.scheduler.runLaterAsync(task, delay); + }); + }) + .exceptionally(throwable -> { + this.noticeService.create() + .notice(messages -> messages.parcel.cannotSend) + .player(sender.getUniqueId()) + .send(); + return null; + }); } } From 273e68b3249415f7352a0abdd7ab4c0fd624caed Mon Sep 17 00:00:00 2001 From: Jakubk15 <77227023+Jakubk15@users.noreply.github.com> Date: Sat, 13 Dec 2025 15:47:29 +0100 Subject: [PATCH 5/9] Improve exception handling --- .../ParcelContentRepositoryOrmLite.java | 4 ++-- .../parcellockers/database/DatabaseManager.java | 3 ++- .../database/persister/ItemStackPersister.java | 6 +++--- .../wrapper/AbstractRepositoryOrmLite.java | 14 +++++++++++++- .../parcellockers/locker/LockerManager.java | 6 +++--- .../repository/LockerRepositoryOrmLite.java | 5 ++--- .../parcellockers/parcel/ParcelServiceImpl.java | 4 ++-- .../repository/ParcelRepositoryOrmLite.java | 3 ++- .../shared/exception/DatabaseException.java | 15 +++++++++++++++ .../{ => exception}/ParcelLockersException.java | 10 ++++++---- .../exception/ParcelOperationException.java | 15 +++++++++++++++ .../shared/exception/ValidationException.java | 11 +++++++++++ .../parcellockers/user/UserManagerImpl.java | 5 +++-- 13 files changed, 79 insertions(+), 22 deletions(-) create mode 100644 src/main/java/com/eternalcode/parcellockers/shared/exception/DatabaseException.java rename src/main/java/com/eternalcode/parcellockers/shared/{ => exception}/ParcelLockersException.java (59%) create mode 100644 src/main/java/com/eternalcode/parcellockers/shared/exception/ParcelOperationException.java create mode 100644 src/main/java/com/eternalcode/parcellockers/shared/exception/ValidationException.java diff --git a/src/main/java/com/eternalcode/parcellockers/content/repository/ParcelContentRepositoryOrmLite.java b/src/main/java/com/eternalcode/parcellockers/content/repository/ParcelContentRepositoryOrmLite.java index 45f2c05e..09110053 100644 --- a/src/main/java/com/eternalcode/parcellockers/content/repository/ParcelContentRepositoryOrmLite.java +++ b/src/main/java/com/eternalcode/parcellockers/content/repository/ParcelContentRepositoryOrmLite.java @@ -4,7 +4,7 @@ import com.eternalcode.parcellockers.content.ParcelContent; import com.eternalcode.parcellockers.database.DatabaseManager; import com.eternalcode.parcellockers.database.wrapper.AbstractRepositoryOrmLite; -import com.eternalcode.parcellockers.shared.ParcelLockersException; +import com.eternalcode.parcellockers.shared.exception.DatabaseException; import com.j256.ormlite.table.TableUtils; import java.sql.SQLException; import java.util.Optional; @@ -19,7 +19,7 @@ public ParcelContentRepositoryOrmLite(DatabaseManager databaseManager, Scheduler try { TableUtils.createTableIfNotExists(databaseManager.connectionSource(), ParcelContentTable.class); } catch (SQLException exception) { - throw new ParcelLockersException("Failed to create ParcelContent table", exception); + throw new DatabaseException("Failed to create ParcelContent table", exception); } } diff --git a/src/main/java/com/eternalcode/parcellockers/database/DatabaseManager.java b/src/main/java/com/eternalcode/parcellockers/database/DatabaseManager.java index e622e001..86890361 100644 --- a/src/main/java/com/eternalcode/parcellockers/database/DatabaseManager.java +++ b/src/main/java/com/eternalcode/parcellockers/database/DatabaseManager.java @@ -1,6 +1,7 @@ package com.eternalcode.parcellockers.database; import com.eternalcode.parcellockers.configuration.implementation.PluginConfig; +import com.eternalcode.parcellockers.shared.exception.DatabaseException; import com.google.common.base.Stopwatch; import com.j256.ormlite.dao.Dao; import com.j256.ormlite.dao.DaoManager; @@ -106,7 +107,7 @@ public Dao getDao(Class type) { return (Dao) dao; } catch (SQLException exception) { - throw new RuntimeException(exception); + throw new DatabaseException("Failed to get DAO for type: " + type.getSimpleName(), exception); } } diff --git a/src/main/java/com/eternalcode/parcellockers/database/persister/ItemStackPersister.java b/src/main/java/com/eternalcode/parcellockers/database/persister/ItemStackPersister.java index b463de68..ec8183d7 100644 --- a/src/main/java/com/eternalcode/parcellockers/database/persister/ItemStackPersister.java +++ b/src/main/java/com/eternalcode/parcellockers/database/persister/ItemStackPersister.java @@ -1,6 +1,6 @@ package com.eternalcode.parcellockers.database.persister; -import com.eternalcode.parcellockers.shared.ParcelLockersException; +import com.eternalcode.parcellockers.shared.exception.DatabaseException; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.json.JsonMapper; @@ -39,7 +39,7 @@ public Object javaToSqlArg(FieldType fieldType, Object javaObject) { try { return JSON.writeValueAsString(stacks); } catch (JsonProcessingException e) { - throw new ParcelLockersException("Failed to serialize itemstacks", e); + throw new DatabaseException("Failed to serialize itemstacks", e); } } @@ -60,7 +60,7 @@ public Object sqlArgToJava(FieldType fieldType, Object sqlArg, int columnPos) { try { return JSON.readValue(string, JSON.getTypeFactory().constructCollectionType(List.class, ItemStack.class)); } catch (JsonProcessingException exception) { - throw new ParcelLockersException("Failed to deserialize itemstacks", exception); + throw new DatabaseException("Failed to deserialize itemstacks", exception); } } } diff --git a/src/main/java/com/eternalcode/parcellockers/database/wrapper/AbstractRepositoryOrmLite.java b/src/main/java/com/eternalcode/parcellockers/database/wrapper/AbstractRepositoryOrmLite.java index bcb1f0d2..eb6db387 100644 --- a/src/main/java/com/eternalcode/parcellockers/database/wrapper/AbstractRepositoryOrmLite.java +++ b/src/main/java/com/eternalcode/parcellockers/database/wrapper/AbstractRepositoryOrmLite.java @@ -3,14 +3,19 @@ import com.eternalcode.commons.ThrowingFunction; import com.eternalcode.commons.scheduler.Scheduler; import com.eternalcode.parcellockers.database.DatabaseManager; +import com.eternalcode.parcellockers.shared.exception.DatabaseException; import com.j256.ormlite.dao.Dao; import java.sql.SQLException; import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; +import java.util.logging.Level; +import java.util.logging.Logger; public abstract class AbstractRepositoryOrmLite { + private static final Logger LOGGER = Logger.getLogger(AbstractRepositoryOrmLite.class.getName()); + protected final DatabaseManager databaseManager; protected final Scheduler scheduler; @@ -59,9 +64,16 @@ protected CompletableFuture action(Class type, ThrowingFunction try { completableFuture.complete(action.apply(dao)); + } catch (SQLException sqlException) { + DatabaseException databaseException = new DatabaseException( + "Database operation failed for type: " + type.getSimpleName(), + sqlException + ); + LOGGER.log(Level.SEVERE, "Database operation failed", databaseException); + completableFuture.completeExceptionally(databaseException); } catch (Throwable throwable) { + LOGGER.log(Level.SEVERE, "Unexpected error during database operation", throwable); completableFuture.completeExceptionally(throwable); - throwable.printStackTrace(); } }); diff --git a/src/main/java/com/eternalcode/parcellockers/locker/LockerManager.java b/src/main/java/com/eternalcode/parcellockers/locker/LockerManager.java index a413c678..eb1a30e8 100644 --- a/src/main/java/com/eternalcode/parcellockers/locker/LockerManager.java +++ b/src/main/java/com/eternalcode/parcellockers/locker/LockerManager.java @@ -11,6 +11,7 @@ import com.eternalcode.parcellockers.shared.validation.ValidationResult; import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; +import eu.okaeri.configs.exception.ValidationException; import java.time.Duration; import java.util.List; import java.util.Optional; @@ -116,7 +117,7 @@ public CompletableFuture create(UUID uniqueId, String name, Position pos ValidationResult validation = this.validationService.validateCreateParameters(uniqueId, name, position); if (!validation.isValid()) { - throw new IllegalArgumentException(validation.errorMessage()); + throw new ValidationException("Invalid locker parameters: " + validation.errorMessage()); } Optional existingByUUID = Optional.ofNullable(this.lockersByUUID.get(uniqueId, uuid -> null)); @@ -126,7 +127,7 @@ public CompletableFuture create(UUID uniqueId, String name, Position pos uniqueId, position, existingByUUID, existingByPosition); if (!conflictCheck.isValid()) { - throw new IllegalStateException(conflictCheck.errorMessage()); + throw new ValidationException(conflictCheck.errorMessage()); } Locker locker = new Locker(uniqueId, name, position); @@ -172,4 +173,3 @@ private void cacheAll() { }))); } } - diff --git a/src/main/java/com/eternalcode/parcellockers/locker/repository/LockerRepositoryOrmLite.java b/src/main/java/com/eternalcode/parcellockers/locker/repository/LockerRepositoryOrmLite.java index 4437155b..abba33be 100644 --- a/src/main/java/com/eternalcode/parcellockers/locker/repository/LockerRepositoryOrmLite.java +++ b/src/main/java/com/eternalcode/parcellockers/locker/repository/LockerRepositoryOrmLite.java @@ -6,8 +6,8 @@ import com.eternalcode.parcellockers.locker.Locker; import com.eternalcode.parcellockers.shared.Page; import com.eternalcode.parcellockers.shared.PageResult; -import com.eternalcode.parcellockers.shared.ParcelLockersException; import com.eternalcode.parcellockers.shared.Position; +import com.eternalcode.parcellockers.shared.exception.DatabaseException; import com.j256.ormlite.table.TableUtils; import java.sql.SQLException; import java.util.List; @@ -24,8 +24,7 @@ public LockerRepositoryOrmLite(DatabaseManager databaseManager, Scheduler schedu try { TableUtils.createTableIfNotExists(databaseManager.connectionSource(), LockerTable.class); } catch (SQLException ex) { - ex.printStackTrace(); - throw new ParcelLockersException("Failed to initialize locker table", ex); + throw new DatabaseException("Failed to initialize locker table", ex); } } diff --git a/src/main/java/com/eternalcode/parcellockers/parcel/ParcelServiceImpl.java b/src/main/java/com/eternalcode/parcellockers/parcel/ParcelServiceImpl.java index 1a9701d2..fd4cf60a 100644 --- a/src/main/java/com/eternalcode/parcellockers/parcel/ParcelServiceImpl.java +++ b/src/main/java/com/eternalcode/parcellockers/parcel/ParcelServiceImpl.java @@ -11,7 +11,7 @@ import com.eternalcode.parcellockers.parcel.repository.ParcelRepository; import com.eternalcode.parcellockers.shared.Page; import com.eternalcode.parcellockers.shared.PageResult; -import com.eternalcode.parcellockers.shared.ParcelLockersException; +import com.eternalcode.parcellockers.shared.exception.ParcelOperationException; import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; import java.util.ArrayList; @@ -105,7 +105,7 @@ public CompletableFuture send(Player sender, Parcel parcel, List messages.parcel.cannotSend) .player(sender.getUniqueId()) .send(); - throw new ParcelLockersException("Failed to save parcel", throwable); + throw new ParcelOperationException("Failed to save parcel", throwable); } this.parcelContentRepository.save(new ParcelContent(parcel.uuid(), items)); diff --git a/src/main/java/com/eternalcode/parcellockers/parcel/repository/ParcelRepositoryOrmLite.java b/src/main/java/com/eternalcode/parcellockers/parcel/repository/ParcelRepositoryOrmLite.java index 21de780a..f368989b 100644 --- a/src/main/java/com/eternalcode/parcellockers/parcel/repository/ParcelRepositoryOrmLite.java +++ b/src/main/java/com/eternalcode/parcellockers/parcel/repository/ParcelRepositoryOrmLite.java @@ -7,6 +7,7 @@ import com.eternalcode.parcellockers.parcel.ParcelStatus; import com.eternalcode.parcellockers.shared.Page; import com.eternalcode.parcellockers.shared.PageResult; +import com.eternalcode.parcellockers.shared.exception.DatabaseException; import com.j256.ormlite.table.TableUtils; import java.sql.SQLException; import java.util.List; @@ -27,7 +28,7 @@ public ParcelRepositoryOrmLite(DatabaseManager databaseManager, Scheduler schedu try { TableUtils.createTableIfNotExists(databaseManager.connectionSource(), ParcelTable.class); } catch (SQLException ex) { - throw new RuntimeException("Failed to initialize parcel table", ex); + throw new DatabaseException("Failed to initialize parcel table", ex); } } diff --git a/src/main/java/com/eternalcode/parcellockers/shared/exception/DatabaseException.java b/src/main/java/com/eternalcode/parcellockers/shared/exception/DatabaseException.java new file mode 100644 index 00000000..371af081 --- /dev/null +++ b/src/main/java/com/eternalcode/parcellockers/shared/exception/DatabaseException.java @@ -0,0 +1,15 @@ +package com.eternalcode.parcellockers.shared.exception; + +/** + * Exception thrown when a database operation fails. + */ +public class DatabaseException extends ParcelLockersException { + + public DatabaseException(String message, Throwable cause) { + super(message, cause); + } + + public DatabaseException(String message) { + super(message); + } +} diff --git a/src/main/java/com/eternalcode/parcellockers/shared/ParcelLockersException.java b/src/main/java/com/eternalcode/parcellockers/shared/exception/ParcelLockersException.java similarity index 59% rename from src/main/java/com/eternalcode/parcellockers/shared/ParcelLockersException.java rename to src/main/java/com/eternalcode/parcellockers/shared/exception/ParcelLockersException.java index f33575ca..fbc4c227 100644 --- a/src/main/java/com/eternalcode/parcellockers/shared/ParcelLockersException.java +++ b/src/main/java/com/eternalcode/parcellockers/shared/exception/ParcelLockersException.java @@ -1,10 +1,11 @@ -package com.eternalcode.parcellockers.shared; +package com.eternalcode.parcellockers.shared.exception; +/** + * Base exception for the ParcelLockers plugin. + * All plugin-specific exceptions should extend this class. + */ public class ParcelLockersException extends RuntimeException { - public ParcelLockersException() { - } - public ParcelLockersException(String message) { super(message); } @@ -13,3 +14,4 @@ public ParcelLockersException(String message, Throwable cause) { super(message, cause); } } + diff --git a/src/main/java/com/eternalcode/parcellockers/shared/exception/ParcelOperationException.java b/src/main/java/com/eternalcode/parcellockers/shared/exception/ParcelOperationException.java new file mode 100644 index 00000000..081e33ae --- /dev/null +++ b/src/main/java/com/eternalcode/parcellockers/shared/exception/ParcelOperationException.java @@ -0,0 +1,15 @@ +package com.eternalcode.parcellockers.shared.exception; + +/** + * Exception thrown when a parcel operation fails. + */ +public class ParcelOperationException extends ParcelLockersException { + + public ParcelOperationException(String message) { + super(message); + } + + public ParcelOperationException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/com/eternalcode/parcellockers/shared/exception/ValidationException.java b/src/main/java/com/eternalcode/parcellockers/shared/exception/ValidationException.java new file mode 100644 index 00000000..340024e3 --- /dev/null +++ b/src/main/java/com/eternalcode/parcellockers/shared/exception/ValidationException.java @@ -0,0 +1,11 @@ +package com.eternalcode.parcellockers.shared.exception; + +/** + * Exception thrown when a validation fails. + */ +public class ValidationException extends ParcelLockersException { + + public ValidationException(String message) { + super(message); + } +} diff --git a/src/main/java/com/eternalcode/parcellockers/user/UserManagerImpl.java b/src/main/java/com/eternalcode/parcellockers/user/UserManagerImpl.java index 3b245c6d..8238c579 100644 --- a/src/main/java/com/eternalcode/parcellockers/user/UserManagerImpl.java +++ b/src/main/java/com/eternalcode/parcellockers/user/UserManagerImpl.java @@ -7,6 +7,7 @@ import com.eternalcode.parcellockers.user.validation.UserValidationService; import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; +import eu.okaeri.configs.exception.ValidationException; import java.util.Optional; import java.util.UUID; import java.util.concurrent.CompletableFuture; @@ -83,7 +84,7 @@ public CompletableFuture create(UUID uuid, String name) { ValidationResult validation = this.validationService.validateCreateParameters(uuid, name); if (!validation.isValid()) { - throw new IllegalArgumentException("Invalid parameters: " + validation.errorMessage()); + throw new ValidationException("Invalid user parameters: " + validation.errorMessage()); } Optional existingByUUID = Optional.ofNullable(this.usersByUUID.get(uuid, uniqueId -> null)); @@ -93,7 +94,7 @@ public CompletableFuture create(UUID uuid, String name) { uuid, name, existingByUUID, existingByName); if (!conflictCheck.isValid()) { - throw new IllegalStateException(conflictCheck.errorMessage()); + throw new ValidationException(conflictCheck.errorMessage()); } User user = new User(uuid, name); From 6b2bf4d28811986369366a9444077c52b5de2ada Mon Sep 17 00:00:00 2001 From: Jakubk15 <77227023+Jakubk15@users.noreply.github.com> Date: Sat, 13 Dec 2025 16:36:37 +0100 Subject: [PATCH 6/9] Log exception --- .../eternalcode/parcellockers/parcel/ParcelDispatchService.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/eternalcode/parcellockers/parcel/ParcelDispatchService.java b/src/main/java/com/eternalcode/parcellockers/parcel/ParcelDispatchService.java index d6ac7a21..f10022a5 100644 --- a/src/main/java/com/eternalcode/parcellockers/parcel/ParcelDispatchService.java +++ b/src/main/java/com/eternalcode/parcellockers/parcel/ParcelDispatchService.java @@ -71,6 +71,7 @@ public void dispatch(Player sender, Parcel parcel, List items) { this.deliveryManager ); + // FIXME: Items will remain in the storage if this fails and parcel will be sent this.itemStorageManager.delete(sender.getUniqueId()).exceptionally(throwable -> { throwable.printStackTrace(); return false; @@ -84,6 +85,7 @@ public void dispatch(Player sender, Parcel parcel, List items) { .notice(messages -> messages.parcel.cannotSend) .player(sender.getUniqueId()) .send(); + throwable.printStackTrace(); return null; }); } From 0075ab8003e9044f1ba3c84ce3d4bee418dba793 Mon Sep 17 00:00:00 2001 From: Jakubk15 <77227023+Jakubk15@users.noreply.github.com> Date: Sat, 13 Dec 2025 17:17:22 +0100 Subject: [PATCH 7/9] Simplify Notice sending --- build.gradle.kts | 2 +- .../parcellockers/ParcelLockersCommand.java | 1 + .../command/debug/DebugCommand.java | 14 +++-------- .../implementation/MessageConfig.java | 9 ++++--- .../controller/LockerBreakController.java | 10 ++------ .../controller/LockerPlaceController.java | 13 +++------- .../locker/prompt/LockerPlacePrompt.java | 6 ++--- .../parcel/ParcelDispatchService.java | 10 ++------ .../parcel/ParcelServiceImpl.java | 25 ++++--------------- .../UpdaterNotificationController.java | 5 +--- 10 files changed, 27 insertions(+), 68 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 19a051e9..c02d205c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -114,7 +114,7 @@ paper { tasks.withType { options.encoding = "UTF-8" - options.setIncremental(true) + options.isIncremental = true options.compilerArgs.add("-parameters") options.release = 21 } diff --git a/src/main/java/com/eternalcode/parcellockers/ParcelLockersCommand.java b/src/main/java/com/eternalcode/parcellockers/ParcelLockersCommand.java index c60409b0..580f64b9 100644 --- a/src/main/java/com/eternalcode/parcellockers/ParcelLockersCommand.java +++ b/src/main/java/com/eternalcode/parcellockers/ParcelLockersCommand.java @@ -38,5 +38,6 @@ void reload(@Sender CommandSender sender) { void get(@Sender Player player) { ItemStack lockerItem = this.config.settings.parcelLockerItem.toGuiItem().getItemStack(); player.getInventory().addItem(lockerItem); + this.noticeService.player(player.getUniqueId(), messages -> messages.locker.addedToInventory); } } diff --git a/src/main/java/com/eternalcode/parcellockers/command/debug/DebugCommand.java b/src/main/java/com/eternalcode/parcellockers/command/debug/DebugCommand.java index 7383b4ef..f5f5049d 100644 --- a/src/main/java/com/eternalcode/parcellockers/command/debug/DebugCommand.java +++ b/src/main/java/com/eternalcode/parcellockers/command/debug/DebugCommand.java @@ -80,26 +80,20 @@ void deleteAll(@Sender CommandSender sender) { @Execute(name = "getrandomitem") void getRandomItem(@Sender Player player, @Arg int stacks) { if (stacks <= 0 || stacks > 36) { - this.noticeService.create() - .notice(Notice.chat("&cInvalid number of stacks. Must be between 1 and 36.")) - .player(player.getUniqueId()) - .send(); + this.noticeService.player(player.getUniqueId(), messages -> Notice.chat("&cInvalid number of stacks. Must be between 1 and 36.")); return; } List itemMaterials = Arrays.stream(Material.values()).filter(Material::isItem).toList(); - if (itemMaterials.isEmpty()) { - this.noticeService.create() - .notice(Notice.chat("&cNo valid item materials found.")) - .player(player.getUniqueId()) - .send(); + if (itemMaterials.isEmpty()) { // should never happen + this.noticeService.player(player.getUniqueId(), messages -> Notice.chat("&cNo valid item materials found.")); return; } Random random = ThreadLocalRandom.current(); - // give player random items + // Give player random items for (int i = 0; i < stacks; i++) { Material randomMaterial = itemMaterials.get(random.nextInt(itemMaterials.size())); int randomAmount = Math.min(random.nextInt(64) + 1, randomMaterial.getMaxStackSize()); diff --git a/src/main/java/com/eternalcode/parcellockers/configuration/implementation/MessageConfig.java b/src/main/java/com/eternalcode/parcellockers/configuration/implementation/MessageConfig.java index 2da416f6..8ac78309 100644 --- a/src/main/java/com/eternalcode/parcellockers/configuration/implementation/MessageConfig.java +++ b/src/main/java/com/eternalcode/parcellockers/configuration/implementation/MessageConfig.java @@ -43,12 +43,11 @@ public static class ParcelMessages extends OkaeriConfig { .chat("&2✔ &aParcel sent successfully.") .sound(Sound.ENTITY_ITEM_PICKUP.key()) .build(); - public Notice cannotSend = Notice.builder() .chat("&4✘ &cAn error occurred while sending the parcel. Check the console for more information.") .sound(Sound.ENTITY_VILLAGER_NO.key()) .build(); - public Notice emptyName = Notice.builder() + public Notice nameCannotBeEmpty = Notice.builder() .chat("&4✘ &cThe parcel name cannot be empty!") .sound(Sound.ENTITY_VILLAGER_NO.key()) .build(); @@ -56,7 +55,7 @@ public static class ParcelMessages extends OkaeriConfig { .chat("&2✔ &aParcel name set successfully.") .sound(Sound.ENTITY_EXPERIENCE_ORB_PICKUP.key()) .build(); - public Notice empty = Notice.builder() + public Notice cannotBeEmpty = Notice.builder() .chat("&4✘ &cThe parcel cannot be empty!") .sound(Sound.ENTITY_ENDERMAN_AMBIENT.key()) .build(); @@ -162,6 +161,10 @@ public static class LockerMessages extends OkaeriConfig { .chat("&4✘ &cYou are already creating a parcel locker!") .sound(Sound.ENTITY_VILLAGER_NO.key()) .build(); + public Notice addedToInventory = Notice.builder() + .chat("&2✔ &aParcel locker item added to your inventory.") + .sound(Sound.ENTITY_ITEM_PICKUP.key()) + .build(); } public static class AdminMessages extends OkaeriConfig { diff --git a/src/main/java/com/eternalcode/parcellockers/locker/controller/LockerBreakController.java b/src/main/java/com/eternalcode/parcellockers/locker/controller/LockerBreakController.java index 8049a826..a09e79bc 100644 --- a/src/main/java/com/eternalcode/parcellockers/locker/controller/LockerBreakController.java +++ b/src/main/java/com/eternalcode/parcellockers/locker/controller/LockerBreakController.java @@ -54,19 +54,13 @@ public void onBlockBreak(BlockBreakEvent event) { if (!player.hasPermission("parcellockers.admin.break")) { event.setCancelled(true); - this.noticeService.create() - .player(player.getUniqueId()) - .notice(messages -> messages.locker.cannotBreak) - .send(); + this.noticeService.player(player.getUniqueId(), messages -> messages.locker.cannotBreak); return; } this.lockerManager.delete(locker.get().uuid()); - this.noticeService.create() - .player(player.getUniqueId()) - .notice(messages -> messages.locker.deleted) - .send(); + this.noticeService.player(player.getUniqueId(), messages -> messages.locker.deleted); Formatter formatter = new Formatter() .register("{X}", position.x()) diff --git a/src/main/java/com/eternalcode/parcellockers/locker/controller/LockerPlaceController.java b/src/main/java/com/eternalcode/parcellockers/locker/controller/LockerPlaceController.java index 561781ad..169e3547 100644 --- a/src/main/java/com/eternalcode/parcellockers/locker/controller/LockerPlaceController.java +++ b/src/main/java/com/eternalcode/parcellockers/locker/controller/LockerPlaceController.java @@ -80,10 +80,7 @@ public void onBlockPlace(BlockPlaceEvent event) { if (this.lockerCreators.getIfPresent(player.getUniqueId()) != null) { event.setCancelled(true); - this.noticeService.create() - .player(player.getUniqueId()) - .notice(messages -> messages.locker.alreadyCreating) - .send(); + this.noticeService.player(player.getUniqueId(), messages -> messages.locker.alreadyCreating); return; } this.lockerCreators.put(player.getUniqueId(), true); @@ -98,13 +95,9 @@ public void onBlockPlace(BlockPlaceEvent event) { Location location = event.getBlockPlaced().getLocation(); this.lockerManager.create(UUID.randomUUID(), description, PositionAdapter.convert(location)); - - this.noticeService.create() - .player(player.getUniqueId()) - .notice(messages -> messages.locker.created) - .send(); - this.lockerCreators.invalidate(player.getUniqueId()); + + this.noticeService.player(player.getUniqueId(), messages -> messages.locker.created); }) .withPrefix(new NullConversationPrefix()) .withModality(false) diff --git a/src/main/java/com/eternalcode/parcellockers/locker/prompt/LockerPlacePrompt.java b/src/main/java/com/eternalcode/parcellockers/locker/prompt/LockerPlacePrompt.java index 28dd684d..7e3890e7 100644 --- a/src/main/java/com/eternalcode/parcellockers/locker/prompt/LockerPlacePrompt.java +++ b/src/main/java/com/eternalcode/parcellockers/locker/prompt/LockerPlacePrompt.java @@ -8,6 +8,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +// TODO replace with dialog api public class LockerPlacePrompt implements Prompt { private final NoticeService noticeService; @@ -26,10 +27,7 @@ public Prompt acceptInput(@NotNull ConversationContext context, @Nullable String @Override public String getPromptText(@NotNull ConversationContext context) { if (context.getForWhom() instanceof Player player) { - this.noticeService.create() - .player(player.getUniqueId()) - .notice(messages -> messages.locker.descriptionPrompt) - .send(); + this.noticeService.player(player.getUniqueId(), messages -> messages.locker.descriptionPrompt); } return StringUtils.EMPTY; } diff --git a/src/main/java/com/eternalcode/parcellockers/parcel/ParcelDispatchService.java b/src/main/java/com/eternalcode/parcellockers/parcel/ParcelDispatchService.java index f10022a5..f131d17f 100644 --- a/src/main/java/com/eternalcode/parcellockers/parcel/ParcelDispatchService.java +++ b/src/main/java/com/eternalcode/parcellockers/parcel/ParcelDispatchService.java @@ -46,10 +46,7 @@ public void dispatch(Player sender, Parcel parcel, List items) { this.lockerManager.isLockerFull(parcel.destinationLocker()) .thenCompose(isFull -> { if (isFull) { - this.noticeService.create() - .notice(messages -> messages.parcel.lockerFull) - .player(sender.getUniqueId()) - .send(); + this.noticeService.player(sender.getUniqueId(), messages -> messages.parcel.lockerFull); return CompletableFuture.completedFuture(null); } @@ -81,10 +78,7 @@ public void dispatch(Player sender, Parcel parcel, List items) { }); }) .exceptionally(throwable -> { - this.noticeService.create() - .notice(messages -> messages.parcel.cannotSend) - .player(sender.getUniqueId()) - .send(); + this.noticeService.player(sender.getUniqueId(), messages -> messages.parcel.cannotSend); throwable.printStackTrace(); return null; }); diff --git a/src/main/java/com/eternalcode/parcellockers/parcel/ParcelServiceImpl.java b/src/main/java/com/eternalcode/parcellockers/parcel/ParcelServiceImpl.java index fd4cf60a..dc9cba9c 100644 --- a/src/main/java/com/eternalcode/parcellockers/parcel/ParcelServiceImpl.java +++ b/src/main/java/com/eternalcode/parcellockers/parcel/ParcelServiceImpl.java @@ -101,18 +101,12 @@ public CompletableFuture send(Player sender, Parcel parcel, List { if (throwable != null) { - this.noticeService.create() - .notice(messages -> messages.parcel.cannotSend) - .player(sender.getUniqueId()) - .send(); + this.noticeService.player(sender.getUniqueId(), messages -> messages.parcel.cannotSend); throw new ParcelOperationException("Failed to save parcel", throwable); } this.parcelContentRepository.save(new ParcelContent(parcel.uuid(), items)); - this.noticeService.create() - .notice(messages -> messages.parcel.sent) - .player(sender.getUniqueId()) - .send(); + this.noticeService.player(sender.getUniqueId(), messages -> messages.parcel.sent); return true; }); } @@ -146,19 +140,13 @@ public CompletableFuture delete(CommandSender sender, Parcel parcel) { public CompletableFuture collect(Player player, Parcel parcel) { return this.parcelContentRepository.fetch(parcel.uuid()).thenAccept(optional -> { if (optional.isEmpty()) { - this.noticeService.create() - .notice(messages -> messages.parcel.cannotCollect) - .player(player.getUniqueId()) - .send(); + this.noticeService.player(player.getUniqueId(), messages -> messages.parcel.cannotCollect); return; } List items = optional.get().items(); if (items.size() > freeSlotsInInventory(player)) { - this.noticeService.create() - .notice(messages -> messages.parcel.noInventorySpace) - .player(player.getUniqueId()) - .send(); + this.noticeService.player(player.getUniqueId(), messages -> messages.parcel.noInventorySpace); return; } @@ -170,10 +158,7 @@ public CompletableFuture collect(Player player, Parcel parcel) { this.parcelRepository.delete(parcel); this.parcelContentRepository.delete(parcel.uuid()); - this.noticeService.create() - .notice(messages -> messages.parcel.collected) - .player(player.getUniqueId()) - .send(); + this.noticeService.player(player.getUniqueId(), messages -> messages.parcel.collected); }); } diff --git a/src/main/java/com/eternalcode/parcellockers/updater/UpdaterNotificationController.java b/src/main/java/com/eternalcode/parcellockers/updater/UpdaterNotificationController.java index 4d4fd399..996f9442 100644 --- a/src/main/java/com/eternalcode/parcellockers/updater/UpdaterNotificationController.java +++ b/src/main/java/com/eternalcode/parcellockers/updater/UpdaterNotificationController.java @@ -40,10 +40,7 @@ void onJoin(PlayerJoinEvent event) { upToDate.thenAccept(isUpToDate -> { if (!isUpToDate) { - this.noticeService.create() - .player(player.getUniqueId()) - .notice(Notice.chat(NEW_VERSION_AVAILABLE)) - .send(); + this.noticeService.player(player.getUniqueId(), messages -> Notice.chat(NEW_VERSION_AVAILABLE)); } }).orTimeout(5, TimeUnit.SECONDS); } From 08063ea9a8d7b74d528ab384a2b71c3c2f4cd559 Mon Sep 17 00:00:00 2001 From: Jakubk15 <77227023+Jakubk15@users.noreply.github.com> Date: Sat, 13 Dec 2025 18:40:18 +0100 Subject: [PATCH 8/9] Use modern Dialog API instead of legacy Conversation API --- .../parcellockers/ParcelLockers.java | 2 +- .../implementation/MessageConfig.java | 5 +- .../gui/implementation/locker/SendingGui.java | 42 ++---- .../controller/LockerPlaceController.java | 139 +++++++++++------- .../locker/prompt/LockerPlacePrompt.java | 39 ----- 5 files changed, 96 insertions(+), 131 deletions(-) delete mode 100644 src/main/java/com/eternalcode/parcellockers/locker/prompt/LockerPlacePrompt.java diff --git a/src/main/java/com/eternalcode/parcellockers/ParcelLockers.java b/src/main/java/com/eternalcode/parcellockers/ParcelLockers.java index 9139be36..a5c75eba 100644 --- a/src/main/java/com/eternalcode/parcellockers/ParcelLockers.java +++ b/src/main/java/com/eternalcode/parcellockers/ParcelLockers.java @@ -190,7 +190,7 @@ public void onEnable() { Stream.of( new LockerInteractionController(lockerManager, lockerGUI), - new LockerPlaceController(config, this, lockerManager, noticeService), + new LockerPlaceController(config, messageConfig, miniMessage, lockerManager, noticeService), new LockerBreakController(lockerManager, noticeService, scheduler), new PrepareUserController(userManager), new LoadUserController(userManager, server) diff --git a/src/main/java/com/eternalcode/parcellockers/configuration/implementation/MessageConfig.java b/src/main/java/com/eternalcode/parcellockers/configuration/implementation/MessageConfig.java index 8ac78309..d6e7cfbb 100644 --- a/src/main/java/com/eternalcode/parcellockers/configuration/implementation/MessageConfig.java +++ b/src/main/java/com/eternalcode/parcellockers/configuration/implementation/MessageConfig.java @@ -144,10 +144,7 @@ public static class LockerMessages extends OkaeriConfig { .chat("&2✔ &aParcel locker created successfully.") .sound(Sound.BLOCK_ANVIL_USE.key()) .build(); - public Notice descriptionPrompt = Notice.builder() - .chat("&6\uD83D\uDDC8 &eEnter a name for the parcel locker:") - .sound(Sound.BLOCK_NOTE_BLOCK_PLING.key()) - .build(); + public String descriptionPrompt = "&6Enter a name for the parcel locker:"; public Notice cannotBreak = Notice.builder() .chat("&4✘ &cYou have no permission to break the parcel locker.") .sound(Sound.ENTITY_VILLAGER_NO.key()) diff --git a/src/main/java/com/eternalcode/parcellockers/gui/implementation/locker/SendingGui.java b/src/main/java/com/eternalcode/parcellockers/gui/implementation/locker/SendingGui.java index a6e5fa0a..f87e5a80 100644 --- a/src/main/java/com/eternalcode/parcellockers/gui/implementation/locker/SendingGui.java +++ b/src/main/java/com/eternalcode/parcellockers/gui/implementation/locker/SendingGui.java @@ -98,19 +98,13 @@ public void show(Player player) { .setLine(0, "Enter parcel name:") .setHandler((p, result) -> { String name = result.getLineWithoutColor(1); - if (name.isBlank()) { - this.noticeService.create() - .notice(messages -> messages.parcel.emptyName) - .player(player.getUniqueId()) - .send(); + if (name == null || name.isBlank()) { + this.noticeService.player(player.getUniqueId(), messages -> messages.parcel.nameCannotBeEmpty); return Collections.emptyList(); } this.state.parcelName(name); - this.noticeService.create() - .notice(messages -> messages.parcel.nameSet) - .player(player.getUniqueId()) - .send(); + this.noticeService.player(player.getUniqueId(), messages -> messages.parcel.nameSet); List lore = nameItem.lore(); if (lore.size() > 1) { @@ -146,10 +140,7 @@ public void show(Player player) { String description = result.getLineWithoutColor(1); this.state.parcelDescription(description); - this.noticeService.create() - .notice(messages -> messages.parcel.descriptionSet) - .player(player.getUniqueId()) - .send(); + this.noticeService.player(player.getUniqueId(), messages -> messages.parcel.descriptionSet); List lore = descriptionItem.clone().lore(); if (lore.size() > 1) { @@ -199,34 +190,22 @@ public void show(Player player) { GuiItem submitItem = this.guiSettings.submitParcelItem.toGuiItem(event -> this.guiManager.getItemStorage(player.getUniqueId()).thenAccept(result -> { if (result.items().isEmpty()) { - this.noticeService.create() - .notice(messages -> messages.parcel.empty) - .player(player.getUniqueId()) - .send(); + this.noticeService.player(player.getUniqueId(), messages -> messages.parcel.cannotBeEmpty); return; } if (this.state.parcelName() == null || this.state.parcelName().isEmpty()) { - this.noticeService.create() - .notice(messages -> messages.parcel.nameNotSet) - .player(player.getUniqueId()) - .send(); + this.noticeService.player(player.getUniqueId(), messages -> messages.parcel.nameNotSet); return; } if (this.state.receiver() == null) { - this.noticeService.create() - .notice(messages -> messages.parcel.receiverNotSet) - .player(player.getUniqueId()) - .send(); + this.noticeService.player(player.getUniqueId(), messages -> messages.parcel.receiverNotSet); return; } if (this.state.destinationLocker() == null) { - this.noticeService.create() - .notice(messages -> messages.parcel.destinationNotSet) - .player(player.getUniqueId()) - .send(); + this.noticeService.player(player.getUniqueId(), messages -> messages.parcel.destinationNotSet); return; } @@ -379,10 +358,7 @@ public void updateReceiverItem(Player player, String receiverName, boolean sendN public void updateDestinationItem(Player player, String destinationLockerDesc, boolean sendNotice) { if (sendNotice){ - this.noticeService.create() - .notice(messages -> messages.parcel.destinationSet) - .player(player.getUniqueId()) - .send(); + this.noticeService.player(player.getUniqueId(), messages -> messages.parcel.destinationSet); } if (destinationLockerDesc == null || destinationLockerDesc.isEmpty()) { diff --git a/src/main/java/com/eternalcode/parcellockers/locker/controller/LockerPlaceController.java b/src/main/java/com/eternalcode/parcellockers/locker/controller/LockerPlaceController.java index 169e3547..f35ef94d 100644 --- a/src/main/java/com/eternalcode/parcellockers/locker/controller/LockerPlaceController.java +++ b/src/main/java/com/eternalcode/parcellockers/locker/controller/LockerPlaceController.java @@ -1,31 +1,43 @@ package com.eternalcode.parcellockers.locker.controller; +import com.eternalcode.parcellockers.configuration.implementation.MessageConfig; import com.eternalcode.parcellockers.configuration.implementation.PluginConfig; import com.eternalcode.parcellockers.locker.LockerManager; -import com.eternalcode.parcellockers.locker.prompt.LockerPlacePrompt; import com.eternalcode.parcellockers.notification.NoticeService; import com.eternalcode.parcellockers.shared.PositionAdapter; import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; -import java.util.HashSet; +import io.papermc.paper.dialog.Dialog; +import io.papermc.paper.dialog.DialogResponseView; +import io.papermc.paper.registry.data.dialog.ActionButton; +import io.papermc.paper.registry.data.dialog.DialogBase; +import io.papermc.paper.registry.data.dialog.action.DialogAction; +import io.papermc.paper.registry.data.dialog.input.DialogInput; +import io.papermc.paper.registry.data.dialog.type.DialogType; +import java.util.List; import java.util.UUID; import java.util.concurrent.TimeUnit; +import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.event.ClickCallback; +import net.kyori.adventure.text.minimessage.MiniMessage; import org.bukkit.Location; -import org.bukkit.conversations.ConversationFactory; -import org.bukkit.conversations.NullConversationPrefix; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.data.BlockData; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.block.BlockPlaceEvent; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.PlayerInventory; -import org.bukkit.inventory.meta.ItemMeta; -import org.bukkit.plugin.Plugin; +@SuppressWarnings("UnstableApiUsage") public class LockerPlaceController implements Listener { private final PluginConfig config; - private final Plugin plugin; + private final MessageConfig messages; + private final MiniMessage miniMessage; private final LockerManager lockerManager; private final NoticeService noticeService; private final Cache lockerCreators = Caffeine.newBuilder() @@ -33,37 +45,17 @@ public class LockerPlaceController implements Listener { .build(); public LockerPlaceController( - PluginConfig config, - Plugin plugin, + PluginConfig config, MessageConfig messages, MiniMessage miniMessage, LockerManager lockerManager, NoticeService noticeService ) { this.config = config; - this.plugin = plugin; + this.messages = messages; + this.miniMessage = miniMessage; this.lockerManager = lockerManager; this.noticeService = noticeService; } - private static boolean compareMeta(ItemStack first, ItemStack second) { - ItemMeta firstMeta = first.getItemMeta(); - if (firstMeta == null) { - return false; - } - - ItemMeta secondMeta = second.getItemMeta(); - - if (secondMeta == null) { - return false; - } - - if (first.getType() != second.getType()) { - return false; - } - - return new HashSet<>(firstMeta.getLore()).containsAll(secondMeta.getLore()) - && firstMeta.getDisplayName().equals(secondMeta.getDisplayName()); - } - @EventHandler public void onBlockPlace(BlockPlaceEvent event) { Player player = event.getPlayer(); @@ -74,37 +66,76 @@ public void onBlockPlace(BlockPlaceEvent event) { ItemStack parcelLockerItem = this.config.settings.parcelLockerItem.toGuiItem().getItemStack(); - if (!compareMeta(itemInMainHand, parcelLockerItem) && !compareMeta(itemInOffHand, parcelLockerItem)) { + if (parcelLockerItem.isSimilar(itemInMainHand) && !parcelLockerItem.isSimilar(itemInOffHand)) { return; } + Block block = event.getBlockPlaced(); + Material type = block.getType(); + BlockData data = block.getBlockData(); + Location location = block.getLocation(); + event.setCancelled(true); + if (this.lockerCreators.getIfPresent(player.getUniqueId()) != null) { - event.setCancelled(true); - this.noticeService.player(player.getUniqueId(), messages -> messages.locker.alreadyCreating); + this.noticeService.create() + .player(player.getUniqueId()) + .notice(messages -> messages.locker.alreadyCreating) + .send(); return; } + this.lockerCreators.put(player.getUniqueId(), true); - ConversationFactory conversationFactory = new ConversationFactory(this.plugin) - .addConversationAbandonedListener(e -> { - if (!e.gracefulExit()) { - event.setCancelled(true); - return; - } - String description = (String) e.getContext().getSessionData("description"); - Location location = event.getBlockPlaced().getLocation(); - - this.lockerManager.create(UUID.randomUUID(), description, PositionAdapter.convert(location)); - this.lockerCreators.invalidate(player.getUniqueId()); - - this.noticeService.player(player.getUniqueId(), messages -> messages.locker.created); - }) - .withPrefix(new NullConversationPrefix()) - .withModality(false) - .withLocalEcho(false) - .withTimeout(60) - .withFirstPrompt(new LockerPlacePrompt(this.noticeService)); - - player.beginConversation(conversationFactory.buildConversation(player)); + Component promptMessage = this.miniMessage.deserialize(this.messages.locker.descriptionPrompt); // Replace with actual config message + + Dialog dialog = Dialog.create(builder -> builder.empty() + .base(DialogBase.builder(promptMessage) + .inputs(List.of( + DialogInput.text("description", this.miniMessage.deserialize("Parcel locker description")) + .build() + )) + .build() + ) + .type(DialogType.confirmation( + ActionButton.create( + this.miniMessage.deserialize("Create"), + this.miniMessage.deserialize("Click to create the locker"), + 100, + DialogAction.customClick((DialogResponseView view, Audience audience) -> { + String description = view.getText("description"); + if (description == null || description.isEmpty()) { + this.lockerCreators.invalidate(player.getUniqueId()); + return; + } + location.getWorld().getBlockAt(location).setType(type); + location.getWorld().getBlockAt(location).setBlockData(data); // ensures the block is facing correctly + this.lockerManager.create(UUID.randomUUID(), description, PositionAdapter.convert(location)); + + this.noticeService.create() + .player(player.getUniqueId()) + .notice(messages -> messages.locker.created) + .send(); + + this.lockerCreators.invalidate(player.getUniqueId()); + }, ClickCallback.Options.builder() + .uses(1) + .lifetime(ClickCallback.DEFAULT_LIFETIME) + .build()) + ), + ActionButton.create( + this.miniMessage.deserialize("Cancel"), + this.miniMessage.deserialize("Click to cancel"), + 100, + DialogAction.customClick( + (DialogResponseView view, Audience audience) -> + this.lockerCreators.invalidate(player.getUniqueId()), ClickCallback.Options.builder() + .uses(1) + .lifetime(ClickCallback.DEFAULT_LIFETIME) + .build()) + ) + )) + ); + + player.showDialog(dialog); } } diff --git a/src/main/java/com/eternalcode/parcellockers/locker/prompt/LockerPlacePrompt.java b/src/main/java/com/eternalcode/parcellockers/locker/prompt/LockerPlacePrompt.java deleted file mode 100644 index 7e3890e7..00000000 --- a/src/main/java/com/eternalcode/parcellockers/locker/prompt/LockerPlacePrompt.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.eternalcode.parcellockers.locker.prompt; - -import com.eternalcode.parcellockers.notification.NoticeService; -import org.apache.commons.lang3.StringUtils; -import org.bukkit.conversations.ConversationContext; -import org.bukkit.conversations.Prompt; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -// TODO replace with dialog api -public class LockerPlacePrompt implements Prompt { - - private final NoticeService noticeService; - - public LockerPlacePrompt(NoticeService noticeService) { - this.noticeService = noticeService; - } - - @Override - public Prompt acceptInput(@NotNull ConversationContext context, @Nullable String input) { - context.setSessionData("description", input); - return END_OF_CONVERSATION; - } - - @NotNull - @Override - public String getPromptText(@NotNull ConversationContext context) { - if (context.getForWhom() instanceof Player player) { - this.noticeService.player(player.getUniqueId(), messages -> messages.locker.descriptionPrompt); - } - return StringUtils.EMPTY; - } - - @Override - public boolean blocksForInput(@NotNull ConversationContext context) { - return true; - } -} From 7e5063a20da8e2937198f6cf25b5a075d453a186 Mon Sep 17 00:00:00 2001 From: Jakubk15 <77227023+Jakubk15@users.noreply.github.com> Date: Sat, 13 Dec 2025 19:35:20 +0100 Subject: [PATCH 9/9] Add toRawItemStack method for ConfigItem and update references --- .../parcellockers/ParcelLockersCommand.java | 2 +- .../serializable/ConfigItem.java | 23 +++++++++++++++++++ .../controller/LockerPlaceController.java | 8 +++---- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/eternalcode/parcellockers/ParcelLockersCommand.java b/src/main/java/com/eternalcode/parcellockers/ParcelLockersCommand.java index 580f64b9..8fb2f764 100644 --- a/src/main/java/com/eternalcode/parcellockers/ParcelLockersCommand.java +++ b/src/main/java/com/eternalcode/parcellockers/ParcelLockersCommand.java @@ -36,7 +36,7 @@ void reload(@Sender CommandSender sender) { @Execute(name = "get") void get(@Sender Player player) { - ItemStack lockerItem = this.config.settings.parcelLockerItem.toGuiItem().getItemStack(); + ItemStack lockerItem = this.config.settings.parcelLockerItem.toRawItemStack(); player.getInventory().addItem(lockerItem); this.noticeService.player(player.getUniqueId(), messages -> messages.locker.addedToInventory); } diff --git a/src/main/java/com/eternalcode/parcellockers/configuration/serializable/ConfigItem.java b/src/main/java/com/eternalcode/parcellockers/configuration/serializable/ConfigItem.java index 54833a4b..ef991e21 100644 --- a/src/main/java/com/eternalcode/parcellockers/configuration/serializable/ConfigItem.java +++ b/src/main/java/com/eternalcode/parcellockers/configuration/serializable/ConfigItem.java @@ -19,6 +19,7 @@ import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.inventory.ItemFlag; import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.ApiStatus; @Getter @Setter @@ -56,6 +57,28 @@ public ItemStack toItemStack() { return this.toBuilder().build(); } + /** + * By default, ConfigItem#toItemStack adds a "parcellockers-mfgui" value to the item's PersistentDataContainer + * to identify it as a GUI item. + * This method, on the other hand creates a raw ItemStack without that extra data. + */ + + @ApiStatus.Internal + public ItemStack toRawItemStack() { + ItemStack itemStack = new ItemStack(this.type); + itemStack.editMeta(meta -> { + meta.displayName(resetItalic(MINI_MESSAGE.deserialize(this.name))); + meta.lore(this.lore.stream() + .map(element -> resetItalic(MINI_MESSAGE.deserialize(element))) + .toList()); + meta.addItemFlags(ItemFlag.HIDE_ENCHANTS); + if (this.glow) { + meta.setEnchantmentGlintOverride(true); + } + }); + return itemStack; + } + @Override public ConfigItem clone() { try { diff --git a/src/main/java/com/eternalcode/parcellockers/locker/controller/LockerPlaceController.java b/src/main/java/com/eternalcode/parcellockers/locker/controller/LockerPlaceController.java index f35ef94d..c8916775 100644 --- a/src/main/java/com/eternalcode/parcellockers/locker/controller/LockerPlaceController.java +++ b/src/main/java/com/eternalcode/parcellockers/locker/controller/LockerPlaceController.java @@ -64,9 +64,9 @@ public void onBlockPlace(BlockPlaceEvent event) { ItemStack itemInMainHand = playerInventory.getItemInMainHand(); ItemStack itemInOffHand = playerInventory.getItemInOffHand(); - ItemStack parcelLockerItem = this.config.settings.parcelLockerItem.toGuiItem().getItemStack(); + ItemStack parcelLockerItem = this.config.settings.parcelLockerItem.toRawItemStack(); - if (parcelLockerItem.isSimilar(itemInMainHand) && !parcelLockerItem.isSimilar(itemInOffHand)) { + if (!parcelLockerItem.isSimilar(itemInMainHand) && !parcelLockerItem.isSimilar(itemInOffHand)) { return; } @@ -100,7 +100,7 @@ public void onBlockPlace(BlockPlaceEvent event) { ActionButton.create( this.miniMessage.deserialize("Create"), this.miniMessage.deserialize("Click to create the locker"), - 100, + 200, DialogAction.customClick((DialogResponseView view, Audience audience) -> { String description = view.getText("description"); if (description == null || description.isEmpty()) { @@ -125,7 +125,7 @@ public void onBlockPlace(BlockPlaceEvent event) { ActionButton.create( this.miniMessage.deserialize("Cancel"), this.miniMessage.deserialize("Click to cancel"), - 100, + 200, DialogAction.customClick( (DialogResponseView view, Audience audience) -> this.lockerCreators.invalidate(player.getUniqueId()), ClickCallback.Options.builder()