Started using multi-threading to improve performance (#24)

This commit is contained in:
Artemis-the-gr8 2022-05-17 23:19:38 +02:00
parent a9dca1db83
commit 2709232e67
12 changed files with 448 additions and 372 deletions

View File

@ -3,8 +3,10 @@ package com.gmail.artemis.the.gr8.playerstats;
import com.gmail.artemis.the.gr8.playerstats.commands.ReloadCommand;
import com.gmail.artemis.the.gr8.playerstats.commands.StatCommand;
import com.gmail.artemis.the.gr8.playerstats.commands.TabCompleter;
import com.gmail.artemis.the.gr8.playerstats.filehandlers.ConfigHandler;
import com.gmail.artemis.the.gr8.playerstats.listeners.JoinListener;
import com.gmail.artemis.the.gr8.playerstats.utils.EnumHandler;
import com.gmail.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
import com.gmail.artemis.the.gr8.playerstats.utils.OutputFormatter;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;
@ -14,14 +16,14 @@ public class Main extends JavaPlugin {
@Override
public void onEnable() {
ConfigHandler config = new ConfigHandler(this);
EnumHandler enumHandler = new EnumHandler();
EnumHandler enumHandler = new EnumHandler(this);
OutputFormatter outputFormatter = new OutputFormatter(config);
StatManager statManager = new StatManager(enumHandler, this);
this.getCommand("statistic").setExecutor(new StatCommand(outputFormatter, statManager, this));
this.getCommand("statistic").setTabCompleter(new TabCompleter(
enumHandler, statManager,this));
//prepare private hashMap of offline players
OfflinePlayerHandler.updateOfflinePlayers();
this.getCommand("statistic").setExecutor(new StatCommand(outputFormatter, enumHandler, this));
this.getCommand("statistic").setTabCompleter(new TabCompleter(enumHandler, this));
this.getCommand("statisticreload").setExecutor(new ReloadCommand(config, outputFormatter, this));
Bukkit.getPluginManager().registerEvents(new JoinListener(), this);

View File

@ -1,250 +0,0 @@
package com.gmail.artemis.the.gr8.playerstats;
import com.gmail.artemis.the.gr8.playerstats.utils.EnumHandler;
import com.gmail.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
import org.bukkit.Material;
import org.bukkit.OfflinePlayer;
import org.bukkit.Statistic;
import org.bukkit.entity.EntityType;
import org.jetbrains.annotations.NotNull;
import java.util.*;
import java.util.stream.Collectors;
public class StatManager {
private final Main plugin;
private final EnumHandler enumHandler;
private final OfflinePlayerHandler offlinePlayerHandler;
private final List<String> statNames;
private final List<String> entityStatNames;
private final List<String> subStatEntryNames;
private final String className;
public StatManager(EnumHandler e, Main p) {
plugin = p;
enumHandler = e;
offlinePlayerHandler = OfflinePlayerHandler.getInstance();
statNames = Arrays.stream(Statistic.values()).map(
Statistic::toString).map(String::toLowerCase).toList();
entityStatNames = Arrays.stream(Statistic.values()).filter(statistic ->
statistic.getType().equals(Statistic.Type.ENTITY)).map(
Statistic::toString).map(String::toLowerCase).collect(Collectors.toList());
subStatEntryNames = new ArrayList<>();
subStatEntryNames.addAll(enumHandler.getBlockNames());
subStatEntryNames.addAll(enumHandler.getEntityTypeNames());
subStatEntryNames.addAll(enumHandler.getItemNames());
className = "StatManger";
}
//returns the integer associated with a certain statistic for a player
public int getStatistic(String statName, String subStatEntryName, String playerName) throws IllegalArgumentException, NullPointerException {
OfflinePlayer player = offlinePlayerHandler.getOfflinePlayer(playerName);
if (player != null) {
Statistic stat = getStatisticEnum(statName);
if (stat != null) {
return getPlayerStat(player, stat, subStatEntryName);
}
throw new IllegalArgumentException("Statistic " + statName + " could not be retrieved!");
}
throw new IllegalArgumentException("Player object for " + playerName + " could not be retrieved!");
}
public LinkedHashMap<String, Integer> getTopStatistics(String statName, String subStatEntry) throws IllegalArgumentException, NullPointerException {
String methodName = "getTopStatistic";
long time = System.currentTimeMillis();
Statistic stat = getStatisticEnum(statName);
time = plugin.logTimeTaken(className, methodName, time, 106);
if (stat != null) {
if (stat.getType().equals(Statistic.Type.UNTYPED) || isMatchingSubStatEntry(stat, subStatEntry)) {
HashMap<String, Integer> playerStats = new HashMap<>((int) (offlinePlayerHandler.getOfflinePlayerCount() * 1.05));
time = plugin.logTimeTaken(className, methodName, time, 111);
for (String playerName : offlinePlayerHandler.getAllOfflinePlayerNames()) {
OfflinePlayer player = offlinePlayerHandler.getOfflinePlayer(playerName);
if (player != null) {
try {
int statistic = getPlayerStat(player, stat, subStatEntry);
if (statistic > 0) {
playerStats.put(playerName, getPlayerStat(player, stat, subStatEntry));
}
}
catch (IllegalArgumentException ignored) {
}
}
}
time = plugin.logTimeTaken(className, methodName, time, 123);
LinkedHashMap<String, Integer> topStats = playerStats.entrySet().stream()
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
.limit(10).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
time = plugin.logTimeTaken(className, methodName, time, 128);
plugin.getLogger().info("Top 10: " + topStats);
plugin.logTimeTaken(className, methodName, time, 131);
return topStats;
}
throw new IllegalArgumentException(subStatEntry + " is not a valid substatistic entry for this statistic!");
}
throw new NullPointerException("Statistic " + statName + " could not be retrieved!");
}
public LinkedHashMap<String, Integer> getTopStatistics2(String statName, String subStatEntry) {
String methodName = "getTopStatistics2";
long time = System.currentTimeMillis();
Statistic stat = getStatisticEnum(statName);
time = plugin.logTimeTaken(className, methodName, time, 144);
if (stat != null) {
if (stat.getType().equals(Statistic.Type.UNTYPED) || isMatchingSubStatEntry(stat, subStatEntry)) {
HashMap<String, Integer> playerStats = new HashMap<>((int) (offlinePlayerHandler.getOfflinePlayerCount() * 1.05));
time = plugin.logTimeTaken(className, methodName, time, 149);
offlinePlayerHandler.getAllOfflinePlayerNames().forEach(playerName -> {
OfflinePlayer player = offlinePlayerHandler.getOfflinePlayer(playerName);
if (player != null)
try {
playerStats.put(playerName, getPlayerStat(player, stat, subStatEntry));
}
catch (IllegalArgumentException ignored) {
}
});
time = plugin.logTimeTaken(className, methodName, time, 162);
LinkedHashMap<String, Integer> topStats = playerStats.entrySet().stream()
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
.limit(10).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
time = plugin.logTimeTaken(className, methodName, time, 167);
plugin.getLogger().info("Top 10: " + topStats);
plugin.logTimeTaken(className, methodName, time, 170);
return topStats;
}
throw new IllegalArgumentException(subStatEntry + " is not a valid substatistic entry for this statistic!");
}
throw new NullPointerException("Statistic " + statName + " could not be retrieved!");
}
//gets the type of the statistic from the string, otherwise returns null (param: statName, not case sensitive)
public Statistic.Type getStatType(String statName) {
try {
return Statistic.valueOf(statName.toUpperCase()).getType();
}
catch (IllegalArgumentException e) {
plugin.getLogger().warning("IllegalArgumentException: " + statName + " is not a valid statistic name!");
return null;
}
catch (NullPointerException e) {
plugin.getLogger().warning("NullPointerException: please provide a statistic name!");
return null;
}
}
//returns the names of all general statistics in lowercase
public List<String> getStatNames() {
return statNames;
}
//returns all statistics that have type entities, in lowercase
public List<String> getEntityStatNames() {
return entityStatNames;
}
//checks if string is a valid statistic (param: statName, not case sensitive)
public boolean isStatistic(String statName) {
return statNames.contains(statName.toLowerCase());
}
//checks if this statistic is a subStatEntry, meaning it is a block, item or entity (param: statName, not case sensitive)
public boolean isSubStatEntry(String statName) {
return subStatEntryNames.contains(statName.toLowerCase());
}
//checks whether a subStatEntry is of the type that the statistic requires
public boolean isMatchingSubStatEntry(String statName, String subStatEntry) {
Statistic stat = getStatisticEnum(statName);
return (stat != null && isMatchingSubStatEntry(stat, subStatEntry));
}
private boolean isMatchingSubStatEntry(@NotNull Statistic stat, String subStatEntry) {
switch (stat.getType()) {
case ENTITY -> {
return subStatEntry != null && enumHandler.isEntityType(subStatEntry);
}
case ITEM -> {
return subStatEntry != null && enumHandler.isItem(subStatEntry);
}
case BLOCK -> {
return subStatEntry != null && enumHandler.isBlock(subStatEntry);
}
case UNTYPED -> {
return subStatEntry==null;
}
default -> {
return false;
}
}
}
private int getPlayerStat(@NotNull OfflinePlayer player, @NotNull Statistic stat, String subStatEntryName) throws IllegalArgumentException {
switch (stat.getType()) {
case UNTYPED -> {
return player.getStatistic(stat);
}
case BLOCK -> {
Material block = enumHandler.getBlock(subStatEntryName);
if (block != null) {
return player.getStatistic(stat, block);
}
else {
throw new IllegalArgumentException(subStatEntryName + " is not a valid block name!");
}
}
case ENTITY -> {
EntityType entity = enumHandler.getEntityType(subStatEntryName);
if (entity != null) {
return player.getStatistic(stat, entity);
}
else {
throw new IllegalArgumentException(subStatEntryName + " is not a valid entity name!");
}
}
case ITEM -> {
Material item = enumHandler.getItem(subStatEntryName);
if (item != null) {
return player.getStatistic(stat, item);
}
else {
throw new IllegalArgumentException(subStatEntryName + " is not a valid item name!");
}
}
default ->
throw new IllegalArgumentException("This statistic does not seem to be of type:untyped/block/entity/item, I think we should panic");
}
}
//returns the statistic enum constant, or null if non-existent (param: statName, not case sensitive)
private Statistic getStatisticEnum(String statName) {
try {
return Statistic.valueOf(statName.toUpperCase());
}
catch (IllegalArgumentException e) {
plugin.getLogger().warning("IllegalArgumentException: " + statName + " is not a valid statistic name!");
return null;
}
catch (NullPointerException e) {
plugin.getLogger().warning("NullPointerException: please provide a statistic name!");
return null;
}
}
}

View File

@ -0,0 +1,67 @@
package com.gmail.artemis.the.gr8.playerstats;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
public class StatRequest {
private final CommandSender sender;
private String statName;
private String subStatEntry;
private String playerName;
private boolean playerFlag;
private boolean topFlag;
//playerFlag and topFlag are false by default, will be set to true if "player" or "top" is in the args
public StatRequest(@NotNull CommandSender s) {
sender = s;
playerFlag = false;
topFlag = false;
}
public CommandSender getCommandSender() {
return sender;
}
public String getStatName() {
return statName;
}
public void setStatName(String statName) {
this.statName = statName;
}
public String getSubStatEntry() {
return subStatEntry;
}
public void setSubStatEntry(String subStatEntry) {
this.subStatEntry = subStatEntry;
}
public String getPlayerName() {
return playerName;
}
public void setPlayerName(String playerName) {
this.playerName = playerName;
}
//the "player" arg in the statCommand is a special case, because it could either be a valid subStatEntry, or indicate that the lookup action should target a specific player
//this is why the playerFlag exists - if this is true, and playerName is null, subStatEntry will be "player"
public boolean playerFlag() {
return playerFlag;
}
public void setPlayerFlag(boolean playerFlag) {
this.playerFlag = playerFlag;
}
public boolean topFlag() {
return topFlag;
}
public void setTopFlag(boolean topFlag) {
this.topFlag = topFlag;
}
}

View File

@ -0,0 +1,193 @@
package com.gmail.artemis.the.gr8.playerstats;
import com.gmail.artemis.the.gr8.playerstats.utils.EnumHandler;
import com.gmail.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
import com.gmail.artemis.the.gr8.playerstats.utils.OutputFormatter;
import org.bukkit.Material;
import org.bukkit.OfflinePlayer;
import org.bukkit.Statistic;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.EntityType;
import org.jetbrains.annotations.NotNull;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.stream.Collectors;
public class StatThread extends Thread {
private final StatRequest request;
private final EnumHandler enumHandler;
private final OutputFormatter outputFormatter;
private final Main plugin;
private String className = "StatThread";
//constructor (called on thread creation)
public StatThread(StatRequest s, EnumHandler e, OutputFormatter o, Main p) {
request = s;
enumHandler = e;
outputFormatter = o;
plugin = p;
}
//what the thread will do once started
@Override
public void run() throws IllegalStateException, NullPointerException {
long time = System.currentTimeMillis();
if (outputFormatter == null || plugin == null) {
throw new IllegalStateException("Not all classes off the plugin are running!");
}
if (request == null) {
throw new NullPointerException("No statistic request was found!");
}
CommandSender sender = request.getCommandSender();
String playerName = request.getPlayerName();
String statName = request.getStatName();
String subStatEntry = request.getSubStatEntry();
boolean topFlag = request.topFlag();
if (playerName != null) {
try {
sender.sendMessage(
outputFormatter.formatPlayerStat(
playerName, statName, subStatEntry, getStatistic(
statName, subStatEntry, playerName)));
} catch (Exception e) {
sender.sendMessage(outputFormatter.formatExceptions(e.toString()));
}
} else if (topFlag) {
try {
LinkedHashMap<String, Integer> topStats = getTopStatisticsForLoop(statName, subStatEntry);
time = plugin.logTimeTaken(className, "run(): for loop", time, 67);
LinkedHashMap<String, Integer> topStats2 = getTopStatisticsForEach(statName, subStatEntry);
time = plugin.logTimeTaken(className, "run(): for each loop", time, 70);
String top2 = outputFormatter.formatTopStats(topStats2, statName, subStatEntry);
sender.sendMessage(top2);
plugin.logTimeTaken(className, "run(): format output", time, 74);
} catch (Exception e) {
sender.sendMessage(outputFormatter.formatExceptions(e.toString()));
e.printStackTrace();
}
}
}
//returns the integer associated with a certain statistic for a player
private int getStatistic(String statName, String subStatEntryName, String playerName) throws IllegalArgumentException, NullPointerException {
OfflinePlayer player = OfflinePlayerHandler.getOfflinePlayer(playerName);
if (player != null) {
Statistic stat = enumHandler.getStatEnum(statName);
if (stat != null) {
return getPlayerStat(player, stat, subStatEntryName);
}
throw new IllegalArgumentException("Statistic " + statName + " could not be retrieved!");
}
throw new IllegalArgumentException("Player object for " + playerName + " could not be retrieved!");
}
private LinkedHashMap<String, Integer> getTopStatisticsForLoop(String statName, String subStatEntry) throws NullPointerException {
long time = System.currentTimeMillis();
Statistic stat = enumHandler.getStatEnum(statName);
if (stat != null) {
HashMap<String, Integer> playerStats = new HashMap<>((int) (OfflinePlayerHandler.getOfflinePlayerCount() * 1.05));
for (String playerName : OfflinePlayerHandler.getAllOfflinePlayerNames()) {
OfflinePlayer player = OfflinePlayerHandler.getOfflinePlayer(playerName);
if (player != null) {
try {
int statistic = getPlayerStat(player, stat, subStatEntry);
if (statistic > 0) {
playerStats.put(playerName, statistic);
}
} catch (IllegalArgumentException ignored) {
}
}
}
time = plugin.logTimeTaken(className, "for loop", time, 116);
LinkedHashMap<String, Integer> topStats = playerStats.entrySet().stream()
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
.limit(10).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
plugin.logTimeTaken(className, "for loop, sorting", time, 122);
return topStats;
}
throw new NullPointerException("Statistic " + statName + " could not be retrieved!");
}
private LinkedHashMap<String, Integer> getTopStatisticsForEach(String statName, String subStatEntry) {
long time = System.currentTimeMillis();
Statistic stat = enumHandler.getStatEnum(statName);
if (stat != null) {
HashMap<String, Integer> playerStats = new HashMap<>((int) (OfflinePlayerHandler.getOfflinePlayerCount() * 1.05));
OfflinePlayerHandler.getAllOfflinePlayerNames().forEach(playerName -> {
OfflinePlayer player = OfflinePlayerHandler.getOfflinePlayer(playerName);
if (player != null)
try {
playerStats.put(playerName, getPlayerStat(player, stat, subStatEntry));
} catch (IllegalArgumentException ignored) {
}
});
time = plugin.logTimeTaken(className, "for each loop", time, 145);
LinkedHashMap<String, Integer> topStats = playerStats.entrySet().stream()
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
.limit(10).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
plugin.logTimeTaken(className, "for each loop, sorting", time, 151);
return topStats;
}
throw new NullPointerException("Statistic " + statName + " could not be retrieved!");
}
private int getPlayerStat(@NotNull OfflinePlayer player, @NotNull Statistic stat, String subStatEntryName) throws IllegalArgumentException {
switch (stat.getType()) {
case UNTYPED -> {
return player.getStatistic(stat);
}
case BLOCK -> {
Material block = enumHandler.getBlock(subStatEntryName);
if (block != null) {
return player.getStatistic(stat, block);
}
else {
throw new IllegalArgumentException(subStatEntryName + " is not a valid block name!");
}
}
case ENTITY -> {
EntityType entity = enumHandler.getEntityType(subStatEntryName);
if (entity != null) {
return player.getStatistic(stat, entity);
}
else {
throw new IllegalArgumentException(subStatEntryName + " is not a valid entity name!");
}
}
case ITEM -> {
Material item = enumHandler.getItem(subStatEntryName);
if (item != null) {
return player.getStatistic(stat, item);
}
else {
throw new IllegalArgumentException(subStatEntryName + " is not a valid item name!");
}
}
default ->
throw new IllegalArgumentException("This statistic does not seem to be of type:untyped/block/entity/item, I think we should panic");
}
}
}

View File

@ -1,6 +1,6 @@
package com.gmail.artemis.the.gr8.playerstats.commands;
import com.gmail.artemis.the.gr8.playerstats.ConfigHandler;
import com.gmail.artemis.the.gr8.playerstats.filehandlers.ConfigHandler;
import com.gmail.artemis.the.gr8.playerstats.Main;
import com.gmail.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
import com.gmail.artemis.the.gr8.playerstats.utils.OutputFormatter;
@ -13,13 +13,11 @@ import org.jetbrains.annotations.NotNull;
public class ReloadCommand implements CommandExecutor {
private final ConfigHandler config;
private final OfflinePlayerHandler offlinePlayerHandler;
private final OutputFormatter outputFormatter;
private final Main plugin;
public ReloadCommand(ConfigHandler c, OutputFormatter o, Main p) {
outputFormatter = o;
offlinePlayerHandler = OfflinePlayerHandler.getInstance();
config = c;
plugin = p;
}
@ -32,11 +30,11 @@ public class ReloadCommand implements CommandExecutor {
outputFormatter.updateOutputColors();
time = plugin.logTimeTaken("ReloadCommand", "onCommand", time, 33);
offlinePlayerHandler.updateOfflinePlayers();
OfflinePlayerHandler.updateOfflinePlayers();
time = plugin.logTimeTaken("ReloadCommand", "onCommand", time, 36);
sender.sendMessage(ChatColor.GREEN + "Config reloaded!");
time = plugin.logTimeTaken("ReloadCommand", "onCommand", time, 39);
plugin.logTimeTaken("ReloadCommand", "onCommand", time, 39);
return true;
}
return false;

View File

@ -1,36 +1,28 @@
package com.gmail.artemis.the.gr8.playerstats.commands;
import com.gmail.artemis.the.gr8.playerstats.Main;
import com.gmail.artemis.the.gr8.playerstats.StatManager;
import com.gmail.artemis.the.gr8.playerstats.utils.EnumHandler;
import com.gmail.artemis.the.gr8.playerstats.StatRequest;
import com.gmail.artemis.the.gr8.playerstats.StatThread;
import com.gmail.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
import com.gmail.artemis.the.gr8.playerstats.utils.OutputFormatter;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.ComponentBuilder;
import net.md_5.bungee.api.chat.TextComponent;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.LinkedHashMap;
public class StatCommand implements CommandExecutor {
private final OfflinePlayerHandler offlinePlayerHandler;
private final OutputFormatter outputFormatter;
private final StatManager statManager;
private final EnumHandler enumHandler;
private final Main plugin;
public StatCommand(OutputFormatter o, StatManager s, Main p) {
public StatCommand(OutputFormatter o, EnumHandler e, Main p) {
outputFormatter = o;
statManager = s;
enumHandler = e;
plugin = p;
offlinePlayerHandler = OfflinePlayerHandler.getInstance();
}
@Override
@ -39,84 +31,65 @@ public class StatCommand implements CommandExecutor {
//part 1: collecting all relevant information from the args
if (args.length >= 2) {
String statName = null;
String subStatEntry = null;
String playerName = null;
boolean playerFlag = false;
boolean topFlag = false;
StatRequest request = new StatRequest(sender);
for (String arg : args) {
if (statManager.isStatistic(arg)) {
statName = (statName == null) ? arg : statName;
if (enumHandler.isStatistic(arg) && request.getStatName() == null) {
request.setStatName(arg);
}
else if (statManager.isSubStatEntry(arg)) {
else if (enumHandler.isSubStatEntry(arg)) {
if (arg.equalsIgnoreCase("player")) {
if (!playerFlag) {
subStatEntry = (subStatEntry == null) ? arg : subStatEntry;
playerFlag = true;
if (request.playerFlag()) {
if (request.getSubStatEntry() == null) request.setSubStatEntry(arg);
}
else {
request.setPlayerFlag(true);
}
}
else {
subStatEntry = (subStatEntry == null || playerFlag) ? arg : subStatEntry;
if (request.getSubStatEntry() == null) request.setSubStatEntry(arg);
}
}
else if (arg.equalsIgnoreCase("top")) {
topFlag = true;
request.setTopFlag(true);
}
else if (arg.equalsIgnoreCase("me") && sender instanceof Player) {
playerName = sender.getName();
request.setPlayerName(sender.getName());
}
else if (offlinePlayerHandler.isOfflinePlayerName(arg)) {
playerName = (playerName == null) ? arg : playerName;
else if (OfflinePlayerHandler.isOfflinePlayerName(arg) && request.getPlayerName() == null) {
request.setPlayerName(arg);
}
}
//part 2: sending the information to the StatManager
if (statName != null) {
subStatEntry = statManager.isMatchingSubStatEntry(statName, subStatEntry) ? subStatEntry : null;
//part 2: sending the information to the StatThread
if (isValidStatRequest(request)) {
StatThread statThread = new StatThread(request, enumHandler, outputFormatter, plugin);
statThread.start();
if (topFlag) {
try {
time = plugin.logTimeTaken("StatCommand", "onCommand", time, 76);
LinkedHashMap<String, Integer> topStats = statManager.getTopStatistics(statName, subStatEntry);
time = plugin.logTimeTaken("StatCommand", "onCommand", time, 79);
LinkedHashMap<String, Integer> topStats2 = statManager.getTopStatistics2(statName, subStatEntry);
time = plugin.logTimeTaken("StatCommand", "onCommand", time, 82);
String top = outputFormatter.formatTopStats(topStats, statName, subStatEntry);
String top2 = outputFormatter.formatTopStats(topStats2, statName, subStatEntry);
sender.sendMessage(top);
sender.sendMessage(top2);
return true;
}
catch (Exception e) {
sender.sendMessage(outputFormatter.formatExceptions(e.toString()));
e.printStackTrace();
}
}
else if (playerName != null) {
try {
BaseComponent[] component = new ComponentBuilder("hi?").color(ChatColor.of("#4a32a8")).create();
sender.spigot().sendMessage(component);
String msg = ChatColor.of("#f27d07") + "... hi";
sender.sendMessage(msg);
sender.sendMessage(outputFormatter.formatPlayerStat(playerName, statName, subStatEntry, statManager.getStatistic
(statName, subStatEntry, playerName)));
}
catch (Exception e) {
sender.sendMessage(outputFormatter.formatExceptions(e.toString()));
}
}
plugin.logTimeTaken("StatCommand", "onCommand", time, 71);
return true;
}
}
plugin.logTimeTaken("StatCommand", "onCommand", time, 90);
return true;
return false;
}
//check whether all necessary ingredients are present to proceed with a lookup
private boolean isValidStatRequest(StatRequest request) {
if (request.getStatName() != null) {
if (request.topFlag() || request.getPlayerName() != null) {
validatePlayerFlag(request);
return enumHandler.isValidStatEntry(request.getStatName(), request.getSubStatEntry());
}
}
return false;
}
//account for the fact that "player" could be either a subStatEntry or a flag to indicate the target for the lookup, and correct the request if necessary
private void validatePlayerFlag(StatRequest request) {
if (!enumHandler.isValidStatEntry(request.getStatName(), request.getSubStatEntry()) && request.playerFlag()) {
request.setSubStatEntry("player");
}
}
}

View File

@ -1,7 +1,6 @@
package com.gmail.artemis.the.gr8.playerstats.commands;
import com.gmail.artemis.the.gr8.playerstats.Main;
import com.gmail.artemis.the.gr8.playerstats.StatManager;
import com.gmail.artemis.the.gr8.playerstats.utils.EnumHandler;
import com.gmail.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
import org.bukkit.command.Command;
@ -15,16 +14,12 @@ import java.util.stream.Collectors;
public class TabCompleter implements org.bukkit.command.TabCompleter {
private final EnumHandler enumHandler;
private final OfflinePlayerHandler offlinePlayerHandler;
private final StatManager statManager;
private final Main plugin;
private final List<String> commandOptions;
public TabCompleter(EnumHandler e, StatManager s, Main p) {
public TabCompleter(EnumHandler e, Main p) {
enumHandler = e;
offlinePlayerHandler = OfflinePlayerHandler.getInstance();
statManager = s;
plugin = p;
commandOptions = new ArrayList<>();
@ -45,14 +40,14 @@ public class TabCompleter implements org.bukkit.command.TabCompleter {
//after typing "stat", suggest a list of viable statistics
if (args.length >= 1) {
if (args.length == 1) {
tabSuggestions = statManager.getStatNames().stream().filter(stat ->
tabSuggestions = enumHandler.getStatNames().stream().filter(stat ->
stat.contains(args[0].toLowerCase())).collect(Collectors.toList());
}
//after checking if args[0] is a viable statistic, suggest substatistic OR commandOptions
else {
if (statManager.isStatistic(args[args.length-2])) {
tabSuggestions = switch (statManager.getStatType(args[args.length-2])) {
if (enumHandler.isStatistic(args[args.length-2])) {
tabSuggestions = switch (enumHandler.getStatType(args[args.length-2])) {
case UNTYPED -> commandOptions;
case BLOCK -> enumHandler.getBlockNames().stream().filter(block ->
block.contains(args[args.length - 1])).collect(Collectors.toList());
@ -61,23 +56,22 @@ public class TabCompleter implements org.bukkit.command.TabCompleter {
case ENTITY -> enumHandler.getEntityTypeNames().stream().filter(entity ->
entity.contains(args[args.length - 1])).collect(Collectors.toList());
};
}
//if previous arg = "player", suggest playerNames
else if (args[args.length-2].equalsIgnoreCase("player")) {
if (args.length >= 3 && statManager.getEntityStatNames().contains(args[args.length-3].toLowerCase())) {
if (args.length >= 3 && enumHandler.getEntityStatNames().contains(args[args.length-3].toLowerCase())) {
tabSuggestions = commandOptions;
}
else {
tabSuggestions = offlinePlayerHandler.getAllOfflinePlayerNames().stream().filter(player ->
tabSuggestions = OfflinePlayerHandler.getAllOfflinePlayerNames().stream().filter(player ->
player.toLowerCase().contains(args[args.length-1].toLowerCase())).collect(Collectors.toList());
}
}
//after a substatistic, suggest commandOptions
else if (statManager.isSubStatEntry(args[args.length-2])) {
else if (enumHandler.isSubStatEntry(args[args.length-2])) {
tabSuggestions = commandOptions;
}
}

View File

@ -1,5 +1,6 @@
package com.gmail.artemis.the.gr8.playerstats;
package com.gmail.artemis.the.gr8.playerstats.filehandlers;
import com.gmail.artemis.the.gr8.playerstats.Main;
import org.bukkit.ChatColor;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.FileConfiguration;

View File

@ -8,16 +8,13 @@ import org.bukkit.event.player.PlayerJoinEvent;
public class JoinListener implements Listener {
private final OfflinePlayerHandler offlinePlayerHandler;
public JoinListener() {
offlinePlayerHandler = OfflinePlayerHandler.getInstance();
}
@EventHandler
public void onPlayerJoin(PlayerJoinEvent joinEvent) {
if (!joinEvent.getPlayer().hasPlayedBefore()) {
offlinePlayerHandler.updateOfflinePlayers();
OfflinePlayerHandler.updateOfflinePlayers();
}
}
}

View File

@ -1,28 +1,51 @@
package com.gmail.artemis.the.gr8.playerstats.utils;
import com.gmail.artemis.the.gr8.playerstats.Main;
import org.bukkit.Material;
import org.bukkit.Statistic;
import org.bukkit.entity.EntityType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class EnumHandler {
private final List<String> blockNames;
private final List<String> entityTypeNames;
private final List<String> itemNames;
private final List<String> statNames;
private final List<String> entityStatNames;
private final List<String> subStatEntryNames;
private final Main plugin;
public EnumHandler() {
public EnumHandler(Main p) {
plugin = p;
blockNames = Arrays.stream(Material.values()).filter(
Material::isBlock).map(Material::toString).map(String::toLowerCase).toList();
entityTypeNames = Arrays.stream(EntityType.values()).map(
EntityType::toString).map(String::toLowerCase).toList();
itemNames = Arrays.stream(Material.values()).filter(
Material::isItem).map(Material::toString).map(String::toLowerCase).toList();
statNames = Arrays.stream(Statistic.values()).map(
Statistic::toString).map(String::toLowerCase).toList();
entityStatNames = Arrays.stream(Statistic.values()).filter(statistic ->
statistic.getType().equals(Statistic.Type.ENTITY)).map(
Statistic::toString).map(String::toLowerCase).collect(Collectors.toList());
subStatEntryNames = new ArrayList<>();
subStatEntryNames.addAll(getBlockNames());
subStatEntryNames.addAll(getEntityTypeNames());
subStatEntryNames.addAll(getItemNames());
}
//checks whether the provided string is a valid item
public boolean isItem(String itemName) {
return itemNames.contains(itemName.toLowerCase());
}
@ -38,6 +61,7 @@ public class EnumHandler {
return itemNames;
}
//checks whether the provided string is a valid entity
public boolean isEntityType(String entityName) {
return entityTypeNames.contains(entityName.toLowerCase());
}
@ -49,7 +73,12 @@ public class EnumHandler {
try {
entityType = EntityType.valueOf(entityName.toUpperCase());
}
catch (IllegalArgumentException | NullPointerException exception) {
catch (IllegalArgumentException e) {
plugin.getLogger().warning("IllegalArgumentException: " + entityName + " is not a valid statistic name!");
return null;
}
catch (NullPointerException e) {
plugin.getLogger().warning("NullPointerException: please provide a statistic name!");
return null;
}
return entityType;
@ -60,6 +89,7 @@ public class EnumHandler {
return entityTypeNames;
}
//checks whether the provided string is a valid block
public boolean isBlock(String materialName) {
return blockNames.contains(materialName.toLowerCase());
}
@ -75,4 +105,85 @@ public class EnumHandler {
return blockNames;
}
//returns the statistic enum constant, or null if non-existent (param: statName, not case sensitive)
public Statistic getStatEnum(String statName) {
try {
return Statistic.valueOf(statName.toUpperCase());
}
catch (IllegalArgumentException e) {
plugin.getLogger().warning("IllegalArgumentException: " + statName + " is not a valid statistic name!");
return null;
}
catch (NullPointerException e) {
plugin.getLogger().warning("NullPointerException: please provide a statistic name!");
return null;
}
}
//gets the type of the statistic from the string, otherwise returns null (param: statName, not case sensitive)
public Statistic.Type getStatType(String statName) {
try {
return Statistic.valueOf(statName.toUpperCase()).getType();
}
catch (IllegalArgumentException e) {
plugin.getLogger().warning("IllegalArgumentException: " + statName + " is not a valid statistic name!");
return null;
}
catch (NullPointerException e) {
plugin.getLogger().warning("NullPointerException: please provide a statistic name!");
return null;
}
}
//checks if string is a valid statistic (param: statName, not case sensitive)
public boolean isStatistic(String statName) {
return statNames.contains(statName.toLowerCase());
}
//returns the names of all general statistics in lowercase
public List<String> getStatNames() {
return statNames;
}
//returns all statistics that have type entities, in lowercase
public List<String> getEntityStatNames() {
return entityStatNames;
}
//checks if this statistic is a subStatEntry, meaning it is a block, item or entity (param: statName, not case sensitive)
public boolean isSubStatEntry(String statName) {
return subStatEntryNames.contains(statName.toLowerCase());
}
//checks if string is a valid statistic (param: statName, not case sensitive)
public boolean isValidStatEntry(String statName) {
return isValidStatEntry(statName, null);
}
//checks whether a subStatEntry is of the type that the statistic requires
public boolean isValidStatEntry(String statName, String subStatEntry) {
Statistic stat = getStatEnum(statName);
return (stat != null && isMatchingSubStatEntry(stat, subStatEntry));
}
//returns true if subStatEntry matches the type the stat requires, or if stat is untyped and subStatEntry is null
private boolean isMatchingSubStatEntry(@NotNull Statistic stat, String subStatEntry) {
switch (stat.getType()) {
case ENTITY -> {
return subStatEntry != null && isEntityType(subStatEntry);
}
case ITEM -> {
return subStatEntry != null && isItem(subStatEntry);
}
case BLOCK -> {
return subStatEntry != null && isBlock(subStatEntry);
}
case UNTYPED -> {
return subStatEntry==null;
}
default -> {
return false;
}
}
}
}

View File

@ -7,41 +7,31 @@ import java.util.*;
public class OfflinePlayerHandler {
private static OfflinePlayerHandler instance;
private HashMap<String, OfflinePlayer> offlinePlayerMap;
private List<String> offlinePlayerNames;
private int totalOfflinePlayers;
private static HashMap<String, OfflinePlayer> offlinePlayerMap;
private static List<String> offlinePlayerNames;
private static int totalOfflinePlayers;
private OfflinePlayerHandler() {
updateOfflinePlayers();
}
public static OfflinePlayerHandler getInstance() {
if (instance == null) {
instance = new OfflinePlayerHandler();
}
return instance;
}
public boolean isOfflinePlayerName(String playerName) {
public static boolean isOfflinePlayerName(String playerName) {
return offlinePlayerNames.contains(playerName);
}
public OfflinePlayer getOfflinePlayer(String playerName) {
long time = System.currentTimeMillis();
public static OfflinePlayer getOfflinePlayer(String playerName) {
return offlinePlayerMap.get(playerName);
}
public int getOfflinePlayerCount() {
public static int getOfflinePlayerCount() {
return totalOfflinePlayers > 0 ? totalOfflinePlayers : 1;
}
public List<String> getAllOfflinePlayerNames() {
public static List<String> getAllOfflinePlayerNames() {
return offlinePlayerNames;
}
//stores a private HashMap with keys:playerName and values:OfflinePlayer, and a private list of the names for easy access
public void updateOfflinePlayers() {
public static void updateOfflinePlayers() {
long totalTime = System.currentTimeMillis();
long time = System.currentTimeMillis();
if (offlinePlayerMap == null) offlinePlayerMap = new HashMap<>();

View File

@ -1,6 +1,6 @@
package com.gmail.artemis.the.gr8.playerstats.utils;
import com.gmail.artemis.the.gr8.playerstats.ConfigHandler;
import com.gmail.artemis.the.gr8.playerstats.filehandlers.ConfigHandler;
import org.bukkit.ChatColor;
import org.bukkit.map.MinecraftFont;