From 4b34f05454f1349e1747fbf3928c898dd56ae8cd Mon Sep 17 00:00:00 2001 From: Krakenied Date: Sun, 18 Aug 2024 09:03:51 +0200 Subject: Storage rework --- .../quests/common/player/QPlayer.java | 118 +++--- .../quests/common/player/QPlayerData.java | 44 +++ .../quests/common/player/QPlayerManager.java | 145 ++++---- .../player/questprogressfile/QuestProgress.java | 344 +++++++++++++---- .../questprogressfile/QuestProgressFile.java | 414 +++++++++++++-------- .../player/questprogressfile/TaskProgress.java | 151 ++++++-- .../quests/common/storage/StorageProvider.java | 82 ++-- 7 files changed, 886 insertions(+), 412 deletions(-) create mode 100644 common/src/main/java/com/leonardobishop/quests/common/player/QPlayerData.java (limited to 'common/src/main/java/com') diff --git a/common/src/main/java/com/leonardobishop/quests/common/player/QPlayer.java b/common/src/main/java/com/leonardobishop/quests/common/player/QPlayer.java index c3e38e67..bcfafdd0 100644 --- a/common/src/main/java/com/leonardobishop/quests/common/player/QPlayer.java +++ b/common/src/main/java/com/leonardobishop/quests/common/player/QPlayer.java @@ -18,29 +18,52 @@ import java.util.UUID; /** * Represents a player. */ -public class QPlayer { +public final class QPlayer { private final Quests plugin; - private final UUID uuid; - private final QPlayerPreferences playerPreferences; - private final QuestProgressFile questProgressFile; + private final QPlayerData playerData; private QuestController questController; - public QPlayer(Quests plugin, UUID uuid, QPlayerPreferences playerPreferences, QuestProgressFile questProgressFile, QuestController questController) { + public QPlayer(final @NotNull Quests plugin, final @NotNull QPlayerData playerData, final @NotNull QuestController questController) { this.plugin = plugin; - this.uuid = uuid; - this.playerPreferences = playerPreferences; - this.questProgressFile = questProgressFile; + this.playerData = playerData; this.questController = questController; } + /** + * Get this players associated {@link QPlayerData} + * + * @return the players data + */ + public @NotNull QPlayerData getPlayerData() { + return this.playerData; + } + /** * Get the player UUID associated with this quest player. The player may not be online. * * @return uuid */ public @NotNull UUID getPlayerUUID() { - return this.uuid; + return this.playerData.playerUUID(); + } + + /** + * Get this players associated {@link QPlayerPreferences} + * + * @return the players preferences + */ + public @NotNull QPlayerPreferences getPlayerPreferences() { + return this.playerData.playerPreferences(); + } + + /** + * Get this players associated {@link QuestProgressFile} + * + * @return the quest progress file + */ + public @NotNull QuestProgressFile getQuestProgressFile() { + return this.playerData.questProgressFile(); } /** @@ -50,31 +73,32 @@ public class QPlayer { * @param quest the quest to complete * @return true (always) */ - public boolean completeQuest(@NotNull Quest quest) { + @SuppressWarnings("UnusedReturnValue") + public boolean completeQuest(final @NotNull Quest quest) { Objects.requireNonNull(quest, "quest cannot be null"); - return questController.completeQuestForPlayer(this, quest); + return this.questController.completeQuestForPlayer(this, quest); } /** * Attempt to track a quest for the player. This will also play all effects (such as titles, messages etc.) - ** + * * @param quest the quest to track */ - public void trackQuest(@Nullable Quest quest) { - questController.trackQuestForPlayer(this, quest); + public void trackQuest(final @Nullable Quest quest) { + this.questController.trackQuestForPlayer(this, quest); } /** - * Gets whether or not the player has started a specific quest. + * Gets whether the player has started a specific quest. * * @param quest the quest to test for * @return true if the quest is started or quest autostart is enabled and the quest is ready to start, false otherwise */ - public boolean hasStartedQuest(@NotNull Quest quest) { + public boolean hasStartedQuest(final @NotNull Quest quest) { Objects.requireNonNull(quest, "quest cannot be null"); - return questController.hasPlayerStartedQuest(this, quest); + return this.questController.hasPlayerStartedQuest(this, quest); } /** @@ -129,17 +153,16 @@ public class QPlayer { /** * Attempt to start a quest for the player. This will also play all effects (such as titles, messages etc.) - * * Warning: will fail if the player is not online. * * @param quest the quest to start * @return the quest start result -- {@code QuestStartResult.QUEST_SUCCESS} indicates success */ // TODO PlaceholderAPI support - public @NotNull QuestStartResult startQuest(@NotNull Quest quest) { + public @NotNull QuestStartResult startQuest(final @NotNull Quest quest) { Objects.requireNonNull(quest, "quest cannot be null"); - return questController.startQuestForPlayer(this, quest); + return this.questController.startQuestForPlayer(this, quest); } /** @@ -148,10 +171,10 @@ public class QPlayer { * @param quest the quest to start * @return true if the quest was cancelled, false otherwise */ - public boolean cancelQuest(@NotNull Quest quest) { + public boolean cancelQuest(final @NotNull Quest quest) { Objects.requireNonNull(quest, "quest cannot be null"); - return questController.cancelQuestForPlayer(this, quest); + return this.questController.cancelQuestForPlayer(this, quest); } /** @@ -160,52 +183,34 @@ public class QPlayer { * @param quest the quest to start * @return true if the quest was expired, false otherwise */ - public boolean expireQuest(@NotNull Quest quest) { + @SuppressWarnings("UnusedReturnValue") + public boolean expireQuest(final @NotNull Quest quest) { Objects.requireNonNull(quest, "quest cannot be null"); - return questController.expireQuestForPlayer(this, quest); + return this.questController.expireQuestForPlayer(this, quest); } /** * Check if the player can start a quest. - * * Warning: will fail if the player is not online. * * @param quest the quest to check * @return the quest start result */ - public @NotNull QuestStartResult canStartQuest(@NotNull Quest quest) { + public @NotNull QuestStartResult canStartQuest(final @NotNull Quest quest) { Objects.requireNonNull(quest, "quest cannot be null"); - return questController.canPlayerStartQuest(this, quest); + return this.questController.canPlayerStartQuest(this, quest); } /** - * Get this players associated {@link QuestProgressFile} - * - * @return the quest progress file - */ - public @NotNull QuestProgressFile getQuestProgressFile() { - return questProgressFile; - } - - /** - * Get this players associated {@link QPlayerPreferences} - * - * @return the players preferences - */ - public @NotNull QPlayerPreferences getPlayerPreferences() { - return playerPreferences; - } - - /** - * Get this players associated {@link QuestController}, usually the servers active quest controller + * Get player's associated {@link QuestController}. It's usually the server's active quest controller. * * @see QPlayerManager#getActiveQuestController() * @return the quest controller for this player */ public @NotNull QuestController getQuestController() { - return questController; + return this.questController; } /** @@ -213,21 +218,24 @@ public class QPlayer { * * @param questController new quest controller */ - public void setQuestController(@NotNull QuestController questController) { + public void setQuestController(final @NotNull QuestController questController) { Objects.requireNonNull(questController, "questController cannot be null"); this.questController = questController; } - @Override //Used by java GC - public boolean equals(Object o) { - if (!(o instanceof QPlayer)) return false; - QPlayer qPlayer = (QPlayer) o; - return this.uuid == qPlayer.getPlayerUUID(); + @Override + public boolean equals(final @Nullable Object o) { + if (o instanceof final QPlayer qPlayer) { + return this.getPlayerUUID() == qPlayer.getPlayerUUID(); + } else { + return false; + } } - @Override //Used by java GC + @Override public int hashCode() { - return uuid.hashCode() * 73; //uuid hash * prime number + // uuid hash * prime number + return this.getPlayerUUID().hashCode() * 73; } } diff --git a/common/src/main/java/com/leonardobishop/quests/common/player/QPlayerData.java b/common/src/main/java/com/leonardobishop/quests/common/player/QPlayerData.java new file mode 100644 index 00000000..cdabd385 --- /dev/null +++ b/common/src/main/java/com/leonardobishop/quests/common/player/QPlayerData.java @@ -0,0 +1,44 @@ +package com.leonardobishop.quests.common.player; + +import com.leonardobishop.quests.common.player.questprogressfile.QuestProgressFile; +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; +import java.util.UUID; + +public final class QPlayerData { + + private final UUID playerUUID; + private final QPlayerPreferences playerPreferences; + private final QuestProgressFile questProgressFile; + + public QPlayerData(final @NotNull UUID playerUUID, final @NotNull QPlayerPreferences playerPreferences, final @NotNull QuestProgressFile questProgressFile) { + this.playerUUID = Objects.requireNonNull(playerUUID, "playerUUID cannot be null"); + this.playerPreferences = Objects.requireNonNull(playerPreferences, "playerPreferences cannot be null"); + this.questProgressFile = Objects.requireNonNull(questProgressFile, "questProgressFile cannot be null"); + } + + public QPlayerData(final @NotNull QPlayerData playerData) { + Objects.requireNonNull(playerData, "playerData cannot be null"); + + this.playerUUID = playerData.playerUUID; + this.playerPreferences = playerData.playerPreferences; + this.questProgressFile = new QuestProgressFile(playerData.questProgressFile); + } + + public @NotNull UUID playerUUID() { + return this.playerUUID; + } + + public @NotNull QPlayerPreferences playerPreferences() { + return this.playerPreferences; + } + + public @NotNull QuestProgressFile questProgressFile() { + return this.questProgressFile; + } + + public void setModified(final boolean modified) { + this.questProgressFile.setModified(modified); + } +} diff --git a/common/src/main/java/com/leonardobishop/quests/common/player/QPlayerManager.java b/common/src/main/java/com/leonardobishop/quests/common/player/QPlayerManager.java index 1475c44a..4c3704de 100644 --- a/common/src/main/java/com/leonardobishop/quests/common/player/QPlayerManager.java +++ b/common/src/main/java/com/leonardobishop/quests/common/player/QPlayerManager.java @@ -6,6 +6,7 @@ import com.leonardobishop.quests.common.questcontroller.QuestController; import com.leonardobishop.quests.common.storage.StorageProvider; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.UnmodifiableView; import java.util.Collection; import java.util.Collections; @@ -19,17 +20,18 @@ import java.util.concurrent.ConcurrentHashMap; * The QPlayerManager is responsible for keeping a reference to all players on the server and is used to * obtain an instance of a player, load new players and save current players. */ -public class QPlayerManager { +public final class QPlayerManager { - private final Map qPlayers = new ConcurrentHashMap<>(); private final Quests plugin; private final StorageProvider storageProvider; + private final Map qPlayerMap; private QuestController activeQuestController; - public QPlayerManager(Quests plugin, StorageProvider storageProvider, QuestController questController) { - this.plugin = plugin; - this.storageProvider = storageProvider; - this.activeQuestController = questController; + public QPlayerManager(final @NotNull Quests plugin, final @NotNull StorageProvider storageProvider, final @NotNull QuestController questController) { + this.plugin = Objects.requireNonNull(plugin, "plugin cannot be null"); + this.storageProvider = Objects.requireNonNull(storageProvider, "storageProvider cannot be null"); + this.activeQuestController = Objects.requireNonNull(questController, "questController cannot be null"); + this.qPlayerMap = new ConcurrentHashMap<>(); } /** @@ -38,7 +40,7 @@ public class QPlayerManager { * @param uuid the uuid * @return {@link QPlayer} if they are loaded, otherwise null */ - public @Nullable QPlayer getPlayer(@NotNull UUID uuid) { + public @Nullable QPlayer getPlayer(final @NotNull UUID uuid) { Objects.requireNonNull(uuid, "uuid cannot be null"); // QPlayer qPlayer = qPlayers.get(uuid); @@ -48,7 +50,7 @@ public class QPlayerManager { // Thread.dumpStack(); // } // } - return qPlayers.get(uuid); + return this.qPlayerMap.get(uuid); } /** @@ -56,12 +58,12 @@ public class QPlayerManager { * * @param uuid the uuid of the player */ - public void removePlayer(@NotNull UUID uuid) { + public void removePlayer(final @NotNull UUID uuid) { Objects.requireNonNull(uuid, "uuid cannot be null"); - plugin.getQuestsLogger().debug("Unloading and saving player " + uuid + "..."); - CompletableFuture future = savePlayer(uuid); - future.thenAccept((v) -> qPlayers.remove(uuid)); + this.plugin.getQuestsLogger().debug("Unloading and saving player " + uuid + "..."); + final CompletableFuture future = this.savePlayer(uuid); + future.thenAccept(unused -> this.qPlayerMap.remove(uuid)); } /** @@ -71,32 +73,30 @@ public class QPlayerManager { * @param uuid the uuid of the player * @return completable future */ - public CompletableFuture savePlayer(@NotNull UUID uuid) { + public @NotNull CompletableFuture savePlayer(final @NotNull UUID uuid) { Objects.requireNonNull(uuid, "uuid cannot be null"); - QPlayer qPlayer = getPlayer(uuid); - if (qPlayer == null) return CompletableFuture.completedFuture(null); - return savePlayer(uuid, qPlayer.getQuestProgressFile()); + final QPlayer qPlayer = this.getPlayer(uuid); + if (qPlayer == null) { + return CompletableFuture.completedFuture(null); + } + + return this.savePlayer(qPlayer.getPlayerData()); } /** * Schedules a save for the player with a specified {@link QuestProgressFile}. The modified status of the * specified progress file will be reset. - * - * @param uuid the uuid of the player - * @param originalProgressFile the quest progress file to associate with and save - * @return completable future */ - public CompletableFuture savePlayer(@NotNull UUID uuid, @NotNull QuestProgressFile originalProgressFile) { - Objects.requireNonNull(uuid, "uuid cannot be null"); - Objects.requireNonNull(originalProgressFile, "originalProgressFile cannot be null"); + public @NotNull CompletableFuture savePlayer(final @NotNull QPlayerData playerData) { + Objects.requireNonNull(playerData, "playerData cannot be null"); - CompletableFuture future = new CompletableFuture<>(); + final CompletableFuture future = new CompletableFuture<>(); + final QPlayerData clonedPlayerData = new QPlayerData(playerData); + playerData.setModified(false); - QuestProgressFile clonedProgressFile = new QuestProgressFile(originalProgressFile); - originalProgressFile.resetModified(); - plugin.getScheduler().doAsync(() -> { - save(uuid, clonedProgressFile); + this.plugin.getScheduler().doAsync(() -> { + this.save(clonedPlayerData); future.complete(null); }); @@ -109,34 +109,35 @@ public class QPlayerManager { * * @param uuid the uuid of the player */ - public void savePlayerSync(@NotNull UUID uuid) { + public void savePlayerSync(final @NotNull UUID uuid) { Objects.requireNonNull(uuid, "uuid cannot be null"); - QPlayer qPlayer = getPlayer(uuid); - if (qPlayer == null) return; - savePlayerSync(uuid, qPlayer.getQuestProgressFile()); + final QPlayer qPlayer = this.getPlayer(uuid); + if (qPlayer == null) { + return; + } + + this.savePlayerSync(qPlayer.getPlayerData()); } /** * Immediately saves the player with a specified {@link QuestProgressFile}, on the same thread. The modified status * of the specified progress file is not changed. - * - * @param uuid the uuid of the player - * @param questProgressFile the quest progress file to associate with and save */ - public void savePlayerSync(@NotNull UUID uuid, @NotNull QuestProgressFile questProgressFile) { - save(uuid, questProgressFile); + public void savePlayerSync(final @NotNull QPlayerData playerData) { + this.save(playerData); } - private void save(@NotNull UUID uuid, @NotNull QuestProgressFile questProgressFile) { - Objects.requireNonNull(uuid, "uuid cannot be null"); - Objects.requireNonNull(questProgressFile, "questProgressFile cannot be null"); + private void save(@NotNull QPlayerData playerData) { + Objects.requireNonNull(playerData, "playerData cannot be null"); + + final String uuidString = playerData.playerUUID().toString(); + this.plugin.getQuestsLogger().debug("Saving player " + uuidString + "..."); - plugin.getQuestsLogger().debug("Saving player " + uuid + "..."); - if (storageProvider.saveProgressFile(uuid, questProgressFile)) { - plugin.getQuestsLogger().debug("Quest progress file saved for player " + uuid + "."); + if (this.storageProvider.savePlayerData(playerData)) { + this.plugin.getQuestsLogger().debug("Quest progress file saved for player " + uuidString + "."); } else { - plugin.getQuestsLogger().severe("Failed to save player " + uuid + "!"); + this.plugin.getQuestsLogger().severe("Failed to save player " + uuidString + "!"); } } @@ -145,20 +146,21 @@ public class QPlayerManager { * * @param uuid the uuid of the player */ - public void dropPlayer(@NotNull UUID uuid) { + public void dropPlayer(final @NotNull UUID uuid) { Objects.requireNonNull(uuid, "uuid cannot be null"); - plugin.getQuestsLogger().debug("Dropping player " + uuid + "."); - qPlayers.remove(uuid); + this.plugin.getQuestsLogger().debug("Dropping player " + uuid + "."); + this.qPlayerMap.remove(uuid); } /** * Gets all QPlayers loaded on the server * - * @return immutable map of quest players + * @return immutable collection of quest players */ - public Collection getQPlayers() { - return Collections.unmodifiableCollection(qPlayers.values()); + @UnmodifiableView + public @NotNull Collection getQPlayers() { + return Collections.unmodifiableCollection(this.qPlayerMap.values()); } /** @@ -168,20 +170,26 @@ public class QPlayerManager { * @param uuid the uuid of the player * @return completable future with the loaded player, or null if there was an error */ - public CompletableFuture loadPlayer(UUID uuid) { - plugin.getQuestsLogger().debug("Loading player " + uuid + "..."); - - CompletableFuture future = new CompletableFuture<>(); - plugin.getScheduler().doAsync(() -> { - QuestProgressFile questProgressFile = storageProvider.loadProgressFile(uuid); - if (questProgressFile == null) { - plugin.getQuestsLogger().debug("A problem occurred trying loading player " + uuid + "; quest progress file is null."); + public @NotNull CompletableFuture loadPlayer(final @NotNull UUID uuid) { + Objects.requireNonNull(uuid, "uuid cannot be null"); + + final String uuidString = uuid.toString(); + this.plugin.getQuestsLogger().debug("Loading player " + uuidString + "..."); + final CompletableFuture future = new CompletableFuture<>(); + + this.plugin.getScheduler().doAsync(() -> { + final QPlayerData playerData = this.storageProvider.loadPlayerData(uuid); + + if (playerData == null) { + this.plugin.getQuestsLogger().debug("A problem occurred trying loading player " + uuidString + "; quest progress file is null."); future.complete(null); return; } - QPlayer qPlayer = new QPlayer(plugin, uuid, new QPlayerPreferences(null), questProgressFile, activeQuestController); - qPlayers.computeIfAbsent(uuid, s -> qPlayer); - plugin.getQuestsLogger().debug("Quest progress file loaded for player " + uuid + "."); + + final QPlayer qPlayer = new QPlayer(this.plugin, playerData, this.activeQuestController); + this.qPlayerMap.putIfAbsent(uuid, qPlayer); + + this.plugin.getQuestsLogger().debug("Quest progress file loaded for player " + uuidString + "."); future.complete(qPlayer); }); @@ -193,17 +201,18 @@ public class QPlayerManager { * * @return {@link StorageProvider} */ - public StorageProvider getStorageProvider() { - return storageProvider; + public @NotNull StorageProvider getStorageProvider() { + return this.storageProvider; } - public QuestController getActiveQuestController() { - return activeQuestController; + public @NotNull QuestController getActiveQuestController() { + return this.activeQuestController; } - public void setActiveQuestController(QuestController activeQuestController) { - this.activeQuestController = activeQuestController; - for (QPlayer qPlayer : qPlayers.values()) { + public void setActiveQuestController(final @NotNull QuestController activeQuestController) { + this.activeQuestController = Objects.requireNonNull(activeQuestController, "activeQuestController cannot be null"); + + for (final QPlayer qPlayer : this.qPlayerMap.values()) { qPlayer.setQuestController(activeQuestController); } } diff --git a/common/src/main/java/com/leonardobishop/quests/common/player/questprogressfile/QuestProgress.java b/common/src/main/java/com/leonardobishop/quests/common/player/questprogressfile/QuestProgress.java index 8e51d769..d8506b83 100644 --- a/common/src/main/java/com/leonardobishop/quests/common/player/questprogressfile/QuestProgress.java +++ b/common/src/main/java/com/leonardobishop/quests/common/player/questprogressfile/QuestProgress.java @@ -1,19 +1,23 @@ package com.leonardobishop.quests.common.player.questprogressfile; import com.leonardobishop.quests.common.plugin.Quests; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.Collection; import java.util.HashMap; import java.util.Map; +import java.util.Set; import java.util.UUID; -public class QuestProgress { +public final class QuestProgress { private final Quests plugin; - - private final Map taskProgress = new HashMap<>(); - private final String questid; - private final UUID player; + private final String questId; + private final UUID playerUUID; + private final Map taskProgressMap; private boolean started; private long startedDate; @@ -22,29 +26,66 @@ public class QuestProgress { private long completionDate; private boolean modified; - public QuestProgress(Quests plugin, String questid, boolean completed, boolean completedBefore, long completionDate, UUID player, boolean started, long startedDate) { + /** + * Constructs a QuestProgress. + * + * @param plugin the plugin instance + * @param questId the associated quest ID + * @param playerUUID the associated player UUID + * @param started whether the quest is started + * @param startedDate the date of the last quest start + * @param completed whether the quest is completed + * @param completedBefore whether the quest has been completed before + * @param completionDate the date of the last quest completion + * @param modified whether the object has been modified and needs to be saved + */ + public QuestProgress(final @NotNull Quests plugin, final @NotNull String questId, final @NotNull UUID playerUUID, final boolean started, final long startedDate, final boolean completed, final boolean completedBefore, final long completionDate, final boolean modified) { this.plugin = plugin; - this.questid = questid; + this.questId = questId; + this.playerUUID = playerUUID; + this.taskProgressMap = new HashMap<>(); + this.started = started; + this.startedDate = startedDate; this.completed = completed; this.completedBefore = completedBefore; this.completionDate = completionDate; - this.player = player; - this.started = started; - this.startedDate = startedDate; + this.modified = modified; } - public QuestProgress(Quests plugin, String questid, boolean completed, boolean completedBefore, long completionDate, UUID player, boolean started, long startedDate, boolean modified) { - this(plugin, questid, completed, completedBefore, completionDate, player, started, startedDate); - this.modified = modified; + /** + * Constructs a QuestProgress with {@link QuestProgress#modified} set to {@code false}. + * + * @param plugin the plugin instance + * @param questId the associated quest ID + * @param playerUUID the associated player UUID + * @param started whether the quest is started + * @param startedDate the date of the last quest start + * @param completed whether the quest is completed + * @param completedBefore whether the quest has been completed before + * @param completionDate the date of the last quest completion + */ + public QuestProgress(final @NotNull Quests plugin, final @NotNull String questId, final @NotNull UUID playerUUID, final boolean started, final long startedDate, final boolean completed, final boolean completedBefore, final long completionDate) { + this(plugin, questId, playerUUID, started, startedDate, completed, completedBefore, completionDate, false); } - public QuestProgress(QuestProgress questProgress) { + /** + * Constructs a data-only clone from a QuestProgress instance. + * + * @param questProgress the quest progress instance + */ + @ApiStatus.Internal + public QuestProgress(final @NotNull QuestProgress questProgress) { + final Set> progressEntries = questProgress.taskProgressMap.entrySet(); + this.plugin = questProgress.plugin; - for (Map.Entry progressEntry : questProgress.taskProgress.entrySet()) { - taskProgress.put(progressEntry.getKey(), new TaskProgress(progressEntry.getValue())); + this.questId = questProgress.questId; + this.playerUUID = questProgress.playerUUID; + this.taskProgressMap = new HashMap<>(progressEntries.size()); + + for (final Map.Entry progressEntry : progressEntries) { + this.taskProgressMap.put(progressEntry.getKey(), new TaskProgress(progressEntry.getValue())); } - this.questid = questProgress.questid; - this.player = questProgress.player; + this.started = questProgress.started; this.startedDate = questProgress.startedDate; this.completed = questProgress.completed; @@ -53,120 +94,259 @@ public class QuestProgress { this.modified = questProgress.modified; } - public String getQuestId() { - return questid; + /** + * @return the associated quest ID + */ + @Contract(pure = true) + public @NotNull String getQuestId() { + return this.questId; } - public boolean isCompleted() { - return completed; + /** + * @return the associated player ID + * @see QuestProgress#getPlayerUUID() + */ + @Deprecated(forRemoval = true) + @Contract(pure = true) + public @NotNull UUID getPlayer() { + return this.playerUUID; } - public void setCompleted(boolean completed) { - this.completed = completed; - this.modified = true; + /** + * @return the associated player ID + */ + @Contract(pure = true) + public @NotNull UUID getPlayerUUID() { + return this.playerUUID; + } + + /** + * @return mutable task progress map + */ + @Contract(pure = true) + public @NotNull Map getTaskProgressMap() { + return this.taskProgressMap; } + /** + * @return mutable task progress map values collection + * @see QuestProgress#getTaskProgress() + */ + @Deprecated(forRemoval = true) + @Contract(pure = true) + public @NotNull Collection getTaskProgress() { + return this.taskProgressMap.values(); + } + + /** + * @return mutable task progress map values collection + */ + @SuppressWarnings("unused") + @Contract(pure = true) + public @NotNull Collection getAllTaskProgress() { + return this.taskProgressMap.values(); + } + + /** + * Gets the {@link TaskProgress} for a specified task ID. Generates a new one if it does not exist. + * + * @param taskId the task ID to get the progress for + * @return {@link TaskProgress} or a blank generated one if the task does not exist + */ + public @NotNull TaskProgress getTaskProgress(final @NotNull String taskId) { + final TaskProgress taskProgress = this.taskProgressMap.get(taskId); + if (taskProgress != null) { + return taskProgress; + } + + final TaskProgress newTaskProgress = new TaskProgress(this, taskId, this.playerUUID, null, false, false); + this.addTaskProgress(newTaskProgress); + return newTaskProgress; + } + + /** + * Gets the {@link TaskProgress} for a specified task ID. Returns null if it does not exist. + * + * @param taskId the task ID to get the progress for + * @return {@link TaskProgress} or null if the task does not exist + */ + @SuppressWarnings("unused") + @Contract(pure = true) + public @Nullable TaskProgress getTaskProgressOrNull(final @NotNull String taskId) { + return this.taskProgressMap.get(taskId); + } + + /** + * @param taskId the task ID to repair the progress for + */ + @Deprecated(forRemoval = true) + @ApiStatus.Internal + public void repairTaskProgress(final @NotNull String taskId) { + final TaskProgress taskProgress = new TaskProgress(this, taskId, this.playerUUID, null, false, false); + this.addTaskProgress(taskProgress); + } + + /** + * @param taskProgress the task progress to put into the task progress map + */ + public void addTaskProgress(final @NotNull TaskProgress taskProgress) { + this.taskProgressMap.put(taskProgress.getTaskId(), taskProgress); + } + + /** + * @return whether the quest is started + */ + @Contract(pure = true) public boolean isStarted() { - return started; + return this.started; } - public void setStarted(boolean started) { + /** + * @param started whether the quest is started + */ + public void setStarted(final boolean started) { this.started = started; this.modified = true; } + /** + * @return the date of the last quest start + */ + @Contract(pure = true) public long getStartedDate() { - return startedDate; + return this.startedDate; } - public void setStartedDate(long startedDate) { + /** + * @param startedDate the date of the last quest start + */ + public void setStartedDate(final long startedDate) { this.startedDate = startedDate; this.modified = true; } - public long getCompletionDate() { - return completionDate; + /** + * @return whether the quest is completed + */ + @Contract(pure = true) + public boolean isCompleted() { + return this.completed; } - public void setCompletionDate(long completionDate) { - this.completionDate = completionDate; + /** + * @param completed whether the quest is completed + */ + public void setCompleted(final boolean completed) { + this.completed = completed; this.modified = true; } - public UUID getPlayer() { - return player; - } - + /** + * @return whether the quest has been completed before + */ + @Contract(pure = true) public boolean isCompletedBefore() { - return completedBefore; + return this.completedBefore; } - public void setCompletedBefore(boolean completedBefore) { + /** + * @param completedBefore whether the quest has been completed before + */ + public void setCompletedBefore(final boolean completedBefore) { this.completedBefore = completedBefore; this.modified = true; } - public void addTaskProgress(TaskProgress taskProgress) { - this.taskProgress.put(taskProgress.getTaskId(), taskProgress); + /** + * @return the date of the last quest completion + */ + @Contract(pure = true) + public long getCompletionDate() { + return this.completionDate; } - public Collection getTaskProgress() { - return taskProgress.values(); + /** + * @param completionDate the date of the last quest completion + */ + public void setCompletionDate(final long completionDate) { + this.completionDate = completionDate; + this.modified = true; } - public Map getTaskProgressMap() { - return taskProgress; - } + /** + * @return whether the object has been modified and needs to be saved + */ + @SuppressWarnings("unused") + @Contract(pure = true) + public boolean isModified() { + if (this.modified) { + return true; + } - public TaskProgress getTaskProgress(String taskId) { - TaskProgress tP = taskProgress.getOrDefault(taskId, null); - if (tP == null) { - repairTaskProgress(taskId); - tP = taskProgress.getOrDefault(taskId, null); + for (final TaskProgress taskProgress : this.taskProgressMap.values()) { + if (taskProgress.isModified()) { + return true; + } } - return tP; + + return false; } - public void repairTaskProgress(String taskid) { - TaskProgress taskProgress = new TaskProgress(this, taskid, null, player, false, false); - this.addTaskProgress(taskProgress); + /** + * It's equivalent to {@code QuestProgress#setModified(false)}. + * + * @see QuestProgress#setModified(boolean) + */ + @Deprecated(forRemoval = true) + public void resetModified() { + this.setModified(false); } - public boolean isModified() { - if (modified) return true; - else { - for (TaskProgress progress : this.taskProgress.values()) { - if (progress.isModified()) return true; - } - return false; + /** + * @param modified whether the object has been modified and needs to be saved + */ + public void setModified(final boolean modified) { + this.modified = modified; + + for (final TaskProgress taskProgress : this.taskProgressMap.values()) { + taskProgress.setModified(modified); } } + /** + * Gets whether the object has non default values. + * + *

+ * Fields checked are:
+ * - {@link QuestProgress#started}
+ * - {@link QuestProgress#startedDate}
+ * - {@link QuestProgress#completed}
+ * - {@link QuestProgress#completedBefore}
+ * - {@link QuestProgress#completionDate} + *

+ * + * @return whether the object has non default values + */ + @Contract(pure = true) public boolean hasNonDefaultValues() { - if (this.started || this.startedDate != 0 || this.completed || this.completedBefore || this.completionDate != 0) return true; - else { - for (TaskProgress progress : this.taskProgress.values()) { - if (progress.getProgress() != null || progress.isCompleted()) return true; - } - return false; + if (this.started || this.startedDate != 0L || this.completed || this.completedBefore || this.completionDate != 0L) { + return true; } - } - public void queueForCompletionTest() { - plugin.getQuestCompleter().queueSingular(this); - } - - public void resetModified() { - this.modified = false; - for (TaskProgress progress : this.taskProgress.values()) { - progress.resetModified(); + for (final TaskProgress taskProgress : this.taskProgressMap.values()) { + if (taskProgress.getProgress() != null || taskProgress.isCompleted()) { + return true; + } } + + return false; } - public void setModified(boolean modified) { - this.modified = modified; - for (TaskProgress progress : this.taskProgress.values()) { - progress.setModified(modified); - } + /** + * Queues the {@link QuestProgress} instance for a completion test. + */ + @SuppressWarnings("unused") + public void queueForCompletionTest() { + this.plugin.getQuestCompleter().queueSingular(this); } } diff --git a/common/src/main/java/com/leonardobishop/quests/common/player/questprogressfile/QuestProgressFile.java b/common/src/main/java/com/leonardobishop/quests/common/player/questprogressfile/QuestProgressFile.java index 0069eb12..7cadaa9a 100644 --- a/common/src/main/java/com/leonardobishop/quests/common/player/questprogressfile/QuestProgressFile.java +++ b/common/src/main/java/com/leonardobishop/quests/common/player/questprogressfile/QuestProgressFile.java @@ -4,62 +4,80 @@ import com.leonardobishop.quests.common.player.QPlayer; import com.leonardobishop.quests.common.plugin.Quests; import com.leonardobishop.quests.common.quest.Quest; import com.leonardobishop.quests.common.quest.Task; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.UUID; import java.util.concurrent.TimeUnit; /** * Represents underlying quest progress for a player. */ -public class QuestProgressFile { +public final class QuestProgressFile { - private final Map questProgress = new HashMap<>(); - private final UUID playerUUID; private final Quests plugin; + private final UUID playerUUID; + private final Map questProgressMap; - public QuestProgressFile(UUID playerUUID, Quests plugin) { - this.playerUUID = playerUUID; + /** + * Constructs a QuestProgressFile. + * + * @param plugin the plugin instance + * @param playerUUID the associated player UUID + */ + public QuestProgressFile(final @NotNull Quests plugin, final @NotNull UUID playerUUID) { this.plugin = plugin; + this.playerUUID = playerUUID; + this.questProgressMap = new HashMap<>(); } - public QuestProgressFile(QuestProgressFile questProgressFile) { - for (Map.Entry progressEntry : questProgressFile.questProgress.entrySet()) { - questProgress.put(progressEntry.getKey(), new QuestProgress(progressEntry.getValue())); - } - this.playerUUID = questProgressFile.playerUUID; + /** + * Constructs a data-only clone from a QuestProgressFile instance. + * + * @param questProgressFile the quest progress file instance + */ + @ApiStatus.Internal + public QuestProgressFile(final @NotNull QuestProgressFile questProgressFile) { + final Set> progressEntries = questProgressFile.questProgressMap.entrySet(); + this.plugin = questProgressFile.plugin; + this.playerUUID = questProgressFile.playerUUID; + this.questProgressMap = new HashMap<>(progressEntries.size()); + + for (final Map.Entry progressEntry : progressEntries) { + this.questProgressMap.put(progressEntry.getKey(), new QuestProgress(progressEntry.getValue())); + } } - public void addQuestProgress(QuestProgress questProgress) { - //TODO don't do here -// if (Options.VERIFY_QUEST_EXISTS_ON_LOAD.getBooleanValue(true) && plugin.getQuestManager().getQuestById(questProgress.getQuestId()) == null) { -// return; -// } - this.questProgress.put(questProgress.getQuestId(), questProgress); + /** + * @param questProgress the quest progress to put into the quest progress map + */ + public void addQuestProgress(final @NotNull QuestProgress questProgress) { + // TODO don't do that here + //if (Options.VERIFY_QUEST_EXISTS_ON_LOAD.getBooleanValue(true) && plugin.getQuestManager().getQuestById(questProgress.getQuestId()) == null) { + // return; + //} + this.questProgressMap.put(questProgress.getQuestId(), questProgress); } /** - * Gets all manually started quests. - * Note: if quest autostart is enabled then this may produce unexpected results as quests are - * not "started" by the player if autostart is true. Consider {@link QPlayer#hasStartedQuest(Quest)} - * or {@link QPlayer#getEffectiveStartedQuests()} instead. + * Gets all manually started quests. If quest autostart is enabled then this may produce unexpected results as + * quests are not "started" by the player if autostart is true. Consider {@link QPlayer#hasStartedQuest(Quest)} + * or {@link QPlayer#getEffectiveStartedQuests()} usage instead. * * @return list of started quests */ - public List getStartedQuests() { - List startedQuests = new ArrayList<>(); - for (QuestProgress questProgress : questProgress.values()) { - Quest quest = plugin.getQuestManager().getQuestById(questProgress.getQuestId()); - if (quest != null && questProgress.isStarted()) { - startedQuests.add(plugin.getQuestManager().getQuestById(questProgress.getQuestId())); - } - } - return startedQuests; + @Contract(pure = true) + public @NotNull List getStartedQuests() { + return this.getAllQuestsFromProgress(QuestsProgressFilter.STARTED); } /** @@ -68,50 +86,86 @@ public class QuestProgressFile { * * @return {@code List} all quests */ - public List getAllQuestsFromProgress(QuestsProgressFilter filter) { - List questsProgress = new ArrayList<>(); - for (QuestProgress qProgress : questProgress.values()) { - boolean condition = false; - if (filter == QuestsProgressFilter.STARTED) { - condition = qProgress.isStarted(); - } else if (filter == QuestsProgressFilter.COMPLETED_BEFORE) { - condition = qProgress.isCompletedBefore(); - } else if (filter == QuestsProgressFilter.COMPLETED) { - condition = qProgress.isCompleted(); - } else if (filter == QuestsProgressFilter.ALL) { - condition = true; + @Contract(pure = true) + public @NotNull List getAllQuestsFromProgress(final @NotNull QuestsProgressFilter filter) { + final List quests = new ArrayList<>(); + + for (final QuestProgress questProgress : this.questProgressMap.values()) { + final boolean matches = filter.matches(questProgress); + if (!matches) { + continue; } - if (condition) { - Quest quest = plugin.getQuestManager().getQuestById(qProgress.getQuestId()); - if (quest != null) { - questsProgress.add(quest); - } + + final Quest quest = this.plugin.getQuestManager().getQuestById(questProgress.getQuestId()); + if (quest == null) { + continue; } + + quests.add(quest); } - return questsProgress; + + return quests; } public enum QuestsProgressFilter { - ALL("all"), - COMPLETED("completed"), - COMPLETED_BEFORE("completedBefore"), - STARTED("started"); + ALL("all") { + @Override + @Contract(pure = true) + public boolean matches(final @NotNull QuestProgress questProgress) { + return true; + } + }, + COMPLETED("completed") { + @Override + @Contract(pure = true) + public boolean matches(final @NotNull QuestProgress questProgress) { + return questProgress.isCompleted(); + } + }, + COMPLETED_BEFORE("completedBefore") { + @Override + @Contract(pure = true) + public boolean matches(final @NotNull QuestProgress questProgress) { + return questProgress.isCompletedBefore(); + } + }, + STARTED("started") { + @Override + @Contract(pure = true) + public boolean matches(final @NotNull QuestProgress questProgress) { + return questProgress.isStarted(); + } + }; private final String legacy; - QuestsProgressFilter(String legacy) { + QuestsProgressFilter(final @NotNull String legacy) { this.legacy = legacy; } - public static QuestsProgressFilter fromLegacy(String filter) { - for (QuestsProgressFilter filterEnum : QuestsProgressFilter.values()) { - if (filterEnum.getLegacy().equals(filter)) return filterEnum; - } - return QuestsProgressFilter.ALL; + @SuppressWarnings("unused") + @Contract(pure = true) + public @NotNull String getLegacy() { + return this.legacy; } - public String getLegacy() { - return legacy; + @Contract(pure = true) + public abstract boolean matches(final @NotNull QuestProgress questProgress); + + // And some static things to improve legacy performance (is it even used?) + + private static final QuestsProgressFilter[] FILTERS = QuestsProgressFilter.values(); + + private static final Map legacyToFilterMap = new HashMap<>(QuestsProgressFilter.FILTERS.length) {{ + for (final QuestsProgressFilter questsProgressFilter : QuestsProgressFilter.FILTERS) { + this.put(questsProgressFilter.legacy, questsProgressFilter); + } + }}; + + @SuppressWarnings("unused") + @Contract(pure = true) + public static @NotNull QuestsProgressFilter fromLegacy(final @NotNull String legacy) { + return QuestsProgressFilter.legacyToFilterMap.getOrDefault(legacy, QuestsProgressFilter.ALL); } } @@ -120,35 +174,50 @@ public class QuestProgressFile { * * @return {@code Collection} all quest progresses */ - public Collection getAllQuestProgress() { - return questProgress.values(); + @Contract(pure = true) + public @NotNull Collection getAllQuestProgress() { + return this.questProgressMap.values(); } /** - * Checks whether or not the player has {@link QuestProgress} for a specified quest + * Checks whether the player has {@link QuestProgress} for a specified quest * * @param quest the quest to check for * @return true if they have quest progress */ - public boolean hasQuestProgress(Quest quest) { - return questProgress.containsKey(quest.getId()); + @Contract(pure = true) + public boolean hasQuestProgress(final @NotNull Quest quest) { + return this.questProgressMap.containsKey(quest.getId()); } /** * Gets the remaining cooldown before being able to start a specific quest. * * @param quest the quest to test for - * @return 0 if no cooldown remaining or the cooldown is disabled, otherwise the cooldown in milliseconds + * @return 0 if no cooldown remaining, -1 if the cooldown is disabled or the quest is not completed, + * otherwise the cooldown in milliseconds */ - public long getCooldownFor(Quest quest) { - QuestProgress questProgress = getQuestProgress(quest); - if (quest.isCooldownEnabled() && questProgress.isCompleted()) { - if (questProgress.getCompletionDate() > 0) { - long date = questProgress.getCompletionDate(); - return (date + TimeUnit.MILLISECONDS.convert(quest.getCooldown(), TimeUnit.MINUTES)) - System.currentTimeMillis(); - } + @Contract(pure = true) + public long getCooldownFor(final @NotNull Quest quest) { + if (!quest.isCooldownEnabled()) { + return -1; + } + + final QuestProgress questProgress = this.getQuestProgressOrNull(quest); + if (questProgress == null || !questProgress.isCompleted()) { + return -1; + } + + final long completionDate = questProgress.getCompletionDate(); + if (completionDate == 0) { + return -1; } - return 0; + + final long currentTimeMillis = System.currentTimeMillis(); + final long cooldownMillis = TimeUnit.MILLISECONDS.convert(quest.getCooldown(), TimeUnit.MINUTES); + + // do the subtraction first to prevent overflow + return Math.max(0L, completionDate - currentTimeMillis + cooldownMillis); } /** @@ -158,101 +227,134 @@ public class QuestProgressFile { * @return 0 if no time remaining, -1 if the time limit is disabled or the quest is not started, * otherwise the time left in milliseconds */ - public long getTimeRemainingFor(Quest quest) { - QuestProgress questProgress = getQuestProgress(quest); - if (quest.isTimeLimitEnabled() && questProgress.isStarted()) { - return Math.max( - questProgress.getStartedDate() - + TimeUnit.MILLISECONDS.convert(quest.getTimeLimit(), TimeUnit.MINUTES) - - System.currentTimeMillis() - , 0); + @Contract(pure = true) + public long getTimeRemainingFor(final @NotNull Quest quest) { + if (!quest.isTimeLimitEnabled()) { + return -1; + } + + final QuestProgress questProgress = this.getQuestProgressOrNull(quest); + if (questProgress == null || !questProgress.isStarted()) { + return -1; + } + + final long startedDate = questProgress.getStartedDate(); + if (startedDate == 0) { + return -1; } - return -1; + + final long currentTimeMillis = System.currentTimeMillis(); + final long timeLimitMillis = TimeUnit.MILLISECONDS.convert(quest.getTimeLimit(), TimeUnit.MINUTES); + + // do the subtraction first to prevent overflow + return Math.max(0L, startedDate - currentTimeMillis + timeLimitMillis); } /** - * Tests whether or not the player meets the requirements to start a specific quest. + * Tests whether the player meets the requirements to start a specific quest. * * @param quest the quest to test for * @return true if they can start the quest */ - //TODO possibly move this - public boolean hasMetRequirements(Quest quest) { - for (String id : quest.getRequirements()) { - Quest q = plugin.getQuestManager().getQuestById(id); - if (q == null) { - continue; - } - if (hasQuestProgress(q) && !getQuestProgress(q).isCompletedBefore()) { + // TODO possibly move this + @Contract(pure = true) + public boolean hasMetRequirements(final @NotNull Quest quest) { + for (final String requiredQuestId : quest.getRequirements()) { + final QuestProgress requiredQuestProgress = this.questProgressMap.get(requiredQuestId); + if (requiredQuestProgress == null || !requiredQuestProgress.isCompletedBefore()) { + // if we decide to change the method return type to states like "DOES_NOT_EXIST" + // or "COMPLETED_BEFORE" we will need to change the quest existance check order return false; - } else if (!hasQuestProgress(q)) { + } + + final Quest requiredQuest = this.plugin.getQuestManager().getQuestById(requiredQuestId); + if (requiredQuest == null) { + // TODO not sure if we actually need this check however probably + // forcing the server owner to fix the quest options instead + // of just ignoring the fact it's broken is better? return false; } } + return true; } /** - * Get the {@link UUID} of the player this QuestProgressFile represents. - * - * @return UUID + * @return the associated player UUID */ - public UUID getPlayerUUID() { - return playerUUID; + @Contract(pure = true) + public @NotNull UUID getPlayerUUID() { + return this.playerUUID; } /** - * Get the {@link QuestProgress} for a specified {@link Quest}. Generates a new one if it does not exist. + * Gets the {@link QuestProgress} for a specified {@link Quest}. Generates a new one if it does not exist. * - * @param quest the quest to get progress for + * @param quest the quest to get the progress for * @return {@link QuestProgress} or a blank generated one if the quest does not exist */ - public QuestProgress getQuestProgress(Quest quest) { - QuestProgress qProgress = questProgress.get(quest.getId()); - return qProgress != null ? qProgress : generateBlankQuestProgress(quest); + public @NotNull QuestProgress getQuestProgress(final @NotNull Quest quest) { + final QuestProgress questProgress = this.getQuestProgressOrNull(quest); + return questProgress != null ? questProgress : this.generateBlankQuestProgress(quest); } /** - * Tests whether or not the player has a specified {@link Quest} started. + * Gets the {@link QuestProgress} for a specified {@link Quest}. Returns null if it does not exist. + * + * @param quest the quest to get the progress for + * @return {@link QuestProgress} or null if the quest does not exist + */ + @Contract(pure = true) + public @Nullable QuestProgress getQuestProgressOrNull(final @NotNull Quest quest) { + return this.questProgressMap.get(quest.getId()); + } + + /** + * Tests whether the player has a specified {@link Quest} started. * * @param quest the quest to check for * @return true if player has the quest started */ - public boolean hasQuestStarted(Quest quest) { - QuestProgress qProgress = questProgress.get(quest.getId()); - return qProgress != null && qProgress.isStarted(); + @Contract(pure = true) + public boolean hasQuestStarted(final @NotNull Quest quest) { + final QuestProgress questProgress = this.getQuestProgressOrNull(quest); + return questProgress != null && questProgress.isStarted(); } /** - * Generate a new blank {@link QuestProgress} for a specified {@code quest}. + * Generate a new blank {@link QuestProgress} for a specified {@link Quest} with {@link QuestProgress#isModified()} set to {@code false}. * - * @param quest the quest to generate progress for + * @param quest the quest to generate the progress for * @return the generated blank {@link QuestProgress} */ - public QuestProgress generateBlankQuestProgress(Quest quest) { - return generateBlankQuestProgress(quest, false); + public @NotNull QuestProgress generateBlankQuestProgress(final @NotNull Quest quest) { + return this.generateBlankQuestProgress(quest, false); } /** - * Generate a new blank {@link QuestProgress} for a specified {@code quest}. + * Generate a new blank {@link QuestProgress} for a specified {@link Quest}. * - * @param quest the quest to generate progress for + * @param quest the quest to generate the progress for * @param modified the modified state of the quest * @return the generated blank {@link QuestProgress} */ - public QuestProgress generateBlankQuestProgress(Quest quest, boolean modified) { - QuestProgress questProgress = new QuestProgress(plugin, quest.getId(), false, false, 0, playerUUID, false, 0, modified); - for (Task task : quest.getTasks()) { - TaskProgress taskProgress = new TaskProgress(questProgress, task.getId(), null, playerUUID, false, modified); + public @NotNull QuestProgress generateBlankQuestProgress(final @NotNull Quest quest, final boolean modified) { + final QuestProgress questProgress = new QuestProgress(this.plugin, quest.getId(), this.playerUUID, false, 0L, false, false, 0L, modified); + + for (final Task task : quest.getTasks()) { + final TaskProgress taskProgress = new TaskProgress(questProgress, task.getId(), this.playerUUID, null, false, modified); questProgress.addTaskProgress(taskProgress); } - addQuestProgress(questProgress); + this.addQuestProgress(questProgress); return questProgress; } + /** + * Clears quest progress map. + */ public void clear() { - questProgress.clear(); + this.questProgressMap.clear(); } /** @@ -261,15 +363,17 @@ public class QuestProgressFile { * set the modified flag in that case. */ public void reset() { - for (QuestProgress questProgress : questProgress.values()) { + for (final QuestProgress questProgress : this.questProgressMap.values()) { if (!questProgress.hasNonDefaultValues()) { continue; } - Quest quest = plugin.getQuestManager().getQuestById(questProgress.getQuestId()); + + final Quest quest = this.plugin.getQuestManager().getQuestById(questProgress.getQuestId()); if (quest == null) { continue; } - generateBlankQuestProgress(quest, true); + + this.generateBlankQuestProgress(quest, true); } } @@ -278,41 +382,61 @@ public class QuestProgressFile { */ @Deprecated public void clean() { - plugin.getQuestsLogger().debug("Cleaning file " + playerUUID + "."); - if (!plugin.getTaskTypeManager().areRegistrationsOpen()) { - ArrayList invalidQuests = new ArrayList<>(); - for (String questId : this.questProgress.keySet()) { - Quest q; - if ((q = plugin.getQuestManager().getQuestById(questId)) == null) { - invalidQuests.add(questId); - } else { - ArrayList invalidTasks = new ArrayList<>(); - for (String taskId : this.questProgress.get(questId).getTaskProgressMap().keySet()) { - if (q.getTaskById(taskId) == null) { - invalidTasks.add(taskId); - } - } - for (String taskId : invalidTasks) { - this.questProgress.get(questId).getTaskProgressMap().remove(taskId); + this.plugin.getQuestsLogger().debug("Cleaning file " + this.playerUUID + "."); + + if (!this.plugin.getTaskTypeManager().areRegistrationsOpen()) { + final List invalidQuestIds = new ArrayList<>(); + + for (final Map.Entry questProgressEntry : this.questProgressMap.entrySet()) { + final String questId = questProgressEntry.getKey(); + + final Quest quest = this.plugin.getQuestManager().getQuestById(questId); + if (quest == null) { + invalidQuestIds.add(questId); + + // tasks will be removed with the quest + continue; + } + + final QuestProgress questProgress = questProgressEntry.getValue(); + final Map taskProgressMap = questProgress.getTaskProgressMap(); + final List invalidTaskIds = new ArrayList<>(); + + for (final String taskId : taskProgressMap.keySet()) { + final Task task = quest.getTaskById(taskId); + + if (task == null) { + invalidTaskIds.add(taskId); } } + + for (final String taskId : invalidTaskIds) { + taskProgressMap.remove(taskId); + } } - for (String questId : invalidQuests) { - this.questProgress.remove(questId); + + for (final String questId : invalidQuestIds) { + this.questProgressMap.remove(questId); } } } + /** + * It's equivalent to {@code QuestProgressFile#setModified(false)}. + * + * @see QuestProgressFile#setModified(boolean) + */ + @Deprecated(forRemoval = true) public void resetModified() { - for (QuestProgress questProgress : questProgress.values()) { - questProgress.resetModified(); - } + this.setModified(false); } - public void setModified(boolean modified) { - for (QuestProgress questProgress : questProgress.values()) { + /** + * @param modified whether the object has been modified and needs to be saved + */ + public void setModified(final boolean modified) { + for (final QuestProgress questProgress : this.questProgressMap.values()) { questProgress.setModified(modified); } } - } diff --git a/common/src/main/java/com/leonardobishop/quests/common/player/questprogressfile/TaskProgress.java b/common/src/main/java/com/leonardobishop/quests/common/player/questprogressfile/TaskProgress.java index a78a4d25..9395634e 100644 --- a/common/src/main/java/com/leonardobishop/quests/common/player/questprogressfile/TaskProgress.java +++ b/common/src/main/java/com/leonardobishop/quests/common/player/questprogressfile/TaskProgress.java @@ -1,84 +1,163 @@ package com.leonardobishop.quests.common.player.questprogressfile; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; import java.util.UUID; -public class TaskProgress { +public final class TaskProgress { - private final String taskid; - private final UUID player; + private final QuestProgress questProgress; + private final String taskId; + private final UUID playerUUID; - private QuestProgress linkedQuestProgress; - private boolean modified; private Object progress; private boolean completed; + private boolean modified; - public TaskProgress(QuestProgress linkedQuestProgress, String taskid, Object progress, UUID player, boolean completed) { - this.linkedQuestProgress = linkedQuestProgress; - this.taskid = taskid; + /** + * Constructs a TaskProgress. + * + * @param questProgress the quest progress + * @param taskId the associated task ID + * @param playerUUID the associated player UUID + * @param progress the progress object + * @param completed whether the task is completed + * @param modified whether the object has been modified and needs to be saved + */ + public TaskProgress(final @Nullable QuestProgress questProgress, final @NotNull String taskId, final @NotNull UUID playerUUID, final @Nullable Object progress, final boolean completed, final boolean modified) { + this.questProgress = questProgress; + this.taskId = taskId; + this.playerUUID = playerUUID; this.progress = progress; - this.player = player; this.completed = completed; + this.modified = modified; } - public TaskProgress(QuestProgress linkedQuestProgress, String taskid, Object progress, UUID player, boolean completed, boolean modified) { - this(linkedQuestProgress, taskid, progress, player, completed); - this.modified = modified; + /** + * Constructs a TaskProgress with {@link TaskProgress#modified} set to {@code false}. + * + * @param questProgress the quest progress + * @param taskId the associated task ID + * @param playerUUID the associated player UUID + * @param progress the progress object + * @param completed whether the task is completed + */ + public TaskProgress(final @NotNull QuestProgress questProgress, final @NotNull String taskId, final @NotNull UUID playerUUID, final @Nullable Object progress, final boolean completed) { + this(questProgress, taskId, playerUUID, progress, completed, false); } - public TaskProgress(TaskProgress taskProgress) { - this.taskid = taskProgress.taskid; - this.player = taskProgress.player; - this.modified = taskProgress.modified; - this.progress = taskProgress.progress; - this.completed = taskProgress.completed; + /** + * Constructs a data-only clone from a TaskProgress instance. + * + * @param taskProgress the task progress instance + */ + @ApiStatus.Internal + public TaskProgress(final @NotNull TaskProgress taskProgress) { + this(null, taskProgress.taskId, taskProgress.playerUUID, taskProgress.progress, taskProgress.completed, taskProgress.modified); } - public String getTaskId() { - return taskid; + /** + * @return the associated task ID + */ + @Contract(pure = true) + public @NotNull String getTaskId() { + return this.taskId; } - public Object getProgress() { - return progress; + /** + * @return the associated player ID + * @see QuestProgress#getPlayerUUID() + */ + @Deprecated(forRemoval = true) + @Contract(pure = true) + public @NotNull UUID getPlayer() { + return this.playerUUID; } - public void setProgress(Object progress) { - if (this.progress != progress) this.modified = true; + /** + * @return the associated player ID + */ + @Contract(pure = true) + public @NotNull UUID getPlayerUUID() { + return this.playerUUID; + } - this.progress = progress; + /** + * @return the progress object + */ + @Contract(pure = true) + public @Nullable Object getProgress() { + return this.progress; } - public UUID getPlayer() { - return player; + /** + * @param progress the progress object + */ + public void setProgress(final @Nullable Object progress) { + if (Objects.equals(progress, this.progress)) { + return; + } + + this.progress = progress; + this.modified = true; } + /** + * @return whether the task is completed + */ + @Contract(pure = true) public boolean isCompleted() { - return completed; + return this.completed; } - public void setCompleted(boolean complete) { + /** + * @param completed whether the task is completed + */ + public void setCompleted(final boolean completed) { + if (this.questProgress == null) { + throw new UnsupportedOperationException("associated quest progress cannot be null"); + } + // do not queue completion for already completed quests // https://github.com/LMBishop/Quests/issues/543 - if (this.completed == complete) { + if (this.completed == completed) { return; } - this.completed = complete; + this.completed = completed; this.modified = true; - if (complete) { - linkedQuestProgress.queueForCompletionTest(); + if (completed) { + this.questProgress.queueForCompletionTest(); } } + /** + * @return whether the object has been modified and needs to be saved + */ + @Contract(pure = true) public boolean isModified() { - return modified; + return this.modified; } + /** + * It's equivalent to {@code TaskProgress#setModified(false)}. + * + * @see TaskProgress#setModified(boolean) + */ + @Deprecated(forRemoval = true) public void resetModified() { - this.modified = false; + this.setModified(false); } - public void setModified(boolean modified) { + /** + * @param modified whether the object has been modified and needs to be saved + */ + public void setModified(final boolean modified) { this.modified = modified; } } diff --git a/common/src/main/java/com/leonardobishop/quests/common/storage/StorageProvider.java b/common/src/main/java/com/leonardobishop/quests/common/storage/StorageProvider.java index ba0ee8c8..a9c7b52f 100644 --- a/common/src/main/java/com/leonardobishop/quests/common/storage/StorageProvider.java +++ b/common/src/main/java/com/leonardobishop/quests/common/storage/StorageProvider.java @@ -1,61 +1,91 @@ package com.leonardobishop.quests.common.storage; -import com.leonardobishop.quests.common.player.questprogressfile.QuestProgressFile; +import com.leonardobishop.quests.common.player.QPlayerData; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.io.IOException; import java.util.List; +import java.util.Objects; import java.util.UUID; /** - * The storage provider is responsible for obtaining a QuestProgressFile for a specified UUID and for - * writing a QuestProgressFile. + * The StorageProvider interface defines the contract for a storage system that handles the persistence + * of player data, such as player preferences and quest progress, for specific players identified by their UUIDs. + * Implementations of this interface are responsible for the actual storage and retrieval of this data. */ public interface StorageProvider { - String getName(); + /** + * Retrieves the name of this storage provider. + * + * @return the name of the storage provider + */ + @NotNull String getName(); - void init(); + /** + * Initializes the storage provider, preparing it for use. This method should be called before any + * other operations are performed. Initialization may involve setting up connections or loading necessary resources. + */ + void init() throws IOException; + /** + * Shuts down the storage provider, ensuring that any open resources are properly closed and that + * any pending data is safely stored. This method should be called during the application's shutdown process. + */ void shutdown(); /** - * Load a QuestProgressFile from the data source by a specific UUID + * Loads the player data associated with the given UUID from the storage. * - * @param uuid the UUID to load - * @return {@link QuestProgressFile} or null + * @param uuid the unique identifier of the player whose data is to be loaded + * @return the {@link QPlayerData} for the player, or null if no data is found for the given UUID */ - @Nullable QuestProgressFile loadProgressFile(@NotNull UUID uuid); + @Nullable QPlayerData loadPlayerData(final @NotNull UUID uuid); /** - * Save a QuestProgressFile to the data source with a specific UUID + * Saves the given player data to the storage. * - * @param uuid the uuid to match the file to - * @param questProgressFile the file to save + * @param playerData the {@link QPlayerData} object containing the player's data to be saved + * @return true if the data was successfully saved, false otherwise */ - boolean saveProgressFile(@NotNull UUID uuid, @NotNull QuestProgressFile questProgressFile); + boolean savePlayerData(final @NotNull QPlayerData playerData); /** - * Load all QuestProgressFiles + * Loads all player data available in the storage. * - * @return {@link List} + * @return a list of {@link QPlayerData} objects */ - @NotNull List loadAllProgressFiles(); + @NotNull List loadAllPlayerData(); /** - * Save a list of QuestProgressFiles + * Saves all provided player data to the storage. * - * @param files the list of QuestProgressFile to save - **/ - void saveAllProgressFiles(List files); + * @param allPlayerData a list of {@link QPlayerData} objects to be saved + * @return true if the data was successfully saved, false otherwise + */ + default boolean saveAllPlayerData(final @NotNull List allPlayerData) { + Objects.requireNonNull(allPlayerData, "allPlayerData cannot be null"); + + // fault check is not needed here as the method + // saving single player data already handles that, + // and it's actually the one we need to check + + boolean result = true; + + for (final QPlayerData playerData : allPlayerData) { + result &= this.savePlayerData(playerData); + } + + return result; + } /** - * Whether this provider is 'similar' to another one. - * Similarity is determined if the provider effectively points to the same data source. + * Compares this storage provider with another to determine if they are similar. + * Similarity is determined by effectively pointing to the same data source. * - * @param provider the provider to compare to - * @return true if similar, false otherwise + * @param otherProvider another StorageProvider to compare against + * @return true if the two storage providers are considered similar, false otherwise */ - boolean isSimilar(StorageProvider provider); - + boolean isSimilar(final @NotNull StorageProvider otherProvider); } -- cgit v1.2.3-70-g09d2