From 17fd2ec5704ab3da2d396141e759d26dfb7d2881 Mon Sep 17 00:00:00 2001 From: TheBusyBiscuit Date: Sat, 23 Jan 2021 19:38:59 +0100 Subject: [PATCH] Added AsyncProfileLoadEvent --- CHANGELOG.md | 1 + .../api/events/AsyncProfileLoadEvent.java | 75 +++++++++++++++ .../slimefun4/api/player/PlayerProfile.java | 26 ++++-- .../testing/tests/profiles/MockProfile.java | 13 +++ .../profiles/TestAsyncProfileLoadEvent.java | 93 +++++++++++++++++++ 5 files changed, 198 insertions(+), 10 deletions(-) create mode 100644 src/main/java/io/github/thebusybiscuit/slimefun4/api/events/AsyncProfileLoadEvent.java create mode 100644 src/test/java/io/github/thebusybiscuit/slimefun4/testing/tests/profiles/MockProfile.java create mode 100644 src/test/java/io/github/thebusybiscuit/slimefun4/testing/tests/profiles/TestAsyncProfileLoadEvent.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 8dec7c590..400880a73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ #### Additions * Added a new language: Bulgarian * Added a new language: Hebrew +* (API) Added AsyncProfileLoadEvent #### Changes * Massive performance improvements to holograms/armorstands diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/api/events/AsyncProfileLoadEvent.java b/src/main/java/io/github/thebusybiscuit/slimefun4/api/events/AsyncProfileLoadEvent.java new file mode 100644 index 000000000..b05f8e5c3 --- /dev/null +++ b/src/main/java/io/github/thebusybiscuit/slimefun4/api/events/AsyncProfileLoadEvent.java @@ -0,0 +1,75 @@ +package io.github.thebusybiscuit.slimefun4.api.events; + +import java.util.UUID; + +import javax.annotation.Nonnull; + +import org.apache.commons.lang.Validate; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +import io.github.thebusybiscuit.slimefun4.api.player.PlayerProfile; + +/** + * This {@link Event} is called when the {@link PlayerProfile} of a {@link Player} + * is loaded into memory. + * The {@link AsyncProfileLoadEvent} is called asynchronously and can be used to "inject" + * a custom {@link PlayerProfile} if necessary. + * + * @author TheBusyBiscuit + * + * @see PlayerProfile + * + */ +public class AsyncProfileLoadEvent extends Event { + + private static final HandlerList handlers = new HandlerList(); + + private final UUID uniqueId; + private PlayerProfile profile; + + public AsyncProfileLoadEvent(@Nonnull PlayerProfile profile) { + super(true); + + Validate.notNull(profile, "The Profile cannot be null"); + + this.uniqueId = profile.getUUID(); + this.profile = profile; + } + + @Nonnull + public UUID getPlayerUUID() { + return uniqueId; + } + + @Nonnull + public PlayerProfile getProfile() { + return profile; + } + + /** + * This method can be used to inject your custom {@link PlayerProfile} implementations. + * However, the passed {@link PlayerProfile} must have the same {@link UUID} as the original one! + * + * @param profile + * The {@link PlayerProfile} + */ + public void setProfile(@Nonnull PlayerProfile profile) { + Validate.notNull(profile, "The PlayerProfile cannot be null!"); + Validate.isTrue(profile.getUUID().equals(uniqueId), "Cannot inject a PlayerProfile with a different UUID"); + + this.profile = profile; + } + + @Nonnull + public static HandlerList getHandlerList() { + return handlers; + } + + @Nonnull + @Override + public HandlerList getHandlers() { + return getHandlerList(); + } +} diff --git a/src/main/java/io/github/thebusybiscuit/slimefun4/api/player/PlayerProfile.java b/src/main/java/io/github/thebusybiscuit/slimefun4/api/player/PlayerProfile.java index b598a33c0..76ad95838 100644 --- a/src/main/java/io/github/thebusybiscuit/slimefun4/api/player/PlayerProfile.java +++ b/src/main/java/io/github/thebusybiscuit/slimefun4/api/player/PlayerProfile.java @@ -1,6 +1,5 @@ package io.github.thebusybiscuit.slimefun4.api.player; -import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -33,6 +32,7 @@ import com.google.common.collect.ImmutableSet; import io.github.thebusybiscuit.cscorelib2.chat.ChatColors; import io.github.thebusybiscuit.cscorelib2.config.Config; +import io.github.thebusybiscuit.slimefun4.api.events.AsyncProfileLoadEvent; import io.github.thebusybiscuit.slimefun4.api.gps.Waypoint; import io.github.thebusybiscuit.slimefun4.api.items.HashedArmorpiece; import io.github.thebusybiscuit.slimefun4.core.attributes.ProtectionType; @@ -56,7 +56,7 @@ import io.github.thebusybiscuit.slimefun4.utils.PatternUtils; * @see HashedArmorpiece * */ -public final class PlayerProfile { +public class PlayerProfile { private final UUID uuid; private final String name; @@ -74,13 +74,17 @@ public final class PlayerProfile { private final HashedArmorpiece[] armor = { new HashedArmorpiece(), new HashedArmorpiece(), new HashedArmorpiece(), new HashedArmorpiece() }; - private PlayerProfile(@Nonnull OfflinePlayer p) { + protected PlayerProfile(@Nonnull OfflinePlayer p) { this.uuid = p.getUniqueId(); this.name = p.getName(); - configFile = new Config(new File("data-storage/Slimefun/Players/" + uuid.toString() + ".yml")); + configFile = new Config("data-storage/Slimefun/Players/" + uuid.toString() + ".yml"); waypointsFile = new Config("data-storage/Slimefun/waypoints/" + uuid.toString() + ".yml"); + loadProfileData(); + } + + private void loadProfileData() { for (Research research : SlimefunPlugin.getRegistry().getResearches()) { if (configFile.contains("researches." + research.getID())) { researches.add(research); @@ -95,7 +99,7 @@ public final class PlayerProfile { waypoints.add(new Waypoint(this, key, loc, waypointName)); } } catch (Exception x) { - SlimefunPlugin.logger().log(Level.WARNING, x, () -> "Could not load Waypoint \"" + key + "\" for Player \"" + p.getName() + '"'); + SlimefunPlugin.logger().log(Level.WARNING, x, () -> "Could not load Waypoint \"" + key + "\" for Player \"" + name + '"'); } } } @@ -268,14 +272,14 @@ public final class PlayerProfile { * Call this method if the Player has left. * The profile can then be removed from RAM. */ - public void markForDeletion() { + public final void markForDeletion() { markedForDeletion = true; } /** * Call this method if this Profile has unsaved changes. */ - public void markDirty() { + public final void markDirty() { dirty = true; } @@ -382,9 +386,11 @@ public final class PlayerProfile { } Bukkit.getScheduler().runTaskAsynchronously(SlimefunPlugin.instance(), () -> { - PlayerProfile pp = new PlayerProfile(p); - SlimefunPlugin.getRegistry().getPlayerProfiles().put(uuid, pp); - callback.accept(pp); + AsyncProfileLoadEvent event = new AsyncProfileLoadEvent(new PlayerProfile(p)); + Bukkit.getPluginManager().callEvent(event); + + SlimefunPlugin.getRegistry().getPlayerProfiles().put(uuid, event.getProfile()); + callback.accept(event.getProfile()); }); return false; diff --git a/src/test/java/io/github/thebusybiscuit/slimefun4/testing/tests/profiles/MockProfile.java b/src/test/java/io/github/thebusybiscuit/slimefun4/testing/tests/profiles/MockProfile.java new file mode 100644 index 000000000..6f26c078f --- /dev/null +++ b/src/test/java/io/github/thebusybiscuit/slimefun4/testing/tests/profiles/MockProfile.java @@ -0,0 +1,13 @@ +package io.github.thebusybiscuit.slimefun4.testing.tests.profiles; + +import org.bukkit.OfflinePlayer; + +import io.github.thebusybiscuit.slimefun4.api.player.PlayerProfile; + +class MockProfile extends PlayerProfile { + + MockProfile(OfflinePlayer p) { + super(p); + } + +} diff --git a/src/test/java/io/github/thebusybiscuit/slimefun4/testing/tests/profiles/TestAsyncProfileLoadEvent.java b/src/test/java/io/github/thebusybiscuit/slimefun4/testing/tests/profiles/TestAsyncProfileLoadEvent.java new file mode 100644 index 000000000..3f542eed7 --- /dev/null +++ b/src/test/java/io/github/thebusybiscuit/slimefun4/testing/tests/profiles/TestAsyncProfileLoadEvent.java @@ -0,0 +1,93 @@ +package io.github.thebusybiscuit.slimefun4.testing.tests.profiles; + +import org.bukkit.OfflinePlayer; +import org.bukkit.event.Event; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import be.seeseemelk.mockbukkit.MockBukkit; +import be.seeseemelk.mockbukkit.ServerMock; +import be.seeseemelk.mockbukkit.entity.OfflinePlayerMock; +import io.github.thebusybiscuit.slimefun4.api.events.AsyncProfileLoadEvent; +import io.github.thebusybiscuit.slimefun4.api.player.PlayerProfile; +import io.github.thebusybiscuit.slimefun4.implementation.SlimefunPlugin; +import io.github.thebusybiscuit.slimefun4.testing.TestUtilities; + +class TestAsyncProfileLoadEvent { + + private static ServerMock server; + + @BeforeAll + public static void load() { + server = MockBukkit.mock(); + MockBukkit.load(SlimefunPlugin.class); + } + + @AfterAll + public static void unload() { + MockBukkit.unmock(); + } + + @Test + @DisplayName("Test AsyncProfileLoadEvent being thrown") + void testEventFired() throws InterruptedException { + server.getPluginManager().clearEvents(); + + OfflinePlayer player = new OfflinePlayerMock("EventFire"); + TestUtilities.awaitProfile(player); + + server.getPluginManager().assertEventFired(AsyncProfileLoadEvent.class, Event::isAsynchronous); + } + + @Test + @DisplayName("Test AsyncProfileLoadEvent#getProfile()") + void testEventGetter() throws InterruptedException { + server.getPluginManager().clearEvents(); + + OfflinePlayer player = new OfflinePlayerMock("GetProfile"); + PlayerProfile profile = TestUtilities.awaitProfile(player); + + server.getPluginManager().assertEventFired(AsyncProfileLoadEvent.class, e -> e.getProfile().equals(profile)); + } + + @Test + @DisplayName("Test Profile Injection") + void testProfileInjection() throws InterruptedException { + OfflinePlayer player = new OfflinePlayerMock("SomePlayer"); + PlayerProfile profile = TestUtilities.awaitProfile(player); + PlayerProfile mockProfile = new MockProfile(player); + + AsyncProfileLoadEvent event = new AsyncProfileLoadEvent(profile); + + Assertions.assertEquals(player.getUniqueId(), event.getPlayerUUID()); + Assertions.assertEquals(profile, event.getProfile()); + Assertions.assertFalse(event.getProfile() instanceof MockProfile); + + event.setProfile(mockProfile); + + Assertions.assertEquals(mockProfile, event.getProfile()); + Assertions.assertTrue(event.getProfile() instanceof MockProfile); + } + + @Test + @DisplayName("Test Profile Mismatch") + void testProfileMismatch() throws InterruptedException { + OfflinePlayer player = new OfflinePlayerMock("ValidProfile"); + OfflinePlayer player2 = new OfflinePlayerMock("UnrelatedProfile"); + + PlayerProfile profile = TestUtilities.awaitProfile(player); + PlayerProfile profile2 = TestUtilities.awaitProfile(player2); + + AsyncProfileLoadEvent event = new AsyncProfileLoadEvent(profile); + + // Profile is null + Assertions.assertThrows(IllegalArgumentException.class, () -> event.setProfile(null)); + + // UUID mismatch + Assertions.assertThrows(IllegalArgumentException.class, () -> event.setProfile(profile2)); + } + +}