From 24796d56d79cacf65faa13a18d85ddabb07b34f6 Mon Sep 17 00:00:00 2001 From: Jay <76460079+PZDonny@users.noreply.github.com> Date: Sat, 17 Oct 2026 18:04:29 -0500 Subject: [PATCH 01/22] add mockito & update junit to `6.1.3` --- pom.xml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b00eeb87..3f5c82da 100644 --- a/pom.xml +++ b/pom.xml @@ -143,7 +143,13 @@ org.junit.jupiter junit-jupiter - 6.1.0 + 6.1.3 + test + + + org.mockito + mockito-junit-jupiter + 5.24.0 test From bf9cc2b96613c8583c279acdf9e1a903249ffd28 Mon Sep 17 00:00:00 2001 From: Jay <76460079+PZDonny@users.noreply.github.com> Date: Sat, 17 Oct 2026 18:07:54 -0500 Subject: [PATCH 02/22] close reader object after test --- .../utils/controller/DisplayControllerTests.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/api/src/test/java/net/donnypz/displayentityutils/utils/controller/DisplayControllerTests.java b/api/src/test/java/net/donnypz/displayentityutils/utils/controller/DisplayControllerTests.java index 0b68f785..1cc5b3d2 100644 --- a/api/src/test/java/net/donnypz/displayentityutils/utils/controller/DisplayControllerTests.java +++ b/api/src/test/java/net/donnypz/displayentityutils/utils/controller/DisplayControllerTests.java @@ -51,9 +51,10 @@ void testInvalidConfigs(String configName) { private DisplayController testConfig(String configName, String packageName){ DisplayController controller; - try (InputStream stream = getClass().getResourceAsStream("/test/controllers/"+packageName+"/" + configName + ".yml")) { - InputStreamReader reader = new InputStreamReader(stream); - + try ( + InputStream stream = getClass().getResourceAsStream("/test/controllers/"+packageName+"/" + configName + ".yml"); + InputStreamReader reader = new InputStreamReader(stream)) + { YamlConfiguration config = YamlConfiguration.loadConfiguration(reader); controller = DisplayControllerReader.read( config, @@ -62,7 +63,7 @@ private DisplayController testConfig(String configName, String packageName){ logger, false ); - } catch (IOException e) { + } catch (IOException | NullPointerException e) { throw new RuntimeException(e); } From 956b81c16321e63aa82ca350f1b1c21091138781 Mon Sep 17 00:00:00 2001 From: Jay <76460079+PZDonny@users.noreply.github.com> Date: Sat, 17 Oct 2026 18:09:21 -0500 Subject: [PATCH 03/22] Add `Versionutils` tests --- .../utils/version/VersionTests.java | 95 +++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 api/src/test/java/net/donnypz/displayentityutils/utils/version/VersionTests.java diff --git a/api/src/test/java/net/donnypz/displayentityutils/utils/version/VersionTests.java b/api/src/test/java/net/donnypz/displayentityutils/utils/version/VersionTests.java new file mode 100644 index 00000000..ac6e5ca3 --- /dev/null +++ b/api/src/test/java/net/donnypz/displayentityutils/utils/version/VersionTests.java @@ -0,0 +1,95 @@ +package net.donnypz.displayentityutils.utils.version; + +import org.bukkit.Bukkit; +import org.bukkit.UnsafeValues; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.yaml.snakeyaml.Yaml; + +import java.io.InputStream; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +@ExtendWith(MockitoExtension.class) +public class VersionTests { + + private static MockedStatic bukkitMock; + + @BeforeAll + static void mockBukkit(){ + bukkitMock = Mockito.mockStatic(Bukkit.class); + UnsafeValues unsafeValues = Mockito.mock(UnsafeValues.class); + + bukkitMock.when(Bukkit::getUnsafe).thenReturn(unsafeValues); + Mockito.when(unsafeValues.getProtocolVersion()).thenReturn(Integer.MAX_VALUE); + } + + @AfterAll + static void closeBukkitMock(){ + bukkitMock.close(); + } + + + @ParameterizedTest + @ValueSource(strings = { + "2.3.2-ex-39a286e|2.3.2", + "3.1.0-main|3.1.0", + "3.5.0-PRE|3.5.0", + "3.6.1-develop|3.6.1", + "3.7.0-dev|3.7.0", + "3.8.0-dev-1da350c|3.8.0", + }) + void shouldCleanVersionString(String version) { + String[] split = version.split("\\|"); + String dirtyVersion = split[0]; + String cleanVersion = split[1]; + + String cleaned = VersionUtils.cleanVersionString(dirtyVersion); + + assertEquals(cleaned, cleanVersion); + } + + + @ParameterizedTest + @ValueSource(strings = { + "2.3.2-ex-39a286e", + "3.1.0-main", + "3.5.3-develop", + "3.5.0-PRE", + "3.7.0-dev", + }) + void shouldBeDevVersion(String version) { + boolean isDevVersion = VersionUtils.isDevVersion(version); + assertTrue(isDevVersion, "Version is not a develop version: " + version); + } + + @ParameterizedTest + @ValueSource(strings = { + "3.7.0", + "3.6.0", + "3.5.5", + "3.5.1", + "3.6.0", + }) + void currentVersionShouldBeNewer(String comparedVersion) { + Yaml yaml = new Yaml(); + + try (InputStream inputStream = getClass().getResourceAsStream("/plugin.yml")) { + Map config = yaml.load(inputStream); + String currentVersion = (String) config.get("version"); + + boolean isNewer = VersionUtils.isOlderThan(comparedVersion, currentVersion); + + assertTrue(isNewer, "Current version (" + currentVersion + ") should be newer than compared version: " + comparedVersion); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} From 86b99a8d2ff29d8a007a5f85ad7bc8aec7bca181 Mon Sep 17 00:00:00 2001 From: Jay <76460079+PZDonny@users.noreply.github.com> Date: Sat, 17 Oct 2026 18:12:19 -0500 Subject: [PATCH 04/22] corrected logic for `#cleanVersionString` & remove `IS_DEV_VERSION` field --- .../utils/version/VersionUtils.java | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/api/src/main/java/net/donnypz/displayentityutils/utils/version/VersionUtils.java b/api/src/main/java/net/donnypz/displayentityutils/utils/version/VersionUtils.java index 97ae846a..f98bbb9f 100644 --- a/api/src/main/java/net/donnypz/displayentityutils/utils/version/VersionUtils.java +++ b/api/src/main/java/net/donnypz/displayentityutils/utils/version/VersionUtils.java @@ -26,13 +26,10 @@ public final class VersionUtils { public static final boolean IS_26_1 = Bukkit.getUnsafe().getProtocolVersion() >= 775; public static final boolean IS_26_2 = Bukkit.getUnsafe().getProtocolVersion() >= 776; - public static final boolean IS_DEV_VERSION; private static Particle ENTITY_EFFECT_PARTICLE; private static Particle ITEM_PARTICLE; static{ - String pluginVer = DisplayAPI.getPlugin().getPluginMeta().getVersion(); - IS_DEV_VERSION = isDevVersion(pluginVer); setParticles(); } @@ -47,23 +44,32 @@ private VersionUtils(){} } public static boolean isDevVersion(){ - return IS_DEV_VERSION; + return isDevVersion(DisplayAPI.getPlugin().getPluginMeta().getVersion()); } @ApiStatus.Internal - public static boolean isDevVersion(@NotNull String version){ + static boolean isDevVersion(@NotNull String version){ String clean = cleanVersionString(version); return !version.equals(clean); } @ApiStatus.Internal public static @NotNull String cleanVersionString(@NotNull String version){ - return version.replaceAll("[^0-9.]", ""); + String[] versionNumbers = version.split("\\."); + + return versionNumbers[0] + + "." + + versionNumbers[1] + + "." + + versionNumbers[2].split("-")[0]; } public static boolean isCurrentOlderThan(@NotNull String comparedVersion){ - String current = getPluginVersion(); - String cleanCurrent = VersionUtils.cleanVersionString(current); + return isOlderThan(getPluginVersion(), comparedVersion); + } + + static boolean isOlderThan(@NotNull String version,@NotNull String comparedVersion){ + String cleanCurrent = VersionUtils.cleanVersionString(version); String cleanComparedVersion = VersionUtils.cleanVersionString(comparedVersion); String[] currentArr = cleanCurrent.split("\\."); From b78b76fdeb3aabedef4e84de7314212bfe44ba4c Mon Sep 17 00:00:00 2001 From: Jay <76460079+PZDonny@users.noreply.github.com> Date: Sat, 17 Oct 2026 18:39:32 -0500 Subject: [PATCH 05/22] Bump version to 3.8.1 --- api/pom.xml | 2 +- plugin/pom.xml | 2 +- pom.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/api/pom.xml b/api/pom.xml index 0010a501..c9d258ff 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -6,7 +6,7 @@ net.donnypz.displayentityutils deu - 3.8.0 + 3.8.1 api diff --git a/plugin/pom.xml b/plugin/pom.xml index 83853a76..a395504b 100644 --- a/plugin/pom.xml +++ b/plugin/pom.xml @@ -6,7 +6,7 @@ net.donnypz.displayentityutils deu - 3.8.0 + 3.8.1 plugin diff --git a/pom.xml b/pom.xml index 3f5c82da..2c87008b 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ net.donnypz.displayentityutils deu - 3.8.0 + 3.8.1 pom DisplayEntityUtils From 453d92193a928329e29b9228a5e8acf300eb6cc5 Mon Sep 17 00:00:00 2001 From: Jay <76460079+PZDonny@users.noreply.github.com> Date: Tue, 20 Oct 2026 16:24:41 -0500 Subject: [PATCH 06/22] rename test method from `#tests` to `#shouldBeValidFrameIds` --- .../displayentityutils/command/CommandFrameIdsTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/src/test/java/net/donnypz/displayentityutils/command/CommandFrameIdsTests.java b/plugin/src/test/java/net/donnypz/displayentityutils/command/CommandFrameIdsTests.java index 84865d7a..ff64e644 100644 --- a/plugin/src/test/java/net/donnypz/displayentityutils/command/CommandFrameIdsTests.java +++ b/plugin/src/test/java/net/donnypz/displayentityutils/command/CommandFrameIdsTests.java @@ -13,7 +13,7 @@ public class CommandFrameIdsTests { "1,2", "9,3,5", }) - void tests(String idString){ + void shouldBeValidFrameIds(String idString){ assertNotNull(CMDUtils.commaSeparatedIDs(idString)); } From df6f5acecf59a23bdaaba4a3c0becda1fb3b52cb Mon Sep 17 00:00:00 2001 From: Jay <76460079+PZDonny@users.noreply.github.com> Date: Wed, 21 Oct 2026 04:03:43 -0500 Subject: [PATCH 07/22] Changed DisplayStorage and DBDisplayStorage to abstract classes to accept Logger in constructor --- .../database/DisplayStorage.java | 31 ++++++++++++------- .../DisplayEntityPlugin.java | 6 ++-- .../database/DBDisplayStorage.java | 12 +++++-- .../database/LocalDisplayStorage.java | 9 ++++-- .../database/MYSQLDisplayStorage.java | 7 ++++- .../database/MongoDisplayStorage.java | 7 ++++- 6 files changed, 50 insertions(+), 22 deletions(-) diff --git a/api/src/main/java/net/donnypz/displayentityutils/database/DisplayStorage.java b/api/src/main/java/net/donnypz/displayentityutils/database/DisplayStorage.java index 20870750..d0d30368 100644 --- a/api/src/main/java/net/donnypz/displayentityutils/database/DisplayStorage.java +++ b/api/src/main/java/net/donnypz/displayentityutils/database/DisplayStorage.java @@ -5,30 +5,37 @@ import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; import java.util.List; -public interface DisplayStorage { +public abstract class DisplayStorage { - boolean isEnabled(); + private final Logger logger; - boolean saveDisplayEntityGroup(@NotNull DisplayEntityGroup group, @Nullable Player saver); + public DisplayStorage(Logger logger) { + this.logger = logger; + } - void deleteDisplayEntityGroup(@NotNull String group, @Nullable Player deleter); + public abstract boolean isEnabled(); - @Nullable DisplayEntityGroup getDisplayEntityGroup(@NotNull String tag); + public abstract boolean saveDisplayEntityGroup(@NotNull DisplayEntityGroup group, @Nullable Player saver); - boolean saveDisplayAnimation(@NotNull DisplayAnimation animation, @Nullable Player saver); + public abstract void deleteDisplayEntityGroup(@NotNull String group, @Nullable Player deleter); - void deleteDisplayAnimation(@NotNull String tag, @Nullable Player deleter); + public @Nullable abstract DisplayEntityGroup getDisplayEntityGroup(@NotNull String tag); - @Nullable DisplayAnimation getDisplayAnimation(@NotNull String tag); + public abstract boolean saveDisplayAnimation(@NotNull DisplayAnimation animation, @Nullable Player saver); - @NotNull List getGroupTags(); + public abstract void deleteDisplayAnimation(@NotNull String tag, @Nullable Player deleter); - @NotNull List getGroupTags(int page, int size); + public @Nullable abstract DisplayAnimation getDisplayAnimation(@NotNull String tag); - @NotNull List getAnimationTags(); + public @NotNull abstract List getGroupTags(); - @NotNull List getAnimationTags(int page, int size); + public @NotNull abstract List getGroupTags(int page, int size); + + public @NotNull abstract List getAnimationTags(); + + public @NotNull abstract List getAnimationTags(int page, int size); } diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/DisplayEntityPlugin.java b/plugin/src/main/java/net/donnypz/displayentityutils/DisplayEntityPlugin.java index 7086cc09..324c321e 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/DisplayEntityPlugin.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/DisplayEntityPlugin.java @@ -70,9 +70,9 @@ public void onLoad() { public void onEnable() { DisplayAPI.plugin = this; Map storages = DisplayAPI.storages; - storages.put(LoadMethod.LOCAL, new LocalDisplayStorage()); - storages.put(LoadMethod.MONGODB, new MongoDisplayStorage()); - storages.put(LoadMethod.MYSQL, new MYSQLDisplayStorage()); + storages.put(LoadMethod.LOCAL, new LocalDisplayStorage(getSLF4JLogger())); + storages.put(LoadMethod.MONGODB, new MongoDisplayStorage(getSLF4JLogger())); + storages.put(LoadMethod.MYSQL, new MYSQLDisplayStorage(getSLF4JLogger())); DisplayAPI.ANIMATION_PLAYER_SERVICE = new AnimationPlayerProviderImpl(); DisplayAPI.BDE_CONVERSION_HANDLER = new BDEConversionHandlerImpl(); diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/database/DBDisplayStorage.java b/plugin/src/main/java/net/donnypz/displayentityutils/database/DBDisplayStorage.java index 635eba9b..881806a0 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/database/DBDisplayStorage.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/database/DBDisplayStorage.java @@ -1,15 +1,21 @@ package net.donnypz.displayentityutils.database; -public interface DBDisplayStorage extends DisplayStorage { +import org.slf4j.Logger; + +public abstract class DBDisplayStorage extends DisplayStorage { + + public DBDisplayStorage(Logger logger) { + super(logger); + } /** * Check if this Database store is connected * @return a boolean */ - boolean isConnected(); + public abstract boolean isConnected(); /** * Close this active database connection */ - void closeConnection(); + public abstract void closeConnection(); } diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/database/LocalDisplayStorage.java b/plugin/src/main/java/net/donnypz/displayentityutils/database/LocalDisplayStorage.java index caa21dea..893f5435 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/database/LocalDisplayStorage.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/database/LocalDisplayStorage.java @@ -14,6 +14,7 @@ import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; import java.io.*; import java.nio.file.DirectoryStream; @@ -21,9 +22,12 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.List; -import java.util.zip.GZIPOutputStream; -public final class LocalDisplayStorage implements DisplayStorage { +public final class LocalDisplayStorage extends DisplayStorage { + + public LocalDisplayStorage(Logger logger) { + super(logger); + } @Override public boolean isEnabled() { @@ -54,6 +58,7 @@ public boolean saveDisplayEntityGroup(@NotNull DisplayEntityGroup displayEntityG } saveFile.delete(); } + saveFile.createNewFile(); FileOutputStream fileOut = new FileOutputStream(saveFile); fileOut.write(data); diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/database/MYSQLDisplayStorage.java b/plugin/src/main/java/net/donnypz/displayentityutils/database/MYSQLDisplayStorage.java index 4d8247fc..2fd90f56 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/database/MYSQLDisplayStorage.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/database/MYSQLDisplayStorage.java @@ -17,6 +17,7 @@ import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -26,7 +27,7 @@ import java.util.List; -public final class MYSQLDisplayStorage implements DBDisplayStorage { +public final class MYSQLDisplayStorage extends DBDisplayStorage { private static final String GROUP_TABLE = "saved_displays"; private static final String GROUP_COLUMN = "display_group"; @@ -42,6 +43,10 @@ public final class MYSQLDisplayStorage implements DBDisplayStorage { private boolean connected = false; private HikariDataSource dataSource; + public MYSQLDisplayStorage(Logger logger) { + super(logger); + } + public void createConnection( String host, int port, diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/database/MongoDisplayStorage.java b/plugin/src/main/java/net/donnypz/displayentityutils/database/MongoDisplayStorage.java index 251fd70f..5d9887fd 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/database/MongoDisplayStorage.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/database/MongoDisplayStorage.java @@ -25,12 +25,13 @@ import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.*; -public final class MongoDisplayStorage implements DBDisplayStorage { +public final class MongoDisplayStorage extends DBDisplayStorage { private static MongoCollection GROUP_COLLECTION; private static final String GROUP_FIELD = "displayGroup"; @@ -46,6 +47,10 @@ public final class MongoDisplayStorage implements DBDisplayStorage { private MongoDatabase database; private boolean isConnected = false; + public MongoDisplayStorage(Logger logger) { + super(logger); + } + public void createConnection( String host, int port, From 8c030103a60125c588ac8c3eedb03e0c6a2519de Mon Sep 17 00:00:00 2001 From: Jay <76460079+PZDonny@users.noreply.github.com> Date: Wed, 21 Oct 2026 04:04:27 -0500 Subject: [PATCH 08/22] create `#connect` method, moving connection logic outside of scheduler --- .../database/MYSQLDisplayStorage.java | 60 +++++++++++-------- 1 file changed, 34 insertions(+), 26 deletions(-) diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/database/MYSQLDisplayStorage.java b/plugin/src/main/java/net/donnypz/displayentityutils/database/MYSQLDisplayStorage.java index 2fd90f56..0e3fe99a 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/database/MYSQLDisplayStorage.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/database/MYSQLDisplayStorage.java @@ -71,40 +71,48 @@ public void createConnection( return; } DisplayAPI.getScheduler().runAsync(() -> { - try { - //Set Data Source - dataSource = new HikariDataSource(); - dataSource.setJdbcUrl(url); - dataSource.setUsername(username); - dataSource.setPassword(password); + connect(url, username, password); + }); + } + + void connect( + String url, + String username, + String password + ) { + try { + //Set Data Source + dataSource = new HikariDataSource(); + dataSource.setJdbcUrl(url); + dataSource.setUsername(username); + dataSource.setPassword(password); - dataSource.setMinimumIdle(3); - dataSource.setMaximumPoolSize(6); + dataSource.setMinimumIdle(3); + dataSource.setMaximumPoolSize(6); - //Test Connection - Connection connection = dataSource.getConnection(); + //Test Connection + Connection connection = dataSource.getConnection(); - //Create Default Table - Statement statement = connection.createStatement(); - String groupTableSQL = "CREATE TABLE IF NOT EXISTS saved_displays(tag VARCHAR(128) UNIQUE, display_group BLOB)"; - statement.execute(groupTableSQL); + //Create Default Table + Statement statement = connection.createStatement(); + String groupTableSQL = "CREATE TABLE IF NOT EXISTS saved_displays(tag VARCHAR(128) UNIQUE, display_group BLOB)"; + statement.execute(groupTableSQL); - String animTableSQL = "CREATE TABLE IF NOT EXISTS saved_animations(tag VARCHAR(128) UNIQUE, display_anim BLOB)"; - statement.execute(animTableSQL); + String animTableSQL = "CREATE TABLE IF NOT EXISTS saved_animations(tag VARCHAR(128) UNIQUE, display_anim BLOB)"; + statement.execute(animTableSQL); - DbUtils.closeQuietly(statement); - DbUtils.closeQuietly(connection); + DbUtils.closeQuietly(statement); + DbUtils.closeQuietly(connection); - Bukkit.getConsoleSender().sendMessage(DisplayAPI.pluginPrefix.append(MiniMessage.miniMessage().deserialize("Successfully connected to MYSQL!"))); - connected = true; - } catch (SQLException e) { - e.printStackTrace(); - Bukkit.getConsoleSender().sendMessage(DisplayAPI.pluginPrefix.append(Component.text("There was an error connecting to the MYSQL database", NamedTextColor.RED))); - closeConnection(); - } - }); + Bukkit.getConsoleSender().sendMessage(DisplayAPI.pluginPrefix.append(MiniMessage.miniMessage().deserialize("Successfully connected to MYSQL!"))); + connected = true; + } catch (SQLException e) { + e.printStackTrace(); + Bukkit.getConsoleSender().sendMessage(DisplayAPI.pluginPrefix.append(Component.text("There was an error connecting to the MYSQL database", NamedTextColor.RED))); + closeConnection(); + } } @Override From 10a6c3289bf2922ba859d6143529d5fc9d05f38e Mon Sep 17 00:00:00 2001 From: Jay <76460079+PZDonny@users.noreply.github.com> Date: Wed, 21 Oct 2026 04:18:32 -0500 Subject: [PATCH 09/22] Move some plugin initialization logic into `DisplayAPIInitializer` --- .../DisplayAPIInitializer.java | 125 ++++++++++++++++++ .../DisplayEntityPlugin.java | 97 +------------- 2 files changed, 127 insertions(+), 95 deletions(-) create mode 100644 plugin/src/main/java/net/donnypz/displayentityutils/DisplayAPIInitializer.java diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/DisplayAPIInitializer.java b/plugin/src/main/java/net/donnypz/displayentityutils/DisplayAPIInitializer.java new file mode 100644 index 00000000..9beab853 --- /dev/null +++ b/plugin/src/main/java/net/donnypz/displayentityutils/DisplayAPIInitializer.java @@ -0,0 +1,125 @@ +package net.donnypz.displayentityutils; + +import ch.njol.skript.Skript; +import ch.njol.skript.util.Version; +import net.donnypz.displayentityutils.database.DisplayStorage; +import net.donnypz.displayentityutils.database.LocalDisplayStorage; +import net.donnypz.displayentityutils.database.MYSQLDisplayStorage; +import net.donnypz.displayentityutils.database.MongoDisplayStorage; +import net.donnypz.displayentityutils.listeners.entity.mythic.DEUMythicListener; +import net.donnypz.displayentityutils.listeners.player.essentials.DEUEssentialsListener; +import net.donnypz.displayentityutils.managers.LoadMethod; +import net.donnypz.displayentityutils.skript.SkriptTypes; +import net.donnypz.displayentityutils.skript.active.ActiveModule; +import net.donnypz.displayentityutils.skript.animation.AnimatorModule; +import net.donnypz.displayentityutils.skript.events.SimpleEvents; +import net.donnypz.displayentityutils.skript.framepoints.FramePointsModule; +import net.donnypz.displayentityutils.skript.group.activegroup.ActiveGroupModule; +import net.donnypz.displayentityutils.skript.group.packetgroup.PacketGroupModule; +import net.donnypz.displayentityutils.skript.group.spawnedgroup.SpawnedGroupModule; +import net.donnypz.displayentityutils.skript.io.IOModule; +import net.donnypz.displayentityutils.skript.misc.MiscModule; +import net.donnypz.displayentityutils.skript.partfilter.PartFilterModule; +import net.donnypz.displayentityutils.skript.parts.PartsModule; +import net.donnypz.displayentityutils.skript.player.PlayerModule; +import net.donnypz.displayentityutils.utils.DisplayEntities.AnimationPlayerProviderImpl; +import net.donnypz.displayentityutils.utils.bdengine.convert.common.BDEConversionHandlerImpl; +import net.donnypz.displayentityutils.utils.version.VersionUtils; +import net.donnypz.displayentityutils.utils.version.folia.SchedulerImpl; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bstats.bukkit.Metrics; +import org.bukkit.Bukkit; +import org.slf4j.Logger; + +import java.util.Map; + +public final class DisplayAPIInitializer { + + private DisplayAPIInitializer() { + } + + public static void initializeImpls(Logger logger) { + DisplayAPI.ANIMATION_PLAYER_SERVICE = new AnimationPlayerProviderImpl(); + DisplayAPI.BDE_CONVERSION_HANDLER = new BDEConversionHandlerImpl(); + DisplayAPI.SCHEDULER = new SchedulerImpl(); + + // Storage providers + Map storages = DisplayAPI.storages; + + storages.put(LoadMethod.LOCAL, new LocalDisplayStorage(logger)); + storages.put(LoadMethod.MONGODB, new MongoDisplayStorage(logger)); + storages.put(LoadMethod.MYSQL, new MYSQLDisplayStorage(logger)); + } + + static void initializeDependencies(DisplayEntityPlugin plugin) { + DisplayAPI.isMythicMobsInstalled = Bukkit.getPluginManager().isPluginEnabled("MythicMobs"); + if (DisplayAPI.isMythicMobsInstalled) { + Bukkit.getPluginManager().registerEvents(new DEUMythicListener(), plugin); + } + + DisplayAPI.isLibsDisguisesInstalled = Bukkit.getPluginManager().isPluginEnabled("LibsDisguises"); + DisplayAPI.isViaVerInstalled = Bukkit.getPluginManager().isPluginEnabled("ViaVersion"); + DisplayAPI.isWorldEditInstalled = Bukkit.getPluginManager().isPluginEnabled("WorldEdit"); + DisplayAPI.isPAPIInstalled = Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI"); + DisplayAPI.isEssentialsInstalled = Bukkit.getPluginManager().isPluginEnabled("Essentials"); + if (DisplayAPI.isEssentialsInstalled) { + Bukkit.getPluginManager().registerEvents(new DEUEssentialsListener(), plugin); + } + + //Skript + DisplayAPI.isSkriptInstalled = Bukkit.getPluginManager().isPluginEnabled("Skript"); + if (DisplayAPI.isSkriptInstalled) registerSkript(plugin); + + //bStats + boolean isDevVersion = VersionUtils.isDevVersion(); + if (!isDevVersion){ + initializeBStats(plugin); + } + else{ + plugin.getLogger().warning("bStats disabled on server using dev version."); + } + } + + private static void initializeBStats(DisplayEntityPlugin plugin) { + int pluginID = 24875; + new Metrics(plugin, pluginID); + } + + private static void registerSkript(DisplayEntityPlugin plugin) { + if (Skript.getVersion().isSmallerThan(new Version(2, 14, 0))) { + plugin + .getComponentLogger() + .info( + DisplayAPI.pluginPrefix + .append(Component.text("Skript Version below 2.10.0 Detected! Skript Syntax Disabled!", NamedTextColor.RED)) + ); + DisplayAPI.isSkriptInstalled = false; + } else { + plugin.addon = Skript.instance().registerAddon(DisplayEntityPlugin.class, "DisplayEntityUtils"); + plugin.addon.loadModules( + new ActiveModule(), + new AnimatorModule(), + new FramePointsModule(), + new ActiveGroupModule(), + new PacketGroupModule(), + new SpawnedGroupModule(), + new IOModule(), + new MiscModule(), + new PartFilterModule(), + new PartsModule(), + new PlayerModule() + ); + plugin.addon.localizer().setSourceDirectories("lang", null); + SimpleEvents.register(); + new SkriptTypes(); + plugin + .getComponentLogger() + .info( + DisplayAPI.pluginPrefix + .append(Component.text("Skript Syntax Enabled!", NamedTextColor.GREEN)) + ); + } + } + +} diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/DisplayEntityPlugin.java b/plugin/src/main/java/net/donnypz/displayentityutils/DisplayEntityPlugin.java index 324c321e..1fe1a7b8 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/DisplayEntityPlugin.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/DisplayEntityPlugin.java @@ -1,7 +1,5 @@ package net.donnypz.displayentityutils; -import ch.njol.skript.Skript; -import ch.njol.skript.util.Version; import com.github.retrooper.packetevents.PacketEvents; import com.github.retrooper.packetevents.event.PacketListenerPriority; import net.donnypz.displayentityutils.command.DisplayEntityPluginCommand; @@ -12,35 +10,14 @@ import net.donnypz.displayentityutils.listeners.entity.DEUEntityListener; import net.donnypz.displayentityutils.listeners.entity.DEUInteractionListener; import net.donnypz.displayentityutils.listeners.entity.DEUMannequinEditorListener; -import net.donnypz.displayentityutils.listeners.entity.mythic.DEUMythicListener; import net.donnypz.displayentityutils.listeners.gizmo.DEUGizmoListener; import net.donnypz.displayentityutils.listeners.player.*; -import net.donnypz.displayentityutils.listeners.player.essentials.DEUEssentialsListener; -import net.donnypz.displayentityutils.managers.LoadMethod; import net.donnypz.displayentityutils.managers.PluginFolders; -import net.donnypz.displayentityutils.skript.SkriptTypes; -import net.donnypz.displayentityutils.skript.active.ActiveModule; -import net.donnypz.displayentityutils.skript.animation.AnimatorModule; -import net.donnypz.displayentityutils.skript.framepoints.FramePointsModule; -import net.donnypz.displayentityutils.skript.events.SimpleEvents; -import net.donnypz.displayentityutils.skript.group.packetgroup.PacketGroupModule; -import net.donnypz.displayentityutils.skript.misc.MiscModule; -import net.donnypz.displayentityutils.skript.group.activegroup.ActiveGroupModule; -import net.donnypz.displayentityutils.skript.group.spawnedgroup.SpawnedGroupModule; -import net.donnypz.displayentityutils.skript.io.IOModule; -import net.donnypz.displayentityutils.skript.parts.PartsModule; -import net.donnypz.displayentityutils.skript.partfilter.PartFilterModule; -import net.donnypz.displayentityutils.skript.player.PlayerModule; -import net.donnypz.displayentityutils.utils.DisplayEntities.AnimationPlayerProviderImpl; import net.donnypz.displayentityutils.utils.DisplayEntities.SpawnedDisplayEntityGroup; import net.donnypz.displayentityutils.utils.DisplayEntities.machine.MachineState; -import net.donnypz.displayentityutils.utils.bdengine.convert.common.BDEConversionHandlerImpl; import net.donnypz.displayentityutils.utils.controller.DisplayControllerUtils; -import net.donnypz.displayentityutils.utils.version.VersionUtils; -import net.donnypz.displayentityutils.utils.version.folia.SchedulerImpl; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; -import org.bstats.bukkit.Metrics; import org.bukkit.Bukkit; import org.bukkit.command.PluginCommand; import org.bukkit.event.EventHandler; @@ -51,8 +28,6 @@ import org.jetbrains.annotations.ApiStatus; import org.skriptlang.skript.addon.SkriptAddon; -import java.util.Map; - public final class DisplayEntityPlugin extends JavaPlugin implements Listener { SkriptAddon addon; @@ -69,30 +44,15 @@ public void onLoad() { @Override public void onEnable() { DisplayAPI.plugin = this; - Map storages = DisplayAPI.storages; - storages.put(LoadMethod.LOCAL, new LocalDisplayStorage(getSLF4JLogger())); - storages.put(LoadMethod.MONGODB, new MongoDisplayStorage(getSLF4JLogger())); - storages.put(LoadMethod.MYSQL, new MYSQLDisplayStorage(getSLF4JLogger())); - - DisplayAPI.ANIMATION_PLAYER_SERVICE = new AnimationPlayerProviderImpl(); - DisplayAPI.BDE_CONVERSION_HANDLER = new BDEConversionHandlerImpl(); - DisplayAPI.SCHEDULER = new SchedulerImpl(); + DisplayAPIInitializer.initializeImpls(this.getSLF4JLogger()); getConfig().options().copyDefaults(true); reloadPlugin(true); ConfigUtils.registerDisplayControllers(); - initializeDependencies(); + DisplayAPIInitializer.initializeDependencies(this); registerListeners(); checkFolia(); - boolean isDevVersion = VersionUtils.isDevVersion(); - if (!isDevVersion){ - initializeBStats(); - } - else{ - getComponentLogger().warn(Component.text("bStats disabled on server using dev version.")); - } - Permission.registerPermissions(); getServer().getConsoleSender().sendMessage(DisplayAPI.pluginPrefix.append(Component.text("Plugin Enabled!", NamedTextColor.GREEN))); } @@ -115,54 +75,6 @@ void checkFolia(){ } } - private void initializeDependencies(){ - //MythicMobs - DisplayAPI.isMythicMobsInstalled = Bukkit.getPluginManager().isPluginEnabled("MythicMobs"); - if (DisplayAPI.isMythicMobsInstalled){ - Bukkit.getPluginManager().registerEvents(new DEUMythicListener(), this); - } - - DisplayAPI.isLibsDisguisesInstalled = Bukkit.getPluginManager().isPluginEnabled("LibsDisguises"); - DisplayAPI.isViaVerInstalled = Bukkit.getPluginManager().isPluginEnabled("ViaVersion"); - DisplayAPI.isWorldEditInstalled = Bukkit.getPluginManager().isPluginEnabled("WorldEdit"); - DisplayAPI.isPAPIInstalled = Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI"); - DisplayAPI.isEssentialsInstalled = Bukkit.getPluginManager().isPluginEnabled("Essentials"); - if (DisplayAPI.isEssentialsInstalled){ - Bukkit.getPluginManager().registerEvents(new DEUEssentialsListener(), this); - } - - //Skript - DisplayAPI.isSkriptInstalled = Bukkit.getPluginManager().isPluginEnabled("Skript"); - if (DisplayAPI.isSkriptInstalled) registerSkript(); - } - - private void registerSkript(){ - if (Skript.getVersion().isSmallerThan(new Version(2,14,0))){ - getServer().getConsoleSender().sendMessage(DisplayAPI.pluginPrefix.append(Component.text("Skript Version below 2.10.0 Detected! Skript Syntax Disabled!", NamedTextColor.RED))); - DisplayAPI.isSkriptInstalled = false; - } - else{ - addon = Skript.instance().registerAddon(DisplayEntityPlugin.class, "DisplayEntityUtils"); - addon.loadModules( - new ActiveModule(), - new AnimatorModule(), - new FramePointsModule(), - new ActiveGroupModule(), - new PacketGroupModule(), - new SpawnedGroupModule(), - new IOModule(), - new MiscModule(), - new PartFilterModule(), - new PartsModule(), - new PlayerModule() - ); - addon.localizer().setSourceDirectories("lang", null); - SimpleEvents.register(); - new SkriptTypes(); - getServer().getConsoleSender().sendMessage(DisplayAPI.pluginPrefix.append(Component.text("Skript Syntax Enabled!", NamedTextColor.GREEN))); - } - } - private void registerListeners(){ Bukkit.getPluginManager().registerEvents(this, this); Bukkit.getPluginManager().registerEvents(new DEULoadingListeners(), this); @@ -179,11 +91,6 @@ private void registerListeners(){ Bukkit.getPluginManager().registerEvents(new DEUMannequinEditorListener(), this); } - private void initializeBStats(){ - int pluginID = 24875; - new Metrics(this, pluginID); - } - /** * Determines whether {@link SpawnedDisplayEntityGroup}s should be unregistered in a world based From c14e43e64891410907dbcfc96485ab11cf5b9207 Mon Sep 17 00:00:00 2001 From: Jay <76460079+PZDonny@users.noreply.github.com> Date: Wed, 21 Oct 2026 04:18:54 -0500 Subject: [PATCH 10/22] Make `DisplayStorage`'s logger `protected` --- .../net/donnypz/displayentityutils/database/DisplayStorage.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/main/java/net/donnypz/displayentityutils/database/DisplayStorage.java b/api/src/main/java/net/donnypz/displayentityutils/database/DisplayStorage.java index d0d30368..7da8d6b1 100644 --- a/api/src/main/java/net/donnypz/displayentityutils/database/DisplayStorage.java +++ b/api/src/main/java/net/donnypz/displayentityutils/database/DisplayStorage.java @@ -11,7 +11,7 @@ public abstract class DisplayStorage { - private final Logger logger; + protected final Logger logger; public DisplayStorage(Logger logger) { this.logger = logger; From 0e0dac609798cd47fa4efdb02fcd97bca3e99ee1 Mon Sep 17 00:00:00 2001 From: Jay <76460079+PZDonny@users.noreply.github.com> Date: Wed, 21 Oct 2026 04:23:40 -0500 Subject: [PATCH 11/22] Use `ComponentLogger` instead of `Logger` --- .../donnypz/displayentityutils/database/DisplayStorage.java | 6 +++--- .../displayentityutils/database/DBDisplayStorage.java | 4 ++-- .../displayentityutils/database/LocalDisplayStorage.java | 3 +-- .../displayentityutils/database/MYSQLDisplayStorage.java | 3 ++- .../displayentityutils/database/MongoDisplayStorage.java | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/api/src/main/java/net/donnypz/displayentityutils/database/DisplayStorage.java b/api/src/main/java/net/donnypz/displayentityutils/database/DisplayStorage.java index 7da8d6b1..b544a3a5 100644 --- a/api/src/main/java/net/donnypz/displayentityutils/database/DisplayStorage.java +++ b/api/src/main/java/net/donnypz/displayentityutils/database/DisplayStorage.java @@ -2,18 +2,18 @@ import net.donnypz.displayentityutils.utils.DisplayEntities.DisplayAnimation; import net.donnypz.displayentityutils.utils.DisplayEntities.DisplayEntityGroup; +import net.kyori.adventure.text.logger.slf4j.ComponentLogger; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.slf4j.Logger; import java.util.List; public abstract class DisplayStorage { - protected final Logger logger; + protected final ComponentLogger logger; - public DisplayStorage(Logger logger) { + public DisplayStorage(ComponentLogger logger) { this.logger = logger; } diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/database/DBDisplayStorage.java b/plugin/src/main/java/net/donnypz/displayentityutils/database/DBDisplayStorage.java index 881806a0..be85ee60 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/database/DBDisplayStorage.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/database/DBDisplayStorage.java @@ -1,10 +1,10 @@ package net.donnypz.displayentityutils.database; -import org.slf4j.Logger; +import net.kyori.adventure.text.logger.slf4j.ComponentLogger; public abstract class DBDisplayStorage extends DisplayStorage { - public DBDisplayStorage(Logger logger) { + public DBDisplayStorage(ComponentLogger logger) { super(logger); } diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/database/LocalDisplayStorage.java b/plugin/src/main/java/net/donnypz/displayentityutils/database/LocalDisplayStorage.java index 893f5435..093cf5c6 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/database/LocalDisplayStorage.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/database/LocalDisplayStorage.java @@ -14,7 +14,6 @@ import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.slf4j.Logger; import java.io.*; import java.nio.file.DirectoryStream; @@ -25,7 +24,7 @@ public final class LocalDisplayStorage extends DisplayStorage { - public LocalDisplayStorage(Logger logger) { + public LocalDisplayStorage(ComponentLogger logger) { super(logger); } diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/database/MYSQLDisplayStorage.java b/plugin/src/main/java/net/donnypz/displayentityutils/database/MYSQLDisplayStorage.java index 0e3fe99a..ede38c47 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/database/MYSQLDisplayStorage.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/database/MYSQLDisplayStorage.java @@ -11,6 +11,7 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.TextDecoration; +import net.kyori.adventure.text.logger.slf4j.ComponentLogger; import net.kyori.adventure.text.minimessage.MiniMessage; import org.apache.commons.dbutils.DbUtils; import org.bukkit.Bukkit; @@ -43,7 +44,7 @@ public final class MYSQLDisplayStorage extends DBDisplayStorage { private boolean connected = false; private HikariDataSource dataSource; - public MYSQLDisplayStorage(Logger logger) { + public MYSQLDisplayStorage(ComponentLogger logger) { super(logger); } diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/database/MongoDisplayStorage.java b/plugin/src/main/java/net/donnypz/displayentityutils/database/MongoDisplayStorage.java index 5d9887fd..8d123d3c 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/database/MongoDisplayStorage.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/database/MongoDisplayStorage.java @@ -17,6 +17,7 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.TextDecoration; +import net.kyori.adventure.text.logger.slf4j.ComponentLogger; import net.kyori.adventure.text.minimessage.MiniMessage; import org.bson.Document; import org.bson.conversions.Bson; @@ -25,7 +26,6 @@ import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.slf4j.Logger; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -47,7 +47,7 @@ public final class MongoDisplayStorage extends DBDisplayStorage { private MongoDatabase database; private boolean isConnected = false; - public MongoDisplayStorage(Logger logger) { + public MongoDisplayStorage(ComponentLogger logger) { super(logger); } From d0583d4e50dbedc300007980325d05d772bd28cf Mon Sep 17 00:00:00 2001 From: Jay <76460079+PZDonny@users.noreply.github.com> Date: Wed, 21 Oct 2026 04:24:42 -0500 Subject: [PATCH 12/22] Use `ComponentLogger` instead of `Logger` --- .../net/donnypz/displayentityutils/DisplayAPIInitializer.java | 4 ++-- .../net/donnypz/displayentityutils/DisplayEntityPlugin.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/DisplayAPIInitializer.java b/plugin/src/main/java/net/donnypz/displayentityutils/DisplayAPIInitializer.java index 9beab853..3620f377 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/DisplayAPIInitializer.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/DisplayAPIInitializer.java @@ -28,9 +28,9 @@ import net.donnypz.displayentityutils.utils.version.folia.SchedulerImpl; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.logger.slf4j.ComponentLogger; import org.bstats.bukkit.Metrics; import org.bukkit.Bukkit; -import org.slf4j.Logger; import java.util.Map; @@ -39,7 +39,7 @@ public final class DisplayAPIInitializer { private DisplayAPIInitializer() { } - public static void initializeImpls(Logger logger) { + public static void initializeImpls(ComponentLogger logger) { DisplayAPI.ANIMATION_PLAYER_SERVICE = new AnimationPlayerProviderImpl(); DisplayAPI.BDE_CONVERSION_HANDLER = new BDEConversionHandlerImpl(); DisplayAPI.SCHEDULER = new SchedulerImpl(); diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/DisplayEntityPlugin.java b/plugin/src/main/java/net/donnypz/displayentityutils/DisplayEntityPlugin.java index 1fe1a7b8..c7f79f73 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/DisplayEntityPlugin.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/DisplayEntityPlugin.java @@ -44,7 +44,7 @@ public void onLoad() { @Override public void onEnable() { DisplayAPI.plugin = this; - DisplayAPIInitializer.initializeImpls(this.getSLF4JLogger()); + DisplayAPIInitializer.initializeImpls(this.getComponentLogger()); getConfig().options().copyDefaults(true); reloadPlugin(true); From afe0ed8dbbfca3954cc403b56f1525f9bf097908 Mon Sep 17 00:00:00 2001 From: Jay <76460079+PZDonny@users.noreply.github.com> Date: Wed, 21 Oct 2026 06:11:24 -0500 Subject: [PATCH 13/22] Use `CommonDisplayStorageUtils` to reduce redundant code --- .../database/LocalDisplayStorage.java | 21 +++---------------- 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/database/LocalDisplayStorage.java b/plugin/src/main/java/net/donnypz/displayentityutils/database/LocalDisplayStorage.java index 093cf5c6..51993e3a 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/database/LocalDisplayStorage.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/database/LocalDisplayStorage.java @@ -10,6 +10,7 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.TextDecoration; +import net.kyori.adventure.text.logger.slf4j.ComponentLogger; import net.kyori.adventure.text.minimessage.MiniMessage; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; @@ -36,15 +37,7 @@ public boolean isEnabled() { @Override public boolean saveDisplayEntityGroup(@NotNull DisplayEntityGroup displayEntityGroup, @Nullable Player saver){ try{ - ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); - GZIPOutputStream gzipOut = new GZIPOutputStream(byteOut); - ObjectOutputStream objOut = new ObjectOutputStream(gzipOut); - objOut.writeObject(displayEntityGroup); - - gzipOut.close(); - objOut.close(); - - byte[] data = byteOut.toByteArray(); + byte[] data = CommonDisplayStorageUtils.toByteArray(displayEntityGroup); File saveFile = new File(PluginFolders.groupSaveFolder, "/"+displayEntityGroup.getTag()+DisplayEntityGroup.FILE_EXTENSION); if (saveFile.exists()){ @@ -103,15 +96,7 @@ public void deleteDisplayEntityGroup(@NotNull String tag, @Nullable Player delet @Override public boolean saveDisplayAnimation(@NotNull DisplayAnimation displayAnimation, @Nullable Player saver){ try{ - ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); - GZIPOutputStream gzipOut = new GZIPOutputStream(byteOut); - ObjectOutputStream objOut = new ObjectOutputStream(gzipOut); - objOut.writeObject(displayAnimation); - gzipOut.close(); - objOut.close(); - - byte[] data = byteOut.toByteArray(); - byteOut.close(); + byte[] data = CommonDisplayStorageUtils.toByteArray(displayAnimation); File saveFile = new File(PluginFolders.animSaveFolder, "/"+displayAnimation.getAnimationTag()+DisplayAnimation.FILE_EXTENSION); if (saveFile.exists()){ From c3411156c591cd51789d5fadc662d9158d8568b6 Mon Sep 17 00:00:00 2001 From: Jay <76460079+PZDonny@users.noreply.github.com> Date: Wed, 21 Oct 2026 06:12:16 -0500 Subject: [PATCH 14/22] Use logger instead of console sender --- .../displayentityutils/database/MYSQLDisplayStorage.java | 3 ++- .../displayentityutils/database/MongoDisplayStorage.java | 9 ++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/database/MYSQLDisplayStorage.java b/plugin/src/main/java/net/donnypz/displayentityutils/database/MYSQLDisplayStorage.java index ede38c47..f6a00213 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/database/MYSQLDisplayStorage.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/database/MYSQLDisplayStorage.java @@ -107,9 +107,10 @@ void connect( DbUtils.closeQuietly(statement); DbUtils.closeQuietly(connection); - Bukkit.getConsoleSender().sendMessage(DisplayAPI.pluginPrefix.append(MiniMessage.miniMessage().deserialize("Successfully connected to MYSQL!"))); + logger.info(DisplayAPI.pluginPrefix.append(MiniMessage.miniMessage().deserialize("Successfully connected to MYSQL!"))); connected = true; } catch (SQLException e) { + logger.error(DisplayAPI.pluginPrefix.append(Component.text("There was an error connecting to the MYSQL database", NamedTextColor.RED))); e.printStackTrace(); Bukkit.getConsoleSender().sendMessage(DisplayAPI.pluginPrefix.append(Component.text("There was an error connecting to the MYSQL database", NamedTextColor.RED))); closeConnection(); diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/database/MongoDisplayStorage.java b/plugin/src/main/java/net/donnypz/displayentityutils/database/MongoDisplayStorage.java index 8d123d3c..578098c8 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/database/MongoDisplayStorage.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/database/MongoDisplayStorage.java @@ -22,7 +22,6 @@ import org.bson.Document; import org.bson.conversions.Bson; import org.bson.types.Binary; -import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -103,12 +102,12 @@ public void createConnection( GROUP_COLLECTION = database.getCollection(groupCollection); ANIMATION_COLLECTION = database.getCollection(animationCollection); - Bukkit.getConsoleSender().sendMessage(DisplayAPI.pluginPrefix.append(MiniMessage.miniMessage().deserialize("Successfully connected to MongoDB!"))); + logger.info(DisplayAPI.pluginPrefix.append(MiniMessage.miniMessage().deserialize("Successfully connected to MongoDB!"))); isConnected = true; } catch (IllegalArgumentException | MongoException e){ isConnected = false; - Bukkit.getConsoleSender().sendMessage(Component.text("There was an error connecting to the MongoDB Database!", NamedTextColor.RED)); + logger.error(Component.text("There was an error connecting to the MongoDB Database!", NamedTextColor.RED)); e.printStackTrace(); } }); @@ -122,7 +121,7 @@ private boolean canConnect( if (isConnected()) return false; if (databaseName.isBlank() || groupCollection.isBlank() || animationCollection.isBlank()){ - Bukkit.getConsoleSender().sendMessage(Component.text("There was an error connecting to the MongoDB Database! Database and/or Collection names are empty!", NamedTextColor.RED)); + logger.error(Component.text("There was an error connecting to the MongoDB Database! Database and/or Collection names are empty!", NamedTextColor.RED)); isConnected = false; return false; } @@ -160,8 +159,8 @@ public void closeConnection(){ } catch(MongoException e){ isConnected = false; + logger.error(Component.text("There was an error closing the connection to the MongoDB Database", NamedTextColor.RED)); e.printStackTrace(); - Bukkit.getConsoleSender().sendMessage(Component.text("There was an error closing the connection to the MongoDB Database", NamedTextColor.RED)); } } From 28c8dfb1af78636834778a9e6f07da6ac7eb1439 Mon Sep 17 00:00:00 2001 From: Jay <76460079+PZDonny@users.noreply.github.com> Date: Wed, 21 Oct 2026 06:13:17 -0500 Subject: [PATCH 15/22] Fix NPE when an error when trying to msg a player deleting an animation/group --- .../displayentityutils/database/MYSQLDisplayStorage.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/database/MYSQLDisplayStorage.java b/plugin/src/main/java/net/donnypz/displayentityutils/database/MYSQLDisplayStorage.java index f6a00213..c551c666 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/database/MYSQLDisplayStorage.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/database/MYSQLDisplayStorage.java @@ -347,7 +347,9 @@ private void deleteEntity(String tag, Player deleter, String tableName, String d } catch(SQLException e){ e.printStackTrace(); - deleter.sendMessage(MiniMessage.miniMessage().deserialize("- Saved "+displayName+" does not exist in MYSQL database!")); + if (deleter != null){ + deleter.sendMessage(MiniMessage.miniMessage().deserialize("- Saved "+displayName+" does not exist in MYSQL database!")); + } } } From 54af8a25a9b574dca786ff5b0fdee57869eba125 Mon Sep 17 00:00:00 2001 From: Jay <76460079+PZDonny@users.noreply.github.com> Date: Wed, 21 Oct 2026 06:14:03 -0500 Subject: [PATCH 16/22] Fix deleting through `MySQL` not working as intended --- .../displayentityutils/database/MYSQLDisplayStorage.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/database/MYSQLDisplayStorage.java b/plugin/src/main/java/net/donnypz/displayentityutils/database/MYSQLDisplayStorage.java index c551c666..92d12205 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/database/MYSQLDisplayStorage.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/database/MYSQLDisplayStorage.java @@ -340,7 +340,7 @@ private void deleteEntity(String tag, Player deleter, String tableName, String d } statement.setString(1, tag); - statement.executeUpdate(delete); + statement.executeUpdate(); if (deleter != null){ deleter.sendMessage(MiniMessage.miniMessage().deserialize("- Successfully deleted "+displayName+" from MYSQL database!")); } From 828c5dba61295271c37257373fb5cc13fa54dac6 Mon Sep 17 00:00:00 2001 From: Jay <76460079+PZDonny@users.noreply.github.com> Date: Wed, 21 Oct 2026 06:14:46 -0500 Subject: [PATCH 17/22] let try-with-resource close `blobStream` --- .../donnypz/displayentityutils/database/MYSQLDisplayStorage.java | 1 - 1 file changed, 1 deletion(-) diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/database/MYSQLDisplayStorage.java b/plugin/src/main/java/net/donnypz/displayentityutils/database/MYSQLDisplayStorage.java index 92d12205..54b63fe4 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/database/MYSQLDisplayStorage.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/database/MYSQLDisplayStorage.java @@ -299,7 +299,6 @@ private boolean saveEntity(String tag, Object entity, String tableName, String d } } statement.executeUpdate(); - blobStream.close(); if (saver != null) { saver.sendMessage(MiniMessage.miniMessage().deserialize("- Successfully saved "+displayName+" to MYSQL!")); } From 2a2d3f970c2253e5ad32bfa71214dfa899668126 Mon Sep 17 00:00:00 2001 From: Jay <76460079+PZDonny@users.noreply.github.com> Date: Wed, 21 Oct 2026 06:32:55 -0500 Subject: [PATCH 18/22] move connection logic outside of scheduler to `#connect` --- .../database/MongoDisplayStorage.java | 65 +++++++++++-------- 1 file changed, 39 insertions(+), 26 deletions(-) diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/database/MongoDisplayStorage.java b/plugin/src/main/java/net/donnypz/displayentityutils/database/MongoDisplayStorage.java index 578098c8..41b75e3b 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/database/MongoDisplayStorage.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/database/MongoDisplayStorage.java @@ -84,35 +84,48 @@ public void createConnection( if (!this.canConnect(databaseName, groupCollection, animationCollection)) return; DisplayAPI.getScheduler().runAsync(() -> { - try{ - ConnectionString cString = new ConnectionString(connectionString); - MongoClientSettings settings = MongoClientSettings.builder() - .applyConnectionString(cString) - .serverApi(ServerApi.builder() - .version(ServerApiVersion.V1) - .build()) - .build(); - - client = MongoClients.create(settings); - database = client.getDatabase(databaseName); - - this.createIfNotExisting(groupCollection); - this.createIfNotExisting(animationCollection); - - GROUP_COLLECTION = database.getCollection(groupCollection); - ANIMATION_COLLECTION = database.getCollection(animationCollection); - - logger.info(DisplayAPI.pluginPrefix.append(MiniMessage.miniMessage().deserialize("Successfully connected to MongoDB!"))); - isConnected = true; - } - catch (IllegalArgumentException | MongoException e){ - isConnected = false; - logger.error(Component.text("There was an error connecting to the MongoDB Database!", NamedTextColor.RED)); - e.printStackTrace(); - } + connect( + connectionString, + databaseName, + groupCollection, + animationCollection + ); }); } + void connect( + String connectionString, + String databaseName, + String groupCollection, + String animationCollection + ) { + try { + ConnectionString cString = new ConnectionString(connectionString); + MongoClientSettings settings = MongoClientSettings.builder() + .applyConnectionString(cString) + .serverApi(ServerApi.builder() + .version(ServerApiVersion.V1) + .build()) + .build(); + + client = MongoClients.create(settings); + database = client.getDatabase(databaseName); + + this.createIfNotExisting(groupCollection); + this.createIfNotExisting(animationCollection); + + GROUP_COLLECTION = database.getCollection(groupCollection); + ANIMATION_COLLECTION = database.getCollection(animationCollection); + + logger.info(DisplayAPI.pluginPrefix.append(MiniMessage.miniMessage().deserialize("Successfully connected to MongoDB!"))); + isConnected = true; + } catch (IllegalArgumentException | MongoException e) { + isConnected = false; + logger.error(Component.text("There was an error connecting to the MongoDB Database!", NamedTextColor.RED)); + e.printStackTrace(); + } + } + private boolean canConnect( String databaseName, String groupCollection, From 2ed709d54de38e82d06e68f70e3145c0c7daf0a9 Mon Sep 17 00:00:00 2001 From: Jay <76460079+PZDonny@users.noreply.github.com> Date: Wed, 21 Oct 2026 06:38:13 -0500 Subject: [PATCH 19/22] Fixed an issue where animations were attempting to be saved and retrieved to/from group collections --- .../database/MongoDisplayStorage.java | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/database/MongoDisplayStorage.java b/plugin/src/main/java/net/donnypz/displayentityutils/database/MongoDisplayStorage.java index 41b75e3b..9e4bad96 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/database/MongoDisplayStorage.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/database/MongoDisplayStorage.java @@ -207,7 +207,7 @@ public void deleteDisplayEntityGroup(@NotNull String tag, @Nullable Player delet @Override public @Nullable DisplayEntityGroup getDisplayEntityGroup(@NotNull String tag){ if (!isConnected) return null; - Document doc = getGroupDocument(tag); + Document doc = getDocument(GROUP_COLLECTION, tag); if (doc == null){ return null; } @@ -245,7 +245,7 @@ public void deleteDisplayAnimation(@NotNull String tag, @Nullable Player deleter if (!isConnected){ return null; } - Document doc = getAnimationDocument(tag); + Document doc = getDocument(ANIMATION_COLLECTION, tag); if (doc == null){ return null; } @@ -324,7 +324,7 @@ private boolean saveEntity( .append(TAG_FIELD, tag) .append(fieldName, data); - Document existing = getGroupDocument(tag); + Document existing = getDocument(collection, tag); if (existing != null){ if (DisplayConfig.overwritexistingSaves()){ Bson updateOperation = new Document ("$set", doc); @@ -364,7 +364,7 @@ private void deleteEntity( Player deleter ){ if (!isConnected()) return; - Document doc = getGroupDocument(tag); + Document doc = getDocument(collection, tag); if (doc != null){ collection.deleteOne(doc); if (deleter != null){ @@ -377,14 +377,16 @@ private void deleteEntity( } } - private Document getGroupDocument(String tag){ - return GROUP_COLLECTION.find(new Document(TAG_FIELD, tag)) - .first(); - } - - private Document getAnimationDocument(String tag){ - return ANIMATION_COLLECTION.find(new Document(TAG_FIELD, tag)) - .first(); + private Document getDocument(MongoCollection collection, String tag){ + if (collection == GROUP_COLLECTION){ + return GROUP_COLLECTION.find(new Document(TAG_FIELD, tag)) + .first(); + } + else if (collection == ANIMATION_COLLECTION){ + return ANIMATION_COLLECTION.find(new Document(TAG_FIELD, tag)) + .first(); + } + else return null; } } From bcc7d5c6df9ae19cc889def35f38816419475843 Mon Sep 17 00:00:00 2001 From: Jay <76460079+PZDonny@users.noreply.github.com> Date: Wed, 21 Oct 2026 06:42:35 -0500 Subject: [PATCH 20/22] Added integration tests for MySQL and MongoDB storages --- plugin/pom.xml | 24 ++++ .../database/MYSQLDisplayStorage.java | 9 +- .../database/MongoDisplayStorage.java | 5 +- .../test/animations/sp_sp_summon.deanim | Bin 0 -> 26337 bytes .../test/animations/wave_anim_wave.deanim | Bin 0 -> 957 bytes .../resources/test/groups/star_platinum.deg | Bin 0 -> 22562 bytes .../src/main/resources/test/groups/wumpus.deg | Bin 0 -> 8038 bytes .../database/DBStorageTest.java | 129 ++++++++++++++++++ .../database/MongoStorageTestIT.java | 46 +++++++ .../database/MySQLStorageTestIT.java | 58 ++++++++ 10 files changed, 262 insertions(+), 9 deletions(-) create mode 100644 plugin/src/main/resources/test/animations/sp_sp_summon.deanim create mode 100644 plugin/src/main/resources/test/animations/wave_anim_wave.deanim create mode 100644 plugin/src/main/resources/test/groups/star_platinum.deg create mode 100644 plugin/src/main/resources/test/groups/wumpus.deg create mode 100644 plugin/src/test/java/net/donnypz/displayentityutils/database/DBStorageTest.java create mode 100644 plugin/src/test/java/net/donnypz/displayentityutils/database/MongoStorageTestIT.java create mode 100644 plugin/src/test/java/net/donnypz/displayentityutils/database/MySQLStorageTestIT.java diff --git a/plugin/pom.xml b/plugin/pom.xml index a395504b..14c01c35 100644 --- a/plugin/pom.xml +++ b/plugin/pom.xml @@ -26,6 +26,7 @@ deg + deanim @@ -150,6 +151,29 @@ 2.12.2 provided + + + + org.testcontainers + mysql + 1.21.4 + test + + + + com.mysql + mysql-connector-j + 8.3.0 + test + + + + + org.testcontainers + mongodb + 1.21.4 + test + diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/database/MYSQLDisplayStorage.java b/plugin/src/main/java/net/donnypz/displayentityutils/database/MYSQLDisplayStorage.java index 54b63fe4..38dde95b 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/database/MYSQLDisplayStorage.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/database/MYSQLDisplayStorage.java @@ -14,11 +14,9 @@ import net.kyori.adventure.text.logger.slf4j.ComponentLogger; import net.kyori.adventure.text.minimessage.MiniMessage; import org.apache.commons.dbutils.DbUtils; -import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.slf4j.Logger; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -30,11 +28,11 @@ public final class MYSQLDisplayStorage extends DBDisplayStorage { - private static final String GROUP_TABLE = "saved_displays"; + static final String GROUP_TABLE = "saved_displays"; private static final String GROUP_COLUMN = "display_group"; private static final String GROUP_DISPLAY_NAME = "display entity group"; - private static final String ANIMATION_TABLE = "saved_animations"; + static final String ANIMATION_TABLE = "saved_animations"; private static final String ANIMATION_COLUMN = "display_anim"; private static final String ANIMATION_DISPLAY_NAME = "animation"; @@ -112,7 +110,6 @@ void connect( } catch (SQLException e) { logger.error(DisplayAPI.pluginPrefix.append(Component.text("There was an error connecting to the MYSQL database", NamedTextColor.RED))); e.printStackTrace(); - Bukkit.getConsoleSender().sendMessage(DisplayAPI.pluginPrefix.append(Component.text("There was an error connecting to the MYSQL database", NamedTextColor.RED))); closeConnection(); } } @@ -139,7 +136,7 @@ public boolean isConnected() { return connected; } - private Connection getConnection(){ + Connection getConnection(){ try{ return dataSource.getConnection(); } diff --git a/plugin/src/main/java/net/donnypz/displayentityutils/database/MongoDisplayStorage.java b/plugin/src/main/java/net/donnypz/displayentityutils/database/MongoDisplayStorage.java index 9e4bad96..0182991a 100644 --- a/plugin/src/main/java/net/donnypz/displayentityutils/database/MongoDisplayStorage.java +++ b/plugin/src/main/java/net/donnypz/displayentityutils/database/MongoDisplayStorage.java @@ -32,11 +32,11 @@ public final class MongoDisplayStorage extends DBDisplayStorage { - private static MongoCollection GROUP_COLLECTION; + static MongoCollection GROUP_COLLECTION; private static final String GROUP_FIELD = "displayGroup"; private static final String GROUP_DISPLAY_NAME = "display entity group"; - private static MongoCollection ANIMATION_COLLECTION; + static MongoCollection ANIMATION_COLLECTION; private static final String ANIMATION_FIELD = "displayAnimation"; private static final String ANIMATION_DISPLAY_NAME = "animation"; @@ -388,5 +388,4 @@ else if (collection == ANIMATION_COLLECTION){ } else return null; } - } diff --git a/plugin/src/main/resources/test/animations/sp_sp_summon.deanim b/plugin/src/main/resources/test/animations/sp_sp_summon.deanim new file mode 100644 index 0000000000000000000000000000000000000000..f73467a28331eb1b1b74c9211f56eb2e65cb919e GIT binary patch literal 26337 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 literal 0 HcmV?d00001 diff --git a/plugin/src/main/resources/test/groups/star_platinum.deg b/plugin/src/main/resources/test/groups/star_platinum.deg new file mode 100644 index 0000000000000000000000000000000000000000..796bdf5ecf81691d01c6fef7d372fe574c161f22 GIT binary patch literal 22562 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 JgO=fk006M-nT!Ac literal 0 HcmV?d00001 diff --git a/plugin/src/main/resources/test/groups/wumpus.deg b/plugin/src/main/resources/test/groups/wumpus.deg new file mode 100644 index 0000000000000000000000000000000000000000..d11b6cb0b57d0dee1cf414275efd9aa92c1aa3f5 GIT binary patch literal 8038 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 mysql = new MySQLContainer<>(imageName) + .withDatabaseName("db"); + + @BeforeAll + void beforeAll() { + DisplayAPIInitializer.initializeImpls(log); + storage = DisplayAPI.getStorage(LoadMethod.MYSQL); + mysql.start(); + } + + @AfterAll + void afterAll() { + mysql.stop(); + } + + @Override + protected void connect() { + ((MYSQLDisplayStorage) storage).connect( + mysql.getJdbcUrl(), + mysql.getUsername(), + mysql.getPassword() + ); + } + + + @Override + public void wipeTables() { + try (Connection c = ((MYSQLDisplayStorage) storage).getConnection()) { + Statement st = c.createStatement(); + + st.execute("TRUNCATE TABLE " + MYSQLDisplayStorage.GROUP_TABLE); + st.execute("TRUNCATE TABLE " + MYSQLDisplayStorage.ANIMATION_TABLE); + } catch (SQLException e) { + throw new RuntimeException("Failed to clean up test data", e); + } + } +} From 4b2d421a7faf84aca11b7733a2d814bb3bf719d7 Mon Sep 17 00:00:00 2001 From: Jay <76460079+PZDonny@users.noreply.github.com> Date: Wed, 21 Oct 2026 08:08:06 -0500 Subject: [PATCH 21/22] improve command usage validation tests --- .../command/CommandUsageTests.java | 85 +++++++++++++------ 1 file changed, 61 insertions(+), 24 deletions(-) diff --git a/plugin/src/test/java/net/donnypz/displayentityutils/command/CommandUsageTests.java b/plugin/src/test/java/net/donnypz/displayentityutils/command/CommandUsageTests.java index cb74b11d..f02e3609 100644 --- a/plugin/src/test/java/net/donnypz/displayentityutils/command/CommandUsageTests.java +++ b/plugin/src/test/java/net/donnypz/displayentityutils/command/CommandUsageTests.java @@ -3,11 +3,17 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertEquals; @TestInstance(TestInstance.Lifecycle.PER_CLASS) public class CommandUsageTests { + record CommandCase(String arg1, String arg2, String commandSubArgs){ } + DisplayEntityPluginCommand deuCmd; @BeforeAll @@ -15,31 +21,62 @@ void before(){ this.deuCmd = new DisplayEntityPluginCommand(); } + Stream shortCommandUsageCases(){ + return Stream.of( + new CommandCase("group", "spawn", null), + new CommandCase("group", "save", null), + new CommandCase("group", "despawnat", null), + new CommandCase("group", "ride", null), + new CommandCase("anim", "addframe", null), + new CommandCase("anim", "editframe", null), + new CommandCase("anim", "frameinfo", null), + new CommandCase("anim", "showpoints", null), + new CommandCase("anim", "drawpoints", null), + new CommandCase("anim", "drawpos", null), + new CommandCase("text", "edit", null), + new CommandCase("parts", "create", null), + new CommandCase("mannequin", "mainhand", null), + new CommandCase("mannequin", "togglegravity", null) + ); + } + + @ParameterizedTest + @MethodSource("shortCommandUsageCases") + void shouldBeValidShortCommandUsage(CommandCase commandCase){ + String arg1 = commandCase.arg1; + String arg2 = commandCase.arg2; + + DEUSubCommand subCmd = deuCmd + .getCommand(arg1) + .getCommand(arg2); + + assertEquals("/deu "+arg1+" "+arg2, subCmd.getShortCommandUsage()); + } + + Stream fullCommandUsageCases() { + return Stream.of( + new CommandCase("group", "spawn", " [-packet]"), + new CommandCase("group", "save", ""), + new CommandCase("group", "despawnat", " [-force] [-packet ] [-world ]"), + new CommandCase("group", "ride", "<-target | player-name | entity-uuid> [group-tag] [storage] [controller-id]"), + new CommandCase("anim", "addframe", " "), + new CommandCase("anim", "drawpoints", " [points-per-frame]"), + new CommandCase("parts", "create", " [-addtogroup]"), + new CommandCase("mannequin", "togglegravity", "[-all ]") + ); + } + @ParameterizedTest - @ValueSource(strings = { - "group,spawn", - "group,save", - "group,despawnat", - "group,ride", - "anim,addframe", - "anim,editframe", - "anim,frameinfo", - "anim,showpoints", - "anim,drawpoints", - "anim,drawpos", - "text,edit", - "parts,create", - "mannequin,mainhand", - "mannequin,togglegravity" - }) - void testCmd(String cmds){ - String[] split = cmds.split(","); + @MethodSource("fullCommandUsageCases") + void shouldBeValidFullCommandUsage(CommandCase commandCase){ + String arg1 = commandCase.arg1; + String arg2 = commandCase.arg2; + String cmdArgs = commandCase.commandSubArgs; + DEUSubCommand subCmd = deuCmd - .getCommand(split[0]) - .getCommand(split[1]); + .getCommand(arg1) + .getCommand(arg2); - System.out.println(); - System.out.println(subCmd.getShortCommandUsage()); - System.out.println(subCmd.getCommandUsage()); + assertEquals("/deu "+arg1+" "+arg2+" "+cmdArgs, subCmd.getCommandUsage()); } } From 2125dc550f0d2e54e99557ded6019289dc705766 Mon Sep 17 00:00:00 2001 From: Jay <76460079+PZDonny@users.noreply.github.com> Date: Wed, 21 Oct 2026 08:11:01 -0500 Subject: [PATCH 22/22] 3.8.1 release notes --- docs/release-notes/3.8.1.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 docs/release-notes/3.8.1.md diff --git a/docs/release-notes/3.8.1.md b/docs/release-notes/3.8.1.md new file mode 100644 index 00000000..2a1f2a04 --- /dev/null +++ b/docs/release-notes/3.8.1.md @@ -0,0 +1,19 @@ +**Release Highlights** +- Breaking API Changes 🔨 +- Bug Fixes 🐛 + +## API Changes +> [!NOTE] +>**New Version:** `3.8.1` + +### Breaking Changes +- Removed `IS_DEV_VERSION` field from `VersionUtils` +- Changed `DisplayStorage` and `DBDisplayStorage` to abstract classes to accept Logger in constructor + - May require recompilation of plugins depending on `DisplayEntityUtils` + + +## Bug Fixes / QOL +- Corrected logic for `VersionUtils#cleanVersionString` +- Fixed an `NPE` that occurred when trying to message a player after deleting groups/animations +- Fixed deleting groups/animations not working as intended when using `MySQL` storage +- Fixed saving/deleting animations not working as intended when using `MongoDB` storage