Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,22 @@
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/** Resolves DataRegistryApi snapshots without importing or managing DataRegistryApi entities. */
/**
* Resolves immutable DataRegistry player snapshots without importing or managing DataRegistry entities.
*
* <p>Synchronous methods are compatibility helpers. They are cache-first and may use a bounded
* persisted fallback only from background threads. They never wait for persistence on a likely
* server, event-loop, or Netty thread. New code that needs offline-player correctness should use
* the explicit asynchronous methods.</p>
*/
public final class PlayerReferenceResolver {

private static final long PERSISTED_LOOKUP_TIMEOUT_SECONDS = 3L;

private final PlayerDirectory playerDirectory;

public PlayerReferenceResolver(DataRegistryApi dataRegistry) {
Expand All @@ -25,37 +37,222 @@ public PlayerReferenceResolver(PlayerDirectory playerDirectory) {
this.playerDirectory = Objects.requireNonNull(playerDirectory, "playerDirectory");
}

public Optional<PlayerReference> findByUuid(UUID uuid) { return findIdentityByUuid(uuid).map(PlayerReference::from); }
public Optional<PlayerReference> findByUuid(String uuid) { return findIdentityByUuid(uuid).map(PlayerReference::from); }
public Optional<PlayerReference> findByIdentifier(String identifier) { return findIdentityByIdentifier(identifier).map(PlayerReference::from); }
public Optional<PlayerIdentity> findIdentityByUuid(UUID uuid) { return playerDirectory.findActiveIdentityCached(uuid); }
public Optional<PlayerIdentity> findIdentityByUuid(String uuid) { return playerDirectory.findActiveIdentityCached(uuid); }
public Optional<PlayerReference> findByUuid(UUID uuid) {
return findIdentityByUuid(uuid).map(PlayerReference::from);
}

public Optional<PlayerReference> findByUuid(String uuid) {
return findIdentityByUuid(uuid).map(PlayerReference::from);
}

public Optional<PlayerReference> findByIdentifier(String identifier) {
return findIdentityByIdentifier(identifier).map(PlayerReference::from);
}

public CompletionStage<Optional<PlayerReference>> findByUuidAsync(UUID uuid) {
return findIdentityByUuidAsync(uuid).thenApply(identity -> identity.map(PlayerReference::from));
}

public CompletionStage<Optional<PlayerReference>> findByUuidAsync(String uuid) {
return findIdentityByUuidAsync(uuid).thenApply(identity -> identity.map(PlayerReference::from));
}

public CompletionStage<Optional<PlayerReference>> findByIdentifierAsync(String identifier) {
return findIdentityByIdentifierAsync(identifier).thenApply(identity -> identity.map(PlayerReference::from));
}

public Optional<PlayerIdentity> findIdentityByUuid(UUID uuid) {
if (uuid == null) {
return Optional.empty();
}
Optional<PlayerIdentity> cached = playerDirectory.findActiveIdentityCached(uuid);
if (cached.isPresent() || !mayWaitForPersistence()) {
return cached;
}
return await(playerDirectory.findByUuid(uuid));
}

public Optional<PlayerIdentity> findIdentityByUuid(String uuid) {
if (uuid == null || uuid.isBlank()) {
return Optional.empty();
}
String normalized = uuid.trim();
Optional<PlayerIdentity> cached = playerDirectory.findActiveIdentityCached(normalized);
if (cached.isPresent() || !mayWaitForPersistence()) {
return cached;
}
return await(playerDirectory.findByUuid(normalized));
}

public CompletionStage<Optional<PlayerIdentity>> findIdentityByUuidAsync(UUID uuid) {
if (uuid == null) {
return completedEmpty();
}
Optional<PlayerIdentity> cached = playerDirectory.findActiveIdentityCached(uuid);
return cached.isPresent()
? CompletableFuture.completedFuture(cached)
: playerDirectory.findByUuid(uuid);
}

public CompletionStage<Optional<PlayerIdentity>> findIdentityByUuidAsync(String uuid) {
if (uuid == null || uuid.isBlank()) {
return completedEmpty();
}
String normalized = uuid.trim();
Optional<PlayerIdentity> cached = playerDirectory.findActiveIdentityCached(normalized);
return cached.isPresent()
? CompletableFuture.completedFuture(cached)
: playerDirectory.findByUuid(normalized);
}

public Optional<PlayerIdentity> findIdentityByUsername(String username) {
if (username == null || username.isBlank()) return Optional.empty();
String normalized = username.trim().toLowerCase(Locale.ROOT);
return playerDirectory.snapshotActiveIdentities().values().stream()
.filter(identity -> identity.username().equalsIgnoreCase(normalized)).findFirst();
if (username == null || username.isBlank()) {
return Optional.empty();
}
String normalized = username.trim();
Optional<PlayerIdentity> cached = findCachedIdentityByUsername(normalized);
if (cached.isPresent() || !mayWaitForPersistence()) {
return cached;
}
return await(playerDirectory.findByUsernameIgnoreCase(normalized));
}

public CompletionStage<Optional<PlayerIdentity>> findIdentityByUsernameAsync(String username) {
if (username == null || username.isBlank()) {
return completedEmpty();
}
String normalized = username.trim();
Optional<PlayerIdentity> cached = findCachedIdentityByUsername(normalized);
return cached.isPresent()
? CompletableFuture.completedFuture(cached)
: playerDirectory.findByUsernameIgnoreCase(normalized);
}

public Optional<PlayerIdentity> findIdentityByIdentifier(String identifier) {
if (identifier == null || identifier.isBlank()) return Optional.empty();
try { return findIdentityByUuid(UUID.fromString(identifier.trim())); }
catch (IllegalArgumentException ignored) { return findIdentityByUsername(identifier); }
if (identifier == null || identifier.isBlank()) {
return Optional.empty();
}
String normalized = identifier.trim();
try {
return findIdentityByUuid(UUID.fromString(normalized));
} catch (IllegalArgumentException ignored) {
return findIdentityByUsername(normalized);
}
}

public CompletionStage<Optional<PlayerIdentity>> findIdentityByIdentifierAsync(String identifier) {
if (identifier == null || identifier.isBlank()) {
return completedEmpty();
}
String normalized = identifier.trim();
try {
return findIdentityByUuidAsync(UUID.fromString(normalized));
} catch (IllegalArgumentException ignored) {
return findIdentityByUsernameAsync(normalized);
}
}

/**
* Compatibility lookup by stable player id. Despite the historical method name, this method is
* cache-first and may use persistence from a background thread.
*/
public Optional<PlayerIdentity> findActiveIdentityById(Long playerId) {
if (playerId == null || playerId <= 0L) return Optional.empty();
return playerDirectory.snapshotActiveIdentities().values().stream()
.filter(identity -> playerId.equals(identity.playerId())).findFirst();
if (playerId == null || playerId <= 0L) {
return Optional.empty();
}
Optional<PlayerIdentity> cached = findCachedIdentityById(playerId);
if (cached.isPresent() || !mayWaitForPersistence()) {
return cached;
}
return await(playerDirectory.findByPlayerId(playerId));
}

public CompletionStage<Optional<PlayerIdentity>> findIdentityByIdAsync(Long playerId) {
if (playerId == null || playerId <= 0L) {
return completedEmpty();
}
Optional<PlayerIdentity> cached = findCachedIdentityById(playerId);
return cached.isPresent()
? CompletableFuture.completedFuture(cached)
: playerDirectory.findByPlayerId(playerId);
}

public CompletionStage<Optional<PlayerIdentity>> findPersistedIdentityByIdentifier(String identifier) {
return playerDirectory.findByIdentifier(identifier);
if (identifier == null || identifier.isBlank()) {
return completedEmpty();
}
return playerDirectory.findByIdentifier(identifier.trim());
}
public CompletableFuture<Optional<PlayerIdentity>> whenReady(UUID uuid) { return playerDirectory.whenReady(uuid); }
public PlayerReference resolveManaged(Session ignored, UUID uuid) { return findByUuid(uuid).orElse(null); }
public PlayerReference resolveManaged(Session ignored, String uuid) { return findByUuid(uuid).orElse(null); }

public CompletableFuture<Optional<PlayerIdentity>> whenReady(UUID uuid) {
return playerDirectory.whenReady(uuid);
}

public PlayerReference resolveManaged(Session ignored, UUID uuid) {
return findByUuid(uuid).orElse(null);
}

public PlayerReference resolveManaged(Session ignored, String uuid) {
return findByUuid(uuid).orElse(null);
}

public PlayerReference resolveManagedById(Session ignored, Long playerId) {
if (playerId == null || playerId <= 0L) return null;
if (playerId == null || playerId <= 0L) {
return null;
}
return findActiveIdentityById(playerId)
.map(PlayerReference::from)
.orElseGet(() -> PlayerReference.byId(playerId));
}

private Optional<PlayerIdentity> findCachedIdentityByUsername(String username) {
String normalized = username.toLowerCase(Locale.ROOT);
return playerDirectory.snapshotActiveIdentities().values().stream()
.filter(identity -> identity.username().toLowerCase(Locale.ROOT).equals(normalized))
.findFirst();
}

private Optional<PlayerIdentity> findCachedIdentityById(Long playerId) {
return playerDirectory.snapshotActiveIdentities().values().stream()
.filter(identity -> playerId.equals(identity.playerId()))
.findFirst().map(PlayerReference::from).orElseGet(() -> PlayerReference.byId(playerId));
.findFirst();
}

private static boolean mayWaitForPersistence() {
return !isLikelyServerEventThread(Thread.currentThread().getName());
}

static boolean isLikelyServerEventThread(String threadName) {
if (threadName == null) {
return false;
}
String normalized = threadName.toLowerCase(Locale.ROOT);
return normalized.contains("server thread")
|| normalized.contains("main")
|| normalized.contains("event")
|| normalized.contains("netty");
}

private static <T> Optional<T> await(CompletionStage<Optional<T>> stage) {
if (stage == null) {
return Optional.empty();
}
CompletableFuture<Optional<T>> future = stage.toCompletableFuture();
try {
Optional<T> result = future.get(PERSISTED_LOOKUP_TIMEOUT_SECONDS, TimeUnit.SECONDS);
return result == null ? Optional.empty() : result;
} catch (InterruptedException exception) {
Thread.currentThread().interrupt();
return Optional.empty();
} catch (TimeoutException exception) {
future.cancel(true);
return Optional.empty();
} catch (ExecutionException | RuntimeException exception) {
return Optional.empty();
}
}

private static <T> CompletionStage<Optional<T>> completedEmpty() {
return CompletableFuture.completedFuture(Optional.empty());
}
}
Loading
Loading