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..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; @@ -25,9 +27,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 +78,13 @@ 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)) { + // 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 new file mode 100644 index 000000000..ae36aa815 --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/database/json/adapters/PairTypeAdapter.java @@ -0,0 +1,50 @@ +package world.bentobox.bentobox.database.json.adapters; + +import java.io.IOException; +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; + +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 { + 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 { + 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); + } +} 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/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 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()); } }