From 755452cd3c76597fb9fd2fa8da6f4b61c6d5f853 Mon Sep 17 00:00:00 2001 From: tastybento Date: Wed, 3 Jul 2024 15:43:17 -0700 Subject: [PATCH 1/4] Added support for serializing Pairs --- .../json/BentoboxTypeAdapterFactory.java | 4 ++ .../json/adapters/PairTypeAdapter.java | 41 +++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 src/main/java/world/bentobox/bentobox/database/json/adapters/PairTypeAdapter.java diff --git a/src/main/java/world/bentobox/bentobox/database/json/BentoboxTypeAdapterFactory.java b/src/main/java/world/bentobox/bentobox/database/json/BentoboxTypeAdapterFactory.java index a8faa19e1..9f90a4793 100644 --- a/src/main/java/world/bentobox/bentobox/database/json/BentoboxTypeAdapterFactory.java +++ b/src/main/java/world/bentobox/bentobox/database/json/BentoboxTypeAdapterFactory.java @@ -25,9 +25,11 @@ import world.bentobox.bentobox.database.json.adapters.FlagTypeAdapter; import world.bentobox.bentobox.database.json.adapters.ItemStackTypeAdapter; import world.bentobox.bentobox.database.json.adapters.LocationTypeAdapter; import world.bentobox.bentobox.database.json.adapters.MaterialTypeAdapter; +import world.bentobox.bentobox.database.json.adapters.PairTypeAdapter; import world.bentobox.bentobox.database.json.adapters.PotionEffectTypeAdapter; import world.bentobox.bentobox.database.json.adapters.VectorTypeAdapter; import world.bentobox.bentobox.database.json.adapters.WorldTypeAdapter; +import world.bentobox.bentobox.util.Pair; /** @@ -74,6 +76,8 @@ public class BentoboxTypeAdapterFactory implements TypeAdapterFactory { return (TypeAdapter) new WorldTypeAdapter(); } else if (Vector.class.isAssignableFrom(rawType)) { return (TypeAdapter) new VectorTypeAdapter(); + } else if (Pair.class.isAssignableFrom(rawType)) { + return (TypeAdapter) new PairTypeAdapter<>(); } else if (ConfigurationSerializable.class.isAssignableFrom(rawType)) { // This covers a lot of Bukkit objects return (TypeAdapter) new BukkitObjectTypeAdapter(gson.getAdapter(Map.class)); diff --git a/src/main/java/world/bentobox/bentobox/database/json/adapters/PairTypeAdapter.java b/src/main/java/world/bentobox/bentobox/database/json/adapters/PairTypeAdapter.java new file mode 100644 index 000000000..20d0fe7cb --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/database/json/adapters/PairTypeAdapter.java @@ -0,0 +1,41 @@ +package world.bentobox.bentobox.database.json.adapters; + +import java.io.IOException; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +import com.google.gson.Gson; +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +import world.bentobox.bentobox.util.Pair; + +// Custom TypeAdapter for Pair +public class PairTypeAdapter extends TypeAdapter> { + + @Override + public void write(JsonWriter out, Pair pair) throws IOException { + if (pair == null || pair.getKey() == null || pair.getValue() == null) { + return; + } + out.beginArray(); + out.value(new Gson().toJson(pair.getKey())); + out.value(new Gson().toJson(pair.getValue())); + out.endArray(); + } + + @Override + public Pair read(JsonReader in) throws IOException { + in.beginArray(); + Type typeX = ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0]; + X x = new Gson().fromJson(in.nextString(), typeX); + Type typeZ = ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[1]; + Z z = new Gson().fromJson(in.nextString(), typeZ); + in.endArray(); + if (x == null || z == null) { + return null; + } + return new Pair<>(x, z); + } +} \ No newline at end of file From f4604b4c270e83a2620fc733188127ace7959001 Mon Sep 17 00:00:00 2001 From: tastybento Date: Wed, 3 Jul 2024 16:39:43 -0700 Subject: [PATCH 2/4] Fix to make it work --- .../json/BentoboxTypeAdapterFactory.java | 9 +++- .../json/adapters/PairTypeAdapter.java | 45 +++++++++++-------- 2 files changed, 35 insertions(+), 19 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/database/json/BentoboxTypeAdapterFactory.java b/src/main/java/world/bentobox/bentobox/database/json/BentoboxTypeAdapterFactory.java index 9f90a4793..e7aca2f8b 100644 --- a/src/main/java/world/bentobox/bentobox/database/json/BentoboxTypeAdapterFactory.java +++ b/src/main/java/world/bentobox/bentobox/database/json/BentoboxTypeAdapterFactory.java @@ -1,5 +1,7 @@ package world.bentobox.bentobox.database.json; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; import java.util.Map; import org.bukkit.Location; @@ -77,7 +79,12 @@ public class BentoboxTypeAdapterFactory implements TypeAdapterFactory { } else if (Vector.class.isAssignableFrom(rawType)) { return (TypeAdapter) new VectorTypeAdapter(); } else if (Pair.class.isAssignableFrom(rawType)) { - return (TypeAdapter) new PairTypeAdapter<>(); + // Add Pair handling here with type safety + Type pairType = type.getType(); + ParameterizedType parameterizedType = (ParameterizedType) pairType; + Type xType = parameterizedType.getActualTypeArguments()[0]; + Type zType = parameterizedType.getActualTypeArguments()[1]; + return (TypeAdapter) new PairTypeAdapter<>(xType, zType); } else if (ConfigurationSerializable.class.isAssignableFrom(rawType)) { // This covers a lot of Bukkit objects return (TypeAdapter) new BukkitObjectTypeAdapter(gson.getAdapter(Map.class)); diff --git a/src/main/java/world/bentobox/bentobox/database/json/adapters/PairTypeAdapter.java b/src/main/java/world/bentobox/bentobox/database/json/adapters/PairTypeAdapter.java index 20d0fe7cb..ae36aa815 100644 --- a/src/main/java/world/bentobox/bentobox/database/json/adapters/PairTypeAdapter.java +++ b/src/main/java/world/bentobox/bentobox/database/json/adapters/PairTypeAdapter.java @@ -1,7 +1,6 @@ package world.bentobox.bentobox.database.json.adapters; import java.io.IOException; -import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import com.google.gson.Gson; @@ -11,31 +10,41 @@ import com.google.gson.stream.JsonWriter; import world.bentobox.bentobox.util.Pair; -// Custom TypeAdapter for Pair public class PairTypeAdapter extends TypeAdapter> { + private final Type xType; + private final Type zType; + + public PairTypeAdapter(Type xType, Type zType) { + this.xType = xType; + this.zType = zType; + } @Override public void write(JsonWriter out, Pair pair) throws IOException { - if (pair == null || pair.getKey() == null || pair.getValue() == null) { - return; - } - out.beginArray(); - out.value(new Gson().toJson(pair.getKey())); - out.value(new Gson().toJson(pair.getValue())); - out.endArray(); + out.beginObject(); + out.name("x"); + Gson gson = new Gson(); + gson.toJson(pair.getKey(), xType, out); + out.name("z"); + gson.toJson(pair.getValue(), zType, out); + out.endObject(); } @Override public Pair read(JsonReader in) throws IOException { - in.beginArray(); - Type typeX = ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0]; - X x = new Gson().fromJson(in.nextString(), typeX); - Type typeZ = ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[1]; - Z z = new Gson().fromJson(in.nextString(), typeZ); - in.endArray(); - if (x == null || z == null) { - return null; + X x = null; + Z z = null; + + in.beginObject(); + while (in.hasNext()) { + String name = in.nextName(); + if (name.equals("x")) { + x = new Gson().fromJson(in, xType); + } else if (name.equals("z")) { + z = new Gson().fromJson(in, zType); + } } + in.endObject(); return new Pair<>(x, z); } -} \ No newline at end of file +} From cb2d0e7efdd9aa1258b5f218ce63d763f65eacd0 Mon Sep 17 00:00:00 2001 From: tastybento Date: Fri, 5 Jul 2024 10:19:21 -0700 Subject: [PATCH 3/4] Add cache for name lookup --- .../bentobox/managers/PlayersManager.java | 14 +++- .../bentobox/managers/PlayersManagerTest.java | 64 +++++++++++++++++-- 2 files changed, 71 insertions(+), 7 deletions(-) diff --git a/src/main/java/world/bentobox/bentobox/managers/PlayersManager.java b/src/main/java/world/bentobox/bentobox/managers/PlayersManager.java index 957f1a37c..47c27bea9 100644 --- a/src/main/java/world/bentobox/bentobox/managers/PlayersManager.java +++ b/src/main/java/world/bentobox/bentobox/managers/PlayersManager.java @@ -3,6 +3,7 @@ package world.bentobox.bentobox.managers; import java.util.Collection; import java.util.Collections; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; @@ -30,7 +31,7 @@ public class PlayersManager { private Database handler; private final Database names; private final Map playerCache = new ConcurrentHashMap<>(); - + private final @NonNull List nameCache; private final Set inTeleport; // this needs databasing /** @@ -46,6 +47,7 @@ public class PlayersManager { handler = new Database<>(plugin, Players.class); // Set up the names database names = new Database<>(plugin, Names.class); + nameCache = names.loadObjects(); inTeleport = new HashSet<>(); } @@ -139,7 +141,7 @@ public class PlayersManager { // Not used } } - return names.loadObjects().stream().filter(n -> n.getUniqueId().equalsIgnoreCase(name)).findFirst() + return nameCache.stream().filter(n -> n.getUniqueId().equalsIgnoreCase(name)).findFirst() .map(Names::getUuid).orElse(null); } @@ -148,10 +150,18 @@ public class PlayersManager { * @param user - the User */ public void setPlayerName(@NonNull User user) { + // Ignore any bots + if (user.getUniqueId() == null) { + return; + } Players player = getPlayer(user.getUniqueId()); player.setPlayerName(user.getName()); handler.saveObject(player); + // Update names Names newName = new Names(user.getName(), user.getUniqueId()); + // Add to cache + nameCache.removeIf(name -> user.getUniqueId().equals(name.getUuid())); + nameCache.add(newName); // Add to names database names.saveObjectAsync(newName); } diff --git a/src/test/java/world/bentobox/bentobox/managers/PlayersManagerTest.java b/src/test/java/world/bentobox/bentobox/managers/PlayersManagerTest.java index 2313e9488..00d19a6c5 100644 --- a/src/test/java/world/bentobox/bentobox/managers/PlayersManagerTest.java +++ b/src/test/java/world/bentobox/bentobox/managers/PlayersManagerTest.java @@ -9,6 +9,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -71,7 +72,7 @@ import world.bentobox.bentobox.util.Util; * */ @RunWith(PowerMockRunner.class) -@PrepareForTest({ Bukkit.class, BentoBox.class, User.class, Util.class, Logger.class, DatabaseSetup.class, }) +@PrepareForTest({ Bukkit.class, BentoBox.class, User.class, Util.class, Logger.class, DatabaseSetup.class }) public class PlayersManagerTest { private static AbstractDatabaseHandler handler; @@ -685,12 +686,65 @@ public class PlayersManagerTest { } /** - * Test method for - * {@link world.bentobox.bentobox.managers.PlayersManager#shutdown()}. + * Test method for {@link world.bentobox.bentobox.managers.PlayersManager#getPlayer(java.util.UUID)}. */ @Test - public void testShutdown() { - pm.shutdown(); // Clears cache + public void testGetPlayer() { + Players p = pm.getPlayer(uuid); + assertNotNull(p); + } + + /** + * Test method for {@link world.bentobox.bentobox.managers.PlayersManager#loadPlayer(java.util.UUID)}. + */ + @Test + public void testLoadPlayer() { + assertNotNull(pm.loadPlayer(uuid)); + } + + /** + * Test method for {@link world.bentobox.bentobox.managers.PlayersManager#getName(java.util.UUID)}. + */ + @Test + public void testGetName() { + assertEquals("", pm.getName(uuid)); + } + + /** + * Test method for {@link world.bentobox.bentobox.managers.PlayersManager#cleanLeavingPlayer(org.bukkit.World, world.bentobox.bentobox.api.user.User, boolean, world.bentobox.bentobox.database.objects.Island)}. + */ + @Test + public void testCleanLeavingPlayer() { + when(user.isOnline()).thenReturn(true); + when(iwm.isOnLeaveResetEnderChest(world)).thenReturn(true); + when(iwm.isOnLeaveResetInventory(world)).thenReturn(true); + when(iwm.isOnLeaveResetMoney(world)).thenReturn(true); + pm.cleanLeavingPlayer(world, user, false, island); + PowerMockito.verifyStatic(Util.class); + Util.runCommands(user, "", plugin.getIWM().getOnLeaveCommands(world), "leave"); + verify(world).getEntitiesByClass(Tameable.class); + verify(inv).clear(); // Enderchest cleared + verify(plugin).getVault(); // Clear money + PowerMockito.verifyStatic(Util.class); + Util.resetHealth(p); + verify(p).setFoodLevel(20); + verify(p).setLevel(0); + verify(p).setExp(0); + // Player total XP (not displayed) + verify(p).setTotalExperience(0); + + } + + /** + * Test method for {@link world.bentobox.bentobox.managers.PlayersManager#savePlayer(java.util.UUID)}. + * @throws IntrospectionException + * @throws InvocationTargetException + * @throws IllegalAccessException + */ + @Test + public void testSavePlayer() throws IllegalAccessException, InvocationTargetException, IntrospectionException { + pm.savePlayer(uuid); + verify(handler, atLeastOnce()).saveObject(any()); } } From a8f01f746e885747f49737ed2e6a2e4cdf787141 Mon Sep 17 00:00:00 2001 From: tastybento Date: Fri, 5 Jul 2024 10:19:37 -0700 Subject: [PATCH 4/4] Add a UUID Fetcher class in Utils - not used right now --- .../bentobox/bentobox/util/UUIDFetcher.java | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 src/main/java/world/bentobox/bentobox/util/UUIDFetcher.java diff --git a/src/main/java/world/bentobox/bentobox/util/UUIDFetcher.java b/src/main/java/world/bentobox/bentobox/util/UUIDFetcher.java new file mode 100644 index 000000000..7f49695ee --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/util/UUIDFetcher.java @@ -0,0 +1,65 @@ +package world.bentobox.bentobox.util; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URI; +import java.util.UUID; + +import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.NotNull; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +/** + * Fetches UUID for a player name from the Internet + * @since 1.24.1 + */ +public class UUIDFetcher { + private static final String API_URL = "https://playerdb.co/api/player/minecraft/%s"; + + @Nullable + public static UUID getUUID(@NotNull String name) { + name = name.toLowerCase(); // Had some issues with upper-case letters in the username, so I added this to make sure that doesn't happen. + + try { + HttpURLConnection connection = (HttpURLConnection) URI.create(String.format(API_URL, name)).toURL() + .openConnection(); + + connection.setUseCaches(false); + connection.setDefaultUseCaches(false); + connection.addRequestProperty("User-Agent", "Mozilla/5.0"); + connection.addRequestProperty("Cache-Control", "no-cache, no-store, must-revalidate"); + connection.addRequestProperty("Pragma", "no-cache"); + connection.setReadTimeout(5000); + + // These connection parameters need to be set or the API won't accept the connection. + + try (BufferedReader bufferedReader = new BufferedReader( + new InputStreamReader(connection.getInputStream()))) { + StringBuilder response = new StringBuilder(); + String line; + + while ((line = bufferedReader.readLine()) != null) + response.append(line); + + final JsonElement parsed = JsonParser.parseString(response.toString()); + + if (parsed == null || !parsed.isJsonObject()) { + return null; + } + + JsonObject data = parsed.getAsJsonObject(); // Read the returned JSON data. + + return UUID.fromString(data.get("data").getAsJsonObject().get("player").getAsJsonObject().get("id") // Grab the UUID. + .getAsString()); + } + } catch (Exception ignored) { + // Ignoring exception since this is usually caused by non-existent usernames. + } + + return null; + } +} \ No newline at end of file