exists(UUID userId) {
+ return CompletableFuture.supplyAsync(() -> {
+ switch (type) {
+ case JSON -> {
+ return new File(usersDirectory, userId + ".json").exists();
+ }
+ case SQL -> {
+ return executeSQLQuery(userId);
+ }
+ case MONGODB -> {
+ Document user = mongoDB.getCollection(collection).find(Filters.eq("userId", userId)).first();
+ return user != null;
+ }
+ default -> {
+ return false;
+ }
+ }
+ });
+ }
+
+ private @NotNull Boolean executeSQLQuery(@NotNull UUID userId) {
+ String query = "SELECT count(*) FROM " + collection + " WHERE user_id = ?";
+ try {
+ PreparedStatement statement = Objects.requireNonNull(connection).prepareStatement(query);
+ statement.setString(1, userId.toString());
+ ResultSet resultSet = statement.executeQuery();
+ if (resultSet.next()) {
+ return resultSet.getInt(1) > 0;
+ } else {
+ throw new RuntimeException("No user found with id: " + userId);
+ }
+ } catch (SQLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Retrieves the {@link User} with the given {@link UUID}.
+ *
+ * If the user is already loaded, it is returned immediately. If not, it is loaded
+ * asynchronously and returned in a {@link CompletableFuture}.
+ *
+ * @param userId the {@link UUID} of the user to retrieve
+ * @return a {@link CompletableFuture} that will contain the {@link User} with the given id
+ */
+ public CompletableFuture getUser(UUID userId) {
+ if (loadedUsers.containsKey(userId)) {
+ return CompletableFuture.completedFuture(loadedUsers.get(userId));
+ }
+
+ return load(userId);
+ }
+
+ /**
+ * Saves all the loaded {@link User}s in the {@link #loadedUsers} map.
+ * For each {@link User} in the map, this method calls the {@link #save(User)} method to persist the {@link User}.
+ */
+ public void saveAll() {
+ loadedUsers.forEach((uuid, user) -> save(user));
+ }
+
+ @Beta
+ @Deprecated
+ public void loadAll() {
+ File[] files = this.usersDirectory.listFiles((dir, name) -> name.endsWith(".json"));
+ if (files != null) {
+ Arrays.stream(files).forEach(file -> {
+ UUID uuid = UUID.fromString(file.getName().replace(".json", ""));
+ load(uuid);
+ });
+ }
+ }
+
+ /**
+ * Retrieves the current map of loaded users.
+ *
+ * @return the map of loaded users with UUID as the key and User object as the value
+ */
+ public ObjectMap getLoadedUsers() {
+ return loadedUsers;
+ }
+
+ /**
+ * A class representing a user in the system.
+ */
+ public static class User {
+ private final UUID userId;
+ private ObjectMap customData;
+
+ /**
+ * Constructs a new user with a random UUID.
+ */
+ public User() {
+ this(UUID.randomUUID());
+ }
+
+ /**
+ * Constructs a new user with the specified UUID and name.
+ *
+ * @param userId the UUID of the user
+ */
+ public User(UUID userId) {
+ this.userId = userId;
+ this.customData = new ConcurrentObjectMap<>();
+ }
+
+ /**
+ * Returns the userId of this `User` object.
+ *
+ * @return the userId of this `User` object.
+ */
+ public UUID getId() {
+ return userId;
+ }
+
+ /**
+ * Adds a key-value pair to the custom data map.
+ *
+ * @param key the key of the data
+ * @param value the value of the data
+ */
+ public User addCustomData(String key, Object value) {
+ customData.append(key, value);
+ return this;
+ }
+
+ /**
+ * Adds a key-value pair to the custom data map if the key does not already exist.
+ *
+ * @param key the key of the data
+ * @param value the value of the data
+ */
+ public User addCustomDataIfNotExists(String key, Object value) {
+ return !customData.containsKey(key) ? addCustomData(key, value) : this;
+ }
+
+ /**
+ * Returns the value of the custom data for the specified key.
+ *
+ * @param key the key of the data
+ * @return the value of the custom data for the specified key
+ */
+ public T getCustomData(String key) {
+ return (T) customData.get(key);
+ }
+ }
+
+ /**
+ * Represents the type of storage to use for user data.
+ */
+ public enum Type {
+ /**
+ * Use a directory of JSON files for storage.
+ */
+ JSON,
+
+ /**
+ * Use a SQL database for storage.
+ */
+ SQL,
+
+ /**
+ * Use a MongoDB database for storage.
+ */
+ MONGODB,
+ ;
+
+ public Type getType() {
+ return this;
+ }
+ }
+}