diff --git a/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeAPI.java b/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeAPI.java index ec29ff2..7bb1d12 100644 --- a/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeAPI.java +++ b/src/main/java/com/imaginarycode/minecraft/redisbungee/RedisBungeeAPI.java @@ -87,7 +87,7 @@ public class RedisBungeeAPI { return Collections2.transform(getPlayersOnline(), new Function() { @Override public String apply(UUID uuid) { - return getNameFromUuid(uuid); + return getNameFromUuid(uuid, false); } }); } @@ -205,24 +205,66 @@ public class RedisBungeeAPI { *

* For the common use case of translating a list of UUIDs into names, use {@link #getHumanPlayersOnline()} * as the efficiency of that function is slightly greater as the names are calculated lazily. + *

+ * If performance is a concern, use {@link #getNameFromUuid(java.util.UUID, boolean)} as this allows you to disable Mojang lookups. * * @param uuid the UUID to fetch the name for * @return the name for the UUID * @since 0.3 */ public final String getNameFromUuid(@NonNull UUID uuid) { - return plugin.getUuidTranslator().getNameFromUuid(uuid); + return getNameFromUuid(uuid, true); + } + + /** + * Fetch a name from the specified UUID. UUIDs are cached locally and in Redis. This function can fall back to Mojang + * as a last resort if {@code expensiveLookups} is true, so calls may be blocking. + *

+ * For the common use case of translating the list of online players into names, use {@link #getHumanPlayersOnline()} + * as the efficiency of that function is slightly greater as the names are calculated lazily. + *

+ * If performance is a concern, set {@code expensiveLookups} to false as this will disable lookups via Mojang. + * + * @param uuid the UUID to fetch the name for + * @param expensiveLookups whether or not to perform potentially expensive lookups + * @return the name for the UUID + * @since 0.3.2 + */ + public final String getNameFromUuid(@NonNull UUID uuid, boolean expensiveLookups) { + return plugin.getUuidTranslator().getNameFromUuid(uuid, expensiveLookups); } /** * Fetch a UUID from the specified name. Names are cached locally and in Redis. This function falls back to Mojang * as a last resort, so calls may be blocking. + *

+ * If performance is a concern, see {@link #getUuidFromName(String, boolean)}, which disables the following functions: + *

* * @param name the UUID to fetch the name for * @return the UUID for the name * @since 0.3 */ public final UUID getUuidFromName(@NonNull String name) { - return plugin.getUuidTranslator().getTranslatedUuid(name); + return getUuidFromName(name, true); + } + + /** + * Fetch a UUID from the specified name. Names are cached locally and in Redis. This function falls back to Mojang + * as a last resort if {@code expensiveLookups} is true, so calls may be blocking. + *

+ * If performance is a concern, set {@code expensiveLookups} to false to disable searching Mojang and searching for usernames + * case-insensitively. + * + * @param name the UUID to fetch the name for + * @param expensiveLookups whether or not to perform potentially expensive lookups + * @return the UUID for the name + * @since 0.3.2 + */ + public final UUID getUuidFromName(@NonNull String name, boolean expensiveLookups) { + return plugin.getUuidTranslator().getTranslatedUuid(name, expensiveLookups); } } diff --git a/src/main/java/com/imaginarycode/minecraft/redisbungee/util/UUIDTranslator.java b/src/main/java/com/imaginarycode/minecraft/redisbungee/util/UUIDTranslator.java index df73142..7ff855a 100644 --- a/src/main/java/com/imaginarycode/minecraft/redisbungee/util/UUIDTranslator.java +++ b/src/main/java/com/imaginarycode/minecraft/redisbungee/util/UUIDTranslator.java @@ -20,24 +20,36 @@ import redis.clients.jedis.exceptions.JedisException; import java.util.Collections; import java.util.Map; import java.util.UUID; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.logging.Level; import java.util.regex.Pattern; @RequiredArgsConstructor public class UUIDTranslator { private final RedisBungee plugin; - private BiMap uuidMap = Maps.synchronizedBiMap(HashBiMap.create()); + private final BiMap uuidMap = HashBiMap.create(); + private final ReadWriteLock lock = new ReentrantReadWriteLock(); public static final Pattern UUID_PATTERN = Pattern.compile("[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}"); public static final Pattern MOJANGIAN_UUID_PATTERN = Pattern.compile("[a-fA-F0-9]{32}"); - public UUID getTranslatedUuid(@NonNull String player) { + public UUID getTranslatedUuid(@NonNull String player, boolean expensiveLookups) { if (ProxyServer.getInstance().getPlayer(player) != null) return ProxyServer.getInstance().getPlayer(player).getUniqueId(); - UUID uuid = uuidMap.get(player); - if (uuid != null) - return uuid; + UUID uuid; + // Check if it exists in the map + lock.readLock().lock(); + try { + uuid = uuidMap.get(player); + if (uuid != null) + return uuid; + } finally { + lock.readLock().unlock(); + } + + // Check if we can exit early if (UUID_PATTERN.matcher(player).find()) { return UUID.fromString(player); } @@ -53,7 +65,21 @@ public class UUIDTranslator { return uuid; } - // Okay, it wasn't locally cached. Let's try Redis. + // We could not exit early. Look for the name, ignoring case. + if (expensiveLookups) { + lock.readLock().lock(); + try { + for (Map.Entry entry : uuidMap.entrySet()) { + if (entry.getKey().equalsIgnoreCase(player)) { + return entry.getValue(); + } + } + } finally { + lock.readLock().unlock(); + } + } + + // Okay, it wasn't locally cached and the expensive search didn't help us. Let's try Redis. Jedis jedis = plugin.getPool().getResource(); try { try { @@ -62,11 +88,19 @@ public class UUIDTranslator { // This is it! uuid = UUID.fromString(stored); storeInfo(player, uuid, jedis); - uuidMap.put(player, uuid); + lock.writeLock().lock(); + try { + uuidMap.put(player, uuid); + } finally { + lock.writeLock().unlock(); + } return uuid; } // That didn't work. Let's ask Mojang. + if (!expensiveLookups) + return null; + Map uuidMap1; try { uuidMap1 = new UUIDFetcher(Collections.singletonList(player)).call(); @@ -76,8 +110,12 @@ public class UUIDTranslator { } for (Map.Entry entry : uuidMap1.entrySet()) { if (entry.getKey().equalsIgnoreCase(player)) { - uuidMap.put(player, entry.getValue()); - storeInfo(player, entry.getValue(), jedis); + try { + uuidMap.put(entry.getKey(), entry.getValue()); + } finally { + lock.writeLock().unlock(); + } + storeInfo(entry.getKey(), entry.getValue(), jedis); return entry.getValue(); } } @@ -93,11 +131,18 @@ public class UUIDTranslator { return null; // Nope, game over! } - public String getNameFromUuid(@NonNull UUID player) { + public String getNameFromUuid(@NonNull UUID player, boolean expensiveLookups) { if (ProxyServer.getInstance().getPlayer(player) != null) return ProxyServer.getInstance().getPlayer(player).getName(); - String name = uuidMap.inverse().get(player); + String name; + + lock.readLock().lock(); + try { + name = uuidMap.inverse().get(player); + } finally { + lock.readLock().unlock(); + } if (name != null) return name; @@ -108,10 +153,18 @@ public class UUIDTranslator { String stored = jedis.hget("player:" + player, "name"); if (stored != null) { name = stored; - uuidMap.put(name, player); + lock.writeLock().lock(); + try { + uuidMap.put(name, player); + } finally { + lock.writeLock().unlock(); + } return name; } + if (!expensiveLookups) + return null; + // That didn't work. Let's ask Mojang. try { name = new NameFetcher(Collections.singletonList(player)).call().get(player); @@ -122,6 +175,12 @@ public class UUIDTranslator { if (name != null) { storeInfo(name, player, jedis); + lock.writeLock().lock(); + try { + uuidMap.put(name, player); + } finally { + lock.writeLock().unlock(); + } return name; }