From f472cfedb5a06948863930d3032456b63fa589cc Mon Sep 17 00:00:00 2001 From: Piotr Zych <77621271+P1otrulla@users.noreply.github.com> Date: Sun, 25 Oct 2026 15:50:17 +0100 Subject: [PATCH 1/3] Expose user management through public API Add public User and UserManager interfaces and expose the manager through EternalCoreApi. Keep user persistence and caching implementations internal, and adapt cached users to the viewer API. --- .../com/eternalcode/core/EternalCoreApi.java | 3 + .../java/com/eternalcode/core/user/User.java | 18 ++++ .../eternalcode/core/user/UserManager.java | 30 +++++++ .../eternalcode/core/EternalCoreApiImpl.java | 6 ++ .../core/feature/ignore/IgnoreCommand.java | 5 +- .../core/feature/ignore/UnIgnoreCommand.java | 5 +- .../java/com/eternalcode/core/user/User.java | 68 -------------- .../com/eternalcode/core/user/UserImpl.java | 67 ++++++++++++++ .../eternalcode/core/user/UserManager.java | 89 ------------------- .../core/user/UserManagerImpl.java | 77 ++++++++++++++++ .../core/user/database/UserRepository.java | 8 +- .../user/database/UserRepositoryImpl.java | 8 +- .../core/user/database/UserTable.java | 17 ++-- .../core/viewer/BukkitViewerProvider.java | 13 +-- .../eternalcode/core/viewer/UserViewer.java | 22 +++++ 15 files changed, 252 insertions(+), 184 deletions(-) create mode 100644 eternalcore-api/src/main/java/com/eternalcode/core/user/User.java create mode 100644 eternalcore-api/src/main/java/com/eternalcode/core/user/UserManager.java delete mode 100644 eternalcore-core/src/main/java/com/eternalcode/core/user/User.java create mode 100644 eternalcore-core/src/main/java/com/eternalcode/core/user/UserImpl.java delete mode 100644 eternalcore-core/src/main/java/com/eternalcode/core/user/UserManager.java create mode 100644 eternalcore-core/src/main/java/com/eternalcode/core/user/UserManagerImpl.java create mode 100644 eternalcore-core/src/main/java/com/eternalcode/core/viewer/UserViewer.java diff --git a/eternalcore-api/src/main/java/com/eternalcode/core/EternalCoreApi.java b/eternalcore-api/src/main/java/com/eternalcode/core/EternalCoreApi.java index 9c56892ce..c53609a6f 100644 --- a/eternalcore-api/src/main/java/com/eternalcode/core/EternalCoreApi.java +++ b/eternalcore-api/src/main/java/com/eternalcode/core/EternalCoreApi.java @@ -10,6 +10,7 @@ import com.eternalcode.core.feature.spawn.SpawnService; import com.eternalcode.core.feature.teleport.TeleportService; import com.eternalcode.core.feature.warp.WarpService; +import com.eternalcode.core.user.UserManager; public interface EternalCoreApi { @@ -32,4 +33,6 @@ public interface EternalCoreApi { TeleportService getTeleportService(); WarpService getWarpService(); + + UserManager getUserManager(); } diff --git a/eternalcore-api/src/main/java/com/eternalcode/core/user/User.java b/eternalcore-api/src/main/java/com/eternalcode/core/user/User.java new file mode 100644 index 000000000..1e4513e93 --- /dev/null +++ b/eternalcore-api/src/main/java/com/eternalcode/core/user/User.java @@ -0,0 +1,18 @@ +package com.eternalcode.core.user; + +import java.time.Instant; +import java.util.UUID; + +/** + * Immutable snapshot of a player's EternalCore account data. + */ +public interface User { + + UUID getUniqueId(); + + String getName(); + + Instant getLastSeen(); + + Instant getAccountCreated(); +} diff --git a/eternalcore-api/src/main/java/com/eternalcode/core/user/UserManager.java b/eternalcore-api/src/main/java/com/eternalcode/core/user/UserManager.java new file mode 100644 index 000000000..79d2ac28e --- /dev/null +++ b/eternalcore-api/src/main/java/com/eternalcode/core/user/UserManager.java @@ -0,0 +1,30 @@ +package com.eternalcode.core.user; + +import java.util.Collection; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +public interface UserManager { + + /** + * Returns the cached user with the given unique id. + */ + Optional getUser(UUID uniqueId); + + /** + * Returns the cached user with the given name. + */ + Optional getUser(String name); + + /** + * Loads the user from cache or database, creating a new one if it does not exist. + * Refreshes the user's name and last seen time. + */ + CompletableFuture findOrCreate(UUID uniqueId, String name); + + /** + * Returns an immutable snapshot of all cached users. + */ + Collection getUsers(); +} diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/EternalCoreApiImpl.java b/eternalcore-core/src/main/java/com/eternalcode/core/EternalCoreApiImpl.java index 4680437ba..7efcc312d 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/EternalCoreApiImpl.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/EternalCoreApiImpl.java @@ -11,6 +11,7 @@ import com.eternalcode.core.feature.teleport.TeleportService; import com.eternalcode.core.feature.warp.WarpService; import com.eternalcode.core.injector.DependencyProvider; +import com.eternalcode.core.user.UserManager; class EternalCoreApiImpl implements EternalCoreApi { @@ -69,4 +70,9 @@ public TeleportService getTeleportService() { public WarpService getWarpService() { return this.dependencyProvider.getDependency(WarpService.class); } + + @Override + public UserManager getUserManager() { + return this.dependencyProvider.getDependency(UserManager.class); + } } diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/ignore/IgnoreCommand.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/ignore/IgnoreCommand.java index 270df1129..f78a159f0 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/ignore/IgnoreCommand.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/ignore/IgnoreCommand.java @@ -33,7 +33,10 @@ void ignore(@Context User sender, @Arg User target) { UUID targetUuid = target.getUniqueId(); if (sender.equals(target)) { - this.noticeService.viewer(sender, translation -> translation.ignore().cannotIgnoreSelf()); + this.noticeService.create() + .user(sender) + .notice(translation -> translation.ignore().cannotIgnoreSelf()) + .send(); return; } diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/ignore/UnIgnoreCommand.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/ignore/UnIgnoreCommand.java index cd514aa44..fb7577061 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/ignore/UnIgnoreCommand.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/ignore/UnIgnoreCommand.java @@ -33,7 +33,10 @@ void unignore(@Context User sender, @Arg("ignored_player") User target) { UUID targetUuid = target.getUniqueId(); if (sender.equals(target)) { - this.noticeService.viewer(sender, translation -> translation.ignore().cannotUnignoreSelf()); + this.noticeService.create() + .user(sender) + .notice(translation -> translation.ignore().cannotUnignoreSelf()) + .send(); return; } diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/user/User.java b/eternalcore-core/src/main/java/com/eternalcode/core/user/User.java deleted file mode 100644 index 73e5dfe49..000000000 --- a/eternalcore-core/src/main/java/com/eternalcode/core/user/User.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.eternalcode.core.user; - -import com.eternalcode.core.viewer.Viewer; - -import java.time.Instant; -import java.util.Objects; -import java.util.UUID; - -public class User implements Viewer { - - private final String name; - private final UUID uuid; - private final Instant lastSeen; - private final Instant accountCreated; - - public User(UUID uuid, String name, Instant lastSeen, Instant accountCreated) { - this.name = name; - this.uuid = uuid; - this.lastSeen = lastSeen; - this.accountCreated = accountCreated; - } - - @Override - public String getName() { - return this.name; - } - - @Override - public UUID getUniqueId() { - return this.uuid; - } - - public Instant getLastSeen() { - return this.lastSeen; - } - - public Instant getAccountCreated() { - return this.accountCreated; - } - - public User updateLastSeen(Instant lastSeen) { - return new User(this.uuid, this.name, lastSeen, this.accountCreated); - } - - @Override - public boolean isConsole() { - return false; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof User user)) { - return false; - } - return this.name.equals(user.name) && - this.uuid.equals(user.uuid) && - Objects.equals(this.lastSeen, user.lastSeen) && - Objects.equals(this.accountCreated, user.accountCreated); - } - - @Override - public int hashCode() { - return Objects.hash(this.name, this.uuid, this.lastSeen, this.accountCreated); - } -} diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/user/UserImpl.java b/eternalcore-core/src/main/java/com/eternalcode/core/user/UserImpl.java new file mode 100644 index 000000000..ad0b00b22 --- /dev/null +++ b/eternalcore-core/src/main/java/com/eternalcode/core/user/UserImpl.java @@ -0,0 +1,67 @@ +package com.eternalcode.core.user; + +import java.time.Instant; +import java.util.UUID; + +public final class UserImpl implements User { + + private final UUID uniqueId; + private final String name; + private final Instant lastSeen; + private final Instant accountCreated; + + public UserImpl(UUID uniqueId, String name, Instant lastSeen, Instant accountCreated) { + this.uniqueId = uniqueId; + this.name = name; + this.lastSeen = lastSeen; + this.accountCreated = accountCreated; + } + + static UserImpl create(UUID uniqueId, String name, Instant lastSeen, Instant accountCreated) { + return new UserImpl(uniqueId, name, lastSeen, accountCreated); + } + + @Override + public UUID getUniqueId() { + return this.uniqueId; + } + + @Override + public String getName() { + return this.name; + } + + @Override + public Instant getLastSeen() { + return this.lastSeen; + } + + @Override + public Instant getAccountCreated() { + return this.accountCreated; + } + + UserImpl withName(String name) { + return new UserImpl(this.uniqueId, name, this.lastSeen, this.accountCreated); + } + + UserImpl withLastSeen(Instant lastSeen) { + return new UserImpl(this.uniqueId, this.name, lastSeen, this.accountCreated); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + + if (!(other instanceof UserImpl user)) { + return false; + } + + return this.uniqueId.equals(user.uniqueId) + && this.name.equals(user.name) + && this.lastSeen.equals(user.lastSeen) + && this.accountCreated.equals(user.accountCreated); + } +} diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/user/UserManager.java b/eternalcore-core/src/main/java/com/eternalcode/core/user/UserManager.java deleted file mode 100644 index aa1222578..000000000 --- a/eternalcore-core/src/main/java/com/eternalcode/core/user/UserManager.java +++ /dev/null @@ -1,89 +0,0 @@ -package com.eternalcode.core.user; - -import com.eternalcode.core.injector.annotations.Inject; -import com.eternalcode.core.injector.annotations.component.Service; -import com.eternalcode.core.user.database.UserRepository; -import java.time.Instant; -import java.util.Map; -import java.util.Optional; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentHashMap; - -@Service -public class UserManager { - - private final Map usersByUniqueId = new ConcurrentHashMap<>(); - private final Map usersByName = new ConcurrentHashMap<>(); - private final UserRepository userRepository; - - @Inject - public UserManager(UserRepository userRepository) { - this.userRepository = userRepository; - } - - public Optional getUser(UUID uniqueId) { - return Optional.ofNullable(this.usersByUniqueId.get(uniqueId)); - } - - public Optional getUser(String name) { - return Optional.ofNullable(this.usersByName.get(name)); - } - - public CompletableFuture findOrCreate(UUID uniqueId, String name) { - User cached = this.usersByUniqueId.get(uniqueId); - - if (cached != null) { - User updatedUser = this.updateNameIfChanged(cached, name); - return CompletableFuture.completedFuture(this.updateLastSeen(updatedUser)); - } - - return this.userRepository.getUser(uniqueId) - .thenApply(optionalUser -> { - User user = optionalUser - .map(existing -> { - User updatedUser = this.updateNameIfChanged(existing, name); - return this.updateLastSeen(updatedUser); - }) - .orElseGet(() -> this.createNewUser(uniqueId, name)); - - this.add(user); - return user; - }); - } - - private User createNewUser(UUID uniqueId, String name) { - Instant now = Instant.now(); - User user = new User(uniqueId, name, now, now); - this.userRepository.saveUser(user); - return user; - } - - private User updateNameIfChanged(User user, String name) { - if (user.getName().equals(name)) { - return user; - } - - User updated = new User(user.getUniqueId(), name, user.getLastSeen(), user.getAccountCreated()); - this.removeNameMapping(user); - this.add(updated); - this.userRepository.saveUser(updated); - return updated; - } - - private User updateLastSeen(User user) { - User updated = user.updateLastSeen(Instant.now()); - this.add(updated); - this.userRepository.saveUser(updated); - return updated; - } - - private void removeNameMapping(User user) { - this.usersByName.remove(user.getName(), user); - } - - private void add(User user) { - this.usersByUniqueId.put(user.getUniqueId(), user); - this.usersByName.put(user.getName(), user); - } -} diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/user/UserManagerImpl.java b/eternalcore-core/src/main/java/com/eternalcode/core/user/UserManagerImpl.java new file mode 100644 index 000000000..341bfa691 --- /dev/null +++ b/eternalcore-core/src/main/java/com/eternalcode/core/user/UserManagerImpl.java @@ -0,0 +1,77 @@ +package com.eternalcode.core.user; + +import com.eternalcode.core.injector.annotations.Inject; +import com.eternalcode.core.injector.annotations.component.Service; +import com.eternalcode.core.user.database.UserRepository; +import java.time.Instant; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; + +@Service +class UserManagerImpl implements UserManager { + + private final Map usersByUniqueId = new ConcurrentHashMap<>(); + private final Map usersByName = new ConcurrentHashMap<>(); + private final UserRepository userRepository; + + @Inject + UserManagerImpl(UserRepository userRepository) { + this.userRepository = userRepository; + } + + @Override + public Optional getUser(UUID uniqueId) { + return Optional.ofNullable(this.usersByUniqueId.get(uniqueId)); + } + + @Override + public Optional getUser(String name) { + return Optional.ofNullable(this.usersByName.get(name)); + } + + @Override + public CompletableFuture findOrCreate(UUID uniqueId, String name) { + UserImpl cached = this.usersByUniqueId.get(uniqueId); + + if (cached != null) { + return CompletableFuture.completedFuture(this.refresh(cached, name)); + } + + return this.userRepository.getUser(uniqueId) + .thenApply(optionalUser -> optionalUser + .map(existing -> this.refresh(existing, name)) + .orElseGet(() -> this.create(uniqueId, name))); + } + + private UserImpl create(UUID uniqueId, String name) { + UserImpl user = UserImpl.create(uniqueId, name, Instant.now(), Instant.now()); + this.cacheAndSave(user); + return user; + } + + private UserImpl refresh(UserImpl user, String name) { + UserImpl refreshed = user + .withName(name) + .withLastSeen(Instant.now()); + + this.usersByName.remove(user.getName(), user); + this.cacheAndSave(refreshed); + return refreshed; + } + + private void cacheAndSave(UserImpl user) { + this.usersByUniqueId.put(user.getUniqueId(), user); + this.usersByName.put(user.getName(), user); + this.userRepository.saveUser(user); + } + + @Override + public Collection getUsers() { + return List.copyOf(this.usersByUniqueId.values()); + } +} diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/user/database/UserRepository.java b/eternalcore-core/src/main/java/com/eternalcode/core/user/database/UserRepository.java index 847c85631..39e8c3ae5 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/user/database/UserRepository.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/user/database/UserRepository.java @@ -1,16 +1,16 @@ package com.eternalcode.core.user.database; -import com.eternalcode.core.user.User; +import com.eternalcode.core.user.UserImpl; import java.util.Optional; import java.util.UUID; import java.util.concurrent.CompletableFuture; public interface UserRepository { - CompletableFuture> getUser(UUID uniqueId); + CompletableFuture> getUser(UUID uniqueId); - CompletableFuture> getUser(String name); + CompletableFuture> getUser(String name); - CompletableFuture saveUser(User user); + CompletableFuture saveUser(UserImpl user); } diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/user/database/UserRepositoryImpl.java b/eternalcore-core/src/main/java/com/eternalcode/core/user/database/UserRepositoryImpl.java index 00964c3c4..744242bfe 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/user/database/UserRepositoryImpl.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/user/database/UserRepositoryImpl.java @@ -5,7 +5,7 @@ import com.eternalcode.core.database.DatabaseManager; import com.eternalcode.core.injector.annotations.Inject; import com.eternalcode.core.injector.annotations.component.Repository; -import com.eternalcode.core.user.User; +import com.eternalcode.core.user.UserImpl; import com.j256.ormlite.table.TableUtils; import java.sql.SQLException; import java.util.Optional; @@ -24,13 +24,13 @@ public UserRepositoryImpl(DatabaseManager databaseManager, Scheduler scheduler) } @Override - public CompletableFuture> getUser(UUID uniqueId) { + public CompletableFuture> getUser(UUID uniqueId) { return this.selectSafe(UserTable.class, uniqueId) .thenApply(optional -> optional.map(UserTable::toUser)); } @Override - public CompletableFuture> getUser(String name) { + public CompletableFuture> getUser(String name) { return this.action( UserTable.class, dao -> Optional.ofNullable( @@ -43,7 +43,7 @@ public CompletableFuture> getUser(String name) { } @Override - public CompletableFuture saveUser(User user) { + public CompletableFuture saveUser(UserImpl user) { return this.save(UserTable.class, UserTable.from(user)) .thenApply(status -> null); } diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/user/database/UserTable.java b/eternalcore-core/src/main/java/com/eternalcode/core/user/database/UserTable.java index 2610a605e..eb11c4669 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/user/database/UserTable.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/user/database/UserTable.java @@ -1,11 +1,10 @@ package com.eternalcode.core.user.database; -import com.eternalcode.core.user.User; +import com.eternalcode.core.user.UserImpl; import com.j256.ormlite.field.DataType; import com.j256.ormlite.field.DatabaseField; import com.j256.ormlite.table.DatabaseTable; import java.util.Date; -import java.time.Instant; import java.util.UUID; @DatabaseTable(tableName = "eternal_core_users") @@ -33,12 +32,16 @@ class UserTable { this.accountCreated = accountCreated; } - User toUser() { - return new User(this.uniqueId, this.name, this.lastSeen.toInstant(), this.accountCreated.toInstant()); + UserImpl toUser() { + return new UserImpl(this.uniqueId, this.name, this.lastSeen.toInstant(), this.accountCreated.toInstant()); } - static UserTable from(User user) { - return new UserTable(user.getUniqueId(), user.getName(), Date.from(user.getLastSeen()), - Date.from(user.getAccountCreated())); + static UserTable from(UserImpl user) { + return new UserTable( + user.getUniqueId(), + user.getName(), + Date.from(user.getLastSeen()), + Date.from(user.getAccountCreated()) + ); } } diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/viewer/BukkitViewerProvider.java b/eternalcore-core/src/main/java/com/eternalcode/core/viewer/BukkitViewerProvider.java index ac5198022..d5d38f72f 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/viewer/BukkitViewerProvider.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/viewer/BukkitViewerProvider.java @@ -7,7 +7,6 @@ import com.eternalcode.multification.viewer.ViewerProvider; import java.util.Collection; import java.util.HashSet; -import java.util.Optional; import java.util.Set; import java.util.UUID; import org.bukkit.Server; @@ -72,13 +71,13 @@ public Viewer console() { @Override public Viewer player(UUID uuid) { return this.userManager.getUser(uuid) - .map(Viewer.class::cast) + .map(this::user) .orElseGet(() -> BukkitViewerImpl.player(uuid)); } @Override public Viewer user(User user) { - return BukkitViewerImpl.player(user.getUniqueId()); + return new UserViewer(user); } public Viewer sender(CommandSender commandSender) { @@ -88,13 +87,7 @@ public Viewer sender(CommandSender commandSender) { @Override public Viewer any(Object any) { if (any instanceof Player player) { - Optional userOption = this.userManager.getUser(player.getUniqueId()); - - if (userOption.isPresent()) { - return userOption.get(); - } - - return BukkitViewerImpl.player(player.getUniqueId()); + return this.player(player.getUniqueId()); } if (any instanceof ConsoleCommandSender || any instanceof RemoteConsoleCommandSender diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/viewer/UserViewer.java b/eternalcore-core/src/main/java/com/eternalcode/core/viewer/UserViewer.java new file mode 100644 index 000000000..eb7f39def --- /dev/null +++ b/eternalcore-core/src/main/java/com/eternalcode/core/viewer/UserViewer.java @@ -0,0 +1,22 @@ +package com.eternalcode.core.viewer; + +import com.eternalcode.core.user.User; +import java.util.UUID; + +record UserViewer(User user) implements Viewer { + + @Override + public UUID getUniqueId() { + return this.user.getUniqueId(); + } + + @Override + public boolean isConsole() { + return false; + } + + @Override + public String getName() { + return this.user.getName(); + } +} From 1acccaafb33b7f4a01ed67f19266552d00c76704 Mon Sep 17 00:00:00 2001 From: Piotr Zych <77621271+P1otrulla@users.noreply.github.com> Date: Sun, 25 Oct 2026 16:36:38 +0100 Subject: [PATCH 2/3] Refactor UserManager to reference User interface instead of UserImpl --- .../eternalcode/core/user/UserManagerImpl.java | 18 ++++++++---------- .../core/user/database/UserRepository.java | 7 ++++--- .../core/user/database/UserRepositoryImpl.java | 7 ++++--- .../core/user/database/UserTable.java | 3 ++- 4 files changed, 18 insertions(+), 17 deletions(-) diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/user/UserManagerImpl.java b/eternalcore-core/src/main/java/com/eternalcode/core/user/UserManagerImpl.java index 341bfa691..1a4fb4ce8 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/user/UserManagerImpl.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/user/UserManagerImpl.java @@ -15,8 +15,8 @@ @Service class UserManagerImpl implements UserManager { - private final Map usersByUniqueId = new ConcurrentHashMap<>(); - private final Map usersByName = new ConcurrentHashMap<>(); + private final Map usersByUniqueId = new ConcurrentHashMap<>(); + private final Map usersByName = new ConcurrentHashMap<>(); private final UserRepository userRepository; @Inject @@ -36,7 +36,7 @@ public Optional getUser(String name) { @Override public CompletableFuture findOrCreate(UUID uniqueId, String name) { - UserImpl cached = this.usersByUniqueId.get(uniqueId); + User cached = this.usersByUniqueId.get(uniqueId); if (cached != null) { return CompletableFuture.completedFuture(this.refresh(cached, name)); @@ -48,23 +48,21 @@ public CompletableFuture findOrCreate(UUID uniqueId, String name) { .orElseGet(() -> this.create(uniqueId, name))); } - private UserImpl create(UUID uniqueId, String name) { - UserImpl user = UserImpl.create(uniqueId, name, Instant.now(), Instant.now()); + private User create(UUID uniqueId, String name) { + User user = UserImpl.create(uniqueId, name, Instant.now(), Instant.now()); this.cacheAndSave(user); return user; } - private UserImpl refresh(UserImpl user, String name) { - UserImpl refreshed = user - .withName(name) - .withLastSeen(Instant.now()); + private User refresh(User user, String name) { + User refreshed = UserImpl.create(user.getUniqueId(), name, Instant.now(), user.getAccountCreated()); this.usersByName.remove(user.getName(), user); this.cacheAndSave(refreshed); return refreshed; } - private void cacheAndSave(UserImpl user) { + private void cacheAndSave(User user) { this.usersByUniqueId.put(user.getUniqueId(), user); this.usersByName.put(user.getName(), user); this.userRepository.saveUser(user); diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/user/database/UserRepository.java b/eternalcore-core/src/main/java/com/eternalcode/core/user/database/UserRepository.java index 39e8c3ae5..95cacef9a 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/user/database/UserRepository.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/user/database/UserRepository.java @@ -1,5 +1,6 @@ package com.eternalcode.core.user.database; +import com.eternalcode.core.user.User; import com.eternalcode.core.user.UserImpl; import java.util.Optional; import java.util.UUID; @@ -7,10 +8,10 @@ public interface UserRepository { - CompletableFuture> getUser(UUID uniqueId); + CompletableFuture> getUser(UUID uniqueId); - CompletableFuture> getUser(String name); + CompletableFuture> getUser(String name); - CompletableFuture saveUser(UserImpl user); + CompletableFuture saveUser(User user); } diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/user/database/UserRepositoryImpl.java b/eternalcore-core/src/main/java/com/eternalcode/core/user/database/UserRepositoryImpl.java index 744242bfe..50076ee55 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/user/database/UserRepositoryImpl.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/user/database/UserRepositoryImpl.java @@ -5,6 +5,7 @@ import com.eternalcode.core.database.DatabaseManager; import com.eternalcode.core.injector.annotations.Inject; import com.eternalcode.core.injector.annotations.component.Repository; +import com.eternalcode.core.user.User; import com.eternalcode.core.user.UserImpl; import com.j256.ormlite.table.TableUtils; import java.sql.SQLException; @@ -24,13 +25,13 @@ public UserRepositoryImpl(DatabaseManager databaseManager, Scheduler scheduler) } @Override - public CompletableFuture> getUser(UUID uniqueId) { + public CompletableFuture> getUser(UUID uniqueId) { return this.selectSafe(UserTable.class, uniqueId) .thenApply(optional -> optional.map(UserTable::toUser)); } @Override - public CompletableFuture> getUser(String name) { + public CompletableFuture> getUser(String name) { return this.action( UserTable.class, dao -> Optional.ofNullable( @@ -43,7 +44,7 @@ public CompletableFuture> getUser(String name) { } @Override - public CompletableFuture saveUser(UserImpl user) { + public CompletableFuture saveUser(User user) { return this.save(UserTable.class, UserTable.from(user)) .thenApply(status -> null); } diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/user/database/UserTable.java b/eternalcore-core/src/main/java/com/eternalcode/core/user/database/UserTable.java index eb11c4669..1996203c8 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/user/database/UserTable.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/user/database/UserTable.java @@ -1,5 +1,6 @@ package com.eternalcode.core.user.database; +import com.eternalcode.core.user.User; import com.eternalcode.core.user.UserImpl; import com.j256.ormlite.field.DataType; import com.j256.ormlite.field.DatabaseField; @@ -36,7 +37,7 @@ UserImpl toUser() { return new UserImpl(this.uniqueId, this.name, this.lastSeen.toInstant(), this.accountCreated.toInstant()); } - static UserTable from(UserImpl user) { + static UserTable from(User user) { return new UserTable( user.getUniqueId(), user.getName(), From f2cd6f1572af7d22867ab839e34e0d117891ab63 Mon Sep 17 00:00:00 2001 From: Piotr Zych <77621271+P1otrulla@users.noreply.github.com> Date: Thu, 29 Oct 2026 00:59:09 +0100 Subject: [PATCH 3/3] Refactor UserManager caching and persistence logic --- .../com/eternalcode/core/user/UserImpl.java | 23 ++--- .../core/user/UserManagerImpl.java | 89 ++++++++++++++----- 2 files changed, 78 insertions(+), 34 deletions(-) diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/user/UserImpl.java b/eternalcore-core/src/main/java/com/eternalcode/core/user/UserImpl.java index ad0b00b22..6cd0d1138 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/user/UserImpl.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/user/UserImpl.java @@ -1,6 +1,7 @@ package com.eternalcode.core.user; import java.time.Instant; +import java.util.Objects; import java.util.UUID; public final class UserImpl implements User { @@ -50,18 +51,20 @@ UserImpl withLastSeen(Instant lastSeen) { } @Override - public boolean equals(Object other) { - if (this == other) { - return true; - } - - if (!(other instanceof UserImpl user)) { + public boolean equals(Object o) { + if (o == null || this.getClass() != o.getClass()) { return false; } + UserImpl user = (UserImpl) o; - return this.uniqueId.equals(user.uniqueId) - && this.name.equals(user.name) - && this.lastSeen.equals(user.lastSeen) - && this.accountCreated.equals(user.accountCreated); + return Objects.equals(this.uniqueId, user.uniqueId) + && Objects.equals(this.name, user.name) + && Objects.equals(this.lastSeen, user.lastSeen) + && Objects.equals(this.accountCreated, user.accountCreated); + } + + @Override + public int hashCode() { + return Objects.hash(this.uniqueId, this.name, this.lastSeen, this.accountCreated); } } diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/user/UserManagerImpl.java b/eternalcore-core/src/main/java/com/eternalcode/core/user/UserManagerImpl.java index 1a4fb4ce8..e551d0c25 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/user/UserManagerImpl.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/user/UserManagerImpl.java @@ -3,9 +3,11 @@ import com.eternalcode.core.injector.annotations.Inject; import com.eternalcode.core.injector.annotations.component.Service; import com.eternalcode.core.user.database.UserRepository; + import java.time.Instant; import java.util.Collection; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.UUID; @@ -15,8 +17,9 @@ @Service class UserManagerImpl implements UserManager { - private final Map usersByUniqueId = new ConcurrentHashMap<>(); - private final Map usersByName = new ConcurrentHashMap<>(); + private final Map users = new ConcurrentHashMap<>(); + private final Map names = new ConcurrentHashMap<>(); + private final UserRepository userRepository; @Inject @@ -26,50 +29,88 @@ class UserManagerImpl implements UserManager { @Override public Optional getUser(UUID uniqueId) { - return Optional.ofNullable(this.usersByUniqueId.get(uniqueId)); + return Optional.ofNullable(this.users.get(uniqueId)); } @Override public Optional getUser(String name) { - return Optional.ofNullable(this.usersByName.get(name)); + UUID uniqueId = this.names.get(normalize(name)); + + return uniqueId == null + ? Optional.empty() + : this.getUser(uniqueId); + } + + @Override + public Collection getUsers() { + return List.copyOf(this.users.values()); } @Override public CompletableFuture findOrCreate(UUID uniqueId, String name) { - User cached = this.usersByUniqueId.get(uniqueId); + User cached = this.users.get(uniqueId); if (cached != null) { - return CompletableFuture.completedFuture(this.refresh(cached, name)); + return this.refreshName(cached, name); } return this.userRepository.getUser(uniqueId) - .thenApply(optionalUser -> optionalUser - .map(existing -> this.refresh(existing, name)) + .thenCompose(optional -> optional + .map(user -> this.refreshName(user, name)) .orElseGet(() -> this.create(uniqueId, name))); } - private User create(UUID uniqueId, String name) { - User user = UserImpl.create(uniqueId, name, Instant.now(), Instant.now()); - this.cacheAndSave(user); - return user; + private CompletableFuture create(UUID uniqueId, String name) { + Instant now = Instant.now(); + + User user = UserImpl.create( + uniqueId, + name, + now, + now + ); + + return this.saveAndCache(user); } - private User refresh(User user, String name) { - User refreshed = UserImpl.create(user.getUniqueId(), name, Instant.now(), user.getAccountCreated()); + private CompletableFuture refreshName(User user, String name) { + if (user.getName().equals(name)) { + this.cache(user); + return CompletableFuture.completedFuture(user); + } + + User updated = UserImpl.create( + user.getUniqueId(), + name, + user.getLastSeen(), + user.getAccountCreated() + ); - this.usersByName.remove(user.getName(), user); - this.cacheAndSave(refreshed); - return refreshed; + return this.saveAndCache(updated); } - private void cacheAndSave(User user) { - this.usersByUniqueId.put(user.getUniqueId(), user); - this.usersByName.put(user.getName(), user); - this.userRepository.saveUser(user); + private CompletableFuture saveAndCache(User user) { + return this.userRepository.saveUser(user) + .thenApply(ignored -> { + this.cache(user); + return user; + }); } - @Override - public Collection getUsers() { - return List.copyOf(this.usersByUniqueId.values()); + private synchronized void cache(User user) { + User previous = this.users.put(user.getUniqueId(), user); + + if (previous != null) { + this.names.remove( + normalize(previous.getName()), + previous.getUniqueId() + ); + } + + this.names.put(normalize(user.getName()), user.getUniqueId()); + } + + private static String normalize(String name) { + return name.toLowerCase(Locale.ROOT); } }