Made ConfigHandler, LanguageKeyHandler, OfflinePlayerHandler and EnumHandler into singletons, merged RequestProcessor into RequestManager and wrote TabComplete logic (#88)

This commit is contained in:
Artemis-the-gr8 2022-10-21 17:34:08 +02:00
parent 6c9e8b2b9d
commit e158b4480d
21 changed files with 400 additions and 344 deletions

View File

@ -4,14 +4,13 @@ import com.artemis.the.gr8.playerstats.api.PlayerStats;
import com.artemis.the.gr8.playerstats.api.StatFormatter;
import com.artemis.the.gr8.playerstats.api.StatManager;
import com.artemis.the.gr8.playerstats.commands.*;
import com.artemis.the.gr8.playerstats.multithreading.ThreadManager;
import com.artemis.the.gr8.playerstats.statistic.RequestManager;
import com.artemis.the.gr8.playerstats.msg.OutputManager;
import com.artemis.the.gr8.playerstats.config.ConfigHandler;
import com.artemis.the.gr8.playerstats.listeners.JoinListener;
import com.artemis.the.gr8.playerstats.msg.msgutils.LanguageKeyHandler;
import com.artemis.the.gr8.playerstats.share.ShareManager;
import com.artemis.the.gr8.playerstats.statistic.RequestProcessor;
import com.artemis.the.gr8.playerstats.utils.EnumHandler;
import com.artemis.the.gr8.playerstats.utils.MyLogger;
import com.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
import me.clip.placeholderapi.PlaceholderAPIPlugin;
@ -39,9 +38,8 @@ public final class Main extends JavaPlugin implements PlayerStats {
private static ThreadManager threadManager;
private static LanguageKeyHandler languageKeyHandler;
private static OfflinePlayerHandler offlinePlayerHandler;
private static EnumHandler enumHandler;
private static RequestManager statRequestManager;
private static RequestManager requestManager;
private static OutputManager outputManager;
private static ShareManager shareManager;
@ -73,7 +71,7 @@ public final class Main extends JavaPlugin implements PlayerStats {
languageKeyHandler.reload();
offlinePlayerHandler.reload();
outputManager.updateSettings();
shareManager.updateSettings(config);
shareManager.updateSettings();
}
/**
@ -103,19 +101,17 @@ public final class Main extends JavaPlugin implements PlayerStats {
private void initializeMainClasses() {
pluginInstance = this;
playerStatsAPI = this;
adventure = BukkitAudiences.create(this);
enumHandler = new EnumHandler();
languageKeyHandler = new LanguageKeyHandler();
config = new ConfigHandler();
offlinePlayerHandler = new OfflinePlayerHandler(config);
shareManager = new ShareManager(config);
outputManager = new OutputManager(adventure, config, languageKeyHandler);
config = ConfigHandler.getInstance();
languageKeyHandler = LanguageKeyHandler.getInstance();
offlinePlayerHandler = OfflinePlayerHandler.getInstance();
RequestProcessor requestProcessor = new RequestProcessor(offlinePlayerHandler, outputManager, shareManager);
statRequestManager = new RequestManager(offlinePlayerHandler, requestProcessor);
threadManager = new ThreadManager(this, config, outputManager, statRequestManager);
outputManager = new OutputManager(adventure);
shareManager = new ShareManager();
requestManager = new RequestManager(outputManager, shareManager);
threadManager = new ThreadManager(this, outputManager);
}
/**
@ -123,16 +119,16 @@ public final class Main extends JavaPlugin implements PlayerStats {
* to the relevant commands.
*/
private void registerCommands() {
TabCompleter tabCompleter = new TabCompleter(enumHandler, offlinePlayerHandler);
TabCompleter tabCompleter = new TabCompleter();
PluginCommand statcmd = this.getCommand("statistic");
if (statcmd != null) {
statcmd.setExecutor(new StatCommand(outputManager, threadManager, config, offlinePlayerHandler, enumHandler));
statcmd.setExecutor(new StatCommand(outputManager, threadManager));
statcmd.setTabCompleter(tabCompleter);
}
PluginCommand excludecmd = this.getCommand("statisticexclude");
if (excludecmd != null) {
excludecmd.setExecutor(new ExcludeCommand(offlinePlayerHandler));
excludecmd.setExecutor(new ExcludeCommand());
excludecmd.setTabCompleter(tabCompleter);
}
@ -177,7 +173,7 @@ public final class Main extends JavaPlugin implements PlayerStats {
@Override
public StatManager getStatManager() {
return statRequestManager;
return requestManager;
}
@Override

View File

@ -1,6 +1,6 @@
package com.artemis.the.gr8.playerstats.api;
import com.artemis.the.gr8.playerstats.statistic.RequestProcessor;
import com.artemis.the.gr8.playerstats.statistic.RequestManager;
import com.artemis.the.gr8.playerstats.statistic.StatRequest;
import org.bukkit.Material;
import org.bukkit.Statistic;
@ -9,7 +9,7 @@ import org.jetbrains.annotations.NotNull;
/**
* Creates an executable {@link StatRequest}. This Request holds all
* the information PlayerStats needs to work with, and is used by the {@link RequestProcessor}
* the information PlayerStats needs to work with, and is used
* to get the desired statistic data.
*/
public interface RequestGenerator<T> {

View File

@ -11,8 +11,8 @@ public final class ExcludeCommand implements CommandExecutor {
private final OfflinePlayerHandler offlinePlayerHandler;
public ExcludeCommand(OfflinePlayerHandler offlinePlayerHandler) {
this.offlinePlayerHandler = offlinePlayerHandler;
public ExcludeCommand() {
this.offlinePlayerHandler = OfflinePlayerHandler.getInstance();
}
@Override

View File

@ -1,6 +1,6 @@
package com.artemis.the.gr8.playerstats.commands;
import com.artemis.the.gr8.playerstats.ThreadManager;
import com.artemis.the.gr8.playerstats.multithreading.ThreadManager;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;

View File

@ -1,6 +1,6 @@
package com.artemis.the.gr8.playerstats.commands;
import com.artemis.the.gr8.playerstats.ThreadManager;
import com.artemis.the.gr8.playerstats.multithreading.ThreadManager;
import com.artemis.the.gr8.playerstats.api.RequestGenerator;
import com.artemis.the.gr8.playerstats.config.ConfigHandler;
import com.artemis.the.gr8.playerstats.enums.StandardMessage;
@ -32,16 +32,15 @@ public final class StatCommand implements CommandExecutor {
private static ThreadManager threadManager;
private static OutputManager outputManager;
private static ConfigHandler config;
private final OfflinePlayerHandler offlinePlayerHandler;
private final ConfigHandler config;
private final EnumHandler enumHandler;
public StatCommand(OutputManager m, ThreadManager t, ConfigHandler c, OfflinePlayerHandler o, EnumHandler e) {
threadManager = t;
outputManager = m;
config = c;
offlinePlayerHandler = o;
enumHandler = e;
public StatCommand(OutputManager outputManager, ThreadManager threadManager) {
StatCommand.threadManager = threadManager;
StatCommand.outputManager = outputManager;
config = ConfigHandler.getInstance();
enumHandler = EnumHandler.getInstance();
}
@Override
@ -135,19 +134,19 @@ public final class StatCommand implements CommandExecutor {
switch (statistic.getType()) {
case UNTYPED -> request = requestGenerator.untyped(statistic);
case BLOCK -> {
Material block = EnumHandler.getBlockEnum(subStatName);
Material block = enumHandler.getBlockEnum(subStatName);
if (block != null) {
request = requestGenerator.blockOrItemType(statistic, block);
}
}
case ITEM -> {
Material item = EnumHandler.getItemEnum(subStatName);
Material item = enumHandler.getItemEnum(subStatName);
if (item != null) {
request = requestGenerator.blockOrItemType(statistic, item);
}
}
case ENTITY -> {
EntityType entity = EnumHandler.getEntityEnum(subStatName);
EntityType entity = enumHandler.getEntityEnum(subStatName);
if (entity != null) {
request = requestGenerator.entityType(statistic, entity);
}
@ -202,7 +201,7 @@ public final class StatCommand implements CommandExecutor {
}
}
if (statName != null) {
statistic = EnumHandler.getStatEnum(statName);
statistic = enumHandler.getStatEnum(statName);
argsToProcess = removeArg(statName);
}
}
@ -241,6 +240,8 @@ public final class StatCommand implements CommandExecutor {
@Contract(pure = true)
private @Nullable String tryToFindPlayerName(@NotNull String[] args) {
OfflinePlayerHandler offlinePlayerHandler = OfflinePlayerHandler.getInstance();
for (String arg : args) {
if (offlinePlayerHandler.isRelevantPlayer(arg)) {
return arg;

View File

@ -7,6 +7,7 @@ import org.bukkit.Statistic;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.EntityType;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -17,16 +18,18 @@ import java.util.stream.Collectors;
public final class TabCompleter implements org.bukkit.command.TabCompleter {
private final EnumHandler enumHandler;
private final OfflinePlayerHandler offlinePlayerHandler;
private final EnumHandler enumHandler;
private List<String> targetSuggestions;
private List<String> itemBrokenSuggestions;
private List<String> entitySuggestions;
private List<String> statCommandTargets;
private List<String> excludeCommandOptions;
private List<String> itemsThatCanBreak;
private List<String> entitiesThatCanDie;
public TabCompleter() {
offlinePlayerHandler = OfflinePlayerHandler.getInstance();
enumHandler = EnumHandler.getInstance();
public TabCompleter(EnumHandler enumHandler, OfflinePlayerHandler offlinePlayerHandler) {
this.enumHandler = enumHandler;
this.offlinePlayerHandler = offlinePlayerHandler;
prepareLists();
}
@ -46,50 +49,58 @@ public final class TabCompleter implements org.bukkit.command.TabCompleter {
}
private @Nullable List<String> getExcludeCommandSuggestions(@NotNull String[] args) {
if (args.length == 1) {
return getDynamicTabSuggestions(offlinePlayerHandler.getOfflinePlayerNames(), args[0]);
if (args.length == 0) {
return null;
}
return null;
List<String> tabSuggestions = new ArrayList<>();
if (args.length == 1) {
tabSuggestions = excludeCommandOptions;
}
else if (args.length == 2) {
tabSuggestions = switch (args[0]) {
case "add" -> offlinePlayerHandler.getOfflinePlayerNames();
case "remove" -> removablePlayerNames();
default -> tabSuggestions;
};
}
return getDynamicTabSuggestions(tabSuggestions, args[args.length-1]);
}
private @Nullable List<String> getStatCommandSuggestions(@NotNull String[] args) {
if (args.length == 1) {
return getFirstArgSuggestions(args[0]);
if (args.length == 0) {
return null;
}
else if (args.length > 1) {
String currentArg = args[args.length-1];
List<String> tabSuggestions = new ArrayList<>();
if (args.length == 1) {
tabSuggestions = firstStatCommandArgSuggestions();
}
else {
String previousArg = args[args.length-2];
//after checking if args[0] is a viable statistic, suggest sub-stat or targets
if (enumHandler.isStatistic(previousArg)) {
Statistic stat = EnumHandler.getStatEnum(previousArg);
Statistic stat = enumHandler.getStatEnum(previousArg);
if (stat != null) {
return getDynamicTabSuggestions(getSuggestionsAfterStat(stat), currentArg);
tabSuggestions = suggestionsAfterFirstStatCommandArg(stat);
}
}
else if (previousArg.equalsIgnoreCase("player")) {
if (args.length >= 3 && enumHandler.isEntityStatistic(args[args.length-3])) {
return targetSuggestions; //if arg before "player" was entity-sub-stat, suggest targets
tabSuggestions = statCommandTargets; //if arg before "player" was entity-sub-stat, suggest targets
}
else { //otherwise "player" is the target: suggest playerNames
return getDynamicTabSuggestions(offlinePlayerHandler.getOfflinePlayerNames(), currentArg);
tabSuggestions = offlinePlayerHandler.getOfflinePlayerNames();
}
}
//after a substatistic, suggest targets
else if (enumHandler.isSubStatEntry(previousArg)) {
return targetSuggestions;
tabSuggestions = statCommandTargets;
}
}
return null;
}
private List<String> getFirstArgSuggestions(String currentArg) {
List<String> suggestions = enumHandler.getStatNames();
suggestions.add("examples");
suggestions.add("help");
return getDynamicTabSuggestions(suggestions, currentArg);
return getDynamicTabSuggestions(tabSuggestions, args[args.length-1]);
}
/**
@ -103,52 +114,53 @@ public final class TabCompleter implements org.bukkit.command.TabCompleter {
.collect(Collectors.toList());
}
private List<String> getSuggestionsAfterStat(@NotNull Statistic stat) {
private @NotNull List<String> firstStatCommandArgSuggestions() {
List<String> suggestions = enumHandler.getAllStatNames();
suggestions.add("examples");
suggestions.add("help");
return suggestions;
}
private List<String> suggestionsAfterFirstStatCommandArg(@NotNull Statistic stat) {
switch (stat.getType()) {
case BLOCK -> {
return getAllBlockNames();
return enumHandler.getAllBlockNames();
}
case ITEM -> {
if (stat == Statistic.BREAK_ITEM) {
return getItemBrokenSuggestions();
return itemsThatCanBreak;
} else {
return getAllItemNames();
return enumHandler.getAllItemNames();
}
}
case ENTITY -> {
return getEntitySuggestions();
return entitiesThatCanDie;
}
default -> {
return targetSuggestions;
return statCommandTargets;
}
}
}
private List<String> getAllItemNames() {
return enumHandler.getItemNames();
}
private List<String> getItemBrokenSuggestions() {
return itemBrokenSuggestions;
}
private List<String> getAllBlockNames() {
return enumHandler.getBlockNames();
}
private List<String> getEntitySuggestions() {
return entitySuggestions;
@Contract(pure = true)
private @Nullable List<String> removablePlayerNames() {
return statCommandTargets;
}
private void prepareLists() {
targetSuggestions = new ArrayList<>();
targetSuggestions.add("top");
targetSuggestions.add("player");
targetSuggestions.add("server");
targetSuggestions.add("me");
statCommandTargets = new ArrayList<>();
statCommandTargets.add("top");
statCommandTargets.add("player");
statCommandTargets.add("server");
statCommandTargets.add("me");
excludeCommandOptions = new ArrayList<>();
excludeCommandOptions.add("add");
excludeCommandOptions.add("list");
excludeCommandOptions.add("remove");
//breaking an item means running its durability negative
itemBrokenSuggestions = Arrays.stream(Material.values())
itemsThatCanBreak = Arrays.stream(Material.values())
.parallel()
.filter(Material::isItem)
.filter(item -> item.getMaxDurability() != 0)
@ -157,7 +169,7 @@ public final class TabCompleter implements org.bukkit.command.TabCompleter {
.collect(Collectors.toList());
//the only statistics dealing with entities are killed_entity and entity_killed_by
entitySuggestions = Arrays.stream(EntityType.values())
entitiesThatCanDie = Arrays.stream(EntityType.values())
.parallel()
.filter(EntityType::isAlive)
.map(EntityType::toString)

View File

@ -13,10 +13,11 @@ import java.util.Map;
/** Handles all PlayerStats' config-settings. */
public final class ConfigHandler extends FileHandler {
private static volatile ConfigHandler instance;
private final int configVersion;
private FileConfiguration config;
public ConfigHandler() {
private ConfigHandler() {
super("config.yml");
config = super.getFileConfiguration();
@ -25,6 +26,20 @@ public final class ConfigHandler extends FileHandler {
MyLogger.setDebugLevel(getDebugLevel());
}
public static ConfigHandler getInstance() {
ConfigHandler localVar = instance;
if (localVar != null) {
return localVar;
}
synchronized (ConfigHandler.class) {
if (instance == null) {
instance = new ConfigHandler();
}
return instance;
}
}
@Override
public void reload() {
super.reload();

View File

@ -1,6 +1,6 @@
package com.artemis.the.gr8.playerstats.listeners;
import com.artemis.the.gr8.playerstats.ThreadManager;
import com.artemis.the.gr8.playerstats.multithreading.ThreadManager;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;

View File

@ -144,7 +144,9 @@ public final class MessageBuilder implements StatFormatter {
return componentFactory.pluginPrefix()
.append(space())
.append(componentFactory.message().content(
"Please add a valid " + EnumHandler.getSubStatTypeName(statType) + " to look up this statistic!"));
"Please add a valid " +
EnumHandler.getInstance().getSubStatTypeName(statType) +
" to look up this statistic!"));
}
public @NotNull TextComponent missingPlayerName() {
@ -160,7 +162,9 @@ public final class MessageBuilder implements StatFormatter {
.append(componentFactory.messageAccent().content("\"" + subStatName + "\""))
.append(space())
.append(componentFactory.message().content(
"is not a valid " + EnumHandler.getSubStatTypeName(statType) + "!"));
"is not a valid " +
EnumHandler.getInstance().getSubStatTypeName(statType) +
"!"));
}
public @NotNull TextComponent requestAlreadyRunning() {
@ -509,12 +513,14 @@ public final class MessageBuilder implements StatFormatter {
}
private TextComponent getStatAndSubStatNameComponent(Statistic statistic, @Nullable String subStatName, Target target) {
EnumHandler enumHandler = EnumHandler.getInstance();
String statKey = languageKeyHandler.getStatKey(statistic);
String subStatKey = switch (statistic.getType()) {
case UNTYPED -> null;
case ENTITY -> languageKeyHandler.getEntityKey(EnumHandler.getEntityEnum(subStatName));
case BLOCK -> languageKeyHandler.getBlockKey(EnumHandler.getBlockEnum(subStatName));
case ITEM -> languageKeyHandler.getItemKey(EnumHandler.getItemEnum(subStatName));
case ENTITY -> languageKeyHandler.getEntityKey(enumHandler.getEntityEnum(subStatName));
case BLOCK -> languageKeyHandler.getBlockKey(enumHandler.getBlockEnum(subStatName));
case ITEM -> languageKeyHandler.getItemKey(enumHandler.getItemEnum(subStatName));
};
if (subStatKey == null) {
subStatKey = StringUtils.prettify(subStatName);

View File

@ -34,17 +34,18 @@ import static com.artemis.the.gr8.playerstats.enums.StandardMessage.*;
public final class OutputManager {
private static BukkitAudiences adventure;
private static ConfigHandler config;
private static EnumMap<StandardMessage, Function<MessageBuilder, TextComponent>> standardMessages;
private final ConfigHandler config;
private final LanguageKeyHandler languageKeyHandler;
private MessageBuilder messageBuilder;
private MessageBuilder consoleMessageBuilder;
public OutputManager(BukkitAudiences adventure, ConfigHandler config, LanguageKeyHandler language) {
public OutputManager(BukkitAudiences adventure) {
OutputManager.adventure = adventure;
OutputManager.config = config;
languageKeyHandler = language;
languageKeyHandler = LanguageKeyHandler.getInstance();
config = ConfigHandler.getInstance();
getMessageBuilders();
prepareFunctions();
}

View File

@ -22,15 +22,30 @@ import java.util.regex.Pattern;
*/
public final class LanguageKeyHandler extends FileHandler {
private static volatile LanguageKeyHandler instance;
private static HashMap<Statistic, String> statisticKeys;
private final Pattern subStatKey;
public LanguageKeyHandler() {
private LanguageKeyHandler() {
super("language.yml");
statisticKeys = generateStatisticKeys();
subStatKey = Pattern.compile("(item|entity|block)\\.minecraft\\.");
}
public static LanguageKeyHandler getInstance() {
LanguageKeyHandler localVar = instance;
if (localVar != null) {
return localVar;
}
synchronized (LanguageKeyHandler.class) {
if (instance == null) {
instance = new LanguageKeyHandler();
}
return instance;
}
}
@Contract(pure = true)
public @NotNull String getKeyForBlockUnit() {
return "soundCategory.block";
@ -222,7 +237,7 @@ public final class LanguageKeyHandler extends FileHandler {
if (block == null) return null;
else if (block.toString().toLowerCase().contains("wall_banner")) { //replace wall_banner with regular banner, since there is no key for wall banners
String blockName = block.toString().toLowerCase().replace("wall_", "");
Material newBlock = EnumHandler.getBlockEnum(blockName);
Material newBlock = EnumHandler.getInstance().getBlockEnum(blockName);
return (newBlock != null) ? "block.minecraft." + newBlock.getKey().getKey() : null;
}
else {

View File

@ -1,6 +1,6 @@
package com.artemis.the.gr8.playerstats.reload;
package com.artemis.the.gr8.playerstats.multithreading;
import com.artemis.the.gr8.playerstats.ThreadManager;
import com.artemis.the.gr8.playerstats.config.ConfigHandler;
import com.artemis.the.gr8.playerstats.utils.MyLogger;
import com.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
import com.artemis.the.gr8.playerstats.utils.UnixTimeHandler;
@ -13,7 +13,7 @@ import java.util.concurrent.RecursiveAction;
/**
* The action that is executed when a reload-command is triggered.
*/
public final class PlayerLoadAction extends RecursiveAction {
final class PlayerLoadAction extends RecursiveAction {
private static int threshold;
@ -21,7 +21,6 @@ public final class PlayerLoadAction extends RecursiveAction {
private final int start;
private final int end;
private final int lastPlayedLimit;
private final ConcurrentHashMap<String, UUID> offlinePlayerUUIDs;
/**
@ -29,25 +28,19 @@ public final class PlayerLoadAction extends RecursiveAction {
* that should be included in statistic calculations.
*
* @param players array of all OfflinePlayers to filter and load
* @param lastPlayedLimit optional limit for amount of days ago players last played
* @param offlinePlayerUUIDs the ConcurrentHashMap to put playerNames and UUIDs in
* @see OfflinePlayerHandler
*/
public PlayerLoadAction(OfflinePlayer[] players,
int lastPlayedLimit, ConcurrentHashMap<String, UUID> offlinePlayerUUIDs) {
this(players, 0, players.length, lastPlayedLimit, offlinePlayerUUIDs);
public PlayerLoadAction(OfflinePlayer[] players, ConcurrentHashMap<String, UUID> offlinePlayerUUIDs) {
this(players, 0, players.length, offlinePlayerUUIDs);
}
private PlayerLoadAction(OfflinePlayer[] players, int start, int end,
int lastPlayedLimit, ConcurrentHashMap<String, UUID> offlinePlayerUUIDs) {
private PlayerLoadAction(OfflinePlayer[] players, int start, int end, ConcurrentHashMap<String, UUID> offlinePlayerUUIDs) {
threshold = ThreadManager.getTaskThreshold();
this.players = players;
this.start = start;
this.end = end;
this.lastPlayedLimit = lastPlayedLimit;
this.offlinePlayerUUIDs = offlinePlayerUUIDs;
MyLogger.subActionCreated(Thread.currentThread().getName());
@ -62,9 +55,9 @@ public final class PlayerLoadAction extends RecursiveAction {
else {
final int split = length / 2;
final PlayerLoadAction subTask1 = new PlayerLoadAction(players, start, (start + split),
lastPlayedLimit, offlinePlayerUUIDs);
offlinePlayerUUIDs);
final PlayerLoadAction subTask2 = new PlayerLoadAction(players, (start + split), end,
lastPlayedLimit, offlinePlayerUUIDs);
offlinePlayerUUIDs);
//queue and compute all subtasks in the right order
invokeAll(subTask1, subTask2);
@ -72,11 +65,16 @@ public final class PlayerLoadAction extends RecursiveAction {
}
private void process() {
OfflinePlayerHandler offlinePlayerHandler = OfflinePlayerHandler.getInstance();
int lastPlayedLimit = ConfigHandler.getInstance().getLastPlayedLimit();
for (int i = start; i < end; i++) {
OfflinePlayer player = players[i];
String playerName = player.getName();
MyLogger.actionRunning(Thread.currentThread().getName());
if (playerName != null && UnixTimeHandler.hasPlayedSince(lastPlayedLimit, player.getLastPlayed())) {
if (playerName != null &&
!offlinePlayerHandler.isExcluded(player.getUniqueId()) &&
UnixTimeHandler.hasPlayedSince(lastPlayedLimit, player.getLastPlayed())) {
offlinePlayerUUIDs.put(playerName, player.getUniqueId());
}
}

View File

@ -1,20 +1,14 @@
package com.artemis.the.gr8.playerstats.reload;
package com.artemis.the.gr8.playerstats.multithreading;
import com.artemis.the.gr8.playerstats.Main;
import com.artemis.the.gr8.playerstats.share.ShareManager;
import com.artemis.the.gr8.playerstats.enums.StandardMessage;
import com.artemis.the.gr8.playerstats.msg.OutputManager;
import com.artemis.the.gr8.playerstats.msg.msgutils.LanguageKeyHandler;
import com.artemis.the.gr8.playerstats.statistic.RequestProcessor;
import com.artemis.the.gr8.playerstats.statistic.StatThread;
import com.artemis.the.gr8.playerstats.utils.MyLogger;
import com.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
import com.artemis.the.gr8.playerstats.enums.DebugLevel;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.Nullable;
/** The Thread that is in charge of reloading PlayerStats. */
public final class ReloadThread extends Thread {
final class ReloadThread extends Thread {
private final Main main;
private static OutputManager outputManager;
@ -34,13 +28,8 @@ public final class ReloadThread extends Thread {
}
/**
* This method will perform a series of tasks. If a {@link StatThread}
* This method will call reload() from Main. If a {@link StatThread}
* is still running, it will join the statThread and wait for it to finish.
* Then, it will reload the config, update the {@link LanguageKeyHandler},
* the {@link OfflinePlayerHandler}, the {@link DebugLevel}, update
* the share-settings in {@link ShareManager} and topListSize-settings
* in {@link RequestProcessor}, and update the MessageBuilders in the
* {@link OutputManager}.
*/
@Override
public void run() {

View File

@ -1,6 +1,6 @@
package com.artemis.the.gr8.playerstats.statistic;
package com.artemis.the.gr8.playerstats.multithreading;
import com.artemis.the.gr8.playerstats.ThreadManager;
import com.artemis.the.gr8.playerstats.statistic.StatRequest;
import com.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
import com.artemis.the.gr8.playerstats.utils.MyLogger;
import com.google.common.collect.ImmutableList;
@ -16,8 +16,6 @@ import java.util.concurrent.RecursiveTask;
final class StatAction extends RecursiveTask<ConcurrentHashMap<String, Integer>> {
private static int threshold;
private final OfflinePlayerHandler offlinePlayerHandler;
private final ImmutableList<String> playerNames;
private final StatRequest.Settings requestSettings;
private final ConcurrentHashMap<String, Integer> allStats;
@ -28,15 +26,13 @@ final class StatAction extends RecursiveTask<ConcurrentHashMap<String, Integer>>
* ForkJoinPool, and returns the ConcurrentHashMap when
* everything is done.
*
* @param offlinePlayerHandler the OfflinePlayerHandler to convert playerNames into Players
* @param playerNames ImmutableList of playerNames for players that should be included in stat calculations
* @param requestSettings a validated requestSettings object
* @param allStats the ConcurrentHashMap to put the results on
*/
public StatAction(OfflinePlayerHandler offlinePlayerHandler, ImmutableList<String> playerNames, StatRequest.Settings requestSettings, ConcurrentHashMap<String, Integer> allStats) {
public StatAction(ImmutableList<String> playerNames, StatRequest.Settings requestSettings, ConcurrentHashMap<String, Integer> allStats) {
threshold = ThreadManager.getTaskThreshold();
this.offlinePlayerHandler = offlinePlayerHandler;
this.playerNames = playerNames;
this.requestSettings = requestSettings;
this.allStats = allStats;
@ -50,8 +46,8 @@ final class StatAction extends RecursiveTask<ConcurrentHashMap<String, Integer>>
return getStatsDirectly();
}
else {
final StatAction subTask1 = new StatAction(offlinePlayerHandler, playerNames.subList(0, playerNames.size()/2), requestSettings, allStats);
final StatAction subTask2 = new StatAction(offlinePlayerHandler, playerNames.subList(playerNames.size()/2, playerNames.size()), requestSettings, allStats);
final StatAction subTask1 = new StatAction(playerNames.subList(0, playerNames.size()/2), requestSettings, allStats);
final StatAction subTask2 = new StatAction(playerNames.subList(playerNames.size()/2, playerNames.size()), requestSettings, allStats);
//queue and compute all subtasks in the right order
subTask1.fork();
@ -61,6 +57,8 @@ final class StatAction extends RecursiveTask<ConcurrentHashMap<String, Integer>>
}
private ConcurrentHashMap<String, Integer> getStatsDirectly() {
OfflinePlayerHandler offlinePlayerHandler = OfflinePlayerHandler.getInstance();
Iterator<String> iterator = playerNames.iterator();
if (iterator.hasNext()) {
do {

View File

@ -1,10 +1,11 @@
package com.artemis.the.gr8.playerstats.statistic;
package com.artemis.the.gr8.playerstats.multithreading;
import com.artemis.the.gr8.playerstats.ThreadManager;
import com.artemis.the.gr8.playerstats.msg.OutputManager;
import com.artemis.the.gr8.playerstats.statistic.RequestManager;
import com.artemis.the.gr8.playerstats.statistic.StatRequest;
import com.artemis.the.gr8.playerstats.statistic.StatResult;
import com.artemis.the.gr8.playerstats.utils.MyLogger;
import com.artemis.the.gr8.playerstats.enums.StandardMessage;
import com.artemis.the.gr8.playerstats.reload.ReloadThread;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.Nullable;
@ -13,17 +14,15 @@ import java.util.*;
/**
* The Thread that is in charge of getting and calculating statistics.
*/
public final class StatThread extends Thread {
final class StatThread extends Thread {
private static OutputManager outputManager;
private final RequestManager statManager;
private final ReloadThread reloadThread;
private final StatRequest<?> statRequest;
public StatThread(OutputManager m, RequestManager stat, int ID, StatRequest<?> s, @Nullable ReloadThread r) {
public StatThread(OutputManager m, int ID, StatRequest<?> s, @Nullable ReloadThread r) {
outputManager = m;
statManager = stat;
reloadThread = r;
statRequest = s;
@ -54,7 +53,7 @@ public final class StatThread extends Thread {
}
try {
StatResult<?> result = statManager.execute(statRequest);
StatResult<?> result = RequestManager.execute(statRequest);
outputManager.sendToCommandSender(statRequester, result.formattedComponent());
}
catch (ConcurrentModificationException e) {

View File

@ -1,17 +1,20 @@
package com.artemis.the.gr8.playerstats;
package com.artemis.the.gr8.playerstats.multithreading;
import com.artemis.the.gr8.playerstats.Main;
import com.artemis.the.gr8.playerstats.msg.OutputManager;
import com.artemis.the.gr8.playerstats.config.ConfigHandler;
import com.artemis.the.gr8.playerstats.enums.StandardMessage;
import com.artemis.the.gr8.playerstats.reload.ReloadThread;
import com.artemis.the.gr8.playerstats.statistic.StatThread;
import com.artemis.the.gr8.playerstats.statistic.StatRequest;
import com.artemis.the.gr8.playerstats.statistic.RequestManager;
import com.artemis.the.gr8.playerstats.utils.MyLogger;
import com.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
import com.google.common.collect.ImmutableList;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
/**
* The ThreadManager is in charge of the Threads that PlayerStats
@ -29,20 +32,18 @@ public final class ThreadManager {
private int reloadThreadID;
private final Main main;
private static ConfigHandler config;
private final ConfigHandler config;
private static OutputManager outputManager;
private final RequestManager statManager;
private ReloadThread activatedReloadThread;
private StatThread activatedStatThread;
private final HashMap<String, Thread> statThreads;
private static long lastRecordedCalcTime;
public ThreadManager(Main main, ConfigHandler config, OutputManager outputManager, RequestManager statManager) {
public ThreadManager(Main main, OutputManager outputManager) {
this.main = main;
ThreadManager.config = config;
this.config = ConfigHandler.getInstance();
ThreadManager.outputManager = outputManager;
this.statManager = statManager;
statThreads = new HashMap<>();
statThreadID = 0;
@ -50,10 +51,27 @@ public final class ThreadManager {
lastRecordedCalcTime = 0;
}
public static int getTaskThreshold() {
static int getTaskThreshold() {
return threshold;
}
public static @NotNull StatAction getStatAction(StatRequest.Settings requestSettings) {
OfflinePlayerHandler offlinePlayerHandler = OfflinePlayerHandler.getInstance();
ImmutableList<String> relevantPlayerNames = ImmutableList.copyOf(offlinePlayerHandler.getOfflinePlayerNames());
ConcurrentHashMap<String, Integer> resultingStatNumbers = new ConcurrentHashMap<>(relevantPlayerNames.size());
StatAction task = new StatAction(relevantPlayerNames, requestSettings, resultingStatNumbers);
MyLogger.actionCreated(relevantPlayerNames.size());
return task;
}
public static @NotNull PlayerLoadAction getPlayerLoadAction(OfflinePlayer[] playersToLoad, ConcurrentHashMap<String, UUID> mapToFill) {
PlayerLoadAction task = new PlayerLoadAction(playersToLoad, mapToFill);
MyLogger.actionCreated(playersToLoad != null ? playersToLoad.length : 0);
return task;
}
public void startReloadThread(CommandSender sender) {
if (activatedReloadThread == null || !activatedReloadThread.isAlive()) {
reloadThreadID += 1;
@ -100,7 +118,7 @@ public final class ThreadManager {
}
private void startNewStatThread(StatRequest<?> request) {
activatedStatThread = new StatThread(outputManager, statManager, statThreadID, request, activatedReloadThread);
activatedStatThread = new StatThread(outputManager, statThreadID, request, activatedReloadThread);
statThreads.put(request.getSettings().getCommandSender().getName(), activatedStatThread);
activatedStatThread.start();
}

View File

@ -33,15 +33,16 @@ public final class ShareManager {
private ConcurrentHashMap<String, Instant> shareTimeStamp;
private ArrayBlockingQueue<Integer> sharedResults;
public ShareManager(ConfigHandler config) {
updateSettings(config);
public ShareManager() {
updateSettings();
}
public static boolean isEnabled() {
return isEnabled;
}
public void updateSettings(ConfigHandler config) {
public void updateSettings() {
ConfigHandler config = ConfigHandler.getInstance();
isEnabled = config.allowStatSharing() && config.useHoverText();
waitingTime = config.getStatShareWaitingTime();

View File

@ -2,10 +2,22 @@ package com.artemis.the.gr8.playerstats.statistic;
import com.artemis.the.gr8.playerstats.api.RequestGenerator;
import com.artemis.the.gr8.playerstats.api.StatManager;
import com.artemis.the.gr8.playerstats.msg.FormattingFunction;
import com.artemis.the.gr8.playerstats.msg.OutputManager;
import com.artemis.the.gr8.playerstats.multithreading.ThreadManager;
import com.artemis.the.gr8.playerstats.share.ShareManager;
import com.artemis.the.gr8.playerstats.utils.MyLogger;
import com.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
import net.kyori.adventure.text.TextComponent;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.jetbrains.annotations.NotNull;
import java.util.LinkedHashMap;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ForkJoinPool;
import java.util.stream.Collectors;
/**
* Turns user input into a {@link StatRequest} that can be
@ -13,15 +25,15 @@ import java.util.LinkedHashMap;
*/
public final class RequestManager implements StatManager {
private static OfflinePlayerHandler offlinePlayerHandler;
private final RequestProcessor processor;
private static RequestProcessor processor;
private final OfflinePlayerHandler offlinePlayerHandler;
public RequestManager(OfflinePlayerHandler offlinePlayerHandler, RequestProcessor processor) {
RequestManager.offlinePlayerHandler = offlinePlayerHandler;
this.processor = processor;
public RequestManager(OutputManager outputManager, ShareManager shareManager) {
processor = new RequestProcessor(outputManager, shareManager);
offlinePlayerHandler = OfflinePlayerHandler.getInstance();
}
public StatResult<?> execute(@NotNull StatRequest<?> request) {
public static StatResult<?> execute(@NotNull StatRequest<?> request) {
return switch (request.getSettings().getTarget()) {
case PLAYER -> processor.processPlayerRequest(request.getSettings());
case SERVER -> processor.processServerRequest(request.getSettings());
@ -64,4 +76,107 @@ public final class RequestManager implements StatManager {
public StatResult<LinkedHashMap<String, Integer>> executeTopRequest(StatRequest<LinkedHashMap<String, Integer>> request) {
return processor.processTopRequest(request.getSettings());
}
private final class RequestProcessor {
private static OutputManager outputManager;
private static ShareManager shareManager;
public RequestProcessor(OutputManager outputManager, ShareManager shareManager) {
RequestProcessor.outputManager = outputManager;
RequestProcessor.shareManager = shareManager;
}
public @NotNull StatResult<Integer> processPlayerRequest(StatRequest.Settings requestSettings) {
int stat = getPlayerStat(requestSettings);
FormattingFunction formattingFunction = outputManager.formatPlayerStat(requestSettings, stat);
TextComponent formattedResult = processFunction(requestSettings.getCommandSender(), formattingFunction);
String resultAsString = outputManager.textComponentToString(formattedResult);
return new StatResult<>(stat, formattedResult, resultAsString);
}
public @NotNull StatResult<Long> processServerRequest(StatRequest.Settings requestSettings) {
long stat = getServerStat(requestSettings);
FormattingFunction formattingFunction = outputManager.formatServerStat(requestSettings, stat);
TextComponent formattedResult = processFunction(requestSettings.getCommandSender(), formattingFunction);
String resultAsString = outputManager.textComponentToString(formattedResult);
return new StatResult<>(stat, formattedResult, resultAsString);
}
public @NotNull StatResult<LinkedHashMap<String, Integer>> processTopRequest(StatRequest.Settings requestSettings) {
LinkedHashMap<String, Integer> stats = getTopStats(requestSettings);
FormattingFunction formattingFunction = outputManager.formatTopStats(requestSettings, stats);
TextComponent formattedResult = processFunction(requestSettings.getCommandSender(), formattingFunction);
String resultAsString = outputManager.textComponentToString(formattedResult);
return new StatResult<>(stats, formattedResult, resultAsString);
}
private int getPlayerStat(@NotNull StatRequest.Settings requestSettings) {
OfflinePlayer player = offlinePlayerHandler.getOfflinePlayer(requestSettings.getPlayerName());
return switch (requestSettings.getStatistic().getType()) {
case UNTYPED -> player.getStatistic(requestSettings.getStatistic());
case ENTITY -> player.getStatistic(requestSettings.getStatistic(), requestSettings.getEntity());
case BLOCK -> player.getStatistic(requestSettings.getStatistic(), requestSettings.getBlock());
case ITEM -> player.getStatistic(requestSettings.getStatistic(), requestSettings.getItem());
};
}
private long getServerStat(StatRequest.Settings requestSettings) {
List<Integer> numbers = getAllStatsAsync(requestSettings)
.values()
.parallelStream()
.toList();
return numbers.parallelStream().mapToLong(Integer::longValue).sum();
}
private LinkedHashMap<String, Integer> getTopStats(StatRequest.Settings requestSettings) {
return getAllStatsAsync(requestSettings).entrySet().stream()
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
.limit(requestSettings.getTopListSize())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
}
private TextComponent processFunction(CommandSender sender, FormattingFunction function) {
if (outputShouldBeStored(sender)) {
int shareCode = shareManager.saveStatResult(sender.getName(), function.getResultWithSharerName(sender));
return function.getResultWithShareButton(shareCode);
}
return function.getDefaultResult();
}
private boolean outputShouldBeStored(CommandSender sender) {
return !(sender instanceof ConsoleCommandSender) &&
ShareManager.isEnabled() &&
shareManager.senderHasPermission(sender);
}
/**
* Invokes a bunch of worker pool threads to get the statistics for
* all players that are stored in the {@link OfflinePlayerHandler}).
*/
private @NotNull ConcurrentHashMap<String, Integer> getAllStatsAsync(StatRequest.Settings requestSettings) {
long time = System.currentTimeMillis();
ForkJoinPool commonPool = ForkJoinPool.commonPool();
ConcurrentHashMap<String, Integer> allStats;
try {
allStats = commonPool.invoke(ThreadManager.getStatAction(requestSettings));
} catch (ConcurrentModificationException e) {
MyLogger.logWarning("The requestSettings could not be executed due to a ConcurrentModificationException. " +
"This likely happened because Bukkit hasn't fully initialized all player-data yet. " +
"Try again and it should be fine!");
throw new ConcurrentModificationException(e.toString());
}
MyLogger.actionFinished();
ThreadManager.recordCalcTime(System.currentTimeMillis() - time);
MyLogger.logMediumLevelTask("Calculated all stats", time);
return allStats;
}
}
}

View File

@ -1,136 +0,0 @@
package com.artemis.the.gr8.playerstats.statistic;
import com.artemis.the.gr8.playerstats.ThreadManager;
import com.artemis.the.gr8.playerstats.msg.FormattingFunction;
import com.artemis.the.gr8.playerstats.msg.OutputManager;
import com.artemis.the.gr8.playerstats.share.ShareManager;
import com.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
import com.artemis.the.gr8.playerstats.utils.MyLogger;
import com.google.common.collect.ImmutableList;
import net.kyori.adventure.text.TextComponent;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.jetbrains.annotations.NotNull;
import java.util.*;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ForkJoinPool;
import java.util.stream.Collectors;
public final class RequestProcessor {
private final OfflinePlayerHandler offlinePlayerHandler;
private static OutputManager outputManager;
private static ShareManager shareManager;
public RequestProcessor(OfflinePlayerHandler offlinePlayerHandler, OutputManager outputManager, ShareManager shareManager) {
this.offlinePlayerHandler = offlinePlayerHandler;
RequestProcessor.outputManager = outputManager;
RequestProcessor.shareManager = shareManager;
}
public @NotNull StatResult<Integer> processPlayerRequest(StatRequest.Settings requestSettings) {
int stat = getPlayerStat(requestSettings);
FormattingFunction formattingFunction = outputManager.formatPlayerStat(requestSettings, stat);
TextComponent formattedResult = processFunction(requestSettings.getCommandSender(), formattingFunction);
String resultAsString = outputManager.textComponentToString(formattedResult);
return new StatResult<>(stat, formattedResult, resultAsString);
}
public @NotNull StatResult<Long> processServerRequest(StatRequest.Settings requestSettings) {
long stat = getServerStat(requestSettings);
FormattingFunction formattingFunction = outputManager.formatServerStat(requestSettings, stat);
TextComponent formattedResult = processFunction(requestSettings.getCommandSender(), formattingFunction);
String resultAsString = outputManager.textComponentToString(formattedResult);
return new StatResult<>(stat, formattedResult, resultAsString);
}
public @NotNull StatResult<LinkedHashMap<String, Integer>> processTopRequest(StatRequest.Settings requestSettings) {
LinkedHashMap<String, Integer> stats = getTopStats(requestSettings);
FormattingFunction formattingFunction = outputManager.formatTopStats(requestSettings, stats);
TextComponent formattedResult = processFunction(requestSettings.getCommandSender(), formattingFunction);
String resultAsString = outputManager.textComponentToString(formattedResult);
return new StatResult<>(stats, formattedResult, resultAsString);
}
private int getPlayerStat(@NotNull StatRequest.Settings requestSettings) {
OfflinePlayer player = offlinePlayerHandler.getOfflinePlayer(requestSettings.getPlayerName());
return switch (requestSettings.getStatistic().getType()) {
case UNTYPED -> player.getStatistic(requestSettings.getStatistic());
case ENTITY -> player.getStatistic(requestSettings.getStatistic(), requestSettings.getEntity());
case BLOCK -> player.getStatistic(requestSettings.getStatistic(), requestSettings.getBlock());
case ITEM -> player.getStatistic(requestSettings.getStatistic(), requestSettings.getItem());
};
}
private long getServerStat(StatRequest.Settings requestSettings) {
List<Integer> numbers = getAllStatsAsync(requestSettings)
.values()
.parallelStream()
.toList();
return numbers.parallelStream().mapToLong(Integer::longValue).sum();
}
private LinkedHashMap<String, Integer> getTopStats(StatRequest.Settings requestSettings) {
return getAllStatsAsync(requestSettings).entrySet().stream()
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
.limit(requestSettings.getTopListSize())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
}
private TextComponent processFunction(CommandSender sender, FormattingFunction function) {
if (outputShouldBeStored(sender)) {
int shareCode = shareManager.saveStatResult(sender.getName(), function.getResultWithSharerName(sender));
return function.getResultWithShareButton(shareCode);
}
return function.getDefaultResult();
}
private boolean outputShouldBeStored(CommandSender sender) {
return !(sender instanceof ConsoleCommandSender) &&
ShareManager.isEnabled() &&
shareManager.senderHasPermission(sender);
}
/**
* Invokes a bunch of worker pool threads to get the statistics for
* all players that are stored in the {@link OfflinePlayerHandler}).
*/
private @NotNull ConcurrentHashMap<String, Integer> getAllStatsAsync(StatRequest.Settings requestSettings) {
long time = System.currentTimeMillis();
ForkJoinPool commonPool = ForkJoinPool.commonPool();
ConcurrentHashMap<String, Integer> allStats;
try {
allStats = commonPool.invoke(getStatTask(requestSettings));
} catch (ConcurrentModificationException e) {
MyLogger.logWarning("The requestSettings could not be executed due to a ConcurrentModificationException. " +
"This likely happened because Bukkit hasn't fully initialized all player-data yet. " +
"Try again and it should be fine!");
throw new ConcurrentModificationException(e.toString());
}
MyLogger.actionFinished();
ThreadManager.recordCalcTime(System.currentTimeMillis() - time);
MyLogger.logMediumLevelTask("Calculated all stats", time);
return allStats;
}
private @NotNull StatAction getStatTask(StatRequest.Settings requestSettings) {
int size = offlinePlayerHandler.getOfflinePlayerCount() != 0 ? offlinePlayerHandler.getOfflinePlayerCount() : 16;
ConcurrentHashMap<String, Integer> allStats = new ConcurrentHashMap<>(size);
ImmutableList<String> playerNames = ImmutableList.copyOf(offlinePlayerHandler.getOfflinePlayerNames());
StatAction task = new StatAction(offlinePlayerHandler, playerNames, requestSettings, allStats);
MyLogger.actionCreated(playerNames.size());
return task;
}
}

View File

@ -21,21 +21,36 @@ import java.util.stream.Stream;
*/
public final class EnumHandler {
private static volatile EnumHandler instance;
private static List<String> blockNames;
private static List<String> itemNames;
private static List<String> statNames;
private static List<String> subStatNames;
public EnumHandler() {
private EnumHandler() {
prepareLists();
}
public static EnumHandler getInstance() {
EnumHandler localVar = instance;
if (localVar != null) {
return localVar;
}
synchronized (EnumHandler.class) {
if (instance == null) {
instance = new EnumHandler();
}
return instance;
}
}
/**
* Returns all block-names in lowercase.
*
* @return the List
*/
public List<String> getBlockNames() {
public List<String> getAllBlockNames() {
return blockNames;
}
@ -44,7 +59,7 @@ public final class EnumHandler {
*
* @return the List
*/
public List<String> getItemNames() {
public List<String> getAllItemNames() {
return itemNames;
}
@ -53,7 +68,7 @@ public final class EnumHandler {
*
* @return the List
*/
public List<String> getStatNames() {
public List<String> getAllStatNames() {
return statNames;
}
@ -64,7 +79,7 @@ public final class EnumHandler {
* @return Material enum constant (uppercase), or null if none
* can be found
*/
public static @Nullable Material getItemEnum(String itemName) {
public @Nullable Material getItemEnum(String itemName) {
if (itemName == null) return null;
Material item = Material.matchMaterial(itemName);
@ -78,7 +93,7 @@ public final class EnumHandler {
* @return EntityType enum constant (uppercase), or null if none
* can be found
*/
public static @Nullable EntityType getEntityEnum(String entityName) {
public @Nullable EntityType getEntityEnum(String entityName) {
try {
return EntityType.valueOf(entityName.toUpperCase());
}
@ -94,7 +109,7 @@ public final class EnumHandler {
* @return Material enum constant (uppercase), or null if none
* can be found
*/
public static @Nullable Material getBlockEnum(String materialName) {
public @Nullable Material getBlockEnum(String materialName) {
if (materialName == null) return null;
Material block = Material.matchMaterial(materialName);
@ -107,7 +122,7 @@ public final class EnumHandler {
* @param statName String (case-insensitive)
* @return the Statistic enum constant, or null
*/
public static @Nullable Statistic getStatEnum(@NotNull String statName) {
public @Nullable Statistic getStatEnum(@NotNull String statName) {
try {
return Statistic.valueOf(statName.toUpperCase());
}
@ -133,7 +148,7 @@ public final class EnumHandler {
* @param statName the String to check (case-insensitive)
* @return true if this String is a Statistic of Type.Entity
*/
public boolean isEntityStatistic(String statName) {
public boolean isEntityStatistic(@NotNull String statName) {
return statName.equalsIgnoreCase(Statistic.ENTITY_KILLED_BY.toString()) ||
statName.equalsIgnoreCase(Statistic.KILL_ENTITY.toString());
}
@ -157,7 +172,7 @@ public final class EnumHandler {
* @return "block", "entity", "item", or "sub-statistic" if the
* provided Type is null.
*/
public static String getSubStatTypeName(Statistic.Type statType) {
public String getSubStatTypeName(Statistic.Type statType) {
String subStat = "sub-statistic";
if (statType == null) return subStat;
switch (statType) {

View File

@ -1,10 +1,11 @@
package com.artemis.the.gr8.playerstats.utils;
import com.artemis.the.gr8.playerstats.config.ConfigHandler;
import com.artemis.the.gr8.playerstats.reload.PlayerLoadAction;
import com.artemis.the.gr8.playerstats.multithreading.ThreadManager;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.configuration.file.FileConfiguration;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import java.util.*;
@ -21,31 +22,47 @@ import java.util.function.Predicate;
*/
public final class OfflinePlayerHandler extends FileHandler {
private static ConfigHandler config;
private static volatile OfflinePlayerHandler instance;
private final ConfigHandler config;
private static FileConfiguration excludedPlayers;
private ConcurrentHashMap<String, UUID> offlinePlayerUUIDs;
private ArrayList<String> playerNames;
private static ConcurrentHashMap<String, UUID> offlinePlayerUUIDs;
public OfflinePlayerHandler(ConfigHandler configHandler) {
private OfflinePlayerHandler() {
super("excluded_players.yml");
config = ConfigHandler.getInstance();
excludedPlayers = super.getFileConfiguration();
config = configHandler;
loadOfflinePlayers();
}
public static OfflinePlayerHandler getInstance() {
OfflinePlayerHandler localVar = instance;
if (localVar != null) {
return localVar;
}
synchronized (OfflinePlayerHandler.class) {
if (instance == null) {
instance = new OfflinePlayerHandler();
}
return instance;
}
}
@Override
public void reload() {
super.reload();
excludedPlayers = super.getFileConfiguration();
loadOfflinePlayers();
}
/**
* Checks if a given playerName is on the private HashMap of players
* that should be included in statistic calculations.
* Checks if a given player is currently
* included for /statistic lookups.
*
* @param playerName String (case-sensitive)
* @return true if this Player should be included in calculations
* @return true if this player is included
*/
public boolean isRelevantPlayer(String playerName) {
return offlinePlayerUUIDs.containsKey(playerName);
@ -55,22 +72,20 @@ public final class OfflinePlayerHandler extends FileHandler {
super.addValueToListInFile("excluded", uniqueID);
}
public static boolean isExcluded(UUID uniqueID) {
public boolean isExcluded(UUID uniqueID) {
List<?> excluded = excludedPlayers.getList("excluded");
if (excluded == null) {
return false;
}
for (Object obj : excluded) {
if (obj.equals(uniqueID)) {
return true;
}
}
return false;
return excluded.stream()
.filter(Objects::nonNull)
.anyMatch(obj -> obj.equals(uniqueID));
}
/**
* Gets the number of OfflinePlayers that are included in
* statistic calculations.
* Gets the number of OfflinePlayers that are
* currently included in statistic calculations.
*
* @return the number of included OfflinePlayers
*/
@ -84,8 +99,9 @@ public final class OfflinePlayerHandler extends FileHandler {
*
* @return the ArrayList
*/
public ArrayList<String> getOfflinePlayerNames() {
return playerNames;
@Contract(" -> new")
public @NotNull ArrayList<String> getOfflinePlayerNames() {
return Collections.list(offlinePlayerUUIDs.keys());
}
/**
@ -127,12 +143,9 @@ public final class OfflinePlayerHandler extends FileHandler {
int size = offlinePlayerUUIDs != null ? offlinePlayerUUIDs.size() : 16;
offlinePlayerUUIDs = new ConcurrentHashMap<>(size);
PlayerLoadAction task = new PlayerLoadAction(offlinePlayers, config.getLastPlayedLimit(), offlinePlayerUUIDs);
MyLogger.actionCreated(offlinePlayers != null ? offlinePlayers.length : 0);
ForkJoinPool.commonPool().invoke(task);
MyLogger.actionFinished();
ForkJoinPool.commonPool().invoke(ThreadManager.getPlayerLoadAction(offlinePlayers, offlinePlayerUUIDs));
playerNames = Collections.list(offlinePlayerUUIDs.keys());
MyLogger.actionFinished();
MyLogger.logLowLevelTask(("Loaded " + offlinePlayerUUIDs.size() + " offline players"), time);
});
}