From acd2a9d59bd53de54801483e098c9873debc9f84 Mon Sep 17 00:00:00 2001 From: Gustavo Paredes <66433837+CroaBeast@users.noreply.github.com> Date: Sun, 30 Nov 2025 11:49:34 -0500 Subject: [PATCH 01/12] 1.0.4 - Placeholders and message fix --- .../com/bitaspire/cyberlevels/BaseSystem.java | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/bitaspire/cyberlevels/BaseSystem.java b/src/main/java/com/bitaspire/cyberlevels/BaseSystem.java index 985edca..c6c3abd 100644 --- a/src/main/java/com/bitaspire/cyberlevels/BaseSystem.java +++ b/src/main/java/com/bitaspire/cyberlevels/BaseSystem.java @@ -430,9 +430,9 @@ void updateLevel(long newLevel, boolean sendMessage, boolean giveRewards) { if (sendMessage) { long diff = level - oldLevel; if (diff > 0) { - cache.lang().sendMessage(getPlayer(), Lang::getGainedLevels, "gainedLevels", diff); + cache.lang().sendMessage(getPlayer(), Lang::getGainedLevels, new String[] {"gainedLevels", "level"}, diff, level); } else if (diff < 0) { - cache.lang().sendMessage(getPlayer(), Lang::getLostLevels, "lostLevels", Math.abs(diff)); + cache.lang().sendMessage(getPlayer(), Lang::getLostLevels, new String[] {"lostLevels", "level"}, Math.abs(diff), level); } } @@ -460,6 +460,7 @@ public void removeLevel(long amount) { private void changeExp(T amount, T difference, boolean sendMessage, boolean doMultiplier, boolean checkLeaderboard) { if (operator.compare(amount, operator.zero()) == 0) return; + long startingLevel = level; if (operator.compare(amount, operator.zero()) > 0 && level >= getMaxLevel()) return; @@ -468,7 +469,6 @@ private void changeExp(T amount, T difference, boolean sendMessage, boolean doMu amount = operator.multiply(amount, operator.fromDouble(getMultiplier())); final T totalAmount = amount; - long levelsChanged = 0; if (operator.compare(amount, operator.zero()) > 0) { while (operator.compare(operator.add(exp, amount), rawRequiredExp()) >= 0) { @@ -480,7 +480,6 @@ private void changeExp(T amount, T difference, boolean sendMessage, boolean doMu amount = operator.add(operator.subtract(amount, rawRequiredExp()), exp); exp = operator.zero(); level++; - levelsChanged++; sendLevelReward(level); } @@ -492,7 +491,6 @@ private void changeExp(T amount, T difference, boolean sendMessage, boolean doMu while (operator.compare(amount, exp) > 0 && level > getStartLevel()) { amount = operator.subtract(amount, exp); level--; - levelsChanged--; exp = rawRequiredExp(); } exp = operator.subtract(exp, amount); @@ -520,12 +518,6 @@ private void changeExp(T amount, T difference, boolean sendMessage, boolean doMu system.roundString(operator.abs(diff)), system.roundString(operator.abs(totalAmount)) ); } - - if (levelsChanged > 0) { - cache.lang().sendMessage(getPlayer(), Lang::getGainedLevels, "gainedLevels", levelsChanged); - } else if (levelsChanged < 0) { - cache.lang().sendMessage(getPlayer(), Lang::getLostLevels, "lostLevels", Math.abs(levelsChanged)); - } } lastAmount = displayTotal; @@ -534,6 +526,15 @@ private void changeExp(T amount, T difference, boolean sendMessage, boolean doMu level = Math.max(getStartLevel(), Math.min(level, getMaxLevel())); if (operator.compare(exp, operator.zero()) < 0) exp = operator.zero(); + if (sendMessage) { + long levelDifference = level - startingLevel; + if (levelDifference > 0) { + cache.lang().sendMessage(getPlayer(), Lang::getGainedLevels, new String[] {"gainedLevels", "level"}, levelDifference, level); + } else if (levelDifference < 0) { + cache.lang().sendMessage(getPlayer(), Lang::getLostLevels, new String[] {"lostLevels", "level"}, Math.abs(levelDifference), level); + } + } + if (checkLeaderboard) system.updateLeaderboard(); } From 16f7d3bd99b03dbe6ad6bf03c12b28055c4bdf13 Mon Sep 17 00:00:00 2001 From: Gustavo Paredes <66433837+CroaBeast@users.noreply.github.com> Date: Sun, 30 Nov 2025 11:49:46 -0500 Subject: [PATCH 02/12] 1.0.4 - Make database more async --- .../cyberlevels/DatabaseFactory.java | 279 +++++++++++------- 1 file changed, 167 insertions(+), 112 deletions(-) diff --git a/src/main/java/com/bitaspire/cyberlevels/DatabaseFactory.java b/src/main/java/com/bitaspire/cyberlevels/DatabaseFactory.java index 882cba9..4f94a13 100644 --- a/src/main/java/com/bitaspire/cyberlevels/DatabaseFactory.java +++ b/src/main/java/com/bitaspire/cyberlevels/DatabaseFactory.java @@ -11,6 +11,7 @@ import java.sql.*; import java.util.*; +import java.util.concurrent.CompletableFuture; @UtilityClass class DatabaseFactory { @@ -102,19 +103,21 @@ public void connect() { main.logger("&dAttempting to connect to " + type + "..."); long l = System.currentTimeMillis(); - try { - dataSource = new HikariDataSource(createConfig()); + main.scheduler().runTaskAsynchronously(() -> { + try { + dataSource = new HikariDataSource(createConfig()); - try (Connection conn = dataSource.getConnection()) { - ensureTargetSchema(conn); - ensureMetaSchema(conn); - } + try (Connection conn = dataSource.getConnection()) { + ensureTargetSchema(conn); + ensureMetaSchema(conn); + } - main.logger("&7Connected to &e" + type + "&7 successfully in &a" + (System.currentTimeMillis() - l) + "ms&7.", ""); - } catch (Exception e) { - main.logger("&cThere was an issue connecting to " + type + " Database.", ""); - e.printStackTrace(); - } + main.logger("&7Connected to &e" + type + "&7 successfully in &a" + (System.currentTimeMillis() - l) + "ms&7.", ""); + } catch (Exception e) { + main.logger("&cThere was an issue connecting to " + type + " Database.", ""); + e.printStackTrace(); + } + }); } @Override @@ -123,14 +126,17 @@ public void disconnect() { main.logger("&dAttempting to disconnect from " + type + "..."); long l = System.currentTimeMillis(); - try { - dataSource.close(); - dataSource = null; - main.logger("&7Disconnected from &e" + type + "&7 successfully in &a" + (System.currentTimeMillis() - l) + "ms&7.", ""); - } catch (Exception e) { - main.logger("&cThere was an issue disconnecting from " + type + " Database.", ""); - e.printStackTrace(); - } + + main.scheduler().runTaskAsynchronously(() -> { + try { + dataSource.close(); + dataSource = null; + main.logger("&7Disconnected from &e" + type + "&7 successfully in &a" + (System.currentTimeMillis() - l) + "ms&7.", ""); + } catch (Exception e) { + main.logger("&cThere was an issue disconnecting from " + type + " Database.", ""); + e.printStackTrace(); + } + }); } void ensureTargetSchema(Connection conn) throws SQLException { @@ -317,18 +323,28 @@ static double safeGetDouble(ResultSet rs) { @Override public boolean isUserLoaded(LevelUser user) { if (!isConnected()) return false; - try (Connection connection = dataSource.getConnection(); - PreparedStatement statement = connection.prepareStatement( - "SELECT 1 FROM " + qTab(getTable()) + " WHERE " + qCol("UUID") + "=?")) { - statement.setString(1, user.getUuid().toString()); - try (ResultSet rs = statement.executeQuery()) { - return rs.next(); + CompletableFuture future = new CompletableFuture<>(); + + main.scheduler().runTaskAsynchronously(() -> { + try (Connection connection = dataSource.getConnection(); + PreparedStatement statement = connection.prepareStatement( + "SELECT 1 FROM " + qTab(getTable()) + " WHERE " + qCol("UUID") + "=?")) { + statement.setString(1, user.getUuid().toString()); + try (ResultSet rs = statement.executeQuery()) { + future.complete(rs.next()); + } + } catch (Exception e) { + main.logger("&cFailed to check if user exists in table."); + e.printStackTrace(); + future.complete(false); } + }); + + try { + return future.get(); } catch (Exception e) { - main.logger("&cFailed to check if user exists in table."); - e.printStackTrace(); + return false; } - return false; } void setRewardLevel(LevelUser user, long level) { @@ -358,73 +374,87 @@ public void addUser(LevelUser user, boolean defValues) { expStr = String.valueOf(user.getExp()); // already string-ish } - String sql = "INSERT INTO " + qTab(getTable()) + " (" + - qCol("UUID") + "," + qCol("LEVEL") + "," + qCol("EXP") + "," + qCol("UPDATED_AT") + - ") VALUES (?,?,?,?)"; - - try (Connection connection = dataSource.getConnection(); - PreparedStatement st = connection.prepareStatement(sql)) { - st.setString(1, user.getUuid().toString()); - st.setLong(2, Long.parseLong(levelStr)); - st.setString(3, expStr); - st.setLong(4, System.currentTimeMillis()); - st.executeUpdate(); - - long now = System.currentTimeMillis(); - long highest = getRewardLevel(user); - try (PreparedStatement pm = prepareUpsertMeta(connection, user.getUuid(), highest, now)) { - pm.executeUpdate(); - } - } catch (Exception e) { - main.logger("&cFailed to add user " + user.getName() + "."); - e.printStackTrace(); - } + final String finalLevelStr = levelStr; + final String finalExpStr = expStr; + + main.scheduler().runTaskAsynchronously(() -> { + String sql = "INSERT INTO " + qTab(getTable()) + " (" + + qCol("UUID") + "," + qCol("LEVEL") + "," + qCol("EXP") + "," + qCol("UPDATED_AT") + + ") VALUES (?,?,?,?)"; + + try (Connection connection = dataSource.getConnection(); + PreparedStatement st = connection.prepareStatement(sql)) + { + st.setString(1, user.getUuid().toString()); + st.setLong(2, Long.parseLong(finalLevelStr)); + st.setString(3, finalExpStr); + st.setLong(4, System.currentTimeMillis()); + st.executeUpdate(); + + long now = System.currentTimeMillis(); + long highest = getRewardLevel(user); + try (PreparedStatement pm = prepareUpsertMeta(connection, user.getUuid(), highest, now)) { + pm.executeUpdate(); + } + } catch (Exception e) { + main.logger("&cFailed to add user " + user.getName() + "."); + e.printStackTrace(); + } + }); } @Override public void updateUser(LevelUser user) { if (!isConnected()) return; - UUID uuid = user.getUuid(); - long now = System.currentTimeMillis(); - - try (Connection connection = dataSource.getConnection()) { - try (PreparedStatement st = prepareUpsert( - connection, - uuid, - user.getLevel(), - String.valueOf(user.getExp()), - now - )) { - st.executeUpdate(); - } + final UUID uuid = user.getUuid(); + final long now = System.currentTimeMillis(); + final String expStr = String.valueOf(user.getExp()); + final long level = user.getLevel(); + final String name = user.getName(); + final long highest = getRewardLevel(user); + + main.scheduler().runTaskAsynchronously(() -> { + try (Connection connection = dataSource.getConnection()) { + try (PreparedStatement st = prepareUpsert( + connection, + uuid, + level, + expStr, + now + )) { + st.executeUpdate(); + } - long highest = getRewardLevel(user); - try (PreparedStatement stm = prepareUpsertMeta(connection, uuid, highest, now)) { - stm.executeUpdate(); + try (PreparedStatement stm = prepareUpsertMeta(connection, uuid, highest, now)) { + stm.executeUpdate(); + } + } catch (Exception e) { + main.logger("&cFailed to update user " + name + "."); + e.printStackTrace(); } - } catch (Exception e) { - main.logger("&cFailed to update user " + user.getName() + "."); - e.printStackTrace(); - } + }); } @Override public void removeUser(UUID uuid) { if (!isConnected()) return; - String sql = "DELETE FROM " + qTab(getTable()) + " WHERE " + qCol("UUID") + "=?"; - String metaSql = "DELETE FROM " + qTab(metaTable()) + " WHERE " + qCol("UUID") + "=?"; - try (Connection connection = dataSource.getConnection(); - PreparedStatement st = connection.prepareStatement(sql); - PreparedStatement sm = connection.prepareStatement(metaSql)) { - st.setString(1, uuid.toString()); - st.executeUpdate(); - - sm.setString(1, uuid.toString()); - sm.executeUpdate(); - } catch (Exception e) { - main.logger("&cFailed to remove user " + uuid + " from " + type + " database."); - e.printStackTrace(); - } + + main.scheduler().runTaskAsynchronously(() -> { + String sql = "DELETE FROM " + qTab(getTable()) + " WHERE " + qCol("UUID") + "=?"; + String metaSql = "DELETE FROM " + qTab(metaTable()) + " WHERE " + qCol("UUID") + "=?"; + try (Connection connection = dataSource.getConnection(); + PreparedStatement st = connection.prepareStatement(sql); + PreparedStatement sm = connection.prepareStatement(metaSql)) { + st.setString(1, uuid.toString()); + st.executeUpdate(); + + sm.setString(1, uuid.toString()); + sm.executeUpdate(); + } catch (Exception e) { + main.logger("&cFailed to remove user " + uuid + " from " + type + " database."); + e.printStackTrace(); + } + }); } @Override @@ -436,31 +466,44 @@ public LevelUser getUser(Player player) { public LevelUser getUser(UUID uuid) { if (!isConnected() || uuid == null) return null; - String sql = "SELECT " + qCol("LEVEL") + "," + qCol("EXP") + " FROM " + qTab(getTable()) + " WHERE " + qCol("UUID") + "=?"; + CompletableFuture> future = new CompletableFuture<>(); + + main.scheduler().runTaskAsynchronously(() -> { + String sql = "SELECT " + qCol("LEVEL") + "," + qCol("EXP") + " FROM " + qTab(getTable()) + " WHERE " + qCol("UUID") + "=?"; - try (Connection connection = dataSource.getConnection(); - PreparedStatement st = connection.prepareStatement(sql)) { - st.setString(1, uuid.toString()); + try (Connection connection = dataSource.getConnection(); + PreparedStatement st = connection.prepareStatement(sql)) { + st.setString(1, uuid.toString()); - try (ResultSet rs = st.executeQuery()) { - if (!rs.next()) return null; + try (ResultSet rs = st.executeQuery()) { + if (!rs.next()) { + future.complete(null); + return; + } - LevelUser user = system.createUser(uuid); - long level = rs.getLong("LEVEL"); - user.setLevel(level, false); + LevelUser user = system.createUser(uuid); + long level = rs.getLong("LEVEL"); + user.setLevel(level, false); - String expStr = rs.getString("EXP"); - if (expStr == null) expStr = "0"; - user.setExp(expStr, false, false, false); + String expStr = rs.getString("EXP"); + if (expStr == null) expStr = "0"; + user.setExp(expStr, false, false, false); - long hr = readHighestRewarded(connection, uuid); - setRewardLevel(user, hr >= 0 ? hr : user.getLevel()); + long hr = readHighestRewarded(connection, uuid); + setRewardLevel(user, hr >= 0 ? hr : user.getLevel()); - return user; + future.complete(user); + } + } catch (Exception e) { + main.logger("&cFailed to get player data for " + uuid + ".", ""); + e.printStackTrace(); + future.complete(null); } + }); + + try { + return future.get(); } catch (Exception e) { - main.logger("&cFailed to get player data for " + uuid + ".", ""); - e.printStackTrace(); return null; } } @@ -470,20 +513,32 @@ public Set getUuids() { Set uuids = new LinkedHashSet<>(); if (!isConnected()) return uuids; - String sql = "SELECT " + qCol("UUID") + " FROM " + qTab(getTable()); - try (Connection connection = dataSource.getConnection(); - PreparedStatement statement = connection.prepareStatement(sql); - ResultSet rs = statement.executeQuery()) { - while (rs.next()) { - try { - uuids.add(UUID.fromString(rs.getString("UUID"))); - } catch (Exception ignored) {} + CompletableFuture> future = new CompletableFuture<>(); + + main.scheduler().runTaskAsynchronously(() -> { + Set result = new LinkedHashSet<>(); + String sql = "SELECT " + qCol("UUID") + " FROM " + qTab(getTable()); + try (Connection connection = dataSource.getConnection(); + PreparedStatement statement = connection.prepareStatement(sql); + ResultSet rs = statement.executeQuery()) { + while (rs.next()) { + try { + result.add(UUID.fromString(rs.getString("UUID"))); + } catch (Exception ignored) {} + } + future.complete(result); + } catch (SQLException e) { + main.logger("&cFailed to fetch UUIDs from " + type + "."); + e.printStackTrace(); + future.complete(new LinkedHashSet<>()); } - } catch (SQLException e) { - main.logger("&cFailed to fetch UUIDs from " + type + "."); - e.printStackTrace(); + }); + + try { + return future.get(); + } catch (Exception e) { + return uuids; } - return uuids; } } From cd89471cc10b0450a158428bb9fd004c0c6a9c65 Mon Sep 17 00:00:00 2001 From: CroaBeast <66433837+CroaBeast@users.noreply.github.com> Date: Sun, 30 Nov 2025 11:58:05 -0500 Subject: [PATCH 03/12] Improve async user loading and leaderboard updates --- .../cyberlevels/UserManagerImpl.java | 121 ++++++++++++------ 1 file changed, 84 insertions(+), 37 deletions(-) diff --git a/src/main/java/com/bitaspire/cyberlevels/UserManagerImpl.java b/src/main/java/com/bitaspire/cyberlevels/UserManagerImpl.java index 8e032c1..d162cd3 100644 --- a/src/main/java/com/bitaspire/cyberlevels/UserManagerImpl.java +++ b/src/main/java/com/bitaspire/cyberlevels/UserManagerImpl.java @@ -22,6 +22,7 @@ import java.nio.file.Path; import java.util.*; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; final class UserManagerImpl implements UserManager { @@ -30,6 +31,7 @@ final class UserManagerImpl implements UserManager { private final BaseSystem system; private final Map> users = new ConcurrentHashMap<>(); + private final AtomicBoolean leaderboardUpdateQueued = new AtomicBoolean(false); BukkitTask autoSaveTask = null; @Getter @@ -179,27 +181,12 @@ private void saveToFlatFile(LevelUser user) { } } - private void loadUser(OfflinePlayer offline) { - Player player = (offline instanceof Player) ? (Player) offline : null; - - UUID uuid = offline.getUniqueId(); - LevelUser user = users.get(uuid); - - if (user != null && player != null && !user.isOnline()) { - LevelUser newUser = system.createUser(uuid); - - newUser.setLevel(user.getLevel(), false); - newUser.setExp(user.getExp() + "", true, false, false); - setRewardLevel(newUser, getRewardLevel(user)); - - users.put(uuid, newUser); - return; - } - + private LoadResult loadUserData(UUID uuid) { + LevelUser user = null; String migrationMessage = ""; if (database != null) { - user = (player != null) ? database.getUser(player) : database.getUser(uuid); + user = database.getUser(uuid); if (user == null) { if ((user = loadFromFlatFile(uuid)) != null) { @@ -219,7 +206,7 @@ private void loadUser(OfflinePlayer offline) { if (user == null) { Database old = main.database; if (old != null) { - LevelUser oldUser = (player != null) ? old.getUser(player) : old.getUser(uuid); + LevelUser oldUser = old.getUser(uuid); if (oldUser != null) { migrationMessage = " from " + old.getClass().getSimpleName() + " to flat-file"; LevelUser copy = system.createUser(oldUser); @@ -232,21 +219,59 @@ private void loadUser(OfflinePlayer offline) { if (user == null) user = system.createUser(uuid); } - if (StringUtils.isNotBlank(migrationMessage)) - main.logger("Migrated " + (player != null ? player.getName() : uuid) + migrationMessage); + return new LoadResult(user, migrationMessage); + } + + private void loadUserAsync(OfflinePlayer offline, boolean updateLeaderboard) { + Player player = (offline instanceof Player) ? (Player) offline : null; + + UUID uuid = offline.getUniqueId(); + LevelUser user = users.get(uuid); + + if (user != null && player != null && !user.isOnline()) { + LevelUser newUser = system.createUser(uuid); - users.put(uuid, user); - system.updateLeaderboard(); + newUser.setLevel(user.getLevel(), false); + newUser.setExp(user.getExp() + "", true, false, false); + setRewardLevel(newUser, getRewardLevel(user)); + + users.put(uuid, newUser); + if (updateLeaderboard) scheduleLeaderboardUpdate(); + return; + } + + Bukkit.getScheduler().runTaskAsynchronously(main, () -> { + LoadResult result = loadUserData(uuid); + Bukkit.getScheduler().runTask(main, () -> finishUserLoad(uuid, player, result, updateLeaderboard)); + }); + } + + private void finishUserLoad(UUID uuid, Player player, LoadResult result, boolean updateLeaderboard) { + if (StringUtils.isNotBlank(result.migrationMessage)) + main.logger("Migrated " + (player != null ? player.getName() : uuid) + result.migrationMessage); + + users.put(uuid, result.user); + if (updateLeaderboard) scheduleLeaderboardUpdate(); + } + + private void scheduleLeaderboardUpdate() { + if (!leaderboardUpdateQueued.compareAndSet(false, true)) return; + Bukkit.getScheduler().runTask(main, () -> { + system.updateLeaderboard(); + leaderboardUpdateQueued.set(false); + }); } + private record LoadResult(LevelUser user, String migrationMessage) { } + @Override public void loadPlayer(OfflinePlayer offline) { - loadUser(offline); + loadUserAsync(offline, true); } @Override public void loadPlayer(Player player) { - loadUser(player); + loadUserAsync(player, true); } @Override @@ -254,7 +279,7 @@ public void savePlayer(Player player, boolean clearData) { LevelUser user = users.get(player.getUniqueId()); if (user == null) return; - saveUser(user); + saveUserAsync(user); if (!clearData) return; UUID uuid = user.getUuid(); @@ -284,6 +309,10 @@ public void saveUser(LevelUser user) { saveToFlatFile(user); } + private void saveUserAsync(LevelUser user) { + Bukkit.getScheduler().runTaskAsynchronously(main, () -> saveUser(user)); + } + @Override public void removeUser(UUID uuid) { users.remove(uuid); @@ -305,18 +334,35 @@ void loadOfflinePlayers() { long l = System.currentTimeMillis(); main.logger("&dLoading data for offline players..."); - int counter = 0; - for (OfflinePlayer player : Bukkit.getOfflinePlayers()) { - loadPlayer(player); - counter++; - } + OfflinePlayer[] players = Bukkit.getOfflinePlayers(); + if (players.length < 1) return; - if (counter < 1) return; + int batchSize = 10; + new BukkitRunnable() { + int index = 0; + int loaded = 0; - main.logger("&7Loaded data for &e" + counter + - " &7offline player(s) in &a" + - (System.currentTimeMillis() - l) + - "ms&7.", ""); + @Override + public void run() { + int processed = 0; + while (index < players.length && processed < batchSize) { + loadUserAsync(players[index++], false); + loaded++; + processed++; + } + + if (index >= players.length) { + cancel(); + if (loaded > 0) + main.logger("&7Loaded data for &e" + loaded + + " &7offline player(s) in &a" + + (System.currentTimeMillis() - l) + + "ms&7.", ""); + + scheduleLeaderboardUpdate(); + } + } + }.runTaskTimer(main, 0L, 1L); } @Override @@ -328,7 +374,7 @@ public void loadOnlinePlayers() { int counter = 0; for (Player player : Bukkit.getOnlinePlayers()) { - loadPlayer(player); + loadUserAsync(player, false); counter++; } @@ -338,6 +384,7 @@ public void loadOnlinePlayers() { " &7online player(s) in &a" + (System.currentTimeMillis() - l) + "ms&7.", ""); + scheduleLeaderboardUpdate(); } @Override From 21065b902b724f1add4d8a07b0c458e3ca836694 Mon Sep 17 00:00:00 2001 From: Gustavo Paredes <66433837+CroaBeast@users.noreply.github.com> Date: Mon, 1 Dec 2025 11:14:51 -0500 Subject: [PATCH 04/12] 1.0.4 - Move expr4j as dependency --- build.gradle | 7 +- .../com/bitaspire/cyberlevels/BaseSystem.java | 8 +- .../cyberlevels/BigDecimalLevelSystem.java | 8 +- .../cyberlevels/DoubleLevelSystem.java | 8 +- .../cyberlevels/UserManagerImpl.java | 88 +++-- .../bitaspire/cyberlevels/cache/EarnExp.java | 57 ++- .../cyberlevels/level/AntiAbuse.java | 18 +- .../cyberlevels/listener/Listeners.java | 17 +- .../formula/BigDecimalExpressionBuilder.java | 175 --------- .../libs/formula/BigDecimalUtils.java | 147 -------- .../libs/formula/DoubleExpressionBuilder.java | 92 ----- .../bitaspire/libs/formula/DoubleUtils.java | 46 --- .../formula/exception/Expr4jException.java | 81 ----- .../libs/formula/expression/Expression.java | 276 --------------- .../formula/expression/ExpressionBuilder.java | 174 --------- .../formula/expression/ExpressionConfig.java | 68 ---- .../expression/ExpressionDictionary.java | 287 --------------- .../formula/expression/ExpressionNode.java | 62 ---- .../expression/ExpressionParameter.java | 76 ---- .../formula/expression/ExpressionParser.java | 334 ------------------ .../expression/ExpressionTokenizer.java | 334 ------------------ .../libs/formula/token/Function.java | 101 ------ .../bitaspire/libs/formula/token/Operand.java | 50 --- .../libs/formula/token/Operation.java | 25 -- .../libs/formula/token/Operator.java | 105 ------ .../libs/formula/token/OperatorType.java | 45 --- .../libs/formula/token/Separator.java | 86 ----- .../bitaspire/libs/formula/token/Token.java | 31 -- .../libs/formula/token/Variable.java | 48 --- 29 files changed, 104 insertions(+), 2750 deletions(-) delete mode 100644 src/main/java/com/bitaspire/libs/formula/BigDecimalExpressionBuilder.java delete mode 100644 src/main/java/com/bitaspire/libs/formula/BigDecimalUtils.java delete mode 100644 src/main/java/com/bitaspire/libs/formula/DoubleExpressionBuilder.java delete mode 100644 src/main/java/com/bitaspire/libs/formula/DoubleUtils.java delete mode 100644 src/main/java/com/bitaspire/libs/formula/exception/Expr4jException.java delete mode 100644 src/main/java/com/bitaspire/libs/formula/expression/Expression.java delete mode 100644 src/main/java/com/bitaspire/libs/formula/expression/ExpressionBuilder.java delete mode 100644 src/main/java/com/bitaspire/libs/formula/expression/ExpressionConfig.java delete mode 100644 src/main/java/com/bitaspire/libs/formula/expression/ExpressionDictionary.java delete mode 100644 src/main/java/com/bitaspire/libs/formula/expression/ExpressionNode.java delete mode 100644 src/main/java/com/bitaspire/libs/formula/expression/ExpressionParameter.java delete mode 100644 src/main/java/com/bitaspire/libs/formula/expression/ExpressionParser.java delete mode 100644 src/main/java/com/bitaspire/libs/formula/expression/ExpressionTokenizer.java delete mode 100644 src/main/java/com/bitaspire/libs/formula/token/Function.java delete mode 100644 src/main/java/com/bitaspire/libs/formula/token/Operand.java delete mode 100644 src/main/java/com/bitaspire/libs/formula/token/Operation.java delete mode 100644 src/main/java/com/bitaspire/libs/formula/token/Operator.java delete mode 100644 src/main/java/com/bitaspire/libs/formula/token/OperatorType.java delete mode 100644 src/main/java/com/bitaspire/libs/formula/token/Separator.java delete mode 100644 src/main/java/com/bitaspire/libs/formula/token/Token.java delete mode 100644 src/main/java/com/bitaspire/libs/formula/token/Variable.java diff --git a/build.gradle b/build.gradle index b3c341d..4128eef 100644 --- a/build.gradle +++ b/build.gradle @@ -62,7 +62,12 @@ dependencies { // Misc utils implementation 'org.bstats:bstats-bukkit:3.0.2' implementation 'me.croabeast:YAML-API:1.1' - implementation 'me.croabeast:GlobalScheduler:1.0' + implementation 'me.croabeast:GlobalScheduler:1.1' + + implementation 'me.croabeast.expr4j:core:1.0' + implementation 'me.croabeast.expr4j:big-decimal:1.0' + implementation 'me.croabeast.expr4j:double:1.0' + compileOnly 'ch.obermuhlner:big-math:2.3.2' compileOnly 'org.apache.commons:commons-lang3:3.18.0' } diff --git a/src/main/java/com/bitaspire/cyberlevels/BaseSystem.java b/src/main/java/com/bitaspire/cyberlevels/BaseSystem.java index c6c3abd..a7c2bed 100644 --- a/src/main/java/com/bitaspire/cyberlevels/BaseSystem.java +++ b/src/main/java/com/bitaspire/cyberlevels/BaseSystem.java @@ -1,7 +1,6 @@ package com.bitaspire.cyberlevels; import com.bitaspire.cyberlevels.user.UserManager; -import com.bitaspire.libs.formula.expression.ExpressionBuilder; import com.bitaspire.cyberlevels.cache.Cache; import com.bitaspire.cyberlevels.cache.Lang; import com.bitaspire.cyberlevels.level.*; @@ -10,6 +9,7 @@ import lombok.RequiredArgsConstructor; import lombok.Setter; import me.croabeast.beanslib.Beans; +import me.croabeast.expr4j.expression.Builder; import org.apache.commons.lang3.StringUtils; import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; @@ -266,7 +266,7 @@ abstract class BaseFormula implements Formula { @Getter private final String asString; - abstract ExpressionBuilder builder(); + abstract Builder builder(); @NotNull public T evaluate(UUID uuid) { @@ -304,14 +304,14 @@ public void update() { List> users = userManager.getUsersList(); updating = true; - Bukkit.getScheduler().runTaskAsynchronously(main, () -> { + main.scheduler().runTaskAsynchronously(() -> { List> list = new ArrayList<>(); for (LevelUser user : users) list.add(toEntry(user)); list.sort(Comparator.naturalOrder()); List> top10 = list.subList(0, Math.min(10, list.size())); - Bukkit.getScheduler().runTask(main, () -> { + main.scheduler().runTask(() -> { topTenPlayers.clear(); topTenPlayers.addAll(top10); updating = false; diff --git a/src/main/java/com/bitaspire/cyberlevels/BigDecimalLevelSystem.java b/src/main/java/com/bitaspire/cyberlevels/BigDecimalLevelSystem.java index 26ff48a..01a9329 100644 --- a/src/main/java/com/bitaspire/cyberlevels/BigDecimalLevelSystem.java +++ b/src/main/java/com/bitaspire/cyberlevels/BigDecimalLevelSystem.java @@ -1,12 +1,12 @@ package com.bitaspire.cyberlevels; import com.bitaspire.cyberlevels.user.UserManager; -import com.bitaspire.libs.formula.BigDecimalExpressionBuilder; import com.bitaspire.cyberlevels.level.Formula; import com.bitaspire.cyberlevels.level.Operator; import com.bitaspire.cyberlevels.user.LevelUser; -import com.bitaspire.libs.formula.expression.ExpressionBuilder; import lombok.Getter; +import me.croabeast.expr4j.BigDecimalBuilder; +import me.croabeast.expr4j.expression.Builder; import org.jetbrains.annotations.NotNull; import java.math.BigDecimal; @@ -121,8 +121,8 @@ public int compareTo(@NotNull Entry other) { Formula createFormula(String string) { return new BaseFormula(operator, string) { @NotNull - ExpressionBuilder builder() { - return new BigDecimalExpressionBuilder(); + Builder builder() { + return new BigDecimalBuilder(); } }; } diff --git a/src/main/java/com/bitaspire/cyberlevels/DoubleLevelSystem.java b/src/main/java/com/bitaspire/cyberlevels/DoubleLevelSystem.java index 69813e1..bd527cd 100644 --- a/src/main/java/com/bitaspire/cyberlevels/DoubleLevelSystem.java +++ b/src/main/java/com/bitaspire/cyberlevels/DoubleLevelSystem.java @@ -1,12 +1,12 @@ package com.bitaspire.cyberlevels; import com.bitaspire.cyberlevels.user.UserManager; -import com.bitaspire.libs.formula.DoubleExpressionBuilder; import com.bitaspire.cyberlevels.level.Formula; import com.bitaspire.cyberlevels.level.Operator; import com.bitaspire.cyberlevels.user.LevelUser; -import com.bitaspire.libs.formula.expression.ExpressionBuilder; import lombok.Getter; +import me.croabeast.expr4j.DoubleBuilder; +import me.croabeast.expr4j.expression.Builder; import org.jetbrains.annotations.NotNull; import java.math.RoundingMode; @@ -132,8 +132,8 @@ public int compareTo(@NotNull Entry other) { Formula createFormula(String string) { return new BaseFormula(operator, string) { @NotNull - ExpressionBuilder builder() { - return new DoubleExpressionBuilder(); + Builder builder() { + return new DoubleBuilder(); } }; } diff --git a/src/main/java/com/bitaspire/cyberlevels/UserManagerImpl.java b/src/main/java/com/bitaspire/cyberlevels/UserManagerImpl.java index d162cd3..47fb0e5 100644 --- a/src/main/java/com/bitaspire/cyberlevels/UserManagerImpl.java +++ b/src/main/java/com/bitaspire/cyberlevels/UserManagerImpl.java @@ -7,12 +7,13 @@ import com.bitaspire.cyberlevels.user.LevelUser; import com.bitaspire.cyberlevels.user.UserManager; import lombok.Getter; +import lombok.RequiredArgsConstructor; +import me.croabeast.scheduler.GlobalRunnable; +import me.croabeast.scheduler.GlobalTask; import org.apache.commons.lang.StringUtils; import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; import org.bukkit.entity.Player; -import org.bukkit.scheduler.BukkitRunnable; -import org.bukkit.scheduler.BukkitTask; import org.jetbrains.annotations.NotNull; import java.io.BufferedReader; @@ -33,7 +34,7 @@ final class UserManagerImpl implements UserManager { private final Map> users = new ConcurrentHashMap<>(); private final AtomicBoolean leaderboardUpdateQueued = new AtomicBoolean(false); - BukkitTask autoSaveTask = null; + GlobalTask autoSaveTask = null; @Getter private Database database = null; @@ -101,12 +102,14 @@ void checkMigration() { @Override public LevelUser getUser(UUID uuid) { LevelUser user = users.get(uuid); + if (user == null) { OfflinePlayer player = Bukkit.getPlayer(uuid); - if (player == null) player = Bukkit.getOfflinePlayer(uuid); - loadUser(player); - return users.get(uuid); + if (player == null) + player = Bukkit.getOfflinePlayer(uuid); + return loadUser(player); } + return user; } @@ -182,7 +185,7 @@ private void saveToFlatFile(LevelUser user) { } private LoadResult loadUserData(UUID uuid) { - LevelUser user = null; + LevelUser user; String migrationMessage = ""; if (database != null) { @@ -240,12 +243,19 @@ private void loadUserAsync(OfflinePlayer offline, boolean updateLeaderboard) { return; } - Bukkit.getScheduler().runTaskAsynchronously(main, () -> { + main.scheduler().runTaskAsynchronously(() -> { LoadResult result = loadUserData(uuid); - Bukkit.getScheduler().runTask(main, () -> finishUserLoad(uuid, player, result, updateLeaderboard)); + main.scheduler().runTask(() -> finishUserLoad(uuid, player, result, updateLeaderboard)); }); } + private LevelUser loadUser(OfflinePlayer offline) { + Player player = (offline instanceof Player) ? (Player) offline : null; + LoadResult result = loadUserData(offline.getUniqueId()); + finishUserLoad(offline.getUniqueId(), player, result, true); + return result.user; + } + private void finishUserLoad(UUID uuid, Player player, LoadResult result, boolean updateLeaderboard) { if (StringUtils.isNotBlank(result.migrationMessage)) main.logger("Migrated " + (player != null ? player.getName() : uuid) + result.migrationMessage); @@ -256,13 +266,17 @@ private void finishUserLoad(UUID uuid, Player player, LoadResult result, boolean private void scheduleLeaderboardUpdate() { if (!leaderboardUpdateQueued.compareAndSet(false, true)) return; - Bukkit.getScheduler().runTask(main, () -> { + main.scheduler().runTask(() -> { system.updateLeaderboard(); leaderboardUpdateQueued.set(false); }); } - private record LoadResult(LevelUser user, String migrationMessage) { } + @RequiredArgsConstructor + private class LoadResult { + final LevelUser user; + final String migrationMessage; + } @Override public void loadPlayer(OfflinePlayer offline) { @@ -310,7 +324,7 @@ public void saveUser(LevelUser user) { } private void saveUserAsync(LevelUser user) { - Bukkit.getScheduler().runTaskAsynchronously(main, () -> saveUser(user)); + main.scheduler().runTaskAsynchronously(() -> saveUser(user)); } @Override @@ -338,7 +352,8 @@ void loadOfflinePlayers() { if (players.length < 1) return; int batchSize = 10; - new BukkitRunnable() { + + new GlobalRunnable(main.scheduler()) { int index = 0; int loaded = 0; @@ -351,18 +366,18 @@ public void run() { processed++; } - if (index >= players.length) { - cancel(); - if (loaded > 0) - main.logger("&7Loaded data for &e" + loaded + - " &7offline player(s) in &a" + - (System.currentTimeMillis() - l) + - "ms&7.", ""); + if (index < players.length) return; - scheduleLeaderboardUpdate(); - } + cancel(); + if (loaded > 0) + main.logger("&7Loaded data for &e" + loaded + + " &7offline player(s) in &a" + + (System.currentTimeMillis() - l) + + "ms&7.", ""); + + scheduleLeaderboardUpdate(); } - }.runTaskTimer(main, 0L, 1L); + }.runTaskTimer(0L, 1L); } @Override @@ -397,24 +412,21 @@ public void startAutoSave() { if (!cache.config().isAutoSaveEnabled()) return; Config config = cache.config(); - autoSaveTask = (new BukkitRunnable() { - @Override - public void run() { - long start = System.currentTimeMillis(); - main.userManager().saveOnlinePlayers(false); + autoSaveTask = main.scheduler().runTaskLater(() -> { + long start = System.currentTimeMillis(); + main.userManager().saveOnlinePlayers(false); - if (config.syncLeaderboardOnAutoSave()) - system.getLeaderboard().update(); + if (config.syncLeaderboardOnAutoSave()) + system.getLeaderboard().update(); - if (config.isMessagesOnAutoSave()) - cache.lang().sendMessage( - null, Lang::getAutoSave, "ms", - System.currentTimeMillis() - start - ); + if (config.isMessagesOnAutoSave()) + cache.lang().sendMessage( + null, Lang::getAutoSave, "ms", + System.currentTimeMillis() - start + ); - startAutoSave(); - } - }).runTaskLater(main, 20L * config.getAutoSaveInterval()); + startAutoSave(); + }, 20L * config.getAutoSaveInterval()); } @Override diff --git a/src/main/java/com/bitaspire/cyberlevels/cache/EarnExp.java b/src/main/java/com/bitaspire/cyberlevels/cache/EarnExp.java index 25911ac..dc30729 100644 --- a/src/main/java/com/bitaspire/cyberlevels/cache/EarnExp.java +++ b/src/main/java/com/bitaspire/cyberlevels/cache/EarnExp.java @@ -7,6 +7,7 @@ import lombok.Getter; import lombok.Setter; import lombok.experimental.Accessors; +import me.croabeast.scheduler.GlobalTask; import org.apache.commons.lang.StringUtils; import org.bukkit.Bukkit; import org.bukkit.Location; @@ -29,8 +30,6 @@ import org.bukkit.inventory.meta.PotionMeta; import org.bukkit.metadata.FixedMetadataValue; import org.bukkit.potion.PotionType; -import org.bukkit.scheduler.BukkitRunnable; -import org.bukkit.scheduler.BukkitTask; import org.jetbrains.annotations.NotNull; import java.io.IOException; @@ -82,15 +81,14 @@ public void update() { void setDefaultEvents() { final SourceImpl source = events.get("timed-giving"); source.setRegistrable(new ExpSource.Registrable() { - private BukkitTask task = null; + private GlobalTask task = null; @Override public void register() { if (!source.isEnabled() && !source.useSpecifics()) return; - task = Bukkit.getScheduler().runTaskTimer( - main, + task = main.scheduler().runTaskTimer( () -> { for (Player p : Bukkit.getOnlinePlayers()) sendPermissionExp(p, source); @@ -254,39 +252,36 @@ private void onBrewing(BrewEvent event) { prePotion[i] = meta.getBasePotionData().getType(); } - (new BukkitRunnable() { - @Override - public void run() { - double counter = 0; + main.scheduler().runTaskLater(() -> { + double counter = 0; - for (int i = 0; i <= 2 ; i++) { - ItemStack stack = event.getContents().getItem(i); - if (stack == null) continue; + for (int i = 0; i <= 2 ; i++) { + ItemStack stack = event.getContents().getItem(i); + if (stack == null) continue; - PotionMeta meta = (PotionMeta) stack.getItemMeta(); - if (meta == null) continue; + PotionMeta meta = (PotionMeta) stack.getItemMeta(); + if (meta == null) continue; - String data = ""; + String data = ""; - PotionType type = meta.getBasePotionData().getType(); - if (prePotion[i] == null || type != prePotion[i]) - data = type.toString(); + PotionType type = meta.getBasePotionData().getType(); + if (prePotion[i] == null || type != prePotion[i]) + data = type.toString(); - if (main.levelSystem().checkAntiAbuse(player, s)) return; - if (s.isEnabled() || s.useSpecifics()) - counter += s.getPartialMatchesExp(data); - } - - LevelUser user = main.userManager().getUser(player); + if (main.levelSystem().checkAntiAbuse(player, s)) return; + if (s.isEnabled() || s.useSpecifics()) + counter += s.getPartialMatchesExp(data); + } - if (counter > 0) { - user.addExp(counter + "", main.cache().config().isMultiplierEvents()); - return; - } + LevelUser user = main.userManager().getUser(player); - if (counter < 0) user.removeExp(Math.abs(counter) + ""); + if (counter > 0) { + user.addExp(counter + "", main.cache().config().isMultiplierEvents()); + return; } - }).runTaskLater(main, 1L); + + if (counter < 0) user.removeExp(Math.abs(counter) + ""); + }, 1L); } }); @@ -360,7 +355,7 @@ private void onChat(AsyncPlayerChatEvent event) { counter += s.getPartialMatchesExp(item); final double finalCounter = counter; - Bukkit.getScheduler().runTask(main, () -> { + main.scheduler().runTask(() -> { LevelUser user = main.userManager().getUser(player); if (finalCounter > 0) { diff --git a/src/main/java/com/bitaspire/cyberlevels/level/AntiAbuse.java b/src/main/java/com/bitaspire/cyberlevels/level/AntiAbuse.java index b202efa..985da2d 100644 --- a/src/main/java/com/bitaspire/cyberlevels/level/AntiAbuse.java +++ b/src/main/java/com/bitaspire/cyberlevels/level/AntiAbuse.java @@ -2,9 +2,7 @@ import com.bitaspire.cyberlevels.CyberLevels; import lombok.Getter; -import org.bukkit.Bukkit; import org.bukkit.entity.Player; -import org.bukkit.scheduler.BukkitRunnable; import java.text.ParseException; import java.text.SimpleDateFormat; @@ -160,11 +158,7 @@ public Timer(CyberLevels main, AntiAbuse antiAbuse, String unformatted) { * Starts the timer asynchronously. */ public void start() { - new BukkitRunnable() { - public void run() { - startScheduler(false); - } - }.runTaskAsynchronously(main); + main.scheduler().runTaskAsynchronously(() -> startScheduler(false)); } private void startScheduler(boolean cancelTimer) { @@ -358,14 +352,8 @@ private class TimedTask extends TimerTask { public void run() { if (main == null || !main.isEnabled()) return; - Bukkit.getScheduler().runTask(main, antiAbuse::resetLimiters); - - new BukkitRunnable() { - @Override - public void run() { - startScheduler(true); - } - }.runTaskLaterAsynchronously(main, 20L); + main.scheduler().runTask(antiAbuse::resetLimiters); + main.scheduler().runTaskLaterAsynchronously(() -> startScheduler(true), 20L); } } diff --git a/src/main/java/com/bitaspire/cyberlevels/listener/Listeners.java b/src/main/java/com/bitaspire/cyberlevels/listener/Listeners.java index e6b9e44..6081499 100644 --- a/src/main/java/com/bitaspire/cyberlevels/listener/Listeners.java +++ b/src/main/java/com/bitaspire/cyberlevels/listener/Listeners.java @@ -12,7 +12,6 @@ import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.metadata.FixedMetadataValue; -import org.bukkit.scheduler.BukkitRunnable; import java.util.HashSet; import java.util.List; @@ -63,15 +62,13 @@ public void unregister() { private void fixPlacedAbuse(List blocks, BlockFace direction) { for (Block block : blocks) { - if (block.hasMetadata("CLV_PLACED")) { - (new BukkitRunnable() { - @Override - public void run() { - Block newBlock = block.getRelative(direction); - newBlock.setMetadata("CLV_PLACED", new FixedMetadataValue(main, true)); - } - }).runTaskLater(main, 1L); - } + if (!block.hasMetadata("CLV_PLACED")) continue; + + main.scheduler().runTaskLater(() -> + block.getRelative(direction).setMetadata( + "CLV_PLACED", + new FixedMetadataValue(main, true) + ), 1L); } } diff --git a/src/main/java/com/bitaspire/libs/formula/BigDecimalExpressionBuilder.java b/src/main/java/com/bitaspire/libs/formula/BigDecimalExpressionBuilder.java deleted file mode 100644 index c9eb4d3..0000000 --- a/src/main/java/com/bitaspire/libs/formula/BigDecimalExpressionBuilder.java +++ /dev/null @@ -1,175 +0,0 @@ -/** - * Copyright 2023 Pratanu Mandal - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.bitaspire.libs.formula; - -import ch.obermuhlner.math.big.BigDecimalMath; -import com.bitaspire.libs.formula.expression.ExpressionBuilder; -import com.bitaspire.libs.formula.expression.ExpressionConfig; -import com.bitaspire.libs.formula.expression.ExpressionDictionary; -import com.bitaspire.libs.formula.expression.ExpressionParameter; -import com.bitaspire.libs.formula.token.Function; -import com.bitaspire.libs.formula.token.Operator; -import com.bitaspire.libs.formula.token.OperatorType; -import lombok.Getter; - -import java.math.BigDecimal; -import java.math.MathContext; -import java.math.RoundingMode; -import java.util.Collections; -import java.util.stream.Collectors; - -/** - * The BigDecimalExpressionBuilder class provides an implementation to parse expressions for parameters of type {@link BigDecimal}.
- * - * @author Pratanu Mandal - * @since 1.0 - * - */ -@Getter -public class BigDecimalExpressionBuilder extends ExpressionBuilder { - - /** Default math context */ - public static final MathContext DEFAULT_CONTEXT = new MathContext(20, RoundingMode.HALF_UP); - - private MathContext mathContext; - - /** - * No-Argument Constructor. - */ - public BigDecimalExpressionBuilder() { - this(DEFAULT_CONTEXT); - } - - /** - * Parameterized constructor. - * - * @param precision Precision - */ - public BigDecimalExpressionBuilder(int precision) { - this(precision, DEFAULT_CONTEXT.getRoundingMode()); - } - - /** - * Parameterized constructor. - * - * @param precision Precision - * @param roundingMode Rounding mode - */ - public BigDecimalExpressionBuilder(int precision, RoundingMode roundingMode) { - this(new MathContext(precision, roundingMode)); - } - - /** - * Parameterized constructor. - * - * @param mathContext Math context - */ - public BigDecimalExpressionBuilder(MathContext mathContext) { - super(new ExpressionConfig() { - @Override - protected BigDecimal stringToOperand(String operand) { - return new BigDecimal(operand); - } - - @Override - protected String operandToString(BigDecimal operand) { - return operand.toString(); - } - }); - - this.mathContext = mathContext; - this.initialize(); - } - - /** - * Initialize the operators, functions, and constants. - */ - protected void initialize() { - ExpressionDictionary expressionDictionary = this.getExpressionDictionary(); - - expressionDictionary.addOperator(new Operator<>("+", OperatorType.PREFIX, Integer.MAX_VALUE, (parameters) -> parameters.get(0).value())); - expressionDictionary.addOperator(new Operator<>("-", OperatorType.PREFIX, Integer.MAX_VALUE, (parameters) -> parameters.get(0).value().negate())); - - expressionDictionary.addOperator(new Operator<>("+", OperatorType.INFIX, 1, (parameters) -> parameters.get(0).value().add(parameters.get(1).value(), mathContext))); - expressionDictionary.addOperator(new Operator<>("-", OperatorType.INFIX, 1, (parameters) -> parameters.get(0).value().subtract(parameters.get(1).value(), mathContext))); - - expressionDictionary.addOperator(new Operator<>("*", OperatorType.INFIX, 2, (parameters) -> parameters.get(0).value().multiply(parameters.get(1).value(), mathContext))); - expressionDictionary.addOperator(new Operator<>("/", OperatorType.INFIX, 2, (parameters) -> parameters.get(0).value().divide(parameters.get(1).value(), mathContext))); - expressionDictionary.addOperator(new Operator<>("%", OperatorType.INFIX, 2, (parameters) -> parameters.get(0).value().remainder(parameters.get(1).value(), mathContext))); - - expressionDictionary.addOperator(new Operator<>("^", OperatorType.INFIX_RTL, 3, (parameters) -> BigDecimalMath.pow(parameters.get(0).value(), parameters.get(1).value(), mathContext))); - - expressionDictionary.addOperator(new Operator<>("!", OperatorType.POSTFIX, 5, (parameters) -> BigDecimalUtils.factorial(parameters.get(0).value()))); - - expressionDictionary.addOperator(new Operator<>("abs", OperatorType.PREFIX, 4, (parameters) -> parameters.get(0).value().abs(mathContext))); - - expressionDictionary.addOperator(new Operator<>("sin", OperatorType.PREFIX, 4, (parameters) -> BigDecimalMath.sin(parameters.get(0).value(), mathContext))); - expressionDictionary.addOperator(new Operator<>("cos", OperatorType.PREFIX, 4, (parameters) -> BigDecimalMath.cos(parameters.get(0).value(), mathContext))); - expressionDictionary.addOperator(new Operator<>("tan", OperatorType.PREFIX, 4, (parameters) -> BigDecimalMath.tan(parameters.get(0).value(), mathContext))); - - expressionDictionary.addOperator(new Operator<>("asin", OperatorType.PREFIX, 4, (parameters) -> BigDecimalMath.asin(parameters.get(0).value(), mathContext))); - expressionDictionary.addOperator(new Operator<>("acos", OperatorType.PREFIX, 4, (parameters) -> BigDecimalMath.acos(parameters.get(0).value(), mathContext))); - expressionDictionary.addOperator(new Operator<>("atan", OperatorType.PREFIX, 4, (parameters) -> BigDecimalMath.atan(parameters.get(0).value(), mathContext))); - - expressionDictionary.addOperator(new Operator<>("sinh", OperatorType.PREFIX, 4, (parameters) -> BigDecimalMath.sinh(parameters.get(0).value(), mathContext))); - expressionDictionary.addOperator(new Operator<>("cosh", OperatorType.PREFIX, 4, (parameters) -> BigDecimalMath.cosh(parameters.get(0).value(), mathContext))); - expressionDictionary.addOperator(new Operator<>("tanh", OperatorType.PREFIX, 4, (parameters) -> BigDecimalMath.tanh(parameters.get(0).value(), mathContext))); - - expressionDictionary.addOperator(new Operator<>("asinh", OperatorType.PREFIX, 4, (parameters) -> BigDecimalMath.asinh(parameters.get(0).value(), mathContext))); - expressionDictionary.addOperator(new Operator<>("acosh", OperatorType.PREFIX, 4, (parameters) -> BigDecimalMath.acosh(parameters.get(0).value(), mathContext))); - expressionDictionary.addOperator(new Operator<>("atanh", OperatorType.PREFIX, 4, (parameters) -> BigDecimalMath.atanh(parameters.get(0).value(), mathContext))); - - expressionDictionary.addFunction(new Function<>("deg", 1, (parameters) -> BigDecimalMath.toDegrees(parameters.get(0).value(), mathContext))); - expressionDictionary.addFunction(new Function<>("rad", 1, (parameters) -> BigDecimalMath.toRadians(parameters.get(0).value(), mathContext))); - - expressionDictionary.addOperator(new Operator<>("round", OperatorType.PREFIX, 4, (parameters) -> parameters.get(0).value().setScale(0, RoundingMode.HALF_UP))); - expressionDictionary.addOperator(new Operator<>("floor", OperatorType.PREFIX, 4, (parameters) -> parameters.get(0).value().setScale(0, RoundingMode.FLOOR))); - expressionDictionary.addOperator(new Operator<>("ceil", OperatorType.PREFIX, 4, (parameters) -> parameters.get(0).value().setScale(0, RoundingMode.CEILING))); - - expressionDictionary.addOperator(new Operator<>("ln", OperatorType.PREFIX, 4, (parameters) -> BigDecimalMath.log(parameters.get(0).value(), mathContext))); - expressionDictionary.addOperator(new Operator<>("log10", OperatorType.PREFIX, 4, (parameters) -> BigDecimalMath.log10(parameters.get(0).value(), mathContext))); - expressionDictionary.addFunction(new Function<>("log", 2, (parameters) -> BigDecimalUtils.log(parameters.get(1).value(), parameters.get(0).value(), mathContext))); - - expressionDictionary.addOperator(new Operator<>("sqrt", OperatorType.PREFIX, 4, (parameters) -> BigDecimalMath.sqrt(parameters.get(0).value(), mathContext))); - expressionDictionary.addOperator(new Operator<>("cbrt", OperatorType.PREFIX, 4, (parameters) -> BigDecimalUtils.cbrt(parameters.get(0).value(), mathContext))); - - expressionDictionary.addFunction(new Function<>("exp", 1, (parameters) -> BigDecimalMath.exp(parameters.get(0).value(), mathContext))); - - expressionDictionary.addFunction(new Function<>("max", (parameters) -> parameters.isEmpty() ? BigDecimal.ZERO : Collections.max(parameters.stream().map(ExpressionParameter::value).collect(Collectors.toList())))); - expressionDictionary.addFunction(new Function<>("min", (parameters) -> parameters.isEmpty() ? BigDecimal.ZERO : Collections.min(parameters.stream().map(ExpressionParameter::value).collect(Collectors.toList())))); - - expressionDictionary.addFunction(new Function<>("mean", (parameters) -> BigDecimalUtils.average(parameters.stream().map(ExpressionParameter::value).collect(Collectors.toList()), mathContext))); - expressionDictionary.addFunction(new Function<>("average", (parameters) -> BigDecimalUtils.average(parameters.stream().map(ExpressionParameter::value).collect(Collectors.toList()), mathContext))); - - expressionDictionary.addFunction(new Function<>("rand", 0, (parameters) -> new BigDecimal(Math.random()))); - - expressionDictionary.addConstant("pi", BigDecimalMath.pi(mathContext)); - expressionDictionary.addConstant("e", BigDecimalMath.e(mathContext)); - } - - /** - * Set the math context. - * - * @param mathContext Math context - */ - public void setMathContext(MathContext mathContext) { - this.reset(); - this.mathContext = mathContext; - this.initialize(); - } -} diff --git a/src/main/java/com/bitaspire/libs/formula/BigDecimalUtils.java b/src/main/java/com/bitaspire/libs/formula/BigDecimalUtils.java deleted file mode 100644 index ae5da5e..0000000 --- a/src/main/java/com/bitaspire/libs/formula/BigDecimalUtils.java +++ /dev/null @@ -1,147 +0,0 @@ -/** - * Copyright 2023 Pratanu Mandal - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.bitaspire.libs.formula; - -import ch.obermuhlner.math.big.BigDecimalMath; -import com.bitaspire.libs.formula.exception.Expr4jException; - -import java.math.BigDecimal; -import java.math.BigInteger; -import java.math.MathContext; -import java.util.List; -import java.util.Objects; - -/** - * The BigDecimalUtils class provides extra math functionality not available for {@link BigDecimal} class. - * - * @author Pratanu Mandal - * @since 1.0 - * - */ -public class BigDecimalUtils { - - /** - * Utility classes should not have public constructors. - */ - private BigDecimalUtils() {} - - /** - * Compare two {@code BigDecimal} instances with a specified precision. - * - * @param x First {@code BigDecimal} instance - * @param y Second {@code BigDecimal} instance - * @param precision Precision - * @return a negative integer, zero, or a positive integer as the first instance is less than, equal to, or greater than the second instance - */ - public static int compare(BigDecimal x, BigDecimal y, int precision) { - BigDecimal epsilon = BigDecimal.ONE.movePointLeft(precision); - BigDecimal absDelta = x.subtract(y).abs(); - int result = absDelta.compareTo(epsilon); - return result <= 0 ? 0 : x.compareTo(y); - } - - /** - * Check if two {@code BigDecimal} instances are equal with a specified precision. - * - * @param x First {@code BigDecimal} instance - * @param y Second {@code BigDecimal} instance - * @param precision Precision - * @return True if both instances are equal, false otherwise - */ - public static boolean equals(BigDecimal x, BigDecimal y, int precision) { - return compare(x, y, precision) == 0; - } - - /** - * Check if a {@code BigDecimal} instance is an integer. - * - * @param x The {@code BigDecimal} instance - * @return True if the instance is an integer, false otherwise - */ - public static boolean isInteger(BigDecimal x) { - return x.stripTrailingZeros().scale() <= 0; - } - - /** - * Calculate the log of x to the base y. - * - * @param x The operand - * @param y The base or radix - * @param mathContext Math context - * @return Log of x to the base y - */ - public static BigDecimal log(BigDecimal x, BigDecimal y, MathContext mathContext) { - return BigDecimalMath.log(x, mathContext).divide(BigDecimalMath.log(y, mathContext), mathContext); - } - - /** - * Calculate the cube root of x. - * - * @param x The operand - * @param mathContext Math context - * @return Cube root of x - */ - public static BigDecimal cbrt(BigDecimal x, MathContext mathContext) { - return BigDecimalMath.pow(x, - BigDecimal.ONE.divide(new BigDecimal(3), mathContext), - mathContext); - } - - /** - * Calculate average (mean) of a list of operands. - * - * @param list List of operands - * @param mathContext Math context - * @return Average (mean) of the list of operands - */ - public static BigDecimal average(List list, MathContext mathContext) { - BigDecimal sum = list.stream() - .map(Objects::requireNonNull) - .reduce(BigDecimal.ZERO, BigDecimal::add); - return sum.divide(new BigDecimal(list.size()), mathContext); - } - - /** - * Calculate the factorial of an integer. - * - * @param n The integer - * @return Factorial of n - */ - private static BigDecimal factorial(BigInteger n) { - BigInteger factorial = BigInteger.ONE; - while (n.compareTo(BigInteger.ONE) > 0) { - factorial = factorial.multiply(n); - n = n.subtract(BigInteger.ONE); - } - return new BigDecimal(factorial); - } - - /** - * Calculate the factorial if it is an integer. - * - * @param x The operand - * @return Factorial of x - */ - public static BigDecimal factorial(BigDecimal x) { - if (x == null || x.compareTo(BigDecimal.ZERO) < 0 || !isInteger(x)) { - throw new Expr4jException("Cannot calculate factorial of " + x); - } - return factorial(x.toBigInteger()); - } - -} diff --git a/src/main/java/com/bitaspire/libs/formula/DoubleExpressionBuilder.java b/src/main/java/com/bitaspire/libs/formula/DoubleExpressionBuilder.java deleted file mode 100644 index 3217852..0000000 --- a/src/main/java/com/bitaspire/libs/formula/DoubleExpressionBuilder.java +++ /dev/null @@ -1,92 +0,0 @@ -package com.bitaspire.libs.formula; - -import com.bitaspire.libs.formula.expression.ExpressionBuilder; -import com.bitaspire.libs.formula.expression.ExpressionConfig; -import com.bitaspire.libs.formula.expression.ExpressionDictionary; -import com.bitaspire.libs.formula.token.Function; -import com.bitaspire.libs.formula.token.Operator; -import com.bitaspire.libs.formula.token.OperatorType; - -import java.util.Collections; -import java.util.stream.Collectors; - -public class DoubleExpressionBuilder extends ExpressionBuilder { - - public DoubleExpressionBuilder() { - super(new ExpressionConfig() { - @Override - protected Double stringToOperand(String operand) { - return Double.parseDouble(operand); - } - - protected String operandToString(Double operand) { - return operand == operand.intValue() ? String.valueOf(operand.intValue()) : operand.toString(); - } - }); - - this.initialize(); - } - - protected void initialize() { - ExpressionDictionary expressionDictionary = this.getExpressionDictionary(); - - expressionDictionary.addOperator(new Operator<>("+", OperatorType.PREFIX, Integer.MAX_VALUE, (parameters) -> parameters.get(0).value())); - expressionDictionary.addOperator(new Operator<>("-", OperatorType.PREFIX, Integer.MAX_VALUE, (parameters) -> -parameters.get(0).value())); - - expressionDictionary.addOperator(new Operator<>("+", OperatorType.INFIX, 1, (parameters) -> parameters.get(0).value() + parameters.get(1).value())); - expressionDictionary.addOperator(new Operator<>("-", OperatorType.INFIX, 1, (parameters) -> parameters.get(0).value() - parameters.get(1).value())); - - expressionDictionary.addOperator(new Operator<>("*", OperatorType.INFIX, 2, (parameters) -> parameters.get(0).value() * parameters.get(1).value())); - expressionDictionary.addOperator(new Operator<>("/", OperatorType.INFIX, 2, (parameters) -> parameters.get(0).value() / parameters.get(1).value())); - expressionDictionary.addOperator(new Operator<>("%", OperatorType.INFIX, 2, (parameters) -> parameters.get(0).value() % parameters.get(1).value())); - - expressionDictionary.addOperator(new Operator<>("^", OperatorType.INFIX_RTL, 3, (parameters) -> Math.pow(parameters.get(0).value(), parameters.get(1).value()))); - - expressionDictionary.addOperator(new Operator<>("!", OperatorType.POSTFIX, 5, (parameters) -> DoubleUtils.factorial(parameters.get(0).value()))); - - expressionDictionary.addOperator(new Operator<>("abs", OperatorType.PREFIX, 4, (parameters) -> Math.abs(parameters.get(0).value()))); - - expressionDictionary.addOperator(new Operator<>("sin", OperatorType.PREFIX, 4, (parameters) -> Math.sin(parameters.get(0).value()))); - expressionDictionary.addOperator(new Operator<>("cos", OperatorType.PREFIX, 4, (parameters) -> Math.cos(parameters.get(0).value()))); - expressionDictionary.addOperator(new Operator<>("tan", OperatorType.PREFIX, 4, (parameters) -> Math.tan(parameters.get(0).value()))); - - expressionDictionary.addOperator(new Operator<>("asin", OperatorType.PREFIX, 4, (parameters) -> Math.asin(parameters.get(0).value()))); - expressionDictionary.addOperator(new Operator<>("acos", OperatorType.PREFIX, 4, (parameters) -> Math.acos(parameters.get(0).value()))); - expressionDictionary.addOperator(new Operator<>("atan", OperatorType.PREFIX, 4, (parameters) -> Math.atan(parameters.get(0).value()))); - - expressionDictionary.addOperator(new Operator<>("sinh", OperatorType.PREFIX, 4, (parameters) -> Math.sinh(parameters.get(0).value()))); - expressionDictionary.addOperator(new Operator<>("cosh", OperatorType.PREFIX, 4, (parameters) -> Math.cosh(parameters.get(0).value()))); - expressionDictionary.addOperator(new Operator<>("tanh", OperatorType.PREFIX, 4, (parameters) -> Math.tanh(parameters.get(0).value()))); - - expressionDictionary.addOperator(new Operator<>("asinh", OperatorType.PREFIX, 4, (parameters) -> DoubleUtils.asinh(parameters.get(0).value()))); - expressionDictionary.addOperator(new Operator<>("acosh", OperatorType.PREFIX, 4, (parameters) -> DoubleUtils.acosh(parameters.get(0).value()))); - expressionDictionary.addOperator(new Operator<>("atanh", OperatorType.PREFIX, 4, (parameters) -> DoubleUtils.atanh(parameters.get(0).value()))); - - expressionDictionary.addFunction(new Function<>("deg", 1, (parameters) -> Math.toDegrees(parameters.get(0).value()))); - expressionDictionary.addFunction(new Function<>("rad", 1, (parameters) -> Math.toRadians(parameters.get(0).value()))); - - expressionDictionary.addOperator(new Operator<>("round", OperatorType.PREFIX, 4, (parameters) -> (double) Math.round(parameters.get(0).value()))); - expressionDictionary.addOperator(new Operator<>("floor", OperatorType.PREFIX, 4, (parameters) -> Math.floor(parameters.get(0).value()))); - expressionDictionary.addOperator(new Operator<>("ceil", OperatorType.PREFIX, 4, (parameters) -> Math.ceil(parameters.get(0).value()))); - - expressionDictionary.addOperator(new Operator<>("ln", OperatorType.PREFIX, 4, (parameters) -> Math.log(parameters.get(0).value()))); - expressionDictionary.addOperator(new Operator<>("log10", OperatorType.PREFIX, 4, (parameters) -> Math.log10(parameters.get(0).value()))); - expressionDictionary.addFunction(new Function<>("log", 2, (parameters) -> DoubleUtils.log(parameters.get(1).value(), parameters.get(0).value()))); - - expressionDictionary.addOperator(new Operator<>("sqrt", OperatorType.PREFIX, 4, (parameters) -> Math.sqrt(parameters.get(0).value()))); - expressionDictionary.addOperator(new Operator<>("cbrt", OperatorType.PREFIX, 4, (parameters) -> Math.cbrt(parameters.get(0).value()))); - - expressionDictionary.addFunction(new Function<>("exp", 1, (parameters) -> Math.exp(parameters.get(0).value()))); - - expressionDictionary.addFunction(new Function<>("max", (parameters) -> parameters.isEmpty() ? 0.0 : Collections.max(parameters.stream().map(e -> e.value()).collect(Collectors.toList())))); - expressionDictionary.addFunction(new Function<>("min", (parameters) -> parameters.isEmpty() ? 0.0 : Collections.min(parameters.stream().map(e -> e.value()).collect(Collectors.toList())))); - - expressionDictionary.addFunction(new Function<>("mean", (parameters) -> DoubleUtils.average(parameters.stream().map(e -> e.value()).collect(Collectors.toList())))); - expressionDictionary.addFunction(new Function<>("average", (parameters) -> DoubleUtils.average(parameters.stream().map(e -> e.value()).collect(Collectors.toList())))); - - expressionDictionary.addFunction(new Function<>("rand", 0, (parameters) -> Math.random())); - - expressionDictionary.addConstant("pi", Math.PI); - expressionDictionary.addConstant("e", Math.E); - } -} diff --git a/src/main/java/com/bitaspire/libs/formula/DoubleUtils.java b/src/main/java/com/bitaspire/libs/formula/DoubleUtils.java deleted file mode 100644 index f219b88..0000000 --- a/src/main/java/com/bitaspire/libs/formula/DoubleUtils.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.bitaspire.libs.formula; - -import com.bitaspire.libs.formula.exception.Expr4jException; - -import java.util.List; - -public class DoubleUtils { - - private DoubleUtils() {} - - public static double asinh(double x) { - return Math.log(x + Math.sqrt(x * x + 1)); - } - - public static double acosh(double x) { - return Math.log(x + Math.sqrt(x * x - 1)); - } - - public static double atanh(double x) { - return 0.5 * Math.log((1 + x) / (1 - x)); - } - - public static double log(double x, double y) { - return Math.log(x) / Math.log(y); - } - - public static Double average(List list) { - return list.stream().mapToDouble(d -> d).average().orElse(0.0); - } - - private static double factorial(int n) { - double factorial = 1.0; - for (int i = 2; i <= n; i++) { - factorial *= i; - } - return factorial; - } - - public static double factorial(double x) { - if (x < 0 || x != (int) x) { - throw new Expr4jException("Cannot calculate factorial of " + x); - } - return factorial((int) x); - } - -} diff --git a/src/main/java/com/bitaspire/libs/formula/exception/Expr4jException.java b/src/main/java/com/bitaspire/libs/formula/exception/Expr4jException.java deleted file mode 100644 index 3b1fd91..0000000 --- a/src/main/java/com/bitaspire/libs/formula/exception/Expr4jException.java +++ /dev/null @@ -1,81 +0,0 @@ -/** - * Copyright 2023 Pratanu Mandal - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.bitaspire.libs.formula.exception; - -/** - * The Expr4jException class represents exceptions that can be thrown during the execution of Expr4j library. - * - * @author Pratanu Mandal - * @since 1.0 - * - */ -public class Expr4jException extends RuntimeException { - - /** - * Serial Version UID for object serialization. - */ - private static final long serialVersionUID = 6989809082307883828L; - - /** - * Constructs a new Expr4j exception with null as its detail message. - */ - public Expr4jException() { - super(); - } - - /** - * Constructs a new Expr4j exception with the specified detail message, cause, suppression enabled or disabled, and writable stack trace enabled or disabled. - * - * @param message the detail message - * @param cause the cause of the exception - * @param enableSuppression whether suppression is enabled or disabled - * @param writableStackTrace whether the stack trace should be writable - */ - public Expr4jException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { - super(message, cause, enableSuppression, writableStackTrace); - } - - /** - * Constructs a new Expr4j exception with the specified detail message and cause. - * - * @param message the detail message - * @param cause the cause of the exception - */ - public Expr4jException(String message, Throwable cause) { - super(message, cause); - } - - /** - * Constructs a new Expr4j exception with the specified detail message. - * - * @param message the detail message - */ - public Expr4jException(String message) { - super(message); - } - - /** - * Constructs a new Expr4j exception with the specified cause and a detail message of (cause==null ? null : cause.toString()) (which typically contains the class and detail message of cause). - * - * @param cause the cause of the exception - */ - public Expr4jException(Throwable cause) { - super(cause); - } - -} diff --git a/src/main/java/com/bitaspire/libs/formula/expression/Expression.java b/src/main/java/com/bitaspire/libs/formula/expression/Expression.java deleted file mode 100644 index f845db9..0000000 --- a/src/main/java/com/bitaspire/libs/formula/expression/Expression.java +++ /dev/null @@ -1,276 +0,0 @@ -/** - * Copyright 2023 Pratanu Mandal - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.bitaspire.libs.formula.expression; - -import com.bitaspire.libs.formula.exception.Expr4jException; -import com.bitaspire.libs.formula.token.Function; -import com.bitaspire.libs.formula.token.Operand; -import com.bitaspire.libs.formula.token.Operator; -import com.bitaspire.libs.formula.token.OperatorType; -import com.bitaspire.libs.formula.token.Variable; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -/** - * The Expression<T> class represents a parsed expression that can be evaluated. - * - * @author Pratanu Mandal - * @since 1.0 - * - * @param The type of operand - */ -public class Expression { - - /** - * Root node of the expression tree. - */ - public ExpressionNode root; - - /** - * Expression dictionary. - */ - private final ExpressionDictionary expressionDictionary; - - /** - * Expression configuration. - */ - private final ExpressionConfig expressionConfig; - - /** - * Parameterized constructor. - * - * @param expressionDictionary The expression dictionary - * @param expressionConfig The expression configuration - */ - public Expression(ExpressionDictionary expressionDictionary, - ExpressionConfig expressionConfig) { - this.expressionDictionary = expressionDictionary; - this.expressionConfig = expressionConfig; - } - - /** - * Recursively evaluate the expression tree and return the result. - * - * @param node Current node of the expression tree - * @param variables Map of variables - * @return Result of expression evaluation - */ - @SuppressWarnings("unchecked") - protected Operand evaluate(ExpressionNode node, Map variables) { - // encountered variable - if (node.token instanceof Variable) { - Variable variable = (Variable) node.token; - - if (!variables.containsKey(variable.label)) { - throw new Expr4jException("Variable not found: " + variable.label); - } - - return new Operand(variables.get(variable.label)); - } - - // encountered function - else if (node.token instanceof Function) { - Function function = (Function) node.token; - - int operandCount = function.parameters; - if (node.children.size() != operandCount) { - throw new Expr4jException("Invalid expression"); - } - - List> parameters = node.children.stream() - .map(e -> new ExpressionParameter(this, e, variables)) - .collect(Collectors.toList()); - return new Operand(function.evaluate(parameters)); - } - - // encountered operator - else if (node.token instanceof Operator) { - Operator operator = (Operator) node.token; - - int operandCount = (operator.type == OperatorType.INFIX || operator.type == OperatorType.INFIX_RTL) ? 2 : 1; - if (node.children.size() != operandCount) { - throw new Expr4jException("Invalid expression"); - } - - List> parameters = node.children.stream() - .map(e -> new ExpressionParameter(this, e, variables)) - .collect(Collectors.toList()); - return new Operand(operator.evaluate(parameters)); - } - - // encountered operand - else { - return (Operand) node.token; - } - } - - /** - * Evaluate the expression against a set of variables.
- * Variables passed to this method override an predefined constants with the same label. - * - * @param variables Map of variables - * @return Evaluated result - */ - public T evaluate(Map variables) { - if (root == null) { - throw new Expr4jException("Invalid expression"); - } - - Map constantsAndVariables = new HashMap<>(expressionDictionary.constants); - if (variables != null) constantsAndVariables.putAll(variables); - - return evaluate(root, constantsAndVariables).value; - } - - /** - * Evaluate the expression. - * - * @return Evaluated result - */ - public T evaluate() { - return evaluate(new HashMap()); - } - - /** - * Form string representation of expression. - * - * @param node Current node of the expression tree - * @return Result of expression evaluation - */ - @SuppressWarnings("unchecked") - protected String toString(ExpressionNode node) { - // encountered variable - if (node.token instanceof Variable) { - Variable variable = (Variable) node.token; - return variable.label; - } - - // encountered function - else if (node.token instanceof Function) { - Function function = (Function) node.token; - - int operandCount = function.parameters; - if (node.children.size() != operandCount) { - throw new Expr4jException("Invalid expression"); - } - - String operands = node.children.stream().map(this::toString).collect(Collectors.joining(", ")); - - return function.label + "(" + operands + ")"; - } - - // encountered operator - else if (node.token instanceof Operator) { - Operator operator = (Operator) node.token; - - int operandCount = (operator.type == OperatorType.INFIX || operator.type == OperatorType.INFIX_RTL) ? 2 : 1; - if (node.children.size() != operandCount) { - throw new Expr4jException("Invalid expression"); - } - - String label; - if (operandCount == 2) label = " " + operator.label + " "; - else label = operator.label; - - if (operandCount == 2) { - StringBuilder sb = new StringBuilder(); - - ExpressionNode left = node.children.get(0); - ExpressionNode right = node.children.get(1); - - if (left.token instanceof Operator) { - Operator leftOperator = (Operator) left.token; - if (!leftOperator.label.equals("*") && - (leftOperator.type == OperatorType.INFIX || leftOperator.type == OperatorType.INFIX_RTL) && - operator.compareTo(leftOperator) < 0) { - sb.append("("); - sb.append(this.toString(left)); - sb.append(")"); - } else { - sb.append(this.toString(left)); - } - } else { - sb.append(this.toString(left)); - } - - sb.append(label); - - if (right.token instanceof Operator) { - Operator rightOperator = (Operator) right.token; - if (!rightOperator.label.equals("*") && - (rightOperator.type == OperatorType.INFIX || rightOperator.type == OperatorType.INFIX_RTL) && - operator.compareTo(rightOperator) < 0) { - sb.append("("); - sb.append(this.toString(right)); - sb.append(")"); - } else { - sb.append(this.toString(right)); - } - } else { - sb.append(this.toString(right)); - } - - return sb.toString(); - } else { - ExpressionNode child = node.children.get(0); - if (operator.label.equals("+") || operator.label.equals("-")) { - if (child.token instanceof Operator) { - Operator childOperator = (Operator) child.token; - if (childOperator.type == OperatorType.PREFIX) { - return label + this.toString(child); - } else { - return label + "(" + this.toString(child) + ")"; - } - } else { - return label + this.toString(child); - } - } else if (child.token instanceof Operator || child.token instanceof Function) { - if (operator.type == OperatorType.PREFIX) { - return label + "(" + this.toString(child) + ")"; - } else { - return "(" + this.toString(child) + ") " + label; - } - } else { - if (operator.type == OperatorType.PREFIX) { - return label + " " + this.toString(child); - } else { - return this.toString(child) + " " + label; - } - } - } - } - - // encountered operand - else { - Operand operand = (Operand) node.token; - return expressionConfig.operandToString(operand.value); - } - } - - /** - * Get string representation of expression. - */ - @Override - public String toString() { - return this.toString(root); - } - -} diff --git a/src/main/java/com/bitaspire/libs/formula/expression/ExpressionBuilder.java b/src/main/java/com/bitaspire/libs/formula/expression/ExpressionBuilder.java deleted file mode 100644 index 2500c11..0000000 --- a/src/main/java/com/bitaspire/libs/formula/expression/ExpressionBuilder.java +++ /dev/null @@ -1,174 +0,0 @@ -/** - * Copyright 2023 Pratanu Mandal - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.bitaspire.libs.formula.expression; - -import com.bitaspire.libs.formula.exception.Expr4jException; -import com.bitaspire.libs.formula.token.Function; -import com.bitaspire.libs.formula.token.Operator; -import com.bitaspire.libs.formula.token.OperatorType; -import com.bitaspire.libs.formula.token.Token; -import lombok.Getter; - -import java.util.List; -import java.util.Stack; - -/** - * The ExpressionBuilder<T> class provides a partial implementation to build expressions independent of the type of operand.
- * An expression is created from the postfix (or RPN) expression. The expression can then be evaluated.

- * - * @author Pratanu Mandal - * @since 1.0 - * - * @param The type of operand - */ -public class ExpressionBuilder { - - /** - * Instance of expression. - */ - private Expression expression; - - /** - * Expression dictionary. - * -- GETTER -- - * Get the expression dictionary. - * - * @return The expression dictionary - - */ - @Getter - private ExpressionDictionary expressionDictionary; - - /** - * Expression configuration. - * -- GETTER -- - * Get the expression configuration. - * - * @return The expression configuration - - */ - @Getter - private final ExpressionConfig expressionConfig; - - /** - * Parameterized constructor - * - * @param expressionConfig The expression configuration - */ - public ExpressionBuilder(ExpressionConfig expressionConfig) { - this.expressionConfig = expressionConfig; - this.reset(); - } - - /** - * Reset the parser. - */ - public void reset() { - expressionDictionary = new ExpressionDictionary<>(); - } - - /** - * Method to form the expression tree recursively. - * - * @param node Current node of the expression tree - * @param token Token to be inserted - * @return true if token could be inserted, otherwise false - */ - @SuppressWarnings("unchecked") - private boolean formTree(ExpressionNode node, Token token) { - if (node.token instanceof Function) { - Function function = (Function) node.token; - - int operandCount = function.parameters; - - if (!node.children.isEmpty() && formTree(node.children.get(0), token)) { - return true; - } - else if (node.children.size() < operandCount) { - node.children.add(0, new ExpressionNode(token)); - return true; - } - } - else if (node.token instanceof Operator) { - Operator operator = (Operator) node.token; - - int operandCount = (operator.type == OperatorType.INFIX || operator.type == OperatorType.INFIX_RTL) ? 2 : 1; - - if (!node.children.isEmpty() && formTree(node.children.get(0), token)) { - return true; - } - else if (node.children.size() < operandCount) { - node.children.add(0, new ExpressionNode(token)); - return true; - } - } - - return false; - } - - /** - * Method to form the expression tree. - */ - private void formTree(Stack postfix) { - while (!postfix.isEmpty()) { - Token token = postfix.pop(); - - if (expression.root == null) { - expression.root = new ExpressionNode(token); - } - else { - boolean flag = formTree(expression.root, token); - - if (!flag) { - throw new Expr4jException("Invalid expression"); - } - } - } - } - - /** - * Method to parse an expression.
- * This method acts as the single point of access for expression parsing. - * - * @param expr Expression string - * @return The parsed expression - */ - public Expression build(String expr) { - try { - // initialize expression - this.expression = new Expression(expressionDictionary, expressionConfig); - - // tokenize the expression - ExpressionTokenizer tokenizer = new ExpressionTokenizer(expressionDictionary, expressionConfig); - List tokenList = tokenizer.tokenize(expr); - - // form the postfix expression - ExpressionParser parser = new ExpressionParser(); - Stack postfix = parser.parse(tokenList); - - // form the tree - this.formTree(postfix); - - return this.expression; - } - finally { - // clean up - expression evaluation can be a memory intensive process - this.expression = null; - } - } -} diff --git a/src/main/java/com/bitaspire/libs/formula/expression/ExpressionConfig.java b/src/main/java/com/bitaspire/libs/formula/expression/ExpressionConfig.java deleted file mode 100644 index 3f9de58..0000000 --- a/src/main/java/com/bitaspire/libs/formula/expression/ExpressionConfig.java +++ /dev/null @@ -1,68 +0,0 @@ -/** - * Copyright 2023 Pratanu Mandal - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.bitaspire.libs.formula.expression; - -import java.util.ArrayList; -import java.util.List; - -/** - * The ExpressionConfig<T> class defines configurations for the expression. - * - * @author Pratanu Mandal - * @since 1.0 - * - * @param The type of operand - */ -public abstract class ExpressionConfig { - - /** - * No-Argument Constructor. - */ - public ExpressionConfig() { - } - - /** - * Method to define procedure to obtain operand from string representation. - * - * @param operand String representation of operand - * @return Operand - */ - protected abstract T stringToOperand(String operand); - - /** - * Method to define procedure to obtain string representation of operand. - * - * @param operand Operand - * @return String representation of operand - */ - protected abstract String operandToString(T operand); - - /** - * Method to define the patterns to identify operands.
- * Override this method if the patterns to identify operands need to be customized. - * - * @return List of patterns to identify operands - */ - protected List getOperandPattern() { - List list = new ArrayList<>(); - list.add("(-?\\d+)(\\.\\d+)?(e-|e\\+|e|\\d+)\\d+"); - list.add("\\d*\\.?\\d+"); - return list; - } - -} diff --git a/src/main/java/com/bitaspire/libs/formula/expression/ExpressionDictionary.java b/src/main/java/com/bitaspire/libs/formula/expression/ExpressionDictionary.java deleted file mode 100644 index 378006c..0000000 --- a/src/main/java/com/bitaspire/libs/formula/expression/ExpressionDictionary.java +++ /dev/null @@ -1,287 +0,0 @@ -/** - * Copyright 2023 Pratanu Mandal - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.bitaspire.libs.formula.expression; - -import com.bitaspire.libs.formula.exception.Expr4jException; -import com.bitaspire.libs.formula.token.Function; -import com.bitaspire.libs.formula.token.Operator; -import com.bitaspire.libs.formula.token.OperatorType; - -import java.util.Collections; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.TreeMap; -import java.util.TreeSet; - -/** - * The ExpressionDictionary<T> class stores all operators, functions and constants of a specified type. - * - * @author Pratanu Mandal - * @since 1.0 - * - * @param The type of operand - */ -public class ExpressionDictionary { - - /** Map of prefix operators */ - protected final Map> prefixMap; - - /** Map of postfix operators */ - protected final Map> postfixMap; - - /** Map of infix operators */ - protected final Map> infixMap; - - /** Map of functions */ - protected final Map> functionMap; - - /** Map of constants */ - protected Map constants; - - /** - * No-Argument Constructor. - */ - public ExpressionDictionary() { - this.prefixMap = new TreeMap<>(); - this.postfixMap = new TreeMap<>(); - this.infixMap = new TreeMap<>(); - this.functionMap = new TreeMap<>(); - this.constants = new TreeMap<>(); - } - - /** - * Add an operator. - * - * @param operator The operator - */ - public void addOperator(Operator operator) { - if (operator.type == OperatorType.PREFIX) { - prefixMap.put(operator.label, operator); - } - else if (operator.type == OperatorType.POSTFIX) { - postfixMap.put(operator.label, operator); - } - else { - infixMap.put(operator.label, operator); - } - } - - /** - * Remove all operators for a specified label irrespective of their type. - * - * @param label The label of the operators - */ - public void removeOperator(String label) { - removeOperator(label, null); - } - - /** - * Remove an operator for a specified label and type. - * If type is null, all operators for the specified label are removed irrespective of their type. - * - * @param label The label of the operator(s) - * @param type The type of the operator - */ - public void removeOperator(String label, OperatorType type) { - if (type == null) { - prefixMap.remove(label); - postfixMap.remove(label); - infixMap.remove(label); - } - else if (type == OperatorType.PREFIX) { - prefixMap.remove(label); - } - else if (type == OperatorType.POSTFIX) { - postfixMap.remove(label); - } - else { - infixMap.remove(label); - } - } - - /** - * Get set of all operators available. - * - * @return Set of all operators - */ - public Set> getOperators() { - Set> operatorSet = new HashSet<>(); - operatorSet.addAll(prefixMap.values()); - operatorSet.addAll(postfixMap.values()); - operatorSet.addAll(infixMap.values()); - return Collections.unmodifiableSet(operatorSet); - } - - /** - * Check if a prefix operator exists with specified label. - * - * @param label The label of the operator - * @return True if found, false otherwise - */ - public boolean hasPrefixOperator(String label) { - return prefixMap.containsKey(label); - } - - /** - * Get the prefix operator with specified label. - * - * @param label The label of the operator - * @return The operator if found, null otherwise - */ - public Operator getPrefixOperator(String label) { - return prefixMap.get(label); - } - - /** - * Check if a postfix operator exists with specified label. - * - * @param label The label of the operator - * @return True if found, false otherwise - */ - public boolean hasPostfixOperator(String label) { - return postfixMap.containsKey(label); - } - - /** - * Get the postfix operator with specified label. - * - * @param label The label of the operator - * @return The operator if found, null otherwise - */ - public Operator getPostfixOperator(String label) { - return postfixMap.get(label); - } - - /** - * Check if an infix operator exists with specified label. - * - * @param label The label of the operator - * @return True if found, false otherwise - */ - public boolean hasInfixOperator(String label) { - return infixMap.containsKey(label); - } - - /** - * Get the infix operator with specified label. - * - * @param label The label of the operator - * @return The operator if found, null otherwise - */ - public Operator getInfixOperator(String label) { - return infixMap.get(label); - } - - /** - * Add a function. - * - * @param function The function - */ - public void addFunction(Function function) { - functionMap.put(function.label, function); - } - - /** - * Remove a function for a specified label. - * - * @param label The label of the function - */ - public void removeFunction(String label) { - functionMap.remove(label); - } - - /** - * Get set of all functions available. - * - * @return Set of all functions - */ - public Set> getFunctions() { - Set> functionSet = new HashSet<>(); - functionSet.addAll(functionMap.values()); - return Collections.unmodifiableSet(functionSet); - } - - /** - * Check if a function exists with specified label. - * - * @param label The label of the function - * @return True if found, false otherwise - */ - public boolean hasFunction(String label) { - return functionMap.containsKey(label); - } - - /** - * Get the function with specified label. - * - * @param label The label of the function - * @return The function if found, null otherwise - */ - public Function getFunction(String label) { - return functionMap.get(label); - } - - /** - * Add a constant to the parser. - * - * @param label Label of the constant - * @param value Value of the constant - */ - public void addConstant(String label, T value) { - constants.put(label, value); - } - - /** - * Remove constant from the parser for the specified label if present. - * - * @param label Label of the constant - * @return Constant for the specified label if present, else null - */ - public T removeConstant(String label) { - return constants.remove(label); - } - - /** - * Get constant present in the parser for the specified label. - * - * @param label Label of the constant - * @return Constant for the specified label if present, else null - */ - public T getConstant(String label) { - if (!constants.containsKey(label)) { - throw new Expr4jException("Constant not found: " + label); - } - return constants.get(label); - } - - /** - * Get list of labels of all executables (operators and functions). - * - * @return The list of labels - */ - Set getExecutables() { - Set executables = new TreeSet<>(); - executables.addAll(prefixMap.keySet()); - executables.addAll(postfixMap.keySet()); - executables.addAll(infixMap.keySet()); - executables.addAll(functionMap.keySet()); - return executables; - } - -} diff --git a/src/main/java/com/bitaspire/libs/formula/expression/ExpressionNode.java b/src/main/java/com/bitaspire/libs/formula/expression/ExpressionNode.java deleted file mode 100644 index 8857ddd..0000000 --- a/src/main/java/com/bitaspire/libs/formula/expression/ExpressionNode.java +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Copyright 2023 Pratanu Mandal - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.bitaspire.libs.formula.expression; - -import com.bitaspire.libs.formula.token.Function; -import com.bitaspire.libs.formula.token.Operator; -import com.bitaspire.libs.formula.token.Token; - -import java.util.ArrayList; -import java.util.List; - -/** - * The ExpressionNode class represents a node of the expression tree.

- * - * @author Pratanu Mandal - * @since 1.0 - * - */ -public class ExpressionNode { - - /** - * Children of this node. - */ - public final List children; - - /** - * Token contained in this node.
- * A token can be an operand, operator, function, variable, or constant. - */ - public final Token token; - - /** - * Parameterized constructor. - * - * @param token The token in this node - */ - public ExpressionNode(Token token) { - this.token = token; - if (token instanceof Function || token instanceof Operator) { - this.children = new ArrayList<>(); - } - else { - this.children = null; - } - } - -} diff --git a/src/main/java/com/bitaspire/libs/formula/expression/ExpressionParameter.java b/src/main/java/com/bitaspire/libs/formula/expression/ExpressionParameter.java deleted file mode 100644 index 191aaf3..0000000 --- a/src/main/java/com/bitaspire/libs/formula/expression/ExpressionParameter.java +++ /dev/null @@ -1,76 +0,0 @@ -/** - * Copyright 2023 Pratanu Mandal - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.bitaspire.libs.formula.expression; - -import java.util.Map; - -/** - * The ExpressionParameter<T> class represents a parameter of an operation.

- * - * @author Pratanu Mandal - * @since 1.0 - * - */ -public class ExpressionParameter { - - /** - * Expression to which this parameter belongs. - */ - private Expression expression; - - /** - * Node of the expression related to this parameter. - */ - private ExpressionNode node; - - /** - * Map of variables. - */ - private Map variables; - - /** - * Result of evaluating this parameter. - */ - private T result; - - /** - * Parameterized constructor. - * - * @param expression The expression - * @param node The node - * @param variables Map of variables - */ - public ExpressionParameter(Expression expression, ExpressionNode node, Map variables) { - this.expression = expression; - this.node = node; - this.variables = variables; - } - - /** - * Get the evaluated result of this parameter. - * - * @return The evaluated result - */ - public T value() { - if (this.result == null) { - this.result = this.expression.evaluate(this.node, this.variables).value; - } - return this.result; - } - -} diff --git a/src/main/java/com/bitaspire/libs/formula/expression/ExpressionParser.java b/src/main/java/com/bitaspire/libs/formula/expression/ExpressionParser.java deleted file mode 100644 index 1a5d9a0..0000000 --- a/src/main/java/com/bitaspire/libs/formula/expression/ExpressionParser.java +++ /dev/null @@ -1,334 +0,0 @@ -/** - * Copyright 2023 Pratanu Mandal - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.bitaspire.libs.formula.expression; - -import com.bitaspire.libs.formula.exception.Expr4jException; -import com.bitaspire.libs.formula.token.Function; -import com.bitaspire.libs.formula.token.Operand; -import com.bitaspire.libs.formula.token.Operator; -import com.bitaspire.libs.formula.token.OperatorType; -import com.bitaspire.libs.formula.token.Separator; -import com.bitaspire.libs.formula.token.Token; -import com.bitaspire.libs.formula.token.Variable; - -import java.util.List; -import java.util.Stack; - -/** - * The ExpressionParser<T> class parses expressions independent of the type of operand.
- * This class parses the tokenized expression to generate a postfix (RPN) expression.

- * - * @author Pratanu Mandal - * @since 1.0 - * - * @param The type of operand - */ -public class ExpressionParser { - - /** - * Stack to hold the postfix (RPN) expression. - */ - private Stack postfix; - - /** - * Stack to hold the operators. - */ - private Stack operatorStack; - - /** - * Stack to hold the count of function parameters. - */ - private Stack functionStack; - - /** - * No-Argument Constructor. - */ - public ExpressionParser() { - } - - /** - * Method to create the postfix (RPN) expression from the infix expression. - * - * @param tokenList The token list - * @return The postfix expression - */ - public Stack parse(List tokenList) { - // initialize members - postfix = new Stack<>(); - operatorStack = new Stack<>(); - functionStack = new Stack<>(); - - boolean probableZeroFunction = false; - - Token lastToken = null; - - // iterate over tokens - for (int i = 0; i < tokenList.size(); i++) { - Token token = tokenList.get(i); - - if (token instanceof Separator) { - Separator separator = (Separator) token; - - // open bracket - if (separator == Separator.OPEN_BRACKET) { - operatorStack.push(separator); - } - - // close bracket - else if (separator == Separator.CLOSE_BRACKET) { - throwIfNotPostfix(lastToken); - throwIfOpenBracketOrComma(lastToken); - - if (probableZeroFunction) { - if (functionStack.isEmpty()) { - throw new Expr4jException("Invalid expression"); - } - functionStack.pop(); - functionStack.push(0); - } - - evaluateParenthesis(); - } - - // comma - else if (separator == Separator.COMMA) { - throwIfFunction(lastToken); - throwIfNotPostfix(lastToken); - throwIfOpenBracketOrComma(lastToken); - - while (!operatorStack.isEmpty() && !(operatorStack.peek() instanceof Function)) { - postfix.push(operatorStack.pop()); - } - - if (functionStack.isEmpty()) { - throw new Expr4jException("Invalid expression"); - } - functionStack.push(functionStack.pop() + 1); - } - - probableZeroFunction = false; - } - - // functions - else if (token instanceof Function) { - Function function = (Function) token; - - Token nextToken = i != tokenList.size() - 1 ? tokenList.get(i + 1) : null; - throwIfNoOpenBracket(nextToken, function); - - i++; - - operatorStack.push(function); - - if (function.parameters == 0) functionStack.push(0); - else functionStack.push(1); - - if (function.parameters == Function.VARIABLE_PARAMETERS) probableZeroFunction = true; - } - - // operators - else if (token instanceof Operator) { - Operator operator = (Operator) token; - - if (operator.type == OperatorType.INFIX || operator.type == OperatorType.INFIX_RTL) { - throwIfNull(lastToken); - throwIfFunction(lastToken); - throwIfNotPostfix(lastToken); - throwIfOpenBracketOrComma(lastToken); - } - else if (operator.type == OperatorType.POSTFIX) { - throwIfNull(lastToken); - throwIfNotPostfix(lastToken); - } - - pushOperator(operator); - probableZeroFunction = false; - } - - // numbers and variables - else if (token instanceof Operand || token instanceof Variable) { - postfix.push(token); - probableZeroFunction = false; - } - - // invalid token - else { - throw new Expr4jException("Invalid expression"); - } - - lastToken = token; - } - - // process operator stack - while (!operatorStack.isEmpty()) { - Token token = operatorStack.peek(); - if (token instanceof Function || token instanceof Separator) { - throw new Expr4jException("Unmatched number of parenthesis"); - } - postfix.push(operatorStack.pop()); - } - - return postfix; - } - - /** - * Push operator to operator stack or postfix stack. - * - * @param operator The operator to push - */ - private void pushOperator(Operator operator) { - if (operator.type != OperatorType.PREFIX) { - while (!operatorStack.isEmpty() && - (operatorStack.peek() instanceof Operator && - operator.compareTo((Operator) operatorStack.peek()) > 0)) { - postfix.push(operatorStack.pop()); - } - } - if (operator.type == OperatorType.POSTFIX) { - postfix.push(operator); - } - else { - operatorStack.push(operator); - } - } - - /** - * Method to evaluate operators at the top of the operator stack until a left parenthesis or a function is encountered. - */ - private void evaluateParenthesis() { - boolean flag = false; - - // pop until left parenthesis - while (!operatorStack.isEmpty()) { - Token token = operatorStack.peek(); - - // encountered a function - if (token instanceof Function) { - Function function = (Function) operatorStack.pop(); - - if (functionStack.isEmpty()) { - throw new Expr4jException("Invalid expression"); - } - int actualParameters = functionStack.pop(); - - if (function.parameters == Function.VARIABLE_PARAMETERS) { - function = new Function(function.label, actualParameters, function.operation); - } - else if (function.parameters != actualParameters) { - throw new Expr4jException("Incorrect number of parameters for function: " + function.label); - } - - postfix.push(function); - - flag = true; - break; - } - - // encountered an open bracket - else if (token instanceof Separator) { - operatorStack.pop(); - - if (!operatorStack.isEmpty() && operatorStack.peek() instanceof Operator) { - Operator operator = (Operator) operatorStack.peek(); - if (operator.type == OperatorType.PREFIX) { - postfix.push(operatorStack.pop()); - } - } - - flag = true; - break; - } - - // evaluate top of stack - postfix.push(operatorStack.pop()); - } - - if (!flag) { - throw new Expr4jException("Unmatched number of parenthesis"); - } - } - - /** - * Throw exception if token is null. - * - * @param token The token - */ - private void throwIfNull(Token token) { - if (token == null) { - throw new Expr4jException("Invalid expression"); - } - } - - /** - * Throw exception token is a function. - * - * @param token The token - */ - private void throwIfFunction(Token token) { - if (token instanceof Function) { - throw new Expr4jException("Invalid expression"); - } - } - - /** - * Throw exception if token is an operator but not of type POSTFIX. - * - * @param token The token - */ - private void throwIfNotPostfix(Token token) { - if (token instanceof Operator) { - Operator operator = (Operator) token; - if (operator.type != OperatorType.POSTFIX) { - throw new Expr4jException("Invalid expression"); - } - } - } - - /** - * Throw exception if function is not followed by open bracket. - * - * @param token The token - * @param function The function - */ - private void throwIfNoOpenBracket(Token token, Function function) { - if (token instanceof Separator) { - Separator separator = (Separator) token; - if (separator != Separator.OPEN_BRACKET) { - throw new Expr4jException("Missing open bracket for function: " + function.label); - } - } - else { - throw new Expr4jException("Missing open bracket for function: " + function.label); - } - } - - /** - * Throw exception if the token is an open bracket or a comma. - * - * @param token The token - */ - private void throwIfOpenBracketOrComma(Token token) { - if (token instanceof Separator) { - Separator separator = (Separator) token; - if (separator == Separator.OPEN_BRACKET || separator == Separator.COMMA) { - throw new Expr4jException("Invalid expression"); - } - } - } - -} diff --git a/src/main/java/com/bitaspire/libs/formula/expression/ExpressionTokenizer.java b/src/main/java/com/bitaspire/libs/formula/expression/ExpressionTokenizer.java deleted file mode 100644 index 54e8cf3..0000000 --- a/src/main/java/com/bitaspire/libs/formula/expression/ExpressionTokenizer.java +++ /dev/null @@ -1,334 +0,0 @@ -/** - * Copyright 2023 Pratanu Mandal - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.bitaspire.libs.formula.expression; - -import com.bitaspire.libs.formula.exception.Expr4jException; -import com.bitaspire.libs.formula.token.Function; -import com.bitaspire.libs.formula.token.Operand; -import com.bitaspire.libs.formula.token.Operator; -import com.bitaspire.libs.formula.token.OperatorType; -import com.bitaspire.libs.formula.token.Separator; -import com.bitaspire.libs.formula.token.Token; -import com.bitaspire.libs.formula.token.Variable; - -import java.util.ArrayList; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - -/** - * The ExpressionTokenizer<T> class tokenizes expressions independent of the type of operand. - * - * @author Pratanu Mandal - * @since 1.0 - * - * @param The type of operand - */ -public class ExpressionTokenizer { - - /** Expression dictionary */ - private final ExpressionDictionary expressionDictionary; - - /** - * Expression confiuration. - */ - private final ExpressionConfig expressionConfig; - - /** - * Parameterized constructor. - * - * @param expressionDictionary The expression dictionary - * @param expressionConfig The expression configuration - */ - public ExpressionTokenizer(ExpressionDictionary expressionDictionary, - ExpressionConfig expressionConfig) { - this.expressionDictionary = expressionDictionary; - this.expressionConfig = expressionConfig; - } - - /** - * Tokenize an expression. - * - * @param expr The expression - * @return The list of tokens - */ - public List tokenize(String expr) { - // do not allow blank expressions - if (isBlank(expr)) { - throw new Expr4jException("Invalid expression"); - } - - // list of tokens - List tokenList = new ArrayList<>(); - - // initialize patterns - Pattern executablePattern = Pattern.compile(expressionDictionary.getExecutables() - .stream() - .map(Pattern::quote) - .sorted((e1, e2) -> (e2.length() - e1.length())) - .collect(Collectors.joining("|"))); - - Pattern unaryPattern = Pattern.compile("\\+|\\-"); - Pattern separatorPattern = Pattern.compile("\\(|\\)|,"); - Pattern variablePattern = Pattern.compile("[a-zA-Z]+[0-9]*[a-zA-Z]*"); - Pattern whitespacePattern = Pattern.compile("\\s+"); - - List operandPatternList = new ArrayList<>(); - for (String patternString : expressionConfig.getOperandPattern()) { - Pattern operandPattern = Pattern.compile(patternString); - operandPatternList.add(operandPattern); - } - - // initialize parsing variables - int index = 0; - Token lastToken = null; - boolean probableUnary = true; - - // while has more characters - outer: - while (index < expr.length()) { - Matcher matcher; - - // check for separator - matcher = separatorPattern.matcher(expr.substring(index)); - if (matcher.lookingAt()) { - String match = matcher.group(); - index += match.length(); - - Separator separator = Separator.getSeparator(match); - - if (separator == Separator.OPEN_BRACKET) { - addImplicitMultiplication(tokenList, lastToken); - probableUnary = true; - } - else if (separator == Separator.CLOSE_BRACKET) { - probableUnary = false; - } - else { - probableUnary = true; - } - tokenList.add(separator); - - lastToken = separator; - - continue; - } - - // check for unary operators - matcher = unaryPattern.matcher(expr.substring(index)); - if (probableUnary && matcher.lookingAt()) { - String match = matcher.group(); - index += match.length(); - - Operator operator = expressionDictionary.getPrefixOperator(match); - tokenList.add(operator); - - probableUnary = false; - lastToken = operator; - - continue; - } - - // check for executables - matcher = executablePattern.matcher(expr.substring(index)); - if (matcher.lookingAt()) { - String match = matcher.group(); - index += match.length(); - - // encountered a function - if (expressionDictionary.hasFunction(match)) { - Function function = expressionDictionary.getFunction(match); - - addImplicitMultiplication(tokenList, lastToken); - tokenList.add(function); - - probableUnary = false; - lastToken = function; - } - - // encountered an operator - else { - Operator operator; - - if (infixOperatorAllowed(lastToken) && - expressionDictionary.hasInfixOperator(match)) { - operator = expressionDictionary.getInfixOperator(match); - } - else if (postfixOperatorAllowed(lastToken) && - expressionDictionary.hasPostfixOperator(match)) { - operator = expressionDictionary.getPostfixOperator(match); - } - else if (expressionDictionary.hasPrefixOperator(match)) { - operator = expressionDictionary.getPrefixOperator(match); - } - else { - throw new Expr4jException("Undefined symbol: " + match); - } - - if (operator.type == OperatorType.PREFIX) { - addImplicitMultiplication(tokenList, lastToken); - } - tokenList.add(operator); - - probableUnary = operator.type != OperatorType.POSTFIX; - lastToken = operator; - } - - continue; - } - - // check for operands - for (Pattern numberPattern : operandPatternList) { - matcher = numberPattern.matcher(expr.substring(index)); - if (matcher.lookingAt()) { - String match = matcher.group(); - index += match.length(); - - addImplicitMultiplication(tokenList, lastToken); - - Operand operand = new Operand(expressionConfig.stringToOperand(match)); - tokenList.add(operand); - - probableUnary = false; - lastToken = operand; - - continue outer; - } - } - - // check for variables - matcher = variablePattern.matcher(expr.substring(index)); - if (matcher.lookingAt()) { - String match = matcher.group(); - index += match.length(); - - addImplicitMultiplication(tokenList, lastToken); - - Variable variable = new Variable(match); - tokenList.add(variable); - - probableUnary = false; - lastToken = variable; - - continue; - } - - // check for whitespace - matcher = whitespacePattern.matcher(expr.substring(index)); - if (matcher.lookingAt()) { - String match = matcher.group(); - index += match.length(); - - continue; - } - - // invalid character - throw new Expr4jException("Invalid expression"); - } - - return tokenList; - } - - /** - * Check if a string is blank or not. - * - * @param str The string - * @return True if blank, false otherwise - */ - private boolean isBlank(String str) { - return str == null || str.length() == 0 || str.chars().allMatch(Character::isWhitespace); - } - - /** - * Add an implicit multiplication operator to the token list. - * - * @param tokenList The token list - * @param lastToken The last token encountered - */ - private void addImplicitMultiplication(List tokenList, Token lastToken) { - if (lastToken instanceof Operator) { - Operator operator = (Operator) lastToken; - if (operator.type == OperatorType.POSTFIX) { - tokenList.add(expressionDictionary.getInfixOperator("*")); - } - } - else if (lastToken instanceof Separator) { - Separator lastSeparator = (Separator) lastToken; - if (lastSeparator == Separator.CLOSE_BRACKET) { - tokenList.add(expressionDictionary.getInfixOperator("*")); - } - } - else if (lastToken instanceof Operand || lastToken instanceof Variable) { - tokenList.add(expressionDictionary.getInfixOperator("*")); - } - } - - /** - * Check if postfix operator is allowed at current position. - * - * @param lastToken Last token encountered - * @return True if postfix operator is allowed, false otherwise - */ - private boolean postfixOperatorAllowed(Token lastToken) { - if (lastToken == null) { - return false; - } - - if (lastToken instanceof Separator) { - Separator separator = (Separator) lastToken; - return (separator == Separator.CLOSE_BRACKET); - } - else if (lastToken instanceof Operator) { - Operator operator = (Operator) lastToken; - return (operator.type == OperatorType.POSTFIX); - } - else if (lastToken instanceof Operand || lastToken instanceof Variable) { - return true; - } - - return false; - } - - /** - * Check if infix operator is allowed at current position. - * - * @param lastToken Last token encountered - * @return True if infix operator is allowed, false otherwise - */ - private boolean infixOperatorAllowed(Token lastToken) { - if (lastToken == null) { - return false; - } - - if (lastToken instanceof Separator) { - Separator separator = (Separator) lastToken; - return (separator == Separator.CLOSE_BRACKET); - } - else if (lastToken instanceof Operator) { - Operator operator = (Operator) lastToken; - return (operator.type == OperatorType.POSTFIX); - } - else if (lastToken instanceof Operand || lastToken instanceof Variable) { - return true; - } - - return false; - } - -} diff --git a/src/main/java/com/bitaspire/libs/formula/token/Function.java b/src/main/java/com/bitaspire/libs/formula/token/Function.java deleted file mode 100644 index 4950a46..0000000 --- a/src/main/java/com/bitaspire/libs/formula/token/Function.java +++ /dev/null @@ -1,101 +0,0 @@ -/** - * Copyright 2023 Pratanu Mandal - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.bitaspire.libs.formula.token; - -import com.bitaspire.libs.formula.exception.Expr4jException; -import com.bitaspire.libs.formula.expression.ExpressionParameter; - -import java.util.List; - -/** - * The Function<T> class represents functions in the expression. - * - * @author Pratanu Mandal - * @since 1.0 - * - * @param The type of operand - */ -public class Function implements Token { - - /** - * Constant to indicate that the function supports variable number of parameters. - */ - public static final int VARIABLE_PARAMETERS = -1; - - /** - * Label of the function. - */ - public final String label; - - /** - * Number of parameters. - */ - public final int parameters; - - /** - * Operation performed by the function. - */ - public final Operation operation; - - /** - * Parameterized constructor. - * - * @param label Label of the function - * @param parameters Number of parameters - * @param operation Operation performed by the function - */ - public Function(String label, int parameters, Operation operation) { - this.label = label; - this.parameters = parameters; - this.operation = operation; - - if (this.parameters < Function.VARIABLE_PARAMETERS) { - throw new Expr4jException("Invalid number of parameters: " + this.parameters); - } - } - - /** - * Parameterized constructor.
- * This constructor creates a function with variable number of parameters. - * - * @param label Label of the function - * @param operation Operation performed by the function - */ - public Function(String label, Operation operation) { - this(label, VARIABLE_PARAMETERS, operation); - } - - /** - * Evaluate the function lazily. - * - * @param parameters List of parameters - * @return Evaluated result - */ - public T evaluate(List> parameters) { - return this.operation.execute(parameters); - } - - @Override - public String toString() { - return "Function{" + - "label='" + label + '\'' + - ", parameters=" + parameters + - '}'; - } - -} diff --git a/src/main/java/com/bitaspire/libs/formula/token/Operand.java b/src/main/java/com/bitaspire/libs/formula/token/Operand.java deleted file mode 100644 index 6c3e6ab..0000000 --- a/src/main/java/com/bitaspire/libs/formula/token/Operand.java +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Copyright 2023 Pratanu Mandal - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.bitaspire.libs.formula.token; - -/** - * The Operand<T> class represents operands in the expression.
- * It acts as a wrapper for value of type T. - * - * @author Pratanu Mandal - * @since 1.0 - * - * @param The type of operand - */ -public class Operand implements Token { - - /** - * Value of the operand. - */ - public final T value; - - /** - * Parameterized constructor. - * - * @param value Value of the operand - */ - public Operand(T value) { - this.value = value; - } - - @Override - public String toString() { - return value.toString(); - } - -} diff --git a/src/main/java/com/bitaspire/libs/formula/token/Operation.java b/src/main/java/com/bitaspire/libs/formula/token/Operation.java deleted file mode 100644 index 910f89d..0000000 --- a/src/main/java/com/bitaspire/libs/formula/token/Operation.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.bitaspire.libs.formula.token; - -import com.bitaspire.libs.formula.expression.ExpressionParameter; - -import java.util.List; - -/** - * The Operation<T> interface represents an operation that can be executed. - * - * @author Pratanu Mandal - * @since 1.0 - * - * @param The type of operand - */ -public interface Operation { - - /** - * Execute the operation. - * - * @param parameters List of parameters - * @return Evaluated result - */ - T execute(List> parameters); - -} diff --git a/src/main/java/com/bitaspire/libs/formula/token/Operator.java b/src/main/java/com/bitaspire/libs/formula/token/Operator.java deleted file mode 100644 index 436d6e4..0000000 --- a/src/main/java/com/bitaspire/libs/formula/token/Operator.java +++ /dev/null @@ -1,105 +0,0 @@ -/** - * Copyright 2023 Pratanu Mandal - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.bitaspire.libs.formula.token; - -import com.bitaspire.libs.formula.exception.Expr4jException; -import com.bitaspire.libs.formula.expression.ExpressionParameter; - -import java.util.List; - -/** - * The Operator<T> class represents operators in the expression. - * - * @author Pratanu Mandal - * @since 1.0 - * - * @param The type of operand - */ -public class Operator implements Token, Comparable> { - - /** - * Label of the operator. - */ - public final String label; - - /** - * The type of operator. - */ - public final OperatorType type; - - /** - * The precedence of this operator.
- * Precedence ranges from 1 to MAX_INT, in ascending order, i.e, with 1 being lowest precedence possible. - */ - public final int precedence; - - /** - * Operation performed by the operation. - */ - public final Operation operation; - - /** - * Parameterized constructor. - * - * @param label Label of the operator - * @param operatorType Type of the operator - * @param precedence Precedence of the operator - * @param operation Operation performed by the operator - */ - public Operator(String label, OperatorType operatorType, int precedence, Operation operation) { - this.label = label; - this.type = operatorType; - this.precedence = precedence; - this.operation = operation; - - if (this.precedence < 1) { - throw new Expr4jException("Invalid precedence: " + this.precedence); - } - } - - /** - * Evaluate the function lazily. - * - * @param parameters List of parameters - * @return Evaluated result - */ - public T evaluate(List> parameters) { - return this.operation.execute(parameters); - } - - /** - * Method to compare this operator to another operator on the basis of precedence. - */ - @Override - public int compareTo(Operator other) { - // compare the precedences and associativity of the two operators - return (this.precedence == other.precedence) ? - (this.type == OperatorType.INFIX || this.type == OperatorType.POSTFIX ? 1 : -1) - : other.precedence - this.precedence; - } - - @Override - public String toString() { - return "Operator{" + - "label='" + label + '\'' + - ", type=" + type + - ", precedence=" + precedence + - '}'; - } - -} diff --git a/src/main/java/com/bitaspire/libs/formula/token/OperatorType.java b/src/main/java/com/bitaspire/libs/formula/token/OperatorType.java deleted file mode 100644 index 00e54be..0000000 --- a/src/main/java/com/bitaspire/libs/formula/token/OperatorType.java +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Copyright 2023 Pratanu Mandal - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.bitaspire.libs.formula.token; - -/** - * The OperatorType enum represents the type of an operator.
- * It can be of four types: PREFIX, POSTFIX, INFIX, and INFIX_RTL.

- * - * PREFIX - must be applied before an operand, right associative.
- * POSTFIX - must be applied after an operand, left associative.
- * INFIX - must be applied in between two operands, left associative.
- * INFIX_RTL - must be applied in between two operands, right associative. - * - * @author Pratanu Mandal - * @since 1.0 - * - */ -public enum OperatorType { - /** Prefix operators */ - PREFIX, - - /** Postfix operators */ - POSTFIX, - - /** Infix operators (left to right) */ - INFIX, - - /** Infix operators (right to left) */ - INFIX_RTL -} diff --git a/src/main/java/com/bitaspire/libs/formula/token/Separator.java b/src/main/java/com/bitaspire/libs/formula/token/Separator.java deleted file mode 100644 index d39cf37..0000000 --- a/src/main/java/com/bitaspire/libs/formula/token/Separator.java +++ /dev/null @@ -1,86 +0,0 @@ -/** - * Copyright 2023 Pratanu Mandal - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.bitaspire.libs.formula.token; - -import com.bitaspire.libs.formula.exception.Expr4jException; - -/** - * The Separator class represents the separators in the expression.
- * - * @author Pratanu Mandal - * @since 1.0 - * - */ -public enum Separator implements Token { - - /** Open bracket */ - OPEN_BRACKET("("), - - /** Close bracket */ - CLOSE_BRACKET(")"), - - /** Comma */ - COMMA(","); - - /** - * Label of the separator. - */ - private final String label; - - /** - * Parameterized constructor. - * - * @param label Label of the separator. - */ - Separator(String label) { - this.label = label; - } - - /** - * Get the separator label. - * - * @return The label - */ - public String label() { - return label; - } - - @Override - public String toString() { - return "Separator{" + - "name='" + name() + '\'' + - ", label='" + label() + '\'' + - '}'; - } - - /** - * Get the separator with specified label. - * - * @param label The label - * @return The separator - */ - public static Separator getSeparator(String label) { - switch (label) { - case "(": return OPEN_BRACKET; - case ")": return CLOSE_BRACKET; - case ",": return COMMA; - default: throw new Expr4jException("Invalid separator"); - } - } - -} diff --git a/src/main/java/com/bitaspire/libs/formula/token/Token.java b/src/main/java/com/bitaspire/libs/formula/token/Token.java deleted file mode 100644 index bd8b8d8..0000000 --- a/src/main/java/com/bitaspire/libs/formula/token/Token.java +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright 2023 Pratanu Mandal - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.bitaspire.libs.formula.token; - -/** - * The Token interface represents any token in expressions.

- * A token is the smallest indivisible unit of any expression.
- * Tokens can be operands, functions, operators, separators, variables, or constants. - * - * @author Pratanu Mandal - * @since 1.0 - * - */ -public interface Token { - -} diff --git a/src/main/java/com/bitaspire/libs/formula/token/Variable.java b/src/main/java/com/bitaspire/libs/formula/token/Variable.java deleted file mode 100644 index 00154d3..0000000 --- a/src/main/java/com/bitaspire/libs/formula/token/Variable.java +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Copyright 2023 Pratanu Mandal - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -package com.bitaspire.libs.formula.token; - -/** - * The Variable class represents the variables in the expression. - * - * @author Pratanu Mandal - * @since 1.0 - * - */ -public class Variable implements Token { - - /** - * Label of the variable. - */ - public final String label; - - /** - * Parameterized constructor. - * - * @param label Label of the variable - */ - public Variable(String label) { - this.label = label; - } - - @Override - public String toString() { - return label; - } - -} From 5351db2cc1ab3ccebabc497fe9f6fdbad1aa208a Mon Sep 17 00:00:00 2001 From: CroaBeast <66433837+CroaBeast@users.noreply.github.com> Date: Mon, 1 Dec 2025 11:19:54 -0500 Subject: [PATCH 05/12] Initialize operators before building formulas --- src/main/java/com/bitaspire/cyberlevels/BaseSystem.java | 3 +++ .../com/bitaspire/cyberlevels/BigDecimalLevelSystem.java | 9 +++++---- .../com/bitaspire/cyberlevels/DoubleLevelSystem.java | 9 +++++---- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/bitaspire/cyberlevels/BaseSystem.java b/src/main/java/com/bitaspire/cyberlevels/BaseSystem.java index a7c2bed..dd89e24 100644 --- a/src/main/java/com/bitaspire/cyberlevels/BaseSystem.java +++ b/src/main/java/com/bitaspire/cyberlevels/BaseSystem.java @@ -34,6 +34,7 @@ abstract class BaseSystem implements LevelSystem { private final long startLevel, maxLevel; private final int startExp; + private final Operator operator; private final Formula formula; private final Map> formulas = new ConcurrentHashMap<>(); @@ -55,6 +56,7 @@ abstract class BaseSystem implements LevelSystem { startLevel = cache.levels().getStartLevel(); maxLevel = cache.levels().getMaxLevel(); + operator = createOperator(); formula = createFormula(cache.levels().getFormula()); cache.levels().getCustomFormulas().forEach((k, v) -> formulas.put(k, createFormula(v))); @@ -62,6 +64,7 @@ abstract class BaseSystem implements LevelSystem { if (cache.config().isRoundingEnabled()) formatter = new DecimalFormatter<>(this); } + abstract Operator createOperator(); abstract Formula createFormula(String formula); @Override diff --git a/src/main/java/com/bitaspire/cyberlevels/BigDecimalLevelSystem.java b/src/main/java/com/bitaspire/cyberlevels/BigDecimalLevelSystem.java index 01a9329..324d01a 100644 --- a/src/main/java/com/bitaspire/cyberlevels/BigDecimalLevelSystem.java +++ b/src/main/java/com/bitaspire/cyberlevels/BigDecimalLevelSystem.java @@ -15,13 +15,14 @@ @Getter final class BigDecimalLevelSystem extends BaseSystem { - private final Operator operator; - BigDecimalLevelSystem(CyberLevels main) { super(main); setLeaderboardFunction(BigDecimalLeaderboard::new); + } - operator = new Operator() { + @Override + Operator createOperator() { + return new Operator() { @Override public BigDecimal zero() { return BigDecimal.ZERO; @@ -119,7 +120,7 @@ public int compareTo(@NotNull Entry other) { @Override Formula createFormula(String string) { - return new BaseFormula(operator, string) { + return new BaseFormula(getOperator(), string) { @NotNull Builder builder() { return new BigDecimalBuilder(); diff --git a/src/main/java/com/bitaspire/cyberlevels/DoubleLevelSystem.java b/src/main/java/com/bitaspire/cyberlevels/DoubleLevelSystem.java index bd527cd..b99300b 100644 --- a/src/main/java/com/bitaspire/cyberlevels/DoubleLevelSystem.java +++ b/src/main/java/com/bitaspire/cyberlevels/DoubleLevelSystem.java @@ -14,13 +14,14 @@ @Getter final class DoubleLevelSystem extends BaseSystem { - private final Operator operator; - DoubleLevelSystem(CyberLevels main) { super(main); setLeaderboardFunction(DoubleLeaderboard::new); + } - operator = new Operator() { + @Override + Operator createOperator() { + return new Operator() { @Override public Double zero() { return 0.0; @@ -130,7 +131,7 @@ public int compareTo(@NotNull Entry other) { @Override Formula createFormula(String string) { - return new BaseFormula(operator, string) { + return new BaseFormula(getOperator(), string) { @NotNull Builder builder() { return new DoubleBuilder(); From 90d394fa4c71aad87a88512279cbe07a93f19140 Mon Sep 17 00:00:00 2001 From: Gustavo Paredes <66433837+CroaBeast@users.noreply.github.com> Date: Fri, 5 Dec 2025 09:20:35 -0500 Subject: [PATCH 06/12] 1.0.4 - Fix database time-outs --- .../java/com/bitaspire/cyberlevels/BaseSystem.java | 11 +++++------ .../java/com/bitaspire/cyberlevels/CyberLevels.java | 1 - .../com/bitaspire/cyberlevels/DatabaseFactory.java | 9 ++++++--- .../com/bitaspire/cyberlevels/UserManagerImpl.java | 6 +++--- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/bitaspire/cyberlevels/BaseSystem.java b/src/main/java/com/bitaspire/cyberlevels/BaseSystem.java index dd89e24..1508731 100644 --- a/src/main/java/com/bitaspire/cyberlevels/BaseSystem.java +++ b/src/main/java/com/bitaspire/cyberlevels/BaseSystem.java @@ -217,9 +217,7 @@ public Map getAntiAbuses() { @NotNull LevelUser createUser(UUID uuid) { Player player = Bukkit.getPlayer(uuid); - return player == null ? - new OfflineUser<>(this, Bukkit.getOfflinePlayer(uuid)) : - new OnlineUser<>(this, player); + return player == null ? createOffline(uuid) : new OnlineUser<>(this, player); } @NotNull @@ -280,6 +278,7 @@ public T evaluate(UUID uuid) { try { return builder().build(parsed).evaluate(); } catch (Throwable t) { + t.printStackTrace(); return operator.fromDouble(0.0); } } @@ -365,10 +364,10 @@ abstract class Entry implements Comparable> { } void updateLeaderboard() { - if (!main.isEnabled() || leaderboard == null) return; + if (!main.isEnabled() || leaderboard == null || + !cache.config().isLeaderboardEnabled()) return; - if (cache.config().leaderboardInstantUpdate() && !leaderboard.isUpdating()) - leaderboard.update(); + if (!leaderboard.isUpdating()) leaderboard.update(); } abstract class BaseUser implements LevelUser { diff --git a/src/main/java/com/bitaspire/cyberlevels/CyberLevels.java b/src/main/java/com/bitaspire/cyberlevels/CyberLevels.java index c7ae30b..420b61a 100644 --- a/src/main/java/com/bitaspire/cyberlevels/CyberLevels.java +++ b/src/main/java/com/bitaspire/cyberlevels/CyberLevels.java @@ -134,7 +134,6 @@ public void onDisable() { userManager.cancelAutoSave(); if (database != null) database.disconnect(); - listeners.unregister(); } diff --git a/src/main/java/com/bitaspire/cyberlevels/DatabaseFactory.java b/src/main/java/com/bitaspire/cyberlevels/DatabaseFactory.java index 4f94a13..afd2f6f 100644 --- a/src/main/java/com/bitaspire/cyberlevels/DatabaseFactory.java +++ b/src/main/java/com/bitaspire/cyberlevels/DatabaseFactory.java @@ -516,11 +516,13 @@ public Set getUuids() { CompletableFuture> future = new CompletableFuture<>(); main.scheduler().runTaskAsynchronously(() -> { - Set result = new LinkedHashSet<>(); String sql = "SELECT " + qCol("UUID") + " FROM " + qTab(getTable()); + try (Connection connection = dataSource.getConnection(); PreparedStatement statement = connection.prepareStatement(sql); - ResultSet rs = statement.executeQuery()) { + ResultSet rs = statement.executeQuery()) + { + Set result = new LinkedHashSet<>(); while (rs.next()) { try { result.add(UUID.fromString(rs.getString("UUID"))); @@ -719,7 +721,8 @@ static class SQLite extends DatabaseImpl { HikariConfig createConfig() { HikariConfig config = new HikariConfig(); config.setJdbcUrl("jdbc:sqlite:" + filePath); - config.setMaximumPoolSize(1); + config.setMaximumPoolSize(10); + config.setMinimumIdle(2); config.setPoolName("CLV-SQLite"); return config; } diff --git a/src/main/java/com/bitaspire/cyberlevels/UserManagerImpl.java b/src/main/java/com/bitaspire/cyberlevels/UserManagerImpl.java index 47fb0e5..ec2f958 100644 --- a/src/main/java/com/bitaspire/cyberlevels/UserManagerImpl.java +++ b/src/main/java/com/bitaspire/cyberlevels/UserManagerImpl.java @@ -30,9 +30,9 @@ final class UserManagerImpl implements UserManager { final CyberLevels main; final Cache cache; + private final AtomicBoolean leaderboardQueued = new AtomicBoolean(false); private final BaseSystem system; private final Map> users = new ConcurrentHashMap<>(); - private final AtomicBoolean leaderboardUpdateQueued = new AtomicBoolean(false); GlobalTask autoSaveTask = null; @Getter @@ -265,10 +265,10 @@ private void finishUserLoad(UUID uuid, Player player, LoadResult result, boolean } private void scheduleLeaderboardUpdate() { - if (!leaderboardUpdateQueued.compareAndSet(false, true)) return; + if (!leaderboardQueued.compareAndSet(false, true)) return; main.scheduler().runTask(() -> { system.updateLeaderboard(); - leaderboardUpdateQueued.set(false); + leaderboardQueued.set(false); }); } From eeae4726b9f39999a8e99bff208926ebc0b10374 Mon Sep 17 00:00:00 2001 From: Gustavo Paredes <66433837+CroaBeast@users.noreply.github.com> Date: Sun, 18 Jan 2026 08:56:07 -0500 Subject: [PATCH 07/12] 1.1.1 - Action bar fix --- .gitignore | 1 + build.gradle | 27 +- .../com/bitaspire/cyberlevels/BaseSystem.java | 34 ++- ...LevelSystem.java => BigDecimalSystem.java} | 4 +- .../bitaspire/cyberlevels/CyberLevels.java | 51 +++- .../cyberlevels/DatabaseFactory.java | 83 +++--- .../cyberlevels/DependencyLoader.java | 256 ++++++++++++++++++ ...ubleLevelSystem.java => DoubleSystem.java} | 4 +- .../com/bitaspire/cyberlevels/Message.java | 30 +- .../cyberlevels/UserManagerImpl.java | 7 +- .../cyberlevels/cache/AntiAbuse.java | 2 +- .../bitaspire/cyberlevels/cache/CLVFile.java | 2 +- .../bitaspire/cyberlevels/cache/Config.java | 4 + .../bitaspire/cyberlevels/cache/EarnExp.java | 16 +- .../com/bitaspire/cyberlevels/cache/Lang.java | 2 +- .../bitaspire/cyberlevels/cache/Levels.java | 4 +- .../bitaspire/cyberlevels/cache/Rewards.java | 7 +- .../cyberlevels/command/CLVCommand.java | 3 +- .../cyberlevels/command/CLVTabComplete.java | 17 +- .../cyberlevels/hook/HookManager.java | 6 +- .../cyberlevels/hook/PlaceholderAPI.java | 5 +- .../bitaspire/cyberlevels/level/Formula.java | 1 - .../cyberlevels/level/LevelSystem.java | 1 - src/main/resources/config.yml | 5 + src/main/resources/plugin.yml | 18 +- 25 files changed, 449 insertions(+), 141 deletions(-) rename src/main/java/com/bitaspire/cyberlevels/{BigDecimalLevelSystem.java => BigDecimalSystem.java} (97%) create mode 100644 src/main/java/com/bitaspire/cyberlevels/DependencyLoader.java rename src/main/java/com/bitaspire/cyberlevels/{DoubleLevelSystem.java => DoubleSystem.java} (97%) diff --git a/.gitignore b/.gitignore index 50f9169..220e628 100644 --- a/.gitignore +++ b/.gitignore @@ -116,3 +116,4 @@ buildNumber.properties # Common working directory run/ /libs/ +/.claude/settings.local.json diff --git a/build.gradle b/build.gradle index 4128eef..6373a5c 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ plugins { } group = 'com.bitaspire' -version = '1.0.4' +version = '1.1.1' repositories { mavenLocal() @@ -42,7 +42,7 @@ dependencies { compileOnly "org.spigotmc:spigot-api:1.16.5-R0.1-SNAPSHOT" // Core dependency - implementation 'com.github.Kihsomray:CyberCore:1.3.32' + implementation files('libs/CyberCore.jar') // Lombok compileOnly "org.projectlombok:lombok:1.18.38" @@ -54,16 +54,12 @@ dependencies { compileOnly files('libs/RivalPickaxesAPI.jar') // Database - compileOnly 'com.zaxxer:HikariCP:3.4.5' - compileOnly 'mysql:mysql-connector-java:8.0.21' - compileOnly 'org.xerial:sqlite-jdbc:3.36.0.3' - compileOnly 'org.postgresql:postgresql:42.7.7' + compileOnly 'com.zaxxer:HikariCP:7.0.2' + compileOnly 'com.mysql:mysql-connector-j:9.5.0' + compileOnly 'org.xerial:sqlite-jdbc:3.51.1.0' + compileOnly 'org.postgresql:postgresql:42.7.8' // Misc utils - implementation 'org.bstats:bstats-bukkit:3.0.2' - implementation 'me.croabeast:YAML-API:1.1' - implementation 'me.croabeast:GlobalScheduler:1.1' - implementation 'me.croabeast.expr4j:core:1.0' implementation 'me.croabeast.expr4j:big-decimal:1.0' implementation 'me.croabeast.expr4j:double:1.0' @@ -104,15 +100,6 @@ tasks.build.dependsOn(tasks.shadowJar) tasks { shadowJar { archiveClassifier.set('') - - exclude( - 'META-INF/**', 'org/apache/commons/**', 'org/intellij/**', - 'org/jetbrains/**', 'me/croabeast/file/plugin/YAMLPlugin.*' - ) - - relocate('org.bstats', 'com.bitaspire.libs.bstats') - relocate('me.croabeast.common', 'com.bitaspire.libs.file') - relocate('me.croabeast', 'com.bitaspire.libs') - relocate('net.zerotoil.dev', 'com.bitaspire.libs') + exclude('META-INF/**', 'org/apache/commons/**', 'org/intellij/**', 'org/jetbrains/**', 'me/croabeast/file/plugin/YAMLPlugin.*') } } \ No newline at end of file diff --git a/src/main/java/com/bitaspire/cyberlevels/BaseSystem.java b/src/main/java/com/bitaspire/cyberlevels/BaseSystem.java index 1508731..b0dc0fe 100644 --- a/src/main/java/com/bitaspire/cyberlevels/BaseSystem.java +++ b/src/main/java/com/bitaspire/cyberlevels/BaseSystem.java @@ -8,7 +8,6 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.Setter; -import me.croabeast.beanslib.Beans; import me.croabeast.expr4j.expression.Builder; import org.apache.commons.lang3.StringUtils; import org.bukkit.Bukkit; @@ -158,7 +157,7 @@ public String replacePlaceholders(String string, UUID uuid, boolean safeForFormu string = StringUtils.replaceEach(string, k, v); } - return Beans.formatPlaceholders(data.isOnline() ? data.getPlayer() : null, string); + return main.library().replace(data.isOnline() ? data.getPlayer() : null, string); } @NotNull @@ -323,13 +322,15 @@ public void update() { @Override public LevelUser getTopPlayer(int position) { - return updating || position < 1 || position > 10 ? null : userManager.getUser(topTenPlayers.get(position - 1).getUuid()); - } + if (updating || position < 1 || position > 10) return null; - @Override - public int checkPosition(Player player) { - UUID uuid = player.getUniqueId(); + int index = position - 1; + if (index >= topTenPlayers.size()) return null; + return userManager.getUser(topTenPlayers.get(index).getUuid()); + } + + int check(UUID uuid) { for (int i = 0; i < topTenPlayers.size(); i++) if (uuid.equals(topTenPlayers.get(i).getUuid())) return i + 1; @@ -337,9 +338,14 @@ public int checkPosition(Player player) { return -1; } + @Override + public int checkPosition(Player player) { + return check(player.getUniqueId()); + } + @Override public int checkPosition(LevelUser user) { - return checkPosition(user.getPlayer()); + return check(user.getUuid()); } abstract Entry toEntry(LevelUser user); @@ -400,6 +406,8 @@ public void setHighestRewardedLevel(long value) { } void sendLevelReward(long level) { + if (!isOnline()) return; + if (!cache.config().preventDuplicateRewards()) { getRewards(level).forEach(r -> r.giveAll(getPlayer())); return; @@ -429,7 +437,7 @@ void updateLevel(long newLevel, boolean sendMessage, boolean giveRewards) { if (operator.compare(exp, operator.zero()) < 0) exp = operator.zero(); - if (sendMessage) { + if (sendMessage && isOnline()) { long diff = level - oldLevel; if (diff > 0) { cache.lang().sendMessage(getPlayer(), Lang::getGainedLevels, new String[] {"gainedLevels", "level"}, diff, level); @@ -506,7 +514,7 @@ private void changeExp(T amount, T difference, boolean sendMessage, boolean doMu T displayTotal = (cache.config().stackComboExp() && System.currentTimeMillis() - lastTime <= 650) ? operator.add(amount, lastAmount) : amount; - if (sendMessage) { + if (sendMessage && isOnline()) { T diff = operator.subtract(Objects.equals(displayTotal, operator.zero()) ? operator.zero() : displayTotal, difference); if (operator.compare(totalAmount, operator.zero()) > 0) { @@ -528,7 +536,7 @@ private void changeExp(T amount, T difference, boolean sendMessage, boolean doMu level = Math.max(getStartLevel(), Math.min(level, getMaxLevel())); if (operator.compare(exp, operator.zero()) < 0) exp = operator.zero(); - if (sendMessage) { + if (sendMessage && isOnline()) { long levelDifference = level - startingLevel; if (levelDifference > 0) { cache.lang().sendMessage(getPlayer(), Lang::getGainedLevels, new String[] {"gainedLevels", "level"}, levelDifference, level); @@ -609,6 +617,7 @@ public String getProgressBar() { @Override public boolean hasParentPerm(String permission, boolean checkOp) { + if (!isOnline()) return false; if (checkOp && getPlayer().isOp()) return true; for (PermissionAttachmentInfo node : getPlayer().getEffectivePermissions()) { @@ -622,8 +631,9 @@ public boolean hasParentPerm(String permission, boolean checkOp) { @Override public double getMultiplier() { - double multiplier = 0; + if (!isOnline()) return 1; + double multiplier = 0; for (PermissionAttachmentInfo perm : getPlayer().getEffectivePermissions()) { if (!perm.getValue()) continue; diff --git a/src/main/java/com/bitaspire/cyberlevels/BigDecimalLevelSystem.java b/src/main/java/com/bitaspire/cyberlevels/BigDecimalSystem.java similarity index 97% rename from src/main/java/com/bitaspire/cyberlevels/BigDecimalLevelSystem.java rename to src/main/java/com/bitaspire/cyberlevels/BigDecimalSystem.java index 324d01a..b1df343 100644 --- a/src/main/java/com/bitaspire/cyberlevels/BigDecimalLevelSystem.java +++ b/src/main/java/com/bitaspire/cyberlevels/BigDecimalSystem.java @@ -13,9 +13,9 @@ import java.math.RoundingMode; @Getter -final class BigDecimalLevelSystem extends BaseSystem { +final class BigDecimalSystem extends BaseSystem { - BigDecimalLevelSystem(CyberLevels main) { + BigDecimalSystem(CyberLevels main) { super(main); setLeaderboardFunction(BigDecimalLeaderboard::new); } diff --git a/src/main/java/com/bitaspire/cyberlevels/CyberLevels.java b/src/main/java/com/bitaspire/cyberlevels/CyberLevels.java index 420b61a..c63293b 100644 --- a/src/main/java/com/bitaspire/cyberlevels/CyberLevels.java +++ b/src/main/java/com/bitaspire/cyberlevels/CyberLevels.java @@ -1,5 +1,8 @@ package com.bitaspire.cyberlevels; +import com.bitaspire.common.util.ServerInfoUtils; +import com.bitaspire.cybercore.CoreSettings; +import com.bitaspire.cybercore.CyberCore; import com.bitaspire.cyberlevels.cache.Cache; import com.bitaspire.cyberlevels.command.CLVCommand; import com.bitaspire.cyberlevels.command.CLVTabComplete; @@ -8,15 +11,15 @@ import com.bitaspire.cyberlevels.listener.Listeners; import com.bitaspire.cyberlevels.user.Database; import com.bitaspire.cyberlevels.user.UserManager; +import com.bitaspire.takion.TakionLib; +import com.bitaspire.takion.message.MessageSender; +import com.bitaspire.scheduler.GlobalScheduler; import lombok.AccessLevel; import lombok.Getter; import lombok.experimental.Accessors; -import me.croabeast.beanslib.utility.LibUtils; -import me.croabeast.scheduler.GlobalScheduler; -import net.zerotoil.dev.cybercore.CoreSettings; -import net.zerotoil.dev.cybercore.CyberCore; import org.bukkit.Bukkit; import org.bukkit.command.PluginCommand; +import org.bukkit.entity.Player; import org.bukkit.plugin.java.JavaPlugin; @Accessors(fluent = true) @@ -46,14 +49,22 @@ public final class CyberLevels extends JavaPlugin { @Override public void onEnable() { - if (!CyberCore.restrictVersions(8, 22, "CLV", getDescription().getVersion())) - return; + if (serverVersion() < 16) { + DependencyLoader loader = DependencyLoader.BUKKIT_LOADER; + loader.load("ch.obermuhlner", "big-math", "2.3.2", true); + loader.load("org.slf4j", "slf4j-api", "1.7.36", true); + loader.load("com.zaxxer", "HikariCP", "4.0.3", true); + loader.load("com.mysql", "mysql-connector-j", "8.0.33", true); + loader.load("org.xerial", "sqlite-jdbc", "3.51.1.0", true); + loader.load("org.postgresql", "postgresql", "42.7.8", true); + loader.load("org.apache.commons", "commons-lang3", "3.18.0", true); + } instance = this; scheduler = GlobalScheduler.getScheduler(this); core = new CyberCore(this); - CoreSettings settings = core.coreSettings(); + CoreSettings settings = core.getSettings(); settings.setBootColor('d'); settings.setBootLogo( "&d╭━━━╮&7╱╱╱&d╭╮&7╱╱╱╱╱╱&d╭╮&7╱╱╱╱╱╱╱╱╱╱╱&d╭╮", @@ -71,7 +82,7 @@ public void onEnable() { PluginCommand command = this.getCommand("clv"); if (command != null) { command.setExecutor(new CLVCommand(this)); - command.setTabCompleter(new CLVTabComplete()); + command.setTabCompleter(new CLVTabComplete(this)); } reloadPlugin(); @@ -91,8 +102,8 @@ public void reloadPlugin() { long start = System.currentTimeMillis(); BaseSystem system = !cache.config().useBigDecimalSystem() ? - new DoubleLevelSystem(this) : - new BigDecimalLevelSystem(this); + new DoubleSystem(this) : + new BigDecimalSystem(this); logger("&dChecking level system type..."); levelSystem = system; @@ -108,6 +119,7 @@ public void reloadPlugin() { manager.checkMigration(); database = (userManager = manager).getDatabase(); + logger(""); manager.loadOfflinePlayers(); userManager.loadOnlinePlayers(); @@ -133,20 +145,31 @@ public void onDisable() { userManager.saveOnlinePlayers(true); userManager.cancelAutoSave(); - if (database != null) database.disconnect(); + if (database != null) { + database.disconnect(); + if (isEnabled()) logger(""); + } listeners.unregister(); } public String getAuthors() { - return this.getDescription().getAuthors().toString().replace("[", "").replace("]", ""); + return this.getDescription().getAuthors().toString().replaceAll("[\\[\\]]", ""); } public double serverVersion() { - return LibUtils.getMainVersion(); + return ServerInfoUtils.SERVER_VERSION; + } + + public TakionLib library() { + return core.getLibrary(); + } + + public MessageSender createSender(Player player) { + return library().getLoadedSender().setTargets(player).setParser(player); } public void logger(String... message) { - core.logger(message); + library().getLogger().log(message); } public boolean isEnabled(String plugin) { diff --git a/src/main/java/com/bitaspire/cyberlevels/DatabaseFactory.java b/src/main/java/com/bitaspire/cyberlevels/DatabaseFactory.java index afd2f6f..a9909d3 100644 --- a/src/main/java/com/bitaspire/cyberlevels/DatabaseFactory.java +++ b/src/main/java/com/bitaspire/cyberlevels/DatabaseFactory.java @@ -41,16 +41,8 @@ String qTab(String name) { return name; } - abstract PreparedStatement prepareUpsert(Connection c, - UUID uuid, - long level, - String exp, - long updatedAt) throws SQLException; - - abstract PreparedStatement prepareUpsertMeta(Connection c, - UUID uuid, - long highestRewarded, - long updatedAt) throws SQLException; + abstract PreparedStatement prepareUpsert(Connection c, UUID uuid, long level, String exp, long updatedAt) throws SQLException; + abstract PreparedStatement prepareUpsertMeta(Connection c, UUID uuid, long highestRewarded, long updatedAt) throws SQLException; abstract Set getExistingColumns(Connection conn) throws SQLException; abstract boolean isExpColumnTextual(Connection conn) throws SQLException; @@ -112,9 +104,9 @@ public void connect() { ensureMetaSchema(conn); } - main.logger("&7Connected to &e" + type + "&7 successfully in &a" + (System.currentTimeMillis() - l) + "ms&7.", ""); + main.logger("&7Connected to &e" + type + "&7 successfully in &a" + (System.currentTimeMillis() - l) + "ms&7."); } catch (Exception e) { - main.logger("&cThere was an issue connecting to " + type + " Database.", ""); + main.logger("&cThere was an issue connecting to " + type + " Database."); e.printStackTrace(); } }); @@ -127,16 +119,23 @@ public void disconnect() { main.logger("&dAttempting to disconnect from " + type + "..."); long l = System.currentTimeMillis(); - main.scheduler().runTaskAsynchronously(() -> { + Runnable close = () -> { try { dataSource.close(); - dataSource = null; - main.logger("&7Disconnected from &e" + type + "&7 successfully in &a" + (System.currentTimeMillis() - l) + "ms&7.", ""); + main.logger("&7Disconnected from &e" + type + "&7 successfully in &a" + (System.currentTimeMillis() - l) + "ms&7."); } catch (Exception e) { - main.logger("&cThere was an issue disconnecting from " + type + " Database.", ""); + main.logger("&cThere was an issue disconnecting from " + type + " Database."); e.printStackTrace(); + } finally { + dataSource = null; } - }); + }; + + if (main.isEnabled()) { + main.scheduler().runTaskAsynchronously(close); + } else { + close.run(); + } } void ensureTargetSchema(Connection conn) throws SQLException { @@ -415,13 +414,7 @@ public void updateUser(LevelUser user) { main.scheduler().runTaskAsynchronously(() -> { try (Connection connection = dataSource.getConnection()) { - try (PreparedStatement st = prepareUpsert( - connection, - uuid, - level, - expStr, - now - )) { + try (PreparedStatement st = prepareUpsert(connection, uuid, level, expStr, now)) { st.executeUpdate(); } @@ -544,8 +537,6 @@ public Set getUuids() { } } - // --------------- MySQL / MariaDB --------------- // - static class MySQL extends DatabaseImpl { final String ip, database, username, password, table; @@ -575,9 +566,20 @@ HikariConfig createConfig() { config.setJdbcUrl("jdbc:mysql://" + ip + ":" + port + "/" + database + "?useSSL=" + ssl + "&autoReconnect=true&useUnicode=true&characterEncoding=utf8"); config.setUsername(username); config.setPassword(password); - config.setMaximumPoolSize(10); - config.setMinimumIdle(2); + config.setConnectionTimeout(10000); + config.setValidationTimeout(5000); + config.setIdleTimeout(600000); + config.setMaxLifetime(1800000); + config.setKeepaliveTime(300000); + config.setMaximumPoolSize(20); + config.setMinimumIdle(4); config.setPoolName("CLV-MySQL"); + config.addDataSourceProperty("cachePrepStmts", "true"); + config.addDataSourceProperty("prepStmtCacheSize", "250"); + config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048"); + config.addDataSourceProperty("useServerPrepStmts", "true"); + config.addDataSourceProperty("tcpKeepAlive", "true"); + config.setTransactionIsolation("TRANSACTION_READ_COMMITTED"); return config; } @@ -702,8 +704,6 @@ void renameTable(Connection conn, String from, String to) throws SQLException { } } - // --------------- SQLite --------------- // - static class SQLite extends DatabaseImpl { private final String filePath, table; @@ -720,9 +720,14 @@ static class SQLite extends DatabaseImpl { @Override HikariConfig createConfig() { HikariConfig config = new HikariConfig(); - config.setJdbcUrl("jdbc:sqlite:" + filePath); - config.setMaximumPoolSize(10); - config.setMinimumIdle(2); + config.setJdbcUrl("jdbc:sqlite:" + filePath + "?busy_timeout=5000&journal_mode=WAL&synchronous=NORMAL"); + config.setMaximumPoolSize(2); + config.setMinimumIdle(1); + config.setConnectionTimeout(10000); + config.setValidationTimeout(5000); + config.setIdleTimeout(600000); + config.setMaxLifetime(1800000); + config.setKeepaliveTime(300000); config.setPoolName("CLV-SQLite"); return config; } @@ -839,8 +844,6 @@ void renameTable(Connection conn, String from, String to) throws SQLException { } } - // --------------- PostgreSQL --------------- // - static class PostgreSQL extends DatabaseImpl { final String ip, database, username, password, table; @@ -867,9 +870,15 @@ HikariConfig createConfig() { config.setJdbcUrl("jdbc:postgresql://" + ip + ":" + port + "/" + database); config.setUsername(username); config.setPassword(password); - config.setMaximumPoolSize(10); - config.setMinimumIdle(2); + config.setConnectionTimeout(10000); + config.setValidationTimeout(5000); + config.setIdleTimeout(600000); + config.setMaxLifetime(1800000); + config.setKeepaliveTime(300000); + config.setMaximumPoolSize(20); + config.setMinimumIdle(4); config.setPoolName("CLV-Postgres"); + config.addDataSourceProperty("tcpKeepAlive", "true"); return config; } diff --git a/src/main/java/com/bitaspire/cyberlevels/DependencyLoader.java b/src/main/java/com/bitaspire/cyberlevels/DependencyLoader.java new file mode 100644 index 0000000..9e8a1aa --- /dev/null +++ b/src/main/java/com/bitaspire/cyberlevels/DependencyLoader.java @@ -0,0 +1,256 @@ +package com.bitaspire.cyberlevels; + +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import lombok.experimental.UtilityClass; +import com.bitaspire.file.YAMLFile; +import org.apache.commons.lang3.StringUtils; +import org.bukkit.Bukkit; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; +import sun.misc.Unsafe; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Field; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; +import java.util.logging.Level; + +class DependencyLoader { + + static final DependencyLoader BUKKIT_LOADER = + new DependencyLoader(Bukkit.getWorldContainer(), "libraries") { + @Override + public void setComplexStructure(boolean complex) { + throw new IllegalStateException("Structure can't be changed."); + } + }; + + static final String[] MAVEN_REPO_URLS = { + "https://repo1.maven.org/maven2/", + "https://repo.maven.apache.org/maven2/" + }; + + private final File librariesFolder; + + @Setter + private boolean complexStructure = true; + + private DependencyLoader(File folder, String newName) { + this.librariesFolder = StringUtils.isBlank(newName) ? folder : new File(folder, newName); + } + + private void log(Log log, String message) { + Bukkit.getLogger().log(log.level, "[DependencyLoader] " + message); + } + + private boolean downloadFile(String urlString, File destiny) throws IOException { + URL url = new URL(urlString); + + try { + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("HEAD"); + connection.setConnectTimeout(5000); + connection.setReadTimeout(5000); + + if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { + log(Log.ERROR, "URL not reachable: " + urlString); + return false; + } + } catch (Exception e) { + log(Log.ERROR, "URL not reachable: " + urlString); + e.printStackTrace(); + return false; + } + + try (FileOutputStream out = new FileOutputStream(destiny); + InputStream in = url.openStream()) { + byte[] buffer = new byte[4096]; + int len; + while ((len = in.read(buffer)) != -1) { + out.write(buffer, 0, len); + } + return true; + } + } + + public final boolean load(String group, String artifact, String version, String repoUrl, boolean replace) { + repoUrl = repoUrl != null ? repoUrl : MAVEN_REPO_URLS[0]; + + try { + String path = group.replace('.', File.separatorChar) + File.separatorChar + + artifact + File.separatorChar + version; + String jarName = artifact + "-" + version + ".jar"; + + File jarFile = new File(librariesFolder, + (complexStructure ? (path + File.separatorChar) : "") + jarName); + if (!jarFile.exists() || replace) { + if (jarFile.exists()) jarFile.delete(); + + log(Log.GOOD, "Downloading: " + jarName); + jarFile.getParentFile().mkdirs(); + + StringBuilder builder = new StringBuilder(repoUrl); + if (!repoUrl.endsWith("/")) builder.append('/'); + + builder.append(path.replace(File.separatorChar, '/')) + .append('/') + .append(jarName); + + if (!downloadFile(builder.toString(), jarFile)) + return false; + + if (jarFile.length() == 0) { + log(Log.ERROR, "Download failed or file is empty: " + jarName); + return false; + } + } + + Utils.load0(jarFile); + log(Log.GOOD, "Loaded: " + jarName); + return true; + } catch (Exception e) { + log(Log.ERROR, "Error loading dependency: " + artifact + " v" + version); + e.printStackTrace(); + return false; + } + } + + public final boolean load(String group, String artifact, String version, boolean replace) { + return load(group, artifact, version, null, replace); + } + + public final boolean load(String group, String artifact, String version, String repoUrl) { + return load(group, artifact, version, repoUrl, false); + } + + public final boolean load(String group, String artifact, String version) { + return load(group, artifact, version, false); + } + + public boolean loadFromConfiguration(FileConfiguration c) { + List> dependencies = c.getMapList("dependencies"); + boolean loadAtLeastOne = false; + + for (Map map : dependencies) { + String group = (String) map.get("group"); + String artifact = (String) map.get("artifact"); + String version = (String) map.get("version"); + + if (group == null || artifact == null || version == null) { + log(Log.BAD, "Invalid dependency: " + map); + continue; + } + + Boolean replace = (Boolean) map.get("replace"); + loadAtLeastOne = load( + group, artifact, version, (String) map.get("repo"), + replace != null && replace + ); + } + + return loadAtLeastOne; + } + + public boolean loadFromFile(File file) { + if (!file.getAbsolutePath().endsWith(".yml")) { + log(Log.BAD, file + " isn't a valid .yml file."); + return false; + } + + if (!file.exists()) { + log(Log.BAD, file + " doesn't exist."); + return false; + } + + FileConfiguration config = YamlConfiguration.loadConfiguration(file); + return loadFromConfiguration(config); + } + + public boolean loadFromYAML(YAMLFile file) { + return loadFromConfiguration(file.getConfiguration()); + } + + public static DependencyLoader fromFolder(File librariesFolder, String folderName) { + return new DependencyLoader(librariesFolder, folderName); + } + + public static DependencyLoader fromFolder(File librariesFolder) { + return fromFolder(librariesFolder, null); + } + + @RequiredArgsConstructor + private enum Log { + GOOD(Level.INFO), + BAD(Level.WARNING), + ERROR(Level.SEVERE); + + private final Level level; + } + + @UtilityClass + private static class Utils { + + private final Unsafe THE_UNSAFE = ((Supplier) () -> { + Field field; + try { + field = Unsafe.class.getDeclaredField("theUnsafe"); + } catch (NoSuchFieldException e) { + throw new RuntimeException(e); + } + field.setAccessible(true); + try { + return (Unsafe) field.get(null); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + }).get(); + + Field findField(Class clazz, String field) { + try { + return clazz.getDeclaredField(field); + } catch (Exception e) { + Class s = clazz.getSuperclass(); + return s == null ? null : findField(s, field); + } + } + + @SuppressWarnings("unchecked") + void load0(File file) throws Exception { + final ClassLoader mainLoader = DependencyLoader.class.getClassLoader(); + + Field field = findField(mainLoader.getClass(), "ucp"); + if (field == null) + throw new IllegalStateException("Couldn't find URLClassLoader field 'ucp'"); + + long offset = THE_UNSAFE.objectFieldOffset(field); + Object ucp = THE_UNSAFE.getObject(mainLoader, offset); + + field = ucp.getClass().getDeclaredField("path"); + offset = THE_UNSAFE.objectFieldOffset(field); + Collection paths = (Collection) THE_UNSAFE.getObject(ucp, offset); + + try { + field = ucp.getClass().getDeclaredField("unopenedUrls"); + } catch (NoSuchFieldException e) { + field = ucp.getClass().getDeclaredField("urls"); + } + + offset = THE_UNSAFE.objectFieldOffset(field); + Collection urls = (Collection) THE_UNSAFE.getObject(ucp, offset); + + URL url = file.toURI().toURL(); + if (paths.contains(url)) return; + + paths.add(url); + urls.add(url); + } + } +} diff --git a/src/main/java/com/bitaspire/cyberlevels/DoubleLevelSystem.java b/src/main/java/com/bitaspire/cyberlevels/DoubleSystem.java similarity index 97% rename from src/main/java/com/bitaspire/cyberlevels/DoubleLevelSystem.java rename to src/main/java/com/bitaspire/cyberlevels/DoubleSystem.java index b99300b..6f16559 100644 --- a/src/main/java/com/bitaspire/cyberlevels/DoubleLevelSystem.java +++ b/src/main/java/com/bitaspire/cyberlevels/DoubleSystem.java @@ -12,9 +12,9 @@ import java.math.RoundingMode; @Getter -final class DoubleLevelSystem extends BaseSystem { +final class DoubleSystem extends BaseSystem { - DoubleLevelSystem(CyberLevels main) { + DoubleSystem(CyberLevels main) { super(main); setLeaderboardFunction(DoubleLeaderboard::new); } diff --git a/src/main/java/com/bitaspire/cyberlevels/Message.java b/src/main/java/com/bitaspire/cyberlevels/Message.java index a6c7c25..2afb850 100644 --- a/src/main/java/com/bitaspire/cyberlevels/Message.java +++ b/src/main/java/com/bitaspire/cyberlevels/Message.java @@ -1,12 +1,12 @@ package com.bitaspire.cyberlevels; +import com.bitaspire.common.util.ReplaceUtils; import com.bitaspire.cyberlevels.cache.Lang; import com.bitaspire.cyberlevels.user.LevelUser; +import com.bitaspire.takion.message.MessageSender; import lombok.Setter; import lombok.experimental.Accessors; -import me.croabeast.beanslib.key.ValueReplacer; -import me.croabeast.beanslib.message.MessageSender; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import org.bukkit.entity.Player; import java.util.*; @@ -23,7 +23,9 @@ public final class Message { private UnaryOperator operator = null; private final Map placeholders = new LinkedHashMap<>(); - private final MessageSender sender = new MessageSender().setLogger(false).setCaseSensitive(false); + private final MessageSender sender = CyberLevels.instance() + .library().getLoadedSender() + .setLogger(false).setSensitive(false); public Message player(Player player) { sender.setTargets(player); @@ -49,8 +51,7 @@ public Message list(String... messages) { } public Message list(Function> function) { - if (lang != null && function != null) - list(function.apply(lang)); + if (lang != null && function != null) list(function.apply(lang)); return this; } @@ -75,24 +76,17 @@ public Values keys(String... strings) { return values; } - public boolean send(Player player) { - player(player); - return send(); - } - - public boolean send(LevelUser user) { - player(user); - return send(); - } - public boolean send() { messages.removeIf(Objects::isNull); - messages.replaceAll(s -> ValueReplacer.forEach(placeholders, s)); + messages.replaceAll(s -> ReplaceUtils.replaceEach(placeholders, s)); if (operator != null) messages.replaceAll(operator); + messages.replaceAll(s -> s.replace("[actionbar]", "[action-bar]")); - return sender.send(messages); + return !messages.isEmpty() && + !StringUtils.isBlank(messages.get(0)) && + sender.send(messages); } public class Values { diff --git a/src/main/java/com/bitaspire/cyberlevels/UserManagerImpl.java b/src/main/java/com/bitaspire/cyberlevels/UserManagerImpl.java index ec2f958..537118b 100644 --- a/src/main/java/com/bitaspire/cyberlevels/UserManagerImpl.java +++ b/src/main/java/com/bitaspire/cyberlevels/UserManagerImpl.java @@ -8,9 +8,9 @@ import com.bitaspire.cyberlevels.user.UserManager; import lombok.Getter; import lombok.RequiredArgsConstructor; -import me.croabeast.scheduler.GlobalRunnable; -import me.croabeast.scheduler.GlobalTask; -import org.apache.commons.lang.StringUtils; +import com.bitaspire.scheduler.GlobalRunnable; +import com.bitaspire.scheduler.GlobalTask; +import org.apache.commons.lang3.StringUtils; import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; import org.bukkit.entity.Player; @@ -63,7 +63,6 @@ void checkMigration() { if (old == null) return; final Database now = database; - if (now != null && old.getClass().equals(now.getClass())) return; main.logger("&eDetected database type change from " + diff --git a/src/main/java/com/bitaspire/cyberlevels/cache/AntiAbuse.java b/src/main/java/com/bitaspire/cyberlevels/cache/AntiAbuse.java index 1cae6de..e25d48c 100644 --- a/src/main/java/com/bitaspire/cyberlevels/cache/AntiAbuse.java +++ b/src/main/java/com/bitaspire/cyberlevels/cache/AntiAbuse.java @@ -4,7 +4,7 @@ import com.bitaspire.cyberlevels.level.ExpSource; import lombok.Getter; import lombok.experimental.Accessors; -import me.croabeast.file.Configurable; +import com.bitaspire.file.Configurable; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; diff --git a/src/main/java/com/bitaspire/cyberlevels/cache/CLVFile.java b/src/main/java/com/bitaspire/cyberlevels/cache/CLVFile.java index f26a8b5..9d52ead 100644 --- a/src/main/java/com/bitaspire/cyberlevels/cache/CLVFile.java +++ b/src/main/java/com/bitaspire/cyberlevels/cache/CLVFile.java @@ -1,7 +1,7 @@ package com.bitaspire.cyberlevels.cache; import com.bitaspire.cyberlevels.CyberLevels; -import me.croabeast.file.ConfigurableFile; +import com.bitaspire.file.ConfigurableFile; import java.io.IOException; diff --git a/src/main/java/com/bitaspire/cyberlevels/cache/Config.java b/src/main/java/com/bitaspire/cyberlevels/cache/Config.java index 23d94d2..873b8c6 100644 --- a/src/main/java/com/bitaspire/cyberlevels/cache/Config.java +++ b/src/main/java/com/bitaspire/cyberlevels/cache/Config.java @@ -31,6 +31,8 @@ public class Config { private boolean autoSaveEnabled = true; private int autoSaveInterval = 300; + private boolean tabCompleteLoadOfflineUsers = true; + @Accessors(fluent = true) private boolean preventDuplicateRewards = false, stackComboExp = true, @@ -69,6 +71,8 @@ public class Config { autoSaveEnabled = file.get("config.auto-save.enabled", true); autoSaveInterval = file.get("config.auto-save.interval", autoSaveInterval); + tabCompleteLoadOfflineUsers = file.get("config.tab-complete.load-offline-users", tabCompleteLoadOfflineUsers); + multiplierCommands = file.get("config.multiplier.commands", false); multiplierEvents = file.get("config.multiplier.events", true); diff --git a/src/main/java/com/bitaspire/cyberlevels/cache/EarnExp.java b/src/main/java/com/bitaspire/cyberlevels/cache/EarnExp.java index dc30729..ccab550 100644 --- a/src/main/java/com/bitaspire/cyberlevels/cache/EarnExp.java +++ b/src/main/java/com/bitaspire/cyberlevels/cache/EarnExp.java @@ -7,8 +7,8 @@ import lombok.Getter; import lombok.Setter; import lombok.experimental.Accessors; -import me.croabeast.scheduler.GlobalTask; -import org.apache.commons.lang.StringUtils; +import com.bitaspire.scheduler.GlobalTask; +import org.apache.commons.lang3.StringUtils; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.block.Block; @@ -482,11 +482,15 @@ public Map getExpSources() { } public void register() { - events.values().forEach(e -> e.getRegistrable().register()); + events.values().forEach(source -> { + if (source.isActive()) source.getRegistrable().register(); + }); } public void unregister() { - events.values().forEach(e -> e.getRegistrable().unregister()); + events.values().forEach(source -> { + if (source.isActive()) source.getRegistrable().unregister(); + }); } @Getter @@ -554,6 +558,10 @@ public void unregister() { events.put(category, this); } + boolean isActive() { + return enabled; + } + T get(String path, T def) { return file.get("earn-exp." + category + "." + path, def); } diff --git a/src/main/java/com/bitaspire/cyberlevels/cache/Lang.java b/src/main/java/com/bitaspire/cyberlevels/cache/Lang.java index 33ddd32..dafd35c 100644 --- a/src/main/java/com/bitaspire/cyberlevels/cache/Lang.java +++ b/src/main/java/com/bitaspire/cyberlevels/cache/Lang.java @@ -5,7 +5,7 @@ import lombok.AccessLevel; import lombok.Getter; import lombok.experimental.Accessors; -import me.croabeast.file.Configurable; +import com.bitaspire.file.Configurable; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Player; diff --git a/src/main/java/com/bitaspire/cyberlevels/cache/Levels.java b/src/main/java/com/bitaspire/cyberlevels/cache/Levels.java index bbe710d..270919c 100644 --- a/src/main/java/com/bitaspire/cyberlevels/cache/Levels.java +++ b/src/main/java/com/bitaspire/cyberlevels/cache/Levels.java @@ -1,10 +1,8 @@ package com.bitaspire.cyberlevels.cache; import com.bitaspire.cyberlevels.CyberLevels; -import lombok.AccessLevel; import lombok.Getter; -import org.apache.commons.lang.StringUtils; -import org.jetbrains.annotations.Nullable; +import org.apache.commons.lang3.StringUtils; import java.io.IOException; import java.util.HashMap; diff --git a/src/main/java/com/bitaspire/cyberlevels/cache/Rewards.java b/src/main/java/com/bitaspire/cyberlevels/cache/Rewards.java index 684be9a..f6fa98f 100644 --- a/src/main/java/com/bitaspire/cyberlevels/cache/Rewards.java +++ b/src/main/java/com/bitaspire/cyberlevels/cache/Rewards.java @@ -4,9 +4,8 @@ import com.bitaspire.cyberlevels.level.Reward; import lombok.Getter; import me.clip.placeholderapi.PlaceholderAPI; -import me.croabeast.beanslib.message.MessageSender; -import me.croabeast.file.Configurable; -import org.apache.commons.lang.StringUtils; +import com.bitaspire.file.Configurable; +import org.apache.commons.lang3.StringUtils; import org.bukkit.Bukkit; import org.bukkit.Sound; import org.bukkit.configuration.ConfigurationSection; @@ -112,7 +111,7 @@ public void executeCommands(Player player) { } void typeMessage(Player player, String message) { - new MessageSender(player).setLogger(false).send(message); + main.createSender(player).setLogger(false).send(message); } public void sendMessages(Player player) { diff --git a/src/main/java/com/bitaspire/cyberlevels/command/CLVCommand.java b/src/main/java/com/bitaspire/cyberlevels/command/CLVCommand.java index 7bfdd1f..a9fdf7f 100644 --- a/src/main/java/com/bitaspire/cyberlevels/command/CLVCommand.java +++ b/src/main/java/com/bitaspire/cyberlevels/command/CLVCommand.java @@ -5,7 +5,6 @@ import com.bitaspire.cyberlevels.level.LevelSystem; import com.bitaspire.cyberlevels.user.LevelUser; import lombok.Getter; -import me.croabeast.beanslib.message.MessageSender; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; @@ -42,7 +41,7 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @N if (args.length == 1) { switch (sub) { case "about": - return isRestricted(player, "player.about") || new MessageSender(player).send( + return isRestricted(player, "player.about") || main.createSender(player).send( " &d&lCyber&f&lLevels &fv" + main.getDescription().getVersion() + " &7(&7&nhttps://bit.ly/2YSlqYq&7).", " &fDeveloped by &d" + main.getAuthors() + "&f.", " A leveling system plugin with MySQL support and custom events." diff --git a/src/main/java/com/bitaspire/cyberlevels/command/CLVTabComplete.java b/src/main/java/com/bitaspire/cyberlevels/command/CLVTabComplete.java index ecf45ad..750687a 100644 --- a/src/main/java/com/bitaspire/cyberlevels/command/CLVTabComplete.java +++ b/src/main/java/com/bitaspire/cyberlevels/command/CLVTabComplete.java @@ -1,5 +1,7 @@ package com.bitaspire.cyberlevels.command; +import com.bitaspire.cyberlevels.CyberLevels; +import lombok.RequiredArgsConstructor; import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; import org.bukkit.command.Command; @@ -11,6 +13,7 @@ import java.util.*; +@RequiredArgsConstructor public class CLVTabComplete implements TabCompleter { private static final String PLAYER_PREFIX = "CyberLevels.player."; @@ -37,6 +40,8 @@ public class CLVTabComplete implements TabCompleter { COMMAND_PERMISSIONS.put("removeLevel", ADMIN_PREFIX + "levels.remove"); } + private final CyberLevels main; + @Override public List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, String[] args) { if (!(sender instanceof Player)) return Collections.emptyList(); @@ -86,9 +91,15 @@ public List onTabComplete(@NotNull CommandSender sender, @NotNull Comman private List getPlayerNames() { List players = new ArrayList<>(); - for (OfflinePlayer p : Bukkit.getOfflinePlayers()) { - String name = p.getName(); - if (name != null) players.add(name); + if (main.cache().config().isTabCompleteLoadOfflineUsers()) { + for (OfflinePlayer p : Bukkit.getOfflinePlayers()) { + String name = p.getName(); + if (name != null) players.add(name); + } + } else { + for (Player player : Bukkit.getOnlinePlayers()) { + players.add(player.getName()); + } } return players; diff --git a/src/main/java/com/bitaspire/cyberlevels/hook/HookManager.java b/src/main/java/com/bitaspire/cyberlevels/hook/HookManager.java index 97b8158..941cc24 100644 --- a/src/main/java/com/bitaspire/cyberlevels/hook/HookManager.java +++ b/src/main/java/com/bitaspire/cyberlevels/hook/HookManager.java @@ -1,9 +1,9 @@ package com.bitaspire.cyberlevels.hook; +import com.bitaspire.common.MetricsLoader; import com.bitaspire.cyberlevels.CyberLevels; import com.bitaspire.cyberlevels.level.ExpSource; import com.bitaspire.cyberlevels.user.LevelUser; -import net.zerotoil.dev.cybercore.addons.Metrics; import org.bukkit.entity.Player; import java.util.HashSet; @@ -18,7 +18,9 @@ public HookManager(CyberLevels main) { (this.main = main).logger("&dLoading plugin hooks..."); long startTime = System.currentTimeMillis(); - new Metrics(main, 13782); + + System.setProperty("bstats.relocatecheck", "false"); + MetricsLoader.initialize(main, 13782); if (main.isEnabled("PlaceholderAPI")) { final long l = System.currentTimeMillis(); diff --git a/src/main/java/com/bitaspire/cyberlevels/hook/PlaceholderAPI.java b/src/main/java/com/bitaspire/cyberlevels/hook/PlaceholderAPI.java index 08c80e3..571a270 100644 --- a/src/main/java/com/bitaspire/cyberlevels/hook/PlaceholderAPI.java +++ b/src/main/java/com/bitaspire/cyberlevels/hook/PlaceholderAPI.java @@ -73,8 +73,7 @@ private String getLeaderboard(OfflinePlayer player, String type, String position } } - return main.core().textSettings() - .colorize(player instanceof Player ? (Player) player : null, value); + return main.library().colorize(player instanceof Player ? (Player) player : null, value); } @Override @@ -118,7 +117,7 @@ public String onRequest(OfflinePlayer player, @NotNull String identifier) { return system.formatNumber(user.getRemainingExp()); case "player_exp_progress_bar": - return main.core().textSettings().colorize(user.getProgressBar()); + return main.library().colorize(user.getProgressBar()); case "player_exp_percent": return user.getPercent(); diff --git a/src/main/java/com/bitaspire/cyberlevels/level/Formula.java b/src/main/java/com/bitaspire/cyberlevels/level/Formula.java index 75a95b1..b3eadc1 100644 --- a/src/main/java/com/bitaspire/cyberlevels/level/Formula.java +++ b/src/main/java/com/bitaspire/cyberlevels/level/Formula.java @@ -1,6 +1,5 @@ package com.bitaspire.cyberlevels.level; -import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import java.util.UUID; diff --git a/src/main/java/com/bitaspire/cyberlevels/level/LevelSystem.java b/src/main/java/com/bitaspire/cyberlevels/level/LevelSystem.java index 8598f16..1852325 100644 --- a/src/main/java/com/bitaspire/cyberlevels/level/LevelSystem.java +++ b/src/main/java/com/bitaspire/cyberlevels/level/LevelSystem.java @@ -5,7 +5,6 @@ import java.util.List; import java.util.Map; -import java.util.Set; import java.util.UUID; /** diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 4446292..e7fb94d 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -47,6 +47,11 @@ config: # How often (in seconds)? interval: 300 + tab-complete: + # Load offline player data when suggesting names? + # If false, will suggest online users only. + load-offline-users: true + # Should adding levels give rewards? add-level-reward: false diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index c88f376..7b5646d 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -3,11 +3,10 @@ version: '${version}' main: com.bitaspire.cyberlevels.CyberLevels api-version: 1.13 prefix: CLV -softdepend: [ PlaceholderAPI, Vault, CyberWorldReset, RivalHarvesterHoes, RivalPickaxes ] authors: [ Kihsomray, CroaBeast ] description: A leveling system plugin -website: bitaspire.com folia-supported: true +website: https://wiki.bitaspire.com/en/docs/cyberlevels commands: clv: @@ -20,10 +19,17 @@ commands: - lvl - lvls +softdepend: + - PlaceholderAPI + - Vault + - CyberWorldReset + - RivalHarvesterHoes + - RivalPickaxes + libraries: - ch.obermuhlner:big-math:2.3.2 - - com.zaxxer:HikariCP:3.4.5 - - mysql:mysql-connector-java:8.0.21 - - org.xerial:sqlite-jdbc:3.36.0.3 - - org.postgresql:postgresql:42.7.7 + - com.zaxxer:HikariCP:7.0.2 + - com.mysql:mysql-connector-j:9.5.0 + - org.xerial:sqlite-jdbc:3.51.1.0 + - org.postgresql:postgresql:42.7.8 - org.apache.commons:commons-lang3:3.18.0 \ No newline at end of file From 9c898f0637eb7a3f7245321b659423b19ae2c979 Mon Sep 17 00:00:00 2001 From: Gustavo Paredes <66433837+CroaBeast@users.noreply.github.com> Date: Sun, 1 Feb 2026 15:26:07 -0500 Subject: [PATCH 08/12] 1.1.2 - Minor fixes --- build.gradle | 2 +- src/main/java/com/bitaspire/cyberlevels/cache/EarnExp.java | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index 6373a5c..e1b40ca 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ plugins { } group = 'com.bitaspire' -version = '1.1.1' +version = '1.1.2' repositories { mavenLocal() diff --git a/src/main/java/com/bitaspire/cyberlevels/cache/EarnExp.java b/src/main/java/com/bitaspire/cyberlevels/cache/EarnExp.java index ccab550..4382ce0 100644 --- a/src/main/java/com/bitaspire/cyberlevels/cache/EarnExp.java +++ b/src/main/java/com/bitaspire/cyberlevels/cache/EarnExp.java @@ -88,12 +88,12 @@ public void register() { if (!source.isEnabled() && !source.useSpecifics()) return; + long delay = 20L * Math.max(1, source.getInterval()); task = main.scheduler().runTaskTimer( () -> { for (Player p : Bukkit.getOnlinePlayers()) sendPermissionExp(p, source); - }, 0L, - 20L * Math.max(1, source.getInterval())); + }, delay, delay); } @Override @@ -559,7 +559,7 @@ public void unregister() { } boolean isActive() { - return enabled; + return isEnabled() || useSpecifics(); } T get(String path, T def) { From 62413b00ba66063b5062a52a5c4d2d963f255aeb Mon Sep 17 00:00:00 2001 From: Adam Klement Date: Fri, 6 Mar 2026 15:15:02 +0100 Subject: [PATCH 09/12] Refactor user loading and leaderboard update logic in UserManagerImpl; enhance PlaceholderAPI with additional experience-related identifiers; remove unused register call in Listeners. --- .../cyberlevels/UserManagerImpl.java | 38 +++++++++++++------ .../cyberlevels/hook/PlaceholderAPI.java | 10 ++++- .../cyberlevels/listener/Listeners.java | 1 - 3 files changed, 36 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/bitaspire/cyberlevels/UserManagerImpl.java b/src/main/java/com/bitaspire/cyberlevels/UserManagerImpl.java index 537118b..0460482 100644 --- a/src/main/java/com/bitaspire/cyberlevels/UserManagerImpl.java +++ b/src/main/java/com/bitaspire/cyberlevels/UserManagerImpl.java @@ -231,13 +231,7 @@ private void loadUserAsync(OfflinePlayer offline, boolean updateLeaderboard) { LevelUser user = users.get(uuid); if (user != null && player != null && !user.isOnline()) { - LevelUser newUser = system.createUser(uuid); - - newUser.setLevel(user.getLevel(), false); - newUser.setExp(user.getExp() + "", true, false, false); - setRewardLevel(newUser, getRewardLevel(user)); - - users.put(uuid, newUser); + users.put(uuid, toOnlineUser(uuid, user)); if (updateLeaderboard) scheduleLeaderboardUpdate(); return; } @@ -249,17 +243,39 @@ private void loadUserAsync(OfflinePlayer offline, boolean updateLeaderboard) { } private LevelUser loadUser(OfflinePlayer offline) { + UUID uuid = offline.getUniqueId(); Player player = (offline instanceof Player) ? (Player) offline : null; - LoadResult result = loadUserData(offline.getUniqueId()); - finishUserLoad(offline.getUniqueId(), player, result, true); - return result.user; + LoadResult result = loadUserData(uuid); + finishUserLoad(uuid, player, result, true); + return users.getOrDefault(uuid, result.user); + } + + private LevelUser toOnlineUser(UUID uuid, LevelUser source) { + LevelUser online = system.createUser(uuid); + online.setLevel(source.getLevel(), false); + online.setExp(source.getExp() + "", true, false, false); + setRewardLevel(online, getRewardLevel(source)); + return online; } private void finishUserLoad(UUID uuid, Player player, LoadResult result, boolean updateLeaderboard) { if (StringUtils.isNotBlank(result.migrationMessage)) main.logger("Migrated " + (player != null ? player.getName() : uuid) + result.migrationMessage); - users.put(uuid, result.user); + LevelUser existing = users.get(uuid); + if (existing != null) { + if (player != null && !existing.isOnline()) + users.put(uuid, toOnlineUser(uuid, existing)); + + if (updateLeaderboard) scheduleLeaderboardUpdate(); + return; + } + + LevelUser loaded = result.user; + if (player != null && !loaded.isOnline()) + loaded = toOnlineUser(uuid, loaded); + + users.put(uuid, loaded); if (updateLeaderboard) scheduleLeaderboardUpdate(); } diff --git a/src/main/java/com/bitaspire/cyberlevels/hook/PlaceholderAPI.java b/src/main/java/com/bitaspire/cyberlevels/hook/PlaceholderAPI.java index 571a270..0a306e6 100644 --- a/src/main/java/com/bitaspire/cyberlevels/hook/PlaceholderAPI.java +++ b/src/main/java/com/bitaspire/cyberlevels/hook/PlaceholderAPI.java @@ -54,7 +54,8 @@ private String getLeaderboard(OfflinePlayer player, String type, String position LevelUser user = system.getLeaderboard().getTopPlayer(position); Lang.LeaderboardKeys keys = main.cache().lang().leaderboardKeys(); - String value = user == null ? keys.getLoadingName() : keys.getNoPlayerName(); + String value = system.getLeaderboard().isUpdating() ? + keys.getLoadingName() : keys.getNoPlayerName(); if (user != null) { switch (type.toLowerCase()) { @@ -85,6 +86,7 @@ public String onRequest(OfflinePlayer player, @NotNull String identifier) { switch (identifier.toLowerCase()) { case "level_maximum": return system.getMaxLevel() + ""; case "exp_minimum": return system.getStartExp() + ""; + case "experience_minimum": return system.getStartExp() + ""; case "level_minimum": return system.getStartLevel() + ""; } @@ -105,21 +107,27 @@ public String onRequest(OfflinePlayer player, @NotNull String identifier) { return String.valueOf(user.getLevel()); case "player_level_next": + case "player_next_level": return (Math.min(user.getLevel() + 1, system.getMaxLevel())) + ""; case "player_exp": + case "player_experience": return system.formatNumber(user.getExp()); case "player_exp_required": + case "player_experience_required": return system.formatNumber(user.getRequiredExp()); case "player_exp_remaining": + case "player_experience_remaining": return system.formatNumber(user.getRemainingExp()); case "player_exp_progress_bar": + case "player_experience_progress_bar": return main.library().colorize(user.getProgressBar()); case "player_exp_percent": + case "player_experience_percent": return user.getPercent(); } diff --git a/src/main/java/com/bitaspire/cyberlevels/listener/Listeners.java b/src/main/java/com/bitaspire/cyberlevels/listener/Listeners.java index 6081499..829e644 100644 --- a/src/main/java/com/bitaspire/cyberlevels/listener/Listeners.java +++ b/src/main/java/com/bitaspire/cyberlevels/listener/Listeners.java @@ -49,7 +49,6 @@ private void onPistonRetract(BlockPistonRetractEvent event) { } }; - register(); } public void register() { From 12c31cace0a18cc3b76ae99f20aa0d89571d1fc6 Mon Sep 17 00:00:00 2001 From: Adam Klement Date: Fri, 6 Mar 2026 15:23:07 +0100 Subject: [PATCH 10/12] Fix leaderboard access by using a snapshot of top players to prevent index errors --- src/main/java/com/bitaspire/cyberlevels/BaseSystem.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/bitaspire/cyberlevels/BaseSystem.java b/src/main/java/com/bitaspire/cyberlevels/BaseSystem.java index b0dc0fe..575a8a3 100644 --- a/src/main/java/com/bitaspire/cyberlevels/BaseSystem.java +++ b/src/main/java/com/bitaspire/cyberlevels/BaseSystem.java @@ -325,9 +325,10 @@ public LevelUser getTopPlayer(int position) { if (updating || position < 1 || position > 10) return null; int index = position - 1; - if (index >= topTenPlayers.size()) return null; + List> snapshot = new ArrayList<>(topTenPlayers); + if (index >= snapshot.size()) return null; - return userManager.getUser(topTenPlayers.get(index).getUuid()); + return userManager.getUser(snapshot.get(index).getUuid()); } int check(UUID uuid) { From 14a5a764a89852ad756acf4b555b31ae2f9585d5 Mon Sep 17 00:00:00 2001 From: Adam Klement Date: Sun, 8 Mar 2026 21:15:24 +0100 Subject: [PATCH 11/12] chore: Move from async to sync saving on reload --- .../bitaspire/cyberlevels/CyberLevels.java | 51 +- .../cyberlevels/DatabaseFactory.java | 1088 +++++++++++++---- .../cyberlevels/UserManagerImpl.java | 277 +++-- .../cyberlevels/command/CLVCommand.java | 331 +++-- 4 files changed, 1322 insertions(+), 425 deletions(-) diff --git a/src/main/java/com/bitaspire/cyberlevels/CyberLevels.java b/src/main/java/com/bitaspire/cyberlevels/CyberLevels.java index c63293b..62ad2a9 100644 --- a/src/main/java/com/bitaspire/cyberlevels/CyberLevels.java +++ b/src/main/java/com/bitaspire/cyberlevels/CyberLevels.java @@ -11,9 +11,9 @@ import com.bitaspire.cyberlevels.listener.Listeners; import com.bitaspire.cyberlevels.user.Database; import com.bitaspire.cyberlevels.user.UserManager; +import com.bitaspire.scheduler.GlobalScheduler; import com.bitaspire.takion.TakionLib; import com.bitaspire.takion.message.MessageSender; -import com.bitaspire.scheduler.GlobalScheduler; import lombok.AccessLevel; import lombok.Getter; import lombok.experimental.Accessors; @@ -41,6 +41,7 @@ public final class CyberLevels extends JavaPlugin { LevelSystem levelSystem; UserManager userManager; + @Getter(AccessLevel.NONE) Database database; @@ -67,14 +68,14 @@ public void onEnable() { CoreSettings settings = core.getSettings(); settings.setBootColor('d'); settings.setBootLogo( - "&d╭━━━╮&7╱╱╱&d╭╮&7╱╱╱╱╱╱&d╭╮&7╱╱╱╱╱╱╱╱╱╱╱&d╭╮", - "&d┃╭━╮┃&7╱╱╱&d┃┃&7╱╱╱╱╱╱&d┃┃&7╱╱╱╱╱╱╱╱╱╱╱&d┃┃", - "&d┃┃&7╱&d╰╋╮&7╱&d╭┫╰━┳━━┳━┫┃&7╱╱&d╭━━┳╮╭┳━━┫┃╭━━╮", - "&d┃┃&7╱&d╭┫┃&7╱&d┃┃╭╮┃┃━┫╭┫┃&7╱&d╭┫┃━┫╰╯┃┃━┫┃┃━━┫", - "&d┃╰━╯┃╰━╯┃╰╯┃┃━┫┃┃╰━╯┃┃━╋╮╭┫┃━┫╰╋━━┃", - "&d╰━━━┻━╮╭┻━━┻━━┻╯╰━━━┻━━╯╰╯╰━━┻━┻━━╯", - "&7╱╱╱╱&d╭━╯┃ &7Authors: &f" + getAuthors(), - "&7╱╱╱╱&d╰━━╯ &7Version: &f" + this.getDescription().getVersion() + "&d╭━━━╮&7╱╱╱&d╭╮&7╱╱╱╱╱╱&d╭╮&7╱╱╱╱╱╱╱╱╱╱╱&d╭╮", + "&d┃╭━╮┃&7╱╱╱&d┃┃&7╱╱╱╱╱╱&d┃┃&7╱╱╱╱╱╱╱╱╱╱╱&d┃┃", + "&d┃┃&7╱&d╰╋╮&7╱&d╭┫╰━┳━━┳━┫┃&7╱╱&d╭━━┳╮╭┳━━┫┃╭━━╮", + "&d┃┃&7╱&d╭┫┃&7╱&d┃┃╭╮┃┃━┫╭┫┃&7╱&d╭┫┃━┫╰╯┃┃━┫┃┃━━┫", + "&d┃╰━╯┃╰━╯┃╰╯┃┃━┫┃┃╰━╯┃┃━╋╮╭┫┃━┫╰╋━━┃", + "&d╰━━━┻━╮╭┻━━┻━━┻╯╰━━━┻━━╯╰╯╰━━┻━┻━━╯", + "&7╱╱╱╱&d╭━╯┃ &7Authors: &f" + getAuthors(), + "&7╱╱╱╱&d╰━━╯ &7Version: &f" + this.getDescription().getVersion() ); core.loadStart(false); @@ -101,18 +102,20 @@ public void reloadPlugin() { cache = new Cache(this); long start = System.currentTimeMillis(); - BaseSystem system = !cache.config().useBigDecimalSystem() ? - new DoubleSystem(this) : - new BigDecimalSystem(this); + BaseSystem system = !cache.config().useBigDecimalSystem() + ? new DoubleSystem(this) + : new BigDecimalSystem(this); logger("&dChecking level system type..."); levelSystem = system; logger( - "&7Loaded &e" + system.getClass().getSimpleName() + - "&7 in &a" + - (System.currentTimeMillis() - start) + - "ms&7.", "" + "&7Loaded &e" + + system.getClass().getSimpleName() + + "&7 in &a" + + (System.currentTimeMillis() - start) + + "ms&7.", + "" ); UserManagerImpl manager = new UserManagerImpl<>(this, system); @@ -142,18 +145,28 @@ public void onDisable() { hookManager.unregister(); - userManager.saveOnlinePlayers(true); + if (userManager instanceof UserManagerImpl) ( + (UserManagerImpl) userManager + ).saveOnlinePlayersSync(true); + else userManager.saveOnlinePlayers(true); + userManager.cancelAutoSave(); if (database != null) { - database.disconnect(); + if (database instanceof DatabaseFactory.DatabaseImpl) ( + (DatabaseFactory.DatabaseImpl) database + ).disconnectSync(); + else database.disconnect(); if (isEnabled()) logger(""); } listeners.unregister(); } public String getAuthors() { - return this.getDescription().getAuthors().toString().replaceAll("[\\[\\]]", ""); + return this.getDescription() + .getAuthors() + .toString() + .replaceAll("[\\[\\]]", ""); } public double serverVersion() { diff --git a/src/main/java/com/bitaspire/cyberlevels/DatabaseFactory.java b/src/main/java/com/bitaspire/cyberlevels/DatabaseFactory.java index a9909d3..e33eadf 100644 --- a/src/main/java/com/bitaspire/cyberlevels/DatabaseFactory.java +++ b/src/main/java/com/bitaspire/cyberlevels/DatabaseFactory.java @@ -5,18 +5,19 @@ import com.bitaspire.cyberlevels.user.LevelUser; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; -import lombok.experimental.UtilityClass; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; - import java.sql.*; import java.util.*; import java.util.concurrent.CompletableFuture; +import lombok.experimental.UtilityClass; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; @UtilityClass class DatabaseFactory { - abstract static class DatabaseImpl implements Database { + abstract static class DatabaseImpl< + N extends Number + > implements Database { final CyberLevels main; final BaseSystem system; @@ -31,6 +32,7 @@ abstract static class DatabaseImpl implements Database { } abstract String getTable(); + abstract HikariConfig createConfig(); String qCol(String name) { @@ -41,16 +43,37 @@ String qTab(String name) { return name; } - abstract PreparedStatement prepareUpsert(Connection c, UUID uuid, long level, String exp, long updatedAt) throws SQLException; - abstract PreparedStatement prepareUpsertMeta(Connection c, UUID uuid, long highestRewarded, long updatedAt) throws SQLException; + abstract PreparedStatement prepareUpsert( + Connection c, + UUID uuid, + long level, + String exp, + long updatedAt + ) throws SQLException; + + abstract PreparedStatement prepareUpsertMeta( + Connection c, + UUID uuid, + long highestRewarded, + long updatedAt + ) throws SQLException; + + abstract Set getExistingColumns(Connection conn) + throws SQLException; - abstract Set getExistingColumns(Connection conn) throws SQLException; - abstract boolean isExpColumnTextual(Connection conn) throws SQLException; - abstract boolean hasPrimaryKeyOnUuid(Connection conn) throws SQLException; + abstract boolean isExpColumnTextual(Connection conn) + throws SQLException; + + abstract boolean hasPrimaryKeyOnUuid(Connection conn) + throws SQLException; abstract void createTargetTable(Connection conn) throws SQLException; - abstract void dropTableIfExists(Connection conn, String table) throws SQLException; - abstract void renameTable(Connection conn, String from, String to) throws SQLException; + + abstract void dropTableIfExists(Connection conn, String table) + throws SQLException; + + abstract void renameTable(Connection conn, String from, String to) + throws SQLException; String metaTable() { return getTable() + "_meta"; @@ -61,19 +84,37 @@ void ensureMetaSchema(Connection conn) throws SQLException { String idType = sqlite ? "TEXT" : "VARCHAR(36)"; String longType = sqlite ? "INTEGER" : "BIGINT"; - String sql = "CREATE TABLE IF NOT EXISTS " + qTab(metaTable()) + " (" + - qCol("UUID") + " " + idType + " PRIMARY KEY," + - qCol("HIGHEST_REWARDED") + " " + longType + "," + - qCol("UPDATED_AT") + " " + longType + " NOT NULL DEFAULT 0" + - ")"; + String sql = + "CREATE TABLE IF NOT EXISTS " + + qTab(metaTable()) + + " (" + + qCol("UUID") + + " " + + idType + + " PRIMARY KEY," + + qCol("HIGHEST_REWARDED") + + " " + + longType + + "," + + qCol("UPDATED_AT") + + " " + + longType + + " NOT NULL DEFAULT 0" + + ")"; try (Statement st = conn.createStatement()) { st.executeUpdate(sql); } } long readHighestRewarded(Connection conn, UUID uuid) { - String sql = "SELECT " + qCol("HIGHEST_REWARDED") + " FROM " + qTab(metaTable()) + - " WHERE " + qCol("UUID") + "=?"; + String sql = + "SELECT " + + qCol("HIGHEST_REWARDED") + + " FROM " + + qTab(metaTable()) + + " WHERE " + + qCol("UUID") + + "=?"; try (PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, uuid.toString()); try (ResultSet rs = ps.executeQuery()) { @@ -95,21 +136,31 @@ public void connect() { main.logger("&dAttempting to connect to " + type + "..."); long l = System.currentTimeMillis(); - main.scheduler().runTaskAsynchronously(() -> { - try { - dataSource = new HikariDataSource(createConfig()); - - try (Connection conn = dataSource.getConnection()) { - ensureTargetSchema(conn); - ensureMetaSchema(conn); - } + try { + dataSource = new HikariDataSource(createConfig()); - main.logger("&7Connected to &e" + type + "&7 successfully in &a" + (System.currentTimeMillis() - l) + "ms&7."); - } catch (Exception e) { - main.logger("&cThere was an issue connecting to " + type + " Database."); - e.printStackTrace(); + try (Connection conn = dataSource.getConnection()) { + ensureTargetSchema(conn); + ensureMetaSchema(conn); } - }); + + main.logger( + "&7Connected to &e" + + type + + "&7 successfully in &a" + + (System.currentTimeMillis() - l) + + "ms&7." + ); + } catch (Exception e) { + main.logger( + "&cThere was an issue connecting to " + type + " Database." + ); + try { + if (dataSource != null) dataSource.close(); + } catch (Exception ignored) {} + dataSource = null; + e.printStackTrace(); + } } @Override @@ -122,9 +173,19 @@ public void disconnect() { Runnable close = () -> { try { dataSource.close(); - main.logger("&7Disconnected from &e" + type + "&7 successfully in &a" + (System.currentTimeMillis() - l) + "ms&7."); + main.logger( + "&7Disconnected from &e" + + type + + "&7 successfully in &a" + + (System.currentTimeMillis() - l) + + "ms&7." + ); } catch (Exception e) { - main.logger("&cThere was an issue disconnecting from " + type + " Database."); + main.logger( + "&cThere was an issue disconnecting from " + + type + + " Database." + ); e.printStackTrace(); } finally { dataSource = null; @@ -138,6 +199,33 @@ public void disconnect() { } } + void disconnectSync() { + if (!isConnected()) return; + + main.logger("&dAttempting to disconnect from " + type + "..."); + long l = System.currentTimeMillis(); + + try { + dataSource.close(); + main.logger( + "&7Disconnected from &e" + + type + + "&7 successfully in &a" + + (System.currentTimeMillis() - l) + + "ms&7." + ); + } catch (Exception e) { + main.logger( + "&cThere was an issue disconnecting from " + + type + + " Database." + ); + e.printStackTrace(); + } finally { + dataSource = null; + } + } + void ensureTargetSchema(Connection conn) throws SQLException { if (!tableExists(conn, getTable())) { createTargetTable(conn); @@ -145,19 +233,27 @@ void ensureTargetSchema(Connection conn) throws SQLException { } Set cols = getExistingColumns(conn); - boolean needMigration = !cols.contains("UUID") || !cols.contains("LEVEL") || !cols.contains("EXP"); - - if (cols.contains("MAX_LEVEL") || - !cols.contains("UPDATED_AT") || - !isExpColumnTextual(conn) || - !hasPrimaryKeyOnUuid(conn)) - needMigration = true; + boolean needMigration = + !cols.contains("UUID") || + !cols.contains("LEVEL") || + !cols.contains("EXP"); + + if ( + cols.contains("MAX_LEVEL") || + !cols.contains("UPDATED_AT") || + !isExpColumnTextual(conn) || + !hasPrimaryKeyOnUuid(conn) + ) needMigration = true; if (needMigration) migrateTableToCanonical(conn); } boolean tableExists(Connection conn, String table) throws SQLException { - try (ResultSet rs = conn.getMetaData().getTables(null, null, table, null)) { + try ( + ResultSet rs = conn + .getMetaData() + .getTables(null, null, table, null) + ) { return rs.next(); } } @@ -166,7 +262,15 @@ void migrateTableToCanonical(Connection conn) throws SQLException { String table = getTable(); String backup = table + "_backup_" + System.currentTimeMillis(); - main.logger("&e" + type + ": migrating table '" + table + "' to canonical schema (backup: " + backup + ")..."); + main.logger( + "&e" + + type + + ": migrating table '" + + table + + "' to canonical schema (backup: " + + backup + + ")..." + ); conn.setAutoCommit(false); try (Statement ignored = conn.createStatement()) { @@ -177,9 +281,10 @@ void migrateTableToCanonical(Connection conn) throws SQLException { Map bestByUuid = new LinkedHashMap<>(); String selectSQL = "SELECT * FROM " + qTab(backup); - try (PreparedStatement ps = conn.prepareStatement(selectSQL); - ResultSet rs = ps.executeQuery()) { - + try ( + PreparedStatement ps = conn.prepareStatement(selectSQL); + ResultSet rs = ps.executeQuery() + ) { while (rs.next()) { String uuidStr = safeGet(rs, "UUID"); if (uuidStr == null || uuidStr.isEmpty()) continue; @@ -204,23 +309,46 @@ void migrateTableToCanonical(Connection conn) throws SQLException { long updated = safeGetLong(rs, "UPDATED_AT", 0L); long maxLevel = safeGetLong(rs, "MAX_LEVEL", -1L); - Row row = new Row(uuid, level, expStr, updated, maxLevel); + Row row = new Row( + uuid, + level, + expStr, + updated, + maxLevel + ); Row prev = bestByUuid.get(uuid); - if (prev == null || - row.updatedAt > prev.updatedAt || - (row.updatedAt == prev.updatedAt && row.level > prev.level)) { - if (prev != null) - row.maxLevel = Math.max(row.maxLevel, prev.maxLevel); + if ( + prev == null || + row.updatedAt > prev.updatedAt || + (row.updatedAt == prev.updatedAt && + row.level > prev.level) + ) { + if (prev != null) row.maxLevel = Math.max( + row.maxLevel, + prev.maxLevel + ); bestByUuid.put(uuid, row); } else { - prev.maxLevel = Math.max(prev.maxLevel, row.maxLevel); + prev.maxLevel = Math.max( + prev.maxLevel, + row.maxLevel + ); } } } - String insertSQL = "INSERT INTO " + qTab(table) + " (" + - qCol("UUID") + "," + qCol("LEVEL") + "," + qCol("EXP") + "," + qCol("UPDATED_AT") + - ") VALUES (?,?,?,?)"; + String insertSQL = + "INSERT INTO " + + qTab(table) + + " (" + + qCol("UUID") + + "," + + qCol("LEVEL") + + "," + + qCol("EXP") + + "," + + qCol("UPDATED_AT") + + ") VALUES (?,?,?,?)"; try (PreparedStatement ins = conn.prepareStatement(insertSQL)) { for (Row r : bestByUuid.values()) { @@ -235,7 +363,14 @@ void migrateTableToCanonical(Connection conn) throws SQLException { for (Row r : bestByUuid.values()) { long highest = (r.maxLevel >= 0 ? r.maxLevel : r.level); - try (PreparedStatement up = prepareUpsertMeta(conn, r.uuid, highest, r.updatedAt)) { + try ( + PreparedStatement up = prepareUpsertMeta( + conn, + r.uuid, + highest, + r.updatedAt + ) + ) { up.executeUpdate(); } } @@ -243,15 +378,32 @@ void migrateTableToCanonical(Connection conn) throws SQLException { dropTableIfExists(conn, backup); conn.commit(); - main.logger("&a" + type + ": migration for '" + table + "' completed (" + bestByUuid.size() + " rows)."); + main.logger( + "&a" + + type + + ": migration for '" + + table + + "' completed (" + + bestByUuid.size() + + " rows)." + ); } catch (SQLException ex) { conn.rollback(); - main.logger("&c" + type + ": migration failed, attempting rollback restore..."); + main.logger( + "&c" + + type + + ": migration failed, attempting rollback restore..." + ); try { dropTableIfExists(conn, getTable()); renameTable(conn, backup, getTable()); } catch (SQLException restoreEx) { - main.logger("&c" + type + ": failed to restore backup: " + restoreEx.getMessage()); + main.logger( + "&c" + + type + + ": failed to restore backup: " + + restoreEx.getMessage() + ); } throw ex; } finally { @@ -260,13 +412,20 @@ void migrateTableToCanonical(Connection conn) throws SQLException { } static class Row { + final UUID uuid; final long level; final String exp; final long updatedAt; long maxLevel; - Row(UUID uuid, long level, String exp, long updatedAt, long maxLevel) { + Row( + UUID uuid, + long level, + String exp, + long updatedAt, + long maxLevel + ) { this.uuid = uuid; this.level = level; this.exp = exp; @@ -324,20 +483,32 @@ public boolean isUserLoaded(LevelUser user) { if (!isConnected()) return false; CompletableFuture future = new CompletableFuture<>(); - main.scheduler().runTaskAsynchronously(() -> { - try (Connection connection = dataSource.getConnection(); - PreparedStatement statement = connection.prepareStatement( - "SELECT 1 FROM " + qTab(getTable()) + " WHERE " + qCol("UUID") + "=?")) { - statement.setString(1, user.getUuid().toString()); - try (ResultSet rs = statement.executeQuery()) { - future.complete(rs.next()); + main + .scheduler() + .runTaskAsynchronously(() -> { + try ( + Connection connection = dataSource.getConnection(); + PreparedStatement statement = + connection.prepareStatement( + "SELECT 1 FROM " + + qTab(getTable()) + + " WHERE " + + qCol("UUID") + + "=?" + ) + ) { + statement.setString(1, user.getUuid().toString()); + try (ResultSet rs = statement.executeQuery()) { + future.complete(rs.next()); + } + } catch (Exception e) { + main.logger( + "&cFailed to check if user exists in table." + ); + e.printStackTrace(); + future.complete(false); } - } catch (Exception e) { - main.logger("&cFailed to check if user exists in table."); - e.printStackTrace(); - future.complete(false); - } - }); + }); try { return future.get(); @@ -348,13 +519,19 @@ public boolean isUserLoaded(LevelUser user) { void setRewardLevel(LevelUser user, long level) { try { - user.getClass().getMethod("setHighestRewardedLevel", long.class).invoke(user, level); + user + .getClass() + .getMethod("setHighestRewardedLevel", long.class) + .invoke(user, level); } catch (Exception ignored) {} } long getRewardLevel(LevelUser user) { try { - return (long) user.getClass().getMethod("getHighestRewardedLevel").invoke(user); + return (long) user + .getClass() + .getMethod("getHighestRewardedLevel") + .invoke(user); } catch (Exception ignored) { return user.getLevel(); } @@ -365,7 +542,9 @@ public void addUser(LevelUser user, boolean defValues) { if (!isConnected()) return; if (isUserLoaded(user)) return; - String levelStr = String.valueOf(main.levelSystem().getStartLevel()); + String levelStr = String.valueOf( + main.levelSystem().getStartLevel() + ); String expStr = String.valueOf(main.levelSystem().getStartExp()); if (!defValues) { @@ -376,14 +555,26 @@ public void addUser(LevelUser user, boolean defValues) { final String finalLevelStr = levelStr; final String finalExpStr = expStr; - main.scheduler().runTaskAsynchronously(() -> { - String sql = "INSERT INTO " + qTab(getTable()) + " (" + - qCol("UUID") + "," + qCol("LEVEL") + "," + qCol("EXP") + "," + qCol("UPDATED_AT") + + main + .scheduler() + .runTaskAsynchronously(() -> { + String sql = + "INSERT INTO " + + qTab(getTable()) + + " (" + + qCol("UUID") + + "," + + qCol("LEVEL") + + "," + + qCol("EXP") + + "," + + qCol("UPDATED_AT") + ") VALUES (?,?,?,?)"; - try (Connection connection = dataSource.getConnection(); - PreparedStatement st = connection.prepareStatement(sql)) - { + try ( + Connection connection = dataSource.getConnection(); + PreparedStatement st = connection.prepareStatement(sql) + ) { st.setString(1, user.getUuid().toString()); st.setLong(2, Long.parseLong(finalLevelStr)); st.setString(3, finalExpStr); @@ -392,19 +583,34 @@ public void addUser(LevelUser user, boolean defValues) { long now = System.currentTimeMillis(); long highest = getRewardLevel(user); - try (PreparedStatement pm = prepareUpsertMeta(connection, user.getUuid(), highest, now)) { + try ( + PreparedStatement pm = prepareUpsertMeta( + connection, + user.getUuid(), + highest, + now + ) + ) { pm.executeUpdate(); } } catch (Exception e) { - main.logger("&cFailed to add user " + user.getName() + "."); + main.logger( + "&cFailed to add user " + user.getName() + "." + ); e.printStackTrace(); } - }); + }); } @Override public void updateUser(LevelUser user) { if (!isConnected()) return; + main.scheduler().runTaskAsynchronously(() -> updateUserSync(user)); + } + + void updateUserSync(LevelUser user) { + if (!isConnected()) return; + final UUID uuid = user.getUuid(); final long now = System.currentTimeMillis(); final String expStr = String.valueOf(user.getExp()); @@ -412,47 +618,84 @@ public void updateUser(LevelUser user) { final String name = user.getName(); final long highest = getRewardLevel(user); - main.scheduler().runTaskAsynchronously(() -> { - try (Connection connection = dataSource.getConnection()) { - try (PreparedStatement st = prepareUpsert(connection, uuid, level, expStr, now)) { - st.executeUpdate(); - } + try (Connection connection = dataSource.getConnection()) { + try ( + PreparedStatement st = prepareUpsert( + connection, + uuid, + level, + expStr, + now + ) + ) { + st.executeUpdate(); + } - try (PreparedStatement stm = prepareUpsertMeta(connection, uuid, highest, now)) { - stm.executeUpdate(); - } - } catch (Exception e) { - main.logger("&cFailed to update user " + name + "."); - e.printStackTrace(); + try ( + PreparedStatement stm = prepareUpsertMeta( + connection, + uuid, + highest, + now + ) + ) { + stm.executeUpdate(); } - }); + } catch (Exception e) { + main.logger("&cFailed to update user " + name + "."); + e.printStackTrace(); + } } @Override public void removeUser(UUID uuid) { if (!isConnected()) return; - main.scheduler().runTaskAsynchronously(() -> { - String sql = "DELETE FROM " + qTab(getTable()) + " WHERE " + qCol("UUID") + "=?"; - String metaSql = "DELETE FROM " + qTab(metaTable()) + " WHERE " + qCol("UUID") + "=?"; - try (Connection connection = dataSource.getConnection(); - PreparedStatement st = connection.prepareStatement(sql); - PreparedStatement sm = connection.prepareStatement(metaSql)) { - st.setString(1, uuid.toString()); - st.executeUpdate(); + main + .scheduler() + .runTaskAsynchronously(() -> { + String sql = + "DELETE FROM " + + qTab(getTable()) + + " WHERE " + + qCol("UUID") + + "=?"; + String metaSql = + "DELETE FROM " + + qTab(metaTable()) + + " WHERE " + + qCol("UUID") + + "=?"; + try ( + Connection connection = dataSource.getConnection(); + PreparedStatement st = connection.prepareStatement(sql); + PreparedStatement sm = connection.prepareStatement( + metaSql + ) + ) { + st.setString(1, uuid.toString()); + st.executeUpdate(); - sm.setString(1, uuid.toString()); - sm.executeUpdate(); - } catch (Exception e) { - main.logger("&cFailed to remove user " + uuid + " from " + type + " database."); - e.printStackTrace(); - } - }); + sm.setString(1, uuid.toString()); + sm.executeUpdate(); + } catch (Exception e) { + main.logger( + "&cFailed to remove user " + + uuid + + " from " + + type + + " database." + ); + e.printStackTrace(); + } + }); } @Override public LevelUser getUser(Player player) { - return !isConnected() || player == null ? null : getUser(player.getUniqueId()); + return !isConnected() || player == null + ? null + : getUser(player.getUniqueId()); } @Override @@ -461,38 +704,57 @@ public LevelUser getUser(UUID uuid) { CompletableFuture> future = new CompletableFuture<>(); - main.scheduler().runTaskAsynchronously(() -> { - String sql = "SELECT " + qCol("LEVEL") + "," + qCol("EXP") + " FROM " + qTab(getTable()) + " WHERE " + qCol("UUID") + "=?"; + main + .scheduler() + .runTaskAsynchronously(() -> { + String sql = + "SELECT " + + qCol("LEVEL") + + "," + + qCol("EXP") + + " FROM " + + qTab(getTable()) + + " WHERE " + + qCol("UUID") + + "=?"; + + try ( + Connection connection = dataSource.getConnection(); + PreparedStatement st = connection.prepareStatement(sql) + ) { + st.setString(1, uuid.toString()); + + try (ResultSet rs = st.executeQuery()) { + if (!rs.next()) { + future.complete(null); + return; + } + + LevelUser user = system.createUser(uuid); + long level = rs.getLong("LEVEL"); + user.setLevel(level, false); + + String expStr = rs.getString("EXP"); + if (expStr == null) expStr = "0"; + user.setExp(expStr, false, false, false); - try (Connection connection = dataSource.getConnection(); - PreparedStatement st = connection.prepareStatement(sql)) { - st.setString(1, uuid.toString()); + long hr = readHighestRewarded(connection, uuid); + setRewardLevel( + user, + hr >= 0 ? hr : user.getLevel() + ); - try (ResultSet rs = st.executeQuery()) { - if (!rs.next()) { - future.complete(null); - return; + future.complete(user); } - - LevelUser user = system.createUser(uuid); - long level = rs.getLong("LEVEL"); - user.setLevel(level, false); - - String expStr = rs.getString("EXP"); - if (expStr == null) expStr = "0"; - user.setExp(expStr, false, false, false); - - long hr = readHighestRewarded(connection, uuid); - setRewardLevel(user, hr >= 0 ? hr : user.getLevel()); - - future.complete(user); + } catch (Exception e) { + main.logger( + "&cFailed to get player data for " + uuid + ".", + "" + ); + e.printStackTrace(); + future.complete(null); } - } catch (Exception e) { - main.logger("&cFailed to get player data for " + uuid + ".", ""); - e.printStackTrace(); - future.complete(null); - } - }); + }); try { return future.get(); @@ -508,26 +770,35 @@ public Set getUuids() { CompletableFuture> future = new CompletableFuture<>(); - main.scheduler().runTaskAsynchronously(() -> { - String sql = "SELECT " + qCol("UUID") + " FROM " + qTab(getTable()); - - try (Connection connection = dataSource.getConnection(); - PreparedStatement statement = connection.prepareStatement(sql); - ResultSet rs = statement.executeQuery()) - { - Set result = new LinkedHashSet<>(); - while (rs.next()) { - try { - result.add(UUID.fromString(rs.getString("UUID"))); - } catch (Exception ignored) {} + main + .scheduler() + .runTaskAsynchronously(() -> { + String sql = + "SELECT " + qCol("UUID") + " FROM " + qTab(getTable()); + + try ( + Connection connection = dataSource.getConnection(); + PreparedStatement statement = + connection.prepareStatement(sql); + ResultSet rs = statement.executeQuery() + ) { + Set result = new LinkedHashSet<>(); + while (rs.next()) { + try { + result.add( + UUID.fromString(rs.getString("UUID")) + ); + } catch (Exception ignored) {} + } + future.complete(result); + } catch (SQLException e) { + main.logger( + "&cFailed to fetch UUIDs from " + type + "." + ); + e.printStackTrace(); + future.complete(new LinkedHashSet<>()); } - future.complete(result); - } catch (SQLException e) { - main.logger("&cFailed to fetch UUIDs from " + type + "."); - e.printStackTrace(); - future.complete(new LinkedHashSet<>()); - } - }); + }); try { return future.get(); @@ -563,7 +834,17 @@ String getTable() { @Override HikariConfig createConfig() { HikariConfig config = new HikariConfig(); - config.setJdbcUrl("jdbc:mysql://" + ip + ":" + port + "/" + database + "?useSSL=" + ssl + "&autoReconnect=true&useUnicode=true&characterEncoding=utf8"); + config.setJdbcUrl( + "jdbc:mysql://" + + ip + + ":" + + port + + "/" + + database + + "?useSSL=" + + ssl + + "&autoReconnect=true&useUnicode=true&characterEncoding=utf8" + ); config.setUsername(username); config.setPassword(password); config.setConnectionTimeout(10000); @@ -594,15 +875,53 @@ String qTab(String name) { } @Override - PreparedStatement prepareUpsert(Connection c, UUID uuid, long level, String exp, long updatedAt) throws SQLException { + PreparedStatement prepareUpsert( + Connection c, + UUID uuid, + long level, + String exp, + long updatedAt + ) throws SQLException { String sql = - "INSERT INTO " + qTab(getTable()) + " (" + - qCol("UUID") + "," + qCol("LEVEL") + "," + qCol("EXP") + "," + qCol("UPDATED_AT") + ") " + - "VALUES (?,?,?,?) " + - "ON DUPLICATE KEY UPDATE " + - qCol("LEVEL") + " = IF(VALUES(" + qCol("UPDATED_AT") + ") >= " + qCol("UPDATED_AT") + ", VALUES(" + qCol("LEVEL") + ")," + qCol("LEVEL") + ")," + - qCol("EXP") + " = IF(VALUES(" + qCol("UPDATED_AT") + ") >= " + qCol("UPDATED_AT") + ", VALUES(" + qCol("EXP") + ")," + qCol("EXP") + ")," + - qCol("UPDATED_AT") + " = GREATEST(" + qCol("UPDATED_AT") + ", VALUES(" + qCol("UPDATED_AT") + "))"; + "INSERT INTO " + + qTab(getTable()) + + " (" + + qCol("UUID") + + "," + + qCol("LEVEL") + + "," + + qCol("EXP") + + "," + + qCol("UPDATED_AT") + + ") " + + "VALUES (?,?,?,?) " + + "ON DUPLICATE KEY UPDATE " + + qCol("LEVEL") + + " = IF(VALUES(" + + qCol("UPDATED_AT") + + ") >= " + + qCol("UPDATED_AT") + + ", VALUES(" + + qCol("LEVEL") + + ")," + + qCol("LEVEL") + + ")," + + qCol("EXP") + + " = IF(VALUES(" + + qCol("UPDATED_AT") + + ") >= " + + qCol("UPDATED_AT") + + ", VALUES(" + + qCol("EXP") + + ")," + + qCol("EXP") + + ")," + + qCol("UPDATED_AT") + + " = GREATEST(" + + qCol("UPDATED_AT") + + ", VALUES(" + + qCol("UPDATED_AT") + + "))"; PreparedStatement ps = c.prepareStatement(sql); ps.setString(1, uuid.toString()); ps.setLong(2, level); @@ -612,13 +931,35 @@ PreparedStatement prepareUpsert(Connection c, UUID uuid, long level, String exp, } @Override - PreparedStatement prepareUpsertMeta(Connection c, UUID uuid, long highest, long updatedAt) throws SQLException { - String sql = "INSERT INTO " + qTab(metaTable()) + " (" + - qCol("UUID") + "," + qCol("HIGHEST_REWARDED") + "," + qCol("UPDATED_AT") + - ") VALUES (?,?,?) " + - "ON DUPLICATE KEY UPDATE " + - qCol("HIGHEST_REWARDED") + " = GREATEST(" + qCol("HIGHEST_REWARDED") + ", VALUES(" + qCol("HIGHEST_REWARDED") + "))," + - qCol("UPDATED_AT") + " = GREATEST(" + qCol("UPDATED_AT") + ", VALUES(" + qCol("UPDATED_AT") + "))"; + PreparedStatement prepareUpsertMeta( + Connection c, + UUID uuid, + long highest, + long updatedAt + ) throws SQLException { + String sql = + "INSERT INTO " + + qTab(metaTable()) + + " (" + + qCol("UUID") + + "," + + qCol("HIGHEST_REWARDED") + + "," + + qCol("UPDATED_AT") + + ") VALUES (?,?,?) " + + "ON DUPLICATE KEY UPDATE " + + qCol("HIGHEST_REWARDED") + + " = GREATEST(" + + qCol("HIGHEST_REWARDED") + + ", VALUES(" + + qCol("HIGHEST_REWARDED") + + "))," + + qCol("UPDATED_AT") + + " = GREATEST(" + + qCol("UPDATED_AT") + + ", VALUES(" + + qCol("UPDATED_AT") + + "))"; PreparedStatement ps = c.prepareStatement(sql); ps.setString(1, uuid.toString()); ps.setLong(2, highest); @@ -629,11 +970,14 @@ PreparedStatement prepareUpsertMeta(Connection c, UUID uuid, long highest, long @Override Set getExistingColumns(Connection conn) throws SQLException { Set cols = new HashSet<>(); - String sql = "SELECT COLUMN_NAME FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = ?"; + String sql = + "SELECT COLUMN_NAME FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = ?"; try (PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, getTable()); try (ResultSet rs = ps.executeQuery()) { - while (rs.next()) cols.add(rs.getString(1).toUpperCase(Locale.ENGLISH)); + while (rs.next()) cols.add( + rs.getString(1).toUpperCase(Locale.ENGLISH) + ); } } return cols; @@ -641,7 +985,8 @@ Set getExistingColumns(Connection conn) throws SQLException { @Override boolean isExpColumnTextual(Connection conn) throws SQLException { - String sql = "SELECT DATA_TYPE FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = ? AND column_name = 'EXP'"; + String sql = + "SELECT DATA_TYPE FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = ? AND column_name = 'EXP'"; try (PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, getTable()); try (ResultSet rs = ps.executeQuery()) { @@ -658,11 +1003,12 @@ boolean isExpColumnTextual(Connection conn) throws SQLException { @Override boolean hasPrimaryKeyOnUuid(Connection conn) throws SQLException { - String sql = "SELECT k.COLUMN_NAME " + - "FROM information_schema.table_constraints t " + - "JOIN information_schema.key_column_usage k " + - "ON t.constraint_name = k.constraint_name AND t.table_schema = k.table_schema AND t.table_name = k.table_name " + - "WHERE t.constraint_type = 'PRIMARY KEY' AND t.table_schema = DATABASE() AND t.table_name = ?"; + String sql = + "SELECT k.COLUMN_NAME " + + "FROM information_schema.table_constraints t " + + "JOIN information_schema.key_column_usage k " + + "ON t.constraint_name = k.constraint_name AND t.table_schema = k.table_schema AND t.table_name = k.table_name " + + "WHERE t.constraint_type = 'PRIMARY KEY' AND t.table_schema = DATABASE() AND t.table_name = ?"; try (PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, getTable()); try (ResultSet rs = ps.executeQuery()) { @@ -677,29 +1023,42 @@ boolean hasPrimaryKeyOnUuid(Connection conn) throws SQLException { @Override void createTargetTable(Connection conn) throws SQLException { - String sql = "CREATE TABLE IF NOT EXISTS " + qTab(getTable()) + " (" + - qCol("UUID") + " VARCHAR(36) NOT NULL," + - qCol("LEVEL") + " BIGINT," + - qCol("EXP") + " TEXT," + - qCol("UPDATED_AT") + " BIGINT NOT NULL DEFAULT 0," + - "PRIMARY KEY (" + qCol("UUID") + ")) " + - "CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"; + String sql = + "CREATE TABLE IF NOT EXISTS " + + qTab(getTable()) + + " (" + + qCol("UUID") + + " VARCHAR(36) NOT NULL," + + qCol("LEVEL") + + " BIGINT," + + qCol("EXP") + + " TEXT," + + qCol("UPDATED_AT") + + " BIGINT NOT NULL DEFAULT 0," + + "PRIMARY KEY (" + + qCol("UUID") + + ")) " + + "CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"; try (Statement st = conn.createStatement()) { st.executeUpdate(sql); } } @Override - void dropTableIfExists(Connection conn, String table) throws SQLException { + void dropTableIfExists(Connection conn, String table) + throws SQLException { try (Statement st = conn.createStatement()) { st.executeUpdate("DROP TABLE IF EXISTS " + qTab(table)); } } @Override - void renameTable(Connection conn, String from, String to) throws SQLException { + void renameTable(Connection conn, String from, String to) + throws SQLException { try (Statement st = conn.createStatement()) { - st.executeUpdate("RENAME TABLE " + qTab(from) + " TO " + qTab(to)); + st.executeUpdate( + "RENAME TABLE " + qTab(from) + " TO " + qTab(to) + ); } } } @@ -715,12 +1074,19 @@ static class SQLite extends DatabaseImpl { this.table = db.getTable(); } - @Override String getTable() { return table; } + @Override + String getTable() { + return table; + } @Override HikariConfig createConfig() { HikariConfig config = new HikariConfig(); - config.setJdbcUrl("jdbc:sqlite:" + filePath + "?busy_timeout=5000&journal_mode=WAL&synchronous=NORMAL"); + config.setJdbcUrl( + "jdbc:sqlite:" + + filePath + + "?busy_timeout=5000&journal_mode=WAL&synchronous=NORMAL" + ); config.setMaximumPoolSize(2); config.setMinimumIdle(1); config.setConnectionTimeout(10000); @@ -743,15 +1109,65 @@ String qTab(String name) { } @Override - PreparedStatement prepareUpsert(Connection c, UUID uuid, long level, String exp, long updatedAt) throws SQLException { + PreparedStatement prepareUpsert( + Connection c, + UUID uuid, + long level, + String exp, + long updatedAt + ) throws SQLException { String sql = - "INSERT INTO " + qTab(getTable()) + " (" + - qCol("UUID") + "," + qCol("LEVEL") + "," + qCol("EXP") + "," + qCol("UPDATED_AT") + ") " + - "VALUES (?,?,?,?) " + - "ON CONFLICT(" + qCol("UUID") + ") DO UPDATE SET " + - qCol("LEVEL") + " = CASE WHEN excluded." + qCol("UPDATED_AT") + " >= " + qTab(getTable()) + "." + qCol("UPDATED_AT") + " THEN excluded." + qCol("LEVEL") + " ELSE " + qTab(getTable()) + "." + qCol("LEVEL") + " END," + - qCol("EXP") + " = CASE WHEN excluded." + qCol("UPDATED_AT") + " >= " + qTab(getTable()) + "." + qCol("UPDATED_AT") + " THEN excluded." + qCol("EXP") + " ELSE " + qTab(getTable()) + "." + qCol("EXP") + " END," + - qCol("UPDATED_AT") + " = MAX(" + qTab(getTable()) + "." + qCol("UPDATED_AT") + ", excluded." + qCol("UPDATED_AT") + ")"; + "INSERT INTO " + + qTab(getTable()) + + " (" + + qCol("UUID") + + "," + + qCol("LEVEL") + + "," + + qCol("EXP") + + "," + + qCol("UPDATED_AT") + + ") " + + "VALUES (?,?,?,?) " + + "ON CONFLICT(" + + qCol("UUID") + + ") DO UPDATE SET " + + qCol("LEVEL") + + " = CASE WHEN excluded." + + qCol("UPDATED_AT") + + " >= " + + qTab(getTable()) + + "." + + qCol("UPDATED_AT") + + " THEN excluded." + + qCol("LEVEL") + + " ELSE " + + qTab(getTable()) + + "." + + qCol("LEVEL") + + " END," + + qCol("EXP") + + " = CASE WHEN excluded." + + qCol("UPDATED_AT") + + " >= " + + qTab(getTable()) + + "." + + qCol("UPDATED_AT") + + " THEN excluded." + + qCol("EXP") + + " ELSE " + + qTab(getTable()) + + "." + + qCol("EXP") + + " END," + + qCol("UPDATED_AT") + + " = MAX(" + + qTab(getTable()) + + "." + + qCol("UPDATED_AT") + + ", excluded." + + qCol("UPDATED_AT") + + ")"; PreparedStatement ps = c.prepareStatement(sql); ps.setString(1, uuid.toString()); ps.setLong(2, level); @@ -761,13 +1177,41 @@ PreparedStatement prepareUpsert(Connection c, UUID uuid, long level, String exp, } @Override - PreparedStatement prepareUpsertMeta(Connection c, UUID uuid, long highest, long updatedAt) throws SQLException { - String sql = "INSERT INTO " + qTab(metaTable()) + " (" + - qCol("UUID") + "," + qCol("HIGHEST_REWARDED") + "," + qCol("UPDATED_AT") + - ") VALUES (?,?,?) " + - "ON CONFLICT(" + qCol("UUID") + ") DO UPDATE SET " + - qCol("HIGHEST_REWARDED") + " = MAX(" + qTab(metaTable()) + "." + qCol("HIGHEST_REWARDED") + ", excluded." + qCol("HIGHEST_REWARDED") + ")," + - qCol("UPDATED_AT") + " = MAX(" + qTab(metaTable()) + "." + qCol("UPDATED_AT") + ", excluded." + qCol("UPDATED_AT") + ")"; + PreparedStatement prepareUpsertMeta( + Connection c, + UUID uuid, + long highest, + long updatedAt + ) throws SQLException { + String sql = + "INSERT INTO " + + qTab(metaTable()) + + " (" + + qCol("UUID") + + "," + + qCol("HIGHEST_REWARDED") + + "," + + qCol("UPDATED_AT") + + ") VALUES (?,?,?) " + + "ON CONFLICT(" + + qCol("UUID") + + ") DO UPDATE SET " + + qCol("HIGHEST_REWARDED") + + " = MAX(" + + qTab(metaTable()) + + "." + + qCol("HIGHEST_REWARDED") + + ", excluded." + + qCol("HIGHEST_REWARDED") + + ")," + + qCol("UPDATED_AT") + + " = MAX(" + + qTab(metaTable()) + + "." + + qCol("UPDATED_AT") + + ", excluded." + + qCol("UPDATED_AT") + + ")"; PreparedStatement ps = c.prepareStatement(sql); ps.setString(1, uuid.toString()); ps.setLong(2, highest); @@ -778,8 +1222,12 @@ PreparedStatement prepareUpsertMeta(Connection c, UUID uuid, long highest, long @Override Set getExistingColumns(Connection conn) throws SQLException { Set cols = new HashSet<>(); - try (PreparedStatement ps = conn.prepareStatement("PRAGMA table_info(" + getTable() + ")"); - ResultSet rs = ps.executeQuery()) { + try ( + PreparedStatement ps = conn.prepareStatement( + "PRAGMA table_info(" + getTable() + ")" + ); + ResultSet rs = ps.executeQuery() + ) { while (rs.next()) { cols.add(rs.getString("name").toUpperCase(Locale.ENGLISH)); } @@ -789,8 +1237,12 @@ Set getExistingColumns(Connection conn) throws SQLException { @Override boolean isExpColumnTextual(Connection conn) throws SQLException { - try (PreparedStatement ps = conn.prepareStatement("PRAGMA table_info(" + getTable() + ")"); - ResultSet rs = ps.executeQuery()) { + try ( + PreparedStatement ps = conn.prepareStatement( + "PRAGMA table_info(" + getTable() + ")" + ); + ResultSet rs = ps.executeQuery() + ) { while (rs.next()) { String name = rs.getString("name"); if (!"EXP".equalsIgnoreCase(name)) continue; @@ -805,8 +1257,12 @@ boolean isExpColumnTextual(Connection conn) throws SQLException { @Override boolean hasPrimaryKeyOnUuid(Connection conn) throws SQLException { - try (PreparedStatement ps = conn.prepareStatement("PRAGMA table_info(" + getTable() + ")"); - ResultSet rs = ps.executeQuery()) { + try ( + PreparedStatement ps = conn.prepareStatement( + "PRAGMA table_info(" + getTable() + ")" + ); + ResultSet rs = ps.executeQuery() + ) { while (rs.next()) { String name = rs.getString("name"); int pk = rs.getInt("pk"); @@ -818,28 +1274,39 @@ boolean hasPrimaryKeyOnUuid(Connection conn) throws SQLException { @Override void createTargetTable(Connection conn) throws SQLException { - String sql = "CREATE TABLE IF NOT EXISTS " + qTab(getTable()) + " (" + - qCol("UUID") + " TEXT PRIMARY KEY," + - qCol("LEVEL") + " INTEGER," + - qCol("EXP") + " TEXT," + - qCol("UPDATED_AT") + " INTEGER NOT NULL DEFAULT 0" + - ")"; + String sql = + "CREATE TABLE IF NOT EXISTS " + + qTab(getTable()) + + " (" + + qCol("UUID") + + " TEXT PRIMARY KEY," + + qCol("LEVEL") + + " INTEGER," + + qCol("EXP") + + " TEXT," + + qCol("UPDATED_AT") + + " INTEGER NOT NULL DEFAULT 0" + + ")"; try (Statement st = conn.createStatement()) { st.executeUpdate(sql); } } @Override - void dropTableIfExists(Connection conn, String table) throws SQLException { + void dropTableIfExists(Connection conn, String table) + throws SQLException { try (Statement st = conn.createStatement()) { st.executeUpdate("DROP TABLE IF EXISTS " + qTab(table)); } } @Override - void renameTable(Connection conn, String from, String to) throws SQLException { + void renameTable(Connection conn, String from, String to) + throws SQLException { try (Statement st = conn.createStatement()) { - st.executeUpdate("ALTER TABLE " + qTab(from) + " RENAME TO " + qTab(to)); + st.executeUpdate( + "ALTER TABLE " + qTab(from) + " RENAME TO " + qTab(to) + ); } } } @@ -860,14 +1327,17 @@ static class PostgreSQL extends DatabaseImpl { this.table = db.getTable(); } - @Override String getTable() { + @Override + String getTable() { return table; } @Override HikariConfig createConfig() { HikariConfig config = new HikariConfig(); - config.setJdbcUrl("jdbc:postgresql://" + ip + ":" + port + "/" + database); + config.setJdbcUrl( + "jdbc:postgresql://" + ip + ":" + port + "/" + database + ); config.setUsername(username); config.setPassword(password); config.setConnectionTimeout(10000); @@ -893,15 +1363,65 @@ String qTab(String name) { } @Override - PreparedStatement prepareUpsert(Connection c, UUID uuid, long level, String exp, long updatedAt) throws SQLException { + PreparedStatement prepareUpsert( + Connection c, + UUID uuid, + long level, + String exp, + long updatedAt + ) throws SQLException { String sql = - "INSERT INTO " + qTab(getTable()) + " (" + - qCol("UUID") + "," + qCol("LEVEL") + "," + qCol("EXP") + "," + qCol("UPDATED_AT") + ") " + - "VALUES (?,?,?,?) " + - "ON CONFLICT (" + qCol("UUID") + ") DO UPDATE SET " + - qCol("LEVEL") + " = CASE WHEN EXCLUDED." + qCol("UPDATED_AT") + " >= " + qTab(getTable()) + "." + qCol("UPDATED_AT") + " THEN EXCLUDED." + qCol("LEVEL") + " ELSE " + qTab(getTable()) + "." + qCol("LEVEL") + " END," + - qCol("EXP") + " = CASE WHEN EXCLUDED." + qCol("UPDATED_AT") + " >= " + qTab(getTable()) + "." + qCol("UPDATED_AT") + " THEN EXCLUDED." + qCol("EXP") + " ELSE " + qTab(getTable()) + "." + qCol("EXP") + " END," + - qCol("UPDATED_AT") + " = GREATEST(" + qTab(getTable()) + "." + qCol("UPDATED_AT") + ", EXCLUDED." + qCol("UPDATED_AT") + ")"; + "INSERT INTO " + + qTab(getTable()) + + " (" + + qCol("UUID") + + "," + + qCol("LEVEL") + + "," + + qCol("EXP") + + "," + + qCol("UPDATED_AT") + + ") " + + "VALUES (?,?,?,?) " + + "ON CONFLICT (" + + qCol("UUID") + + ") DO UPDATE SET " + + qCol("LEVEL") + + " = CASE WHEN EXCLUDED." + + qCol("UPDATED_AT") + + " >= " + + qTab(getTable()) + + "." + + qCol("UPDATED_AT") + + " THEN EXCLUDED." + + qCol("LEVEL") + + " ELSE " + + qTab(getTable()) + + "." + + qCol("LEVEL") + + " END," + + qCol("EXP") + + " = CASE WHEN EXCLUDED." + + qCol("UPDATED_AT") + + " >= " + + qTab(getTable()) + + "." + + qCol("UPDATED_AT") + + " THEN EXCLUDED." + + qCol("EXP") + + " ELSE " + + qTab(getTable()) + + "." + + qCol("EXP") + + " END," + + qCol("UPDATED_AT") + + " = GREATEST(" + + qTab(getTable()) + + "." + + qCol("UPDATED_AT") + + ", EXCLUDED." + + qCol("UPDATED_AT") + + ")"; PreparedStatement ps = c.prepareStatement(sql); ps.setString(1, uuid.toString()); ps.setLong(2, level); @@ -911,14 +1431,42 @@ PreparedStatement prepareUpsert(Connection c, UUID uuid, long level, String exp, } @Override - PreparedStatement prepareUpsertMeta(Connection c, UUID uuid, long highest, long updatedAt) throws SQLException { + PreparedStatement prepareUpsertMeta( + Connection c, + UUID uuid, + long highest, + long updatedAt + ) throws SQLException { String sql = - "INSERT INTO " + qTab(metaTable()) + " (" + - qCol("UUID") + "," + qCol("HIGHEST_REWARDED") + "," + qCol("UPDATED_AT") + ") " + - "VALUES (?,?,?) " + - "ON CONFLICT (" + qCol("UUID") + ") DO UPDATE SET " + - qCol("HIGHEST_REWARDED") + " = GREATEST(" + qTab(metaTable()) + "." + qCol("HIGHEST_REWARDED") + ", EXCLUDED." + qCol("HIGHEST_REWARDED") + ")," + - qCol("UPDATED_AT") + " = GREATEST(" + qTab(metaTable()) + "." + qCol("UPDATED_AT") + ", EXCLUDED." + qCol("UPDATED_AT") + ")"; + "INSERT INTO " + + qTab(metaTable()) + + " (" + + qCol("UUID") + + "," + + qCol("HIGHEST_REWARDED") + + "," + + qCol("UPDATED_AT") + + ") " + + "VALUES (?,?,?) " + + "ON CONFLICT (" + + qCol("UUID") + + ") DO UPDATE SET " + + qCol("HIGHEST_REWARDED") + + " = GREATEST(" + + qTab(metaTable()) + + "." + + qCol("HIGHEST_REWARDED") + + ", EXCLUDED." + + qCol("HIGHEST_REWARDED") + + ")," + + qCol("UPDATED_AT") + + " = GREATEST(" + + qTab(metaTable()) + + "." + + qCol("UPDATED_AT") + + ", EXCLUDED." + + qCol("UPDATED_AT") + + ")"; PreparedStatement ps = c.prepareStatement(sql); ps.setString(1, uuid.toString()); ps.setLong(2, highest); @@ -929,11 +1477,14 @@ PreparedStatement prepareUpsertMeta(Connection c, UUID uuid, long highest, long @Override Set getExistingColumns(Connection conn) throws SQLException { Set cols = new HashSet<>(); - String sql = "SELECT column_name FROM information_schema.columns WHERE table_schema = current_schema() AND table_name = ?"; + String sql = + "SELECT column_name FROM information_schema.columns WHERE table_schema = current_schema() AND table_name = ?"; try (PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, getTable()); try (ResultSet rs = ps.executeQuery()) { - while (rs.next()) cols.add(rs.getString(1).toUpperCase(Locale.ENGLISH)); + while (rs.next()) cols.add( + rs.getString(1).toUpperCase(Locale.ENGLISH) + ); } } return cols; @@ -941,7 +1492,8 @@ Set getExistingColumns(Connection conn) throws SQLException { @Override boolean isExpColumnTextual(Connection conn) throws SQLException { - String sql = "SELECT data_type FROM information_schema.columns WHERE table_schema = current_schema() AND table_name = ? AND column_name = 'exp'"; + String sql = + "SELECT data_type FROM information_schema.columns WHERE table_schema = current_schema() AND table_name = ? AND column_name = 'exp'"; try (PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, getTable()); try (ResultSet rs = ps.executeQuery()) { @@ -959,10 +1511,10 @@ boolean isExpColumnTextual(Connection conn) throws SQLException { @Override boolean hasPrimaryKeyOnUuid(Connection conn) throws SQLException { String sql = - "SELECT a.attname " + - "FROM pg_index i " + - "JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY(i.indkey) " + - "WHERE i.indrelid = ?::regclass AND i.indisprimary"; + "SELECT a.attname " + + "FROM pg_index i " + + "JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY(i.indkey) " + + "WHERE i.indrelid = ?::regclass AND i.indisprimary"; try (PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, getTable()); try (ResultSet rs = ps.executeQuery()) { @@ -977,33 +1529,47 @@ boolean hasPrimaryKeyOnUuid(Connection conn) throws SQLException { @Override void createTargetTable(Connection conn) throws SQLException { - String sql = "CREATE TABLE IF NOT EXISTS " + qTab(getTable()) + " (" + - qCol("UUID") + " VARCHAR(36) PRIMARY KEY," + - qCol("LEVEL") + " BIGINT," + - qCol("EXP") + " TEXT," + - qCol("UPDATED_AT") + " BIGINT NOT NULL DEFAULT 0" + - ")"; + String sql = + "CREATE TABLE IF NOT EXISTS " + + qTab(getTable()) + + " (" + + qCol("UUID") + + " VARCHAR(36) PRIMARY KEY," + + qCol("LEVEL") + + " BIGINT," + + qCol("EXP") + + " TEXT," + + qCol("UPDATED_AT") + + " BIGINT NOT NULL DEFAULT 0" + + ")"; try (Statement st = conn.createStatement()) { st.executeUpdate(sql); } } @Override - void dropTableIfExists(Connection conn, String table) throws SQLException { + void dropTableIfExists(Connection conn, String table) + throws SQLException { try (Statement st = conn.createStatement()) { st.executeUpdate("DROP TABLE IF EXISTS " + qTab(table)); } } @Override - void renameTable(Connection conn, String from, String to) throws SQLException { + void renameTable(Connection conn, String from, String to) + throws SQLException { try (Statement st = conn.createStatement()) { - st.executeUpdate("ALTER TABLE " + qTab(from) + " RENAME TO " + qTab(to)); + st.executeUpdate( + "ALTER TABLE " + qTab(from) + " RENAME TO " + qTab(to) + ); } } } - static Database createDatabase(CyberLevels main, BaseSystem system) { + static Database createDatabase( + CyberLevels main, + BaseSystem system + ) { String type = main.cache().config().database().getType(); switch (type.toUpperCase(Locale.ENGLISH)) { diff --git a/src/main/java/com/bitaspire/cyberlevels/UserManagerImpl.java b/src/main/java/com/bitaspire/cyberlevels/UserManagerImpl.java index 0460482..a84f00b 100644 --- a/src/main/java/com/bitaspire/cyberlevels/UserManagerImpl.java +++ b/src/main/java/com/bitaspire/cyberlevels/UserManagerImpl.java @@ -6,16 +6,8 @@ import com.bitaspire.cyberlevels.user.Database; import com.bitaspire.cyberlevels.user.LevelUser; import com.bitaspire.cyberlevels.user.UserManager; -import lombok.Getter; -import lombok.RequiredArgsConstructor; import com.bitaspire.scheduler.GlobalRunnable; import com.bitaspire.scheduler.GlobalTask; -import org.apache.commons.lang3.StringUtils; -import org.bukkit.Bukkit; -import org.bukkit.OfflinePlayer; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; - import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; @@ -24,6 +16,13 @@ import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; final class UserManagerImpl implements UserManager { @@ -35,6 +34,7 @@ final class UserManagerImpl implements UserManager { private final Map> users = new ConcurrentHashMap<>(); GlobalTask autoSaveTask = null; + @Getter private Database database = null; @@ -65,9 +65,13 @@ void checkMigration() { final Database now = database; if (now != null && old.getClass().equals(now.getClass())) return; - main.logger("&eDetected database type change from " + - old.getClass().getSimpleName() + " to " + - (now == null ? "FlatFile" : now.getClass().getSimpleName()) + ". Starting migration..."); + main.logger( + "&eDetected database type change from " + + old.getClass().getSimpleName() + + " to " + + (now == null ? "FlatFile" : now.getClass().getSimpleName()) + + ". Starting migration..." + ); long start = System.currentTimeMillis(); int migrated = 0; @@ -88,9 +92,17 @@ void checkMigration() { } if (migrated > 0) { - main.logger("&aMigrated " + migrated + " users in " + (System.currentTimeMillis() - start) + "ms."); + main.logger( + "&aMigrated " + + migrated + + " users in " + + (System.currentTimeMillis() - start) + + "ms." + ); } else { - main.logger("&eNo players were found to migrate. Ending migration..."); + main.logger( + "&eNo players were found to migrate. Ending migration..." + ); } } catch (Exception e) { main.logger("&cMigration failed."); @@ -104,8 +116,7 @@ public LevelUser getUser(UUID uuid) { if (user == null) { OfflinePlayer player = Bukkit.getPlayer(uuid); - if (player == null) - player = Bukkit.getOfflinePlayer(uuid); + if (player == null) player = Bukkit.getOfflinePlayer(uuid); return loadUser(player); } @@ -114,24 +125,46 @@ public LevelUser getUser(UUID uuid) { @Override public LevelUser getUser(String name) { - for (LevelUser user : users.values()) - try { - String n = Objects.requireNonNull(user.getName()); - if (n.equalsIgnoreCase(name)) return user; - } catch (Exception ignored) {} + if (StringUtils.isBlank(name)) return null; + + Player online = Bukkit.getPlayerExact(name); + if (online != null) return getUser(online); + + for (Player player : Bukkit.getOnlinePlayers()) + if (player.getName().equalsIgnoreCase(name)) return getUser(player); + + for (OfflinePlayer offline : Bukkit.getOfflinePlayers()) { + String offlineName = offline.getName(); + if ( + offlineName != null && offlineName.equalsIgnoreCase(name) + ) return getUser(offline.getUniqueId()); + } + + for (LevelUser user : users.values()) { + String loadedName = user.getName(); + if ( + loadedName != null && loadedName.equalsIgnoreCase(name) + ) return user; + } return null; } void setRewardLevel(LevelUser user, long level) { try { - user.getClass().getMethod("setHighestRewardedLevel", long.class).invoke(user, level); + user + .getClass() + .getMethod("setHighestRewardedLevel", long.class) + .invoke(user, level); } catch (Exception ignored) {} } long getRewardLevel(LevelUser user) { try { - return (long) user.getClass().getMethod("getHighestRewardedLevel").invoke(user); + return (long) user + .getClass() + .getMethod("getHighestRewardedLevel") + .invoke(user); } catch (Exception ignored) { return user.getLevel(); } @@ -140,7 +173,10 @@ long getRewardLevel(LevelUser user) { private LevelUser loadFromFlatFile(UUID uuid) { LevelUser user = system.createUser(uuid); - Path file = new File(main.getDataFolder(), "player_data" + File.separator + uuid + ".clv").toPath(); + Path file = new File( + main.getDataFolder(), + "player_data" + File.separator + uuid + ".clv" + ).toPath(); if (!Files.exists(file)) return null; try (BufferedReader reader = Files.newBufferedReader(file)) { @@ -155,7 +191,9 @@ private LevelUser loadFromFlatFile(UUID uuid) { user.setExp(line.trim(), false, false, false); line = reader.readLine(); - long claimed = (line != null) ? Long.parseLong(line.trim()) : user.getLevel(); + long claimed = (line != null) + ? Long.parseLong(line.trim()) + : user.getLevel(); setRewardLevel(user, claimed); return user; @@ -178,7 +216,9 @@ private void saveToFlatFile(LevelUser user) { long claimed = getRewardLevel(user); writer.write(claimed + "\n"); } catch (Exception e) { - main.logger("&cFailed to save data for UUID " + user.getUuid() + "."); + main.logger( + "&cFailed to save data for UUID " + user.getUuid() + "." + ); e.printStackTrace(); } } @@ -192,11 +232,15 @@ private LoadResult loadUserData(UUID uuid) { if (user == null) { if ((user = loadFromFlatFile(uuid)) != null) { - migrationMessage = " from flat-file to " + database.getClass().getSimpleName(); + migrationMessage = + " from flat-file to " + + database.getClass().getSimpleName(); try { database.addUser(user, false); } catch (Exception e) { - main.logger("&cFailed to migrate user to database: " + uuid); + main.logger( + "&cFailed to migrate user to database: " + uuid + ); } } else { user = system.createUser(uuid); @@ -210,7 +254,10 @@ private LoadResult loadUserData(UUID uuid) { if (old != null) { LevelUser oldUser = old.getUser(uuid); if (oldUser != null) { - migrationMessage = " from " + old.getClass().getSimpleName() + " to flat-file"; + migrationMessage = + " from " + + old.getClass().getSimpleName() + + " to flat-file"; LevelUser copy = system.createUser(oldUser); saveToFlatFile(copy); user = copy; @@ -224,7 +271,10 @@ private LoadResult loadUserData(UUID uuid) { return new LoadResult(user, migrationMessage); } - private void loadUserAsync(OfflinePlayer offline, boolean updateLeaderboard) { + private void loadUserAsync( + OfflinePlayer offline, + boolean updateLeaderboard + ) { Player player = (offline instanceof Player) ? (Player) offline : null; UUID uuid = offline.getUniqueId(); @@ -236,10 +286,16 @@ private void loadUserAsync(OfflinePlayer offline, boolean updateLeaderboard) { return; } - main.scheduler().runTaskAsynchronously(() -> { - LoadResult result = loadUserData(uuid); - main.scheduler().runTask(() -> finishUserLoad(uuid, player, result, updateLeaderboard)); - }); + main + .scheduler() + .runTaskAsynchronously(() -> { + LoadResult result = loadUserData(uuid); + main + .scheduler() + .runTask(() -> + finishUserLoad(uuid, player, result, updateLeaderboard) + ); + }); } private LevelUser loadUser(OfflinePlayer offline) { @@ -258,22 +314,34 @@ private LevelUser toOnlineUser(UUID uuid, LevelUser source) { return online; } - private void finishUserLoad(UUID uuid, Player player, LoadResult result, boolean updateLeaderboard) { - if (StringUtils.isNotBlank(result.migrationMessage)) - main.logger("Migrated " + (player != null ? player.getName() : uuid) + result.migrationMessage); + private void finishUserLoad( + UUID uuid, + Player player, + LoadResult result, + boolean updateLeaderboard + ) { + if (StringUtils.isNotBlank(result.migrationMessage)) main.logger( + "Migrated " + + (player != null ? player.getName() : uuid) + + result.migrationMessage + ); LevelUser existing = users.get(uuid); if (existing != null) { - if (player != null && !existing.isOnline()) - users.put(uuid, toOnlineUser(uuid, existing)); + if (player != null && !existing.isOnline()) users.put( + uuid, + toOnlineUser(uuid, existing) + ); if (updateLeaderboard) scheduleLeaderboardUpdate(); return; } LevelUser loaded = result.user; - if (player != null && !loaded.isOnline()) - loaded = toOnlineUser(uuid, loaded); + if (player != null && !loaded.isOnline()) loaded = toOnlineUser( + uuid, + loaded + ); users.put(uuid, loaded); if (updateLeaderboard) scheduleLeaderboardUpdate(); @@ -281,14 +349,17 @@ private void finishUserLoad(UUID uuid, Player player, LoadResult result, boolean private void scheduleLeaderboardUpdate() { if (!leaderboardQueued.compareAndSet(false, true)) return; - main.scheduler().runTask(() -> { - system.updateLeaderboard(); - leaderboardQueued.set(false); - }); + main + .scheduler() + .runTask(() -> { + system.updateLeaderboard(); + leaderboardQueued.set(false); + }); } @RequiredArgsConstructor private class LoadResult { + final LevelUser user; final String migrationMessage; } @@ -321,10 +392,41 @@ public void savePlayer(Player player, boolean clearData) { setRewardLevel(offline, getRewardLevel(user)); users.put(uuid, offline); + } catch (Exception e) { + users.remove(uuid); + main.logger( + "&cNot able to convert to OfflineUser for: " + + user.getName() + + ". Deleting cache..." + ); + e.printStackTrace(); } - catch (Exception e) { + } + + void savePlayerSync(Player player, boolean clearData) { + LevelUser user = users.get(player.getUniqueId()); + if (user == null) return; + + saveUserSync(user); + if (!clearData) return; + + UUID uuid = user.getUuid(); + + try { + LevelUser offline = system.createOffline(uuid); + offline.setLevel(user.getLevel(), false); + offline.setExp(user.getExp(), false, false, false); + + setRewardLevel(offline, getRewardLevel(user)); + + users.put(uuid, offline); + } catch (Exception e) { users.remove(uuid); - main.logger("&cNot able to convert to OfflineUser for: " + user.getName() + ". Deleting cache..."); + main.logger( + "&cNot able to convert to OfflineUser for: " + + user.getName() + + ". Deleting cache..." + ); e.printStackTrace(); } } @@ -342,6 +444,13 @@ private void saveUserAsync(LevelUser user) { main.scheduler().runTaskAsynchronously(() -> saveUser(user)); } + void saveUserSync(LevelUser user) { + if (database instanceof DatabaseFactory.DatabaseImpl) ( + (DatabaseFactory.DatabaseImpl) database + ).updateUserSync(user); + else saveToFlatFile(user); + } + @Override public void removeUser(UUID uuid) { users.remove(uuid); @@ -351,10 +460,15 @@ public void removeUser(UUID uuid) { return; } - File file = new File(main.getDataFolder() + File.separator + "player_data", uuid + ".clv"); + File file = new File( + main.getDataFolder() + File.separator + "player_data", + uuid + ".clv" + ); if (!file.exists()) return; - if (!file.delete()) main.logger("&cFailed to delete flat-file for user " + uuid); + if (!file.delete()) main.logger( + "&cFailed to delete flat-file for user " + uuid + ); } void loadOfflinePlayers() { @@ -384,15 +498,19 @@ public void run() { if (index < players.length) return; cancel(); - if (loaded > 0) - main.logger("&7Loaded data for &e" + loaded + - " &7offline player(s) in &a" + - (System.currentTimeMillis() - l) + - "ms&7.", ""); + if (loaded > 0) main.logger( + "&7Loaded data for &e" + + loaded + + " &7offline player(s) in &a" + + (System.currentTimeMillis() - l) + + "ms&7.", + "" + ); scheduleLeaderboardUpdate(); } - }.runTaskTimer(0L, 1L); + } + .runTaskTimer(0L, 1L); } @Override @@ -410,10 +528,14 @@ public void loadOnlinePlayers() { if (counter < 1) return; - main.logger("&7Loaded data for &e" + counter + + main.logger( + "&7Loaded data for &e" + + counter + " &7online player(s) in &a" + (System.currentTimeMillis() - l) + - "ms&7.", ""); + "ms&7.", + "" + ); scheduleLeaderboardUpdate(); } @@ -422,26 +544,39 @@ public void saveOnlinePlayers(boolean clearData) { Bukkit.getOnlinePlayers().forEach(p -> savePlayer(p, clearData)); } + void saveOnlinePlayersSync(boolean clearData) { + Bukkit.getOnlinePlayers().forEach(p -> savePlayerSync(p, clearData)); + } + @Override public void startAutoSave() { if (!cache.config().isAutoSaveEnabled()) return; Config config = cache.config(); - autoSaveTask = main.scheduler().runTaskLater(() -> { - long start = System.currentTimeMillis(); - main.userManager().saveOnlinePlayers(false); - - if (config.syncLeaderboardOnAutoSave()) - system.getLeaderboard().update(); - - if (config.isMessagesOnAutoSave()) - cache.lang().sendMessage( - null, Lang::getAutoSave, "ms", - System.currentTimeMillis() - start - ); - - startAutoSave(); - }, 20L * config.getAutoSaveInterval()); + autoSaveTask = main + .scheduler() + .runTaskLater( + () -> { + long start = System.currentTimeMillis(); + main.userManager().saveOnlinePlayers(false); + + if (config.syncLeaderboardOnAutoSave()) system + .getLeaderboard() + .update(); + + if (config.isMessagesOnAutoSave()) cache + .lang() + .sendMessage( + null, + Lang::getAutoSave, + "ms", + System.currentTimeMillis() - start + ); + + startAutoSave(); + }, + 20L * config.getAutoSaveInterval() + ); } @Override diff --git a/src/main/java/com/bitaspire/cyberlevels/command/CLVCommand.java b/src/main/java/com/bitaspire/cyberlevels/command/CLVCommand.java index a9fdf7f..457b9dc 100644 --- a/src/main/java/com/bitaspire/cyberlevels/command/CLVCommand.java +++ b/src/main/java/com/bitaspire/cyberlevels/command/CLVCommand.java @@ -4,6 +4,9 @@ import com.bitaspire.cyberlevels.cache.Lang; import com.bitaspire.cyberlevels.level.LevelSystem; import com.bitaspire.cyberlevels.user.LevelUser; +import java.util.Arrays; +import java.util.List; +import java.util.function.Function; import lombok.Getter; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; @@ -11,10 +14,6 @@ import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; -import java.util.Arrays; -import java.util.List; -import java.util.function.Function; - public class CLVCommand implements CommandExecutor { private final CyberLevels main; @@ -22,15 +21,33 @@ public class CLVCommand implements CommandExecutor { public CLVCommand(CyberLevels main) { this.main = main; - this.consoleCmds = Arrays.asList("about", "reload", "addexp", "setexp", "removeexp", "addlevel", "setlevel", "removelevel", "purge"); + this.consoleCmds = Arrays.asList( + "about", + "reload", + "addexp", + "setexp", + "removeexp", + "addlevel", + "setlevel", + "removelevel", + "purge" + ); } @Override - public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @NotNull String label, String[] args) { + public boolean onCommand( + @NotNull CommandSender sender, + @NotNull Command cmd, + @NotNull String label, + String[] args + ) { Player player = (sender instanceof Player) ? (Player) sender : null; // Console restrictions - if (player == null && (args.length == 0 || !consoleCmds.contains(args[0].toLowerCase()))) { + if ( + player == null && + (args.length == 0 || !consoleCmds.contains(args[0].toLowerCase())) + ) { main.logger("&cConsole cannot use this command!"); return true; } @@ -41,12 +58,20 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @N if (args.length == 1) { switch (sub) { case "about": - return isRestricted(player, "player.about") || main.createSender(player).send( - " &d&lCyber&f&lLevels &fv" + main.getDescription().getVersion() + " &7(&7&nhttps://bit.ly/2YSlqYq&7).", - " &fDeveloped by &d" + main.getAuthors() + "&f.", - " A leveling system plugin with MySQL support and custom events." + return ( + isRestricted(player, "player.about") || + main + .createSender(player) + .send( + " &d&lCyber&f&lLevels &fv" + + main.getDescription().getVersion() + + " &7(&7&nhttps://bit.ly/2YSlqYq&7).", + " &fDeveloped by &d" + + main.getAuthors() + + "&f.", + " A leveling system plugin with MySQL support and custom events." + ) ); - case "reload": if (isRestricted(player, "admin.reload")) return true; @@ -54,23 +79,39 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @N main.onDisable(); main.reloadPlugin(); - return main.cache().lang().sendMessage(player, Lang::getReloaded); - - case "info": return sendLevelInfo(player); - + return main + .cache() + .lang() + .sendMessage(player, Lang::getReloaded); + case "info": + return sendLevelInfo(player); case "top": if (isRestricted(player, "player.top")) return true; main.cache().lang().sendMessage(player, Lang::getTopHeader); int i = 1; - for (LevelUser user : main.levelSystem().getLeaderboard().getTopTenPlayers()) { - main.cache().lang().sendMessage( - player, Lang::getTopContent, - new String[] {"position", "player", "level", "exp"}, - i++, user.getName(), - user.getLevel(), user.getExp() - ); + for (LevelUser user : main + .levelSystem() + .getLeaderboard() + .getTopTenPlayers()) { + main + .cache() + .lang() + .sendMessage( + player, + Lang::getTopContent, + new String[] { + "position", + "player", + "level", + "exp", + }, + i++, + user.getName(), + user.getLevel(), + user.getExp() + ); } main.cache().lang().sendMessage(player, Lang::getTopFooter); @@ -83,7 +124,15 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @N if (target != null) { main.userManager().removeUser(target.getUuid()); main.levelSystem().getLeaderboard().update(); - return main.cache().lang().sendMessage(player, Lang::getPurgePlayer, "player", args[1]); + return main + .cache() + .lang() + .sendMessage( + player, + Lang::getPurgePlayer, + "player", + args[1] + ); } return isRestricted(player, "admin.info") || sendLevelInfo(player); @@ -93,23 +142,49 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @N if (isRestricted(player, "admin.info")) return true; LevelUser target = main.userManager().getUser(args[1]); - if (target == null) - return main.cache().lang().sendMessage(player, Lang::getPlayerNotFound, "player", args[1]); + if (target == null) return main + .cache() + .lang() + .sendMessage( + player, + Lang::getPlayerNotFound, + "player", + args[1] + ); return sendLevelInfo(player, target); } if (args.length >= 2) { - LevelUser user = null; - - if (args.length == 3) - user = main.userManager().getUser(args[2]); - - if (user == null && player != null) - user = main.userManager().getUser(player); + String targetName = args.length >= 3 ? args[2] : null; + LevelUser user = + targetName != null + ? main.userManager().getUser(targetName) + : (player != null + ? main.userManager().getUser(player) + : null); if (user == null) { - main.cache().lang().sendMessage(player, Lang::getPlayerNotFound, "player", args[2]); + if (targetName == null) { + if (player == null) { + main.logger( + "&cConsole must specify a player name for this command." + ); + return true; + } + + return sendLevelInfo(player); + } + + main + .cache() + .lang() + .sendMessage( + player, + Lang::getPlayerNotFound, + "player", + targetName + ); return true; } @@ -117,31 +192,69 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @N switch (sub) { case "addexp": - return handleExp(player, user, value, "exp.add", true, ExpAction.ADD); - + return handleExp( + player, + user, + value, + "exp.add", + true, + ExpAction.ADD + ); case "setexp": - return handleExp(player, user, value, "exp.set", true, ExpAction.SET); - + return handleExp( + player, + user, + value, + "exp.set", + true, + ExpAction.SET + ); case "removeexp": - return handleExp(player, user, value, "exp.remove", false, ExpAction.REMOVE); - + return handleExp( + player, + user, + value, + "exp.remove", + false, + ExpAction.REMOVE + ); case "addlevel": - return handleLevel(player, user, value, "level.add", LevelAction.ADD); - + return handleLevel( + player, + user, + value, + "level.add", + LevelAction.ADD + ); case "setlevel": - return handleLevel(player, user, value, "level.set", LevelAction.SET); - + return handleLevel( + player, + user, + value, + "level.set", + LevelAction.SET + ); case "removelevel": - return handleLevel(player, user, value, "level.remove", LevelAction.REMOVE); + return handleLevel( + player, + user, + value, + "level.remove", + LevelAction.REMOVE + ); } } if (player != null) { - if (player.hasPermission("CyberLevels.admin.help")) - return main.cache().lang().sendMessage(player, Lang::getHelpAdmin); - - if (player.hasPermission("CyberLevels.player.help")) - return main.cache().lang().sendMessage(player, Lang::getHelpPlayer); + if (player.hasPermission("CyberLevels.admin.help")) return main + .cache() + .lang() + .sendMessage(player, Lang::getHelpAdmin); + + if (player.hasPermission("CyberLevels.player.help")) return main + .cache() + .lang() + .sendMessage(player, Lang::getHelpPlayer); } return main.cache().lang().sendMessage(player, Lang::getNoPermission); @@ -151,24 +264,49 @@ private boolean sendLevelInfo(Player player) { LevelUser user = main.userManager().getUser(player); LevelSystem system = main.levelSystem(); - return main.cache().lang().sendMessage( - player, Lang::getLevelInfo, - new String[] {"player", "level", "maxLevel", "playerEXP", "requiredEXP", "percent", "progressBar"}, - user.getName(), user.getLevel(), + return main + .cache() + .lang() + .sendMessage( + player, + Lang::getLevelInfo, + new String[] { + "player", + "level", + "maxLevel", + "playerEXP", + "requiredEXP", + "percent", + "progressBar", + }, + user.getName(), + user.getLevel(), main.cache().levels().getMaxLevel(), system.formatNumber(user.getExp()), system.formatNumber(user.getRequiredExp()), user.getPercent(), user.getProgressBar() - ); + ); } private boolean sendLevelInfo(Player viewer, LevelUser target) { LevelSystem system = main.levelSystem(); - return main.cache().lang().sendMessage( - viewer, Lang::getLevelInfo, - new String[]{"player","level","maxLevel","playerEXP","requiredEXP","percent","progressBar"}, + return main + .cache() + .lang() + .sendMessage( + viewer, + Lang::getLevelInfo, + new String[] { + "player", + "level", + "maxLevel", + "playerEXP", + "requiredEXP", + "percent", + "progressBar", + }, target.getName(), target.getLevel(), main.cache().levels().getMaxLevel(), @@ -176,10 +314,17 @@ private boolean sendLevelInfo(Player viewer, LevelUser target) { system.formatNumber(target.getRequiredExp()), target.getPercent(), target.getProgressBar() - ); + ); } - private boolean handleExp(Player player, LevelUser user, String arg, String perm, boolean allowMultiplier, ExpAction action) { + private boolean handleExp( + Player player, + LevelUser user, + String arg, + String perm, + boolean allowMultiplier, + ExpAction action + ) { perm = "admin.levels." + perm; if (isRestricted(player, perm) || notDouble(player, arg)) return true; @@ -187,7 +332,10 @@ private boolean handleExp(Player player, LevelUser user, String arg, String p switch (action) { case ADD: - user.addExp(value + "", main.cache().config().isMultiplierCommands()); + user.addExp( + value + "", + main.cache().config().isMultiplierCommands() + ); break; case SET: user.setExp(value + "", allowMultiplier, true, true); @@ -199,14 +347,32 @@ private boolean handleExp(Player player, LevelUser user, String arg, String p LevelSystem system = main.levelSystem(); - return main.cache().lang().sendMessage( - player, action.getMessage(), - new String[] {"player", action.getPlaceholder(), "level", "playerEXP"}, - user.getName(), arg, user.getLevel(), system.formatNumber(user.getExp()) - ); + return main + .cache() + .lang() + .sendMessage( + player, + action.getMessage(), + new String[] { + "player", + action.getPlaceholder(), + "level", + "playerEXP", + }, + user.getName(), + arg, + user.getLevel(), + system.formatNumber(user.getExp()) + ); } - private boolean handleLevel(Player player, LevelUser user, String arg, String perm, LevelAction action) { + private boolean handleLevel( + Player player, + LevelUser user, + String arg, + String perm, + LevelAction action + ) { if (isRestricted(player, perm) || notLong(player, arg)) return true; long value = Math.abs(Long.parseLong(arg)); @@ -224,16 +390,31 @@ private boolean handleLevel(Player player, LevelUser user, String arg, String LevelSystem system = main.levelSystem(); - return main.cache().lang().sendMessage( - player, action.getMessage(), - new String[] {"player", action.getPlaceholder(), "level", "playerEXP"}, - user.getName(), arg, user.getLevel(), system.formatNumber(user.getExp()) - ); + return main + .cache() + .lang() + .sendMessage( + player, + action.getMessage(), + new String[] { + "player", + action.getPlaceholder(), + "level", + "playerEXP", + }, + user.getName(), + arg, + user.getLevel(), + system.formatNumber(user.getExp()) + ); } private boolean isRestricted(Player player, String permissionKey) { - return player != null && (!player.hasPermission("CyberLevels." + permissionKey) && - main.cache().lang().sendMessage(player, Lang::getNoPermission)); + return ( + player != null && + (!player.hasPermission("CyberLevels." + permissionKey) && + main.cache().lang().sendMessage(player, Lang::getNoPermission)) + ); } private boolean notLong(Player player, String arg) { @@ -260,6 +441,7 @@ private enum ExpAction { REMOVE(Lang::getRemovedExp, "removedEXP"); private final Function> lang; + @Getter private final String placeholder; @@ -279,6 +461,7 @@ private enum LevelAction { REMOVE(Lang::getRemovedLevels, "removedLevels"); private final Function> lang; + @Getter private final String placeholder; From e9e1398073c6dcc193e18e36874255fe2a3a2952 Mon Sep 17 00:00:00 2001 From: Adam Klement Date: Sun, 8 Mar 2026 21:25:23 +0100 Subject: [PATCH 12/12] chore: Add support comments to better understand how some features work --- src/main/resources/config.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index e7fb94d..568fac0 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -2,7 +2,7 @@ config: # Should data be stored in MySQL? # Tested on MySQL 10.4 and MariaDB 10.6. mysql: - enabled: true + enabled: true # When enabled, will use type below. When disabled, this will fall-back to file storage. type: SQLITE # Can also be MARIADB, SQLITE, POSTGRES, POSTGRESQL host: 'localhost' port: '3306' @@ -13,6 +13,8 @@ config: ssl: true sqlite-file: "plugins/CyberLevels/data.db" + # Should the plugin use BigDecimal for all calculations? + # Can be required if you plan to use very large numbers, otherwise keep disabled. use-big-decimal-system: false # Should numbers be rounded?