mirror of
https://github.com/itHotL/PlayerStats.git
synced 2024-11-29 13:05:32 +01:00
Made LanguageKeyHandler static, improved MyLogger, added a while statement in StatThread in an attempt to enable ReloadThread to kill it (#64), Re-enabled PrideComponentFactory
This commit is contained in:
parent
82a0196214
commit
9ca234975b
@ -31,12 +31,11 @@ public class Main extends JavaPlugin {
|
|||||||
//initialize the Adventure library
|
//initialize the Adventure library
|
||||||
adventure = BukkitAudiences.create(this);
|
adventure = BukkitAudiences.create(this);
|
||||||
|
|
||||||
//first get an instance of the ConfigHandler and LanguageKeyHandler
|
//first get an instance of the ConfigHandler
|
||||||
ConfigHandler config = new ConfigHandler(this);
|
ConfigHandler config = new ConfigHandler(this);
|
||||||
LanguageKeyHandler language = new LanguageKeyHandler();
|
|
||||||
|
|
||||||
//for now always use the PrideComponentFactory (it'll use the regular formatting when needed)
|
//for now always use the PrideComponentFactory (it'll use the regular formatting when needed)
|
||||||
MessageWriter messageWriter = new MessageWriter(config, language);
|
MessageWriter messageWriter = new MessageWriter(config);
|
||||||
|
|
||||||
//initialize the threadManager
|
//initialize the threadManager
|
||||||
ThreadManager threadManager = new ThreadManager(adventure(), config, messageWriter, this);
|
ThreadManager threadManager = new ThreadManager(adventure(), config, messageWriter, this);
|
||||||
|
@ -41,7 +41,7 @@ public class ThreadManager {
|
|||||||
if (reloadThread == null || !reloadThread.isAlive()) {
|
if (reloadThread == null || !reloadThread.isAlive()) {
|
||||||
reloadThreadID += 1;
|
reloadThreadID += 1;
|
||||||
|
|
||||||
reloadThread = new ReloadThread(adventure, config, messageWriter, plugin, threshold, reloadThreadID, statThread, sender);
|
reloadThread = new ReloadThread(adventure, config, messageWriter, threshold, reloadThreadID, statThread, sender);
|
||||||
reloadThread.start();
|
reloadThread.start();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -26,7 +26,7 @@ public class ConfigHandler {
|
|||||||
configVersion = 4;
|
configVersion = 4;
|
||||||
checkConfigVersion();
|
checkConfigVersion();
|
||||||
|
|
||||||
MyLogger.setDebugLevel(debugLevel());
|
MyLogger.setDebugLevel(getDebugLevel());
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Checks the number that "config-version" returns to see if the config needs updating, and if so, send it to the Updater.
|
/** Checks the number that "config-version" returns to see if the config needs updating, and if so, send it to the Updater.
|
||||||
@ -56,13 +56,14 @@ public class ConfigHandler {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
config = YamlConfiguration.loadConfiguration(configFile);
|
config = YamlConfiguration.loadConfiguration(configFile);
|
||||||
MyLogger.setDebugLevel(debugLevel());
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
catch (IllegalArgumentException e) {
|
catch (IllegalArgumentException e) {
|
||||||
MyLogger.logException(e, "ConfigHandler", "reloadConfig");
|
MyLogger.logException(e, "ConfigHandler", "reloadConfig");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
//TODO Move this to ReloadThread
|
||||||
|
MyLogger.setDebugLevel(getDebugLevel());
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the desired debugging level.
|
/** Returns the desired debugging level.
|
||||||
@ -70,7 +71,7 @@ public class ConfigHandler {
|
|||||||
<p>2 = medium (detail all encountered exceptions, log main tasks and show time taken)</p>
|
<p>2 = medium (detail all encountered exceptions, log main tasks and show time taken)</p>
|
||||||
<p>3 = high (log all tasks and time taken)</p>
|
<p>3 = high (log all tasks and time taken)</p>
|
||||||
<p>Default: 1</p>*/
|
<p>Default: 1</p>*/
|
||||||
public int debugLevel() {
|
public int getDebugLevel() {
|
||||||
return config.getInt("debug-level", 1);
|
return config.getInt("debug-level", 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,7 +89,7 @@ public class ConfigHandler {
|
|||||||
|
|
||||||
/** Returns the number of maximum days since a player has last been online.
|
/** Returns the number of maximum days since a player has last been online.
|
||||||
<p>Default: 0 (which signals not to use this limit)</p>*/
|
<p>Default: 0 (which signals not to use this limit)</p>*/
|
||||||
public int lastPlayedLimit() {
|
public int getLastPlayedLimit() {
|
||||||
return config.getInt("number-of-days-since-last-joined", 0);
|
return config.getInt("number-of-days-since-last-joined", 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,7 +113,7 @@ public class ConfigHandler {
|
|||||||
|
|
||||||
/** Whether to use rainbow colors for the [PlayerStats] prefix rather than the default gold/purple.
|
/** Whether to use rainbow colors for the [PlayerStats] prefix rather than the default gold/purple.
|
||||||
<p>Default: false</p> */
|
<p>Default: false</p> */
|
||||||
public boolean useRainbowPrefix() {
|
public boolean useRainbowMode() {
|
||||||
return config.getBoolean("rainbow-mode", false);
|
return config.getBoolean("rainbow-mode", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,15 +21,15 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
import static net.kyori.adventure.text.Component.*;
|
import static net.kyori.adventure.text.Component.*;
|
||||||
import static net.kyori.adventure.text.Component.text;
|
import static net.kyori.adventure.text.Component.text;
|
||||||
|
|
||||||
/** Constructs Components with */
|
/** Creates Components with the desired formatting. This class can put Strings
|
||||||
|
into formatted Components with TextColor and TextDecoration, and turn
|
||||||
|
certain Strings into appropriate LanguageKeys to return a TranslatableComponent.*/
|
||||||
public class ComponentFactory {
|
public class ComponentFactory {
|
||||||
|
|
||||||
private static ConfigHandler config;
|
private static ConfigHandler config;
|
||||||
private final LanguageKeyHandler language;
|
|
||||||
|
|
||||||
public ComponentFactory(ConfigHandler c, LanguageKeyHandler l) {
|
public ComponentFactory(ConfigHandler c) {
|
||||||
config = c;
|
config = c;
|
||||||
language = l;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns [PlayerStats] followed by a single space. */
|
/** Returns [PlayerStats] followed by a single space. */
|
||||||
@ -188,11 +188,11 @@ public class ComponentFactory {
|
|||||||
subStatName = getPrettyName(subStatName);
|
subStatName = getPrettyName(subStatName);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
statName = language.getStatKey(request.getStatistic());
|
statName = LanguageKeyHandler.getStatKey(request.getStatistic());
|
||||||
switch (request.getStatistic().getType()) {
|
switch (request.getStatistic().getType()) {
|
||||||
case BLOCK -> subStatName = language.getBlockKey(request.getBlock());
|
case BLOCK -> subStatName = LanguageKeyHandler.getBlockKey(request.getBlock());
|
||||||
case ENTITY -> subStatName = language.getEntityKey(request.getEntity());
|
case ENTITY -> subStatName = LanguageKeyHandler.getEntityKey(request.getEntity());
|
||||||
case ITEM -> subStatName = language.getItemKey(request.getItem());
|
case ITEM -> subStatName = LanguageKeyHandler.getItemKey(request.getItem());
|
||||||
case UNTYPED -> {
|
case UNTYPED -> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,14 +12,17 @@ import java.util.HashMap;
|
|||||||
|
|
||||||
public class LanguageKeyHandler {
|
public class LanguageKeyHandler {
|
||||||
|
|
||||||
private final HashMap<Statistic, String> statNameKeys;
|
private final static HashMap<Statistic, String> statNameKeys;
|
||||||
|
|
||||||
public LanguageKeyHandler() {
|
static {
|
||||||
statNameKeys = new HashMap<>();
|
statNameKeys = new HashMap<>();
|
||||||
generateStatNameKeys();
|
generateStatNameKeys();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getStatKey(@NotNull Statistic statistic) {
|
private LanguageKeyHandler() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getStatKey(@NotNull Statistic statistic) {
|
||||||
if (statistic.getType() == Statistic.Type.UNTYPED) {
|
if (statistic.getType() == Statistic.Type.UNTYPED) {
|
||||||
return "stat.minecraft." + statNameKeys.get(statistic);
|
return "stat.minecraft." + statNameKeys.get(statistic);
|
||||||
}
|
}
|
||||||
@ -30,7 +33,7 @@ public class LanguageKeyHandler {
|
|||||||
|
|
||||||
/** Get the official Key from the NameSpacedKey for this entityType,
|
/** Get the official Key from the NameSpacedKey for this entityType,
|
||||||
or return null if no enum constant can be retrieved or entityType is UNKNOWN.*/
|
or return null if no enum constant can be retrieved or entityType is UNKNOWN.*/
|
||||||
public @Nullable String getEntityKey(EntityType entity) {
|
public static @Nullable String getEntityKey(EntityType entity) {
|
||||||
if (entity == null || entity == EntityType.UNKNOWN) return null;
|
if (entity == null || entity == EntityType.UNKNOWN) return null;
|
||||||
else {
|
else {
|
||||||
return "entity.minecraft." + entity.getKey().getKey();
|
return "entity.minecraft." + entity.getKey().getKey();
|
||||||
@ -39,7 +42,7 @@ public class LanguageKeyHandler {
|
|||||||
|
|
||||||
/** Get the official Key from the NameSpacedKey for this item Material,
|
/** Get the official Key from the NameSpacedKey for this item Material,
|
||||||
or return null if no enum constant can be retrieved.*/
|
or return null if no enum constant can be retrieved.*/
|
||||||
public @Nullable String getItemKey(Material item) {
|
public static @Nullable String getItemKey(Material item) {
|
||||||
if (item == null) return null;
|
if (item == null) return null;
|
||||||
else if (item.isBlock()) {
|
else if (item.isBlock()) {
|
||||||
return getBlockKey(item);
|
return getBlockKey(item);
|
||||||
@ -51,7 +54,7 @@ public class LanguageKeyHandler {
|
|||||||
|
|
||||||
/** Returns the official Key from the NameSpacedKey for the block Material provided,
|
/** Returns the official Key from the NameSpacedKey for the block Material provided,
|
||||||
or return null if no enum constant can be retrieved.*/
|
or return null if no enum constant can be retrieved.*/
|
||||||
public @Nullable String getBlockKey(Material block) {
|
public static @Nullable String getBlockKey(Material block) {
|
||||||
if (block == null) return null;
|
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
|
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_", "");
|
String blockName = block.toString().toLowerCase().replace("wall_", "");
|
||||||
@ -63,11 +66,11 @@ public class LanguageKeyHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void generateDefaultKeys() {
|
private static void generateDefaultKeys() {
|
||||||
Arrays.stream(Statistic.values()).forEach(statistic -> statNameKeys.put(statistic, statistic.toString().toLowerCase()));
|
Arrays.stream(Statistic.values()).forEach(statistic -> statNameKeys.put(statistic, statistic.toString().toLowerCase()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void generateStatNameKeys() {
|
private static void generateStatNameKeys() {
|
||||||
//get the enum names for all statistics first
|
//get the enum names for all statistics first
|
||||||
generateDefaultKeys();
|
generateDefaultKeys();
|
||||||
|
|
||||||
|
@ -25,9 +25,23 @@ public class MessageWriter {
|
|||||||
private static ConfigHandler config;
|
private static ConfigHandler config;
|
||||||
private static ComponentFactory componentFactory;
|
private static ComponentFactory componentFactory;
|
||||||
|
|
||||||
public MessageWriter(ConfigHandler c, LanguageKeyHandler l) {
|
public MessageWriter(ConfigHandler c) {
|
||||||
config = c;
|
config = c;
|
||||||
componentFactory = new ComponentFactory(c, l);
|
getComponentFactory();
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO Make ReloadThread do an update
|
||||||
|
public static void updateComponentFactory() {
|
||||||
|
getComponentFactory();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void getComponentFactory() {
|
||||||
|
if (config.useFestiveFormatting() || config.useRainbowMode()) {
|
||||||
|
componentFactory = new PrideComponentFactory(config);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
componentFactory = new ComponentFactory(config);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public TextComponent reloadedConfig(boolean isBukkitConsole) {
|
public TextComponent reloadedConfig(boolean isBukkitConsole) {
|
||||||
@ -37,7 +51,7 @@ public class MessageWriter {
|
|||||||
|
|
||||||
public TextComponent stillReloading(boolean isBukkitConsole) {
|
public TextComponent stillReloading(boolean isBukkitConsole) {
|
||||||
return componentFactory.msg(
|
return componentFactory.msg(
|
||||||
"The plugin is still (re)loading, " +
|
"The plugin is (re)loading, " +
|
||||||
"your request will be processed when it is done!", isBukkitConsole);
|
"your request will be processed when it is done!", isBukkitConsole);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,6 +62,10 @@ public class MessageWriter {
|
|||||||
"please reload PlayerStats again to fix it!", isBukkitConsole);
|
"please reload PlayerStats again to fix it!", isBukkitConsole);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TextComponent interruptedRequest() {
|
||||||
|
return componentFactory.msg("Your request was interrupted, please try again in a moment!", false);
|
||||||
|
}
|
||||||
|
|
||||||
public TextComponent waitAMoment(boolean longWait, boolean isBukkitConsole) {
|
public TextComponent waitAMoment(boolean longWait, boolean isBukkitConsole) {
|
||||||
String msg = longWait ? "Calculating statistics, this may take a minute..." :
|
String msg = longWait ? "Calculating statistics, this may take a minute..." :
|
||||||
"Calculating statistics, this may take a few moments...";
|
"Calculating statistics, this may take a few moments...";
|
||||||
|
@ -4,7 +4,6 @@ import com.gmail.artemis.the.gr8.playerstats.config.ConfigHandler;
|
|||||||
|
|
||||||
import net.kyori.adventure.text.TextComponent;
|
import net.kyori.adventure.text.TextComponent;
|
||||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||||
import org.bukkit.Bukkit;
|
|
||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.Month;
|
import java.time.Month;
|
||||||
@ -16,8 +15,8 @@ public class PrideComponentFactory extends ComponentFactory {
|
|||||||
|
|
||||||
private static ConfigHandler config;
|
private static ConfigHandler config;
|
||||||
|
|
||||||
public PrideComponentFactory(ConfigHandler c, LanguageKeyHandler l) {
|
public PrideComponentFactory(ConfigHandler c) {
|
||||||
super(c, l);
|
super(c);
|
||||||
config = c;
|
config = c;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,7 +61,7 @@ public class PrideComponentFactory extends ComponentFactory {
|
|||||||
if festive formatting is disabled or it is not pride month,
|
if festive formatting is disabled or it is not pride month,
|
||||||
or the commandsender is a Bukkit or Spigot console.*/
|
or the commandsender is a Bukkit or Spigot console.*/
|
||||||
private boolean cancelRainbow(boolean isBukkitConsole) {
|
private boolean cancelRainbow(boolean isBukkitConsole) {
|
||||||
return !(config.useRainbowPrefix() || (config.useFestiveFormatting() && LocalDate.now().getMonth().equals(Month.JUNE))) ||
|
return !(config.useRainbowMode() || (config.useFestiveFormatting() && LocalDate.now().getMonth().equals(Month.JUNE))) ||
|
||||||
(isBukkitConsole);
|
(isBukkitConsole);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,8 +1,8 @@
|
|||||||
package com.gmail.artemis.the.gr8.playerstats.reload;
|
package com.gmail.artemis.the.gr8.playerstats.reload;
|
||||||
|
|
||||||
import com.gmail.artemis.the.gr8.playerstats.Main;
|
|
||||||
import com.gmail.artemis.the.gr8.playerstats.ThreadManager;
|
import com.gmail.artemis.the.gr8.playerstats.ThreadManager;
|
||||||
import com.gmail.artemis.the.gr8.playerstats.config.ConfigHandler;
|
import com.gmail.artemis.the.gr8.playerstats.config.ConfigHandler;
|
||||||
|
import com.gmail.artemis.the.gr8.playerstats.enums.DebugLevel;
|
||||||
import com.gmail.artemis.the.gr8.playerstats.msg.MessageWriter;
|
import com.gmail.artemis.the.gr8.playerstats.msg.MessageWriter;
|
||||||
import com.gmail.artemis.the.gr8.playerstats.statistic.StatThread;
|
import com.gmail.artemis.the.gr8.playerstats.statistic.StatThread;
|
||||||
import com.gmail.artemis.the.gr8.playerstats.utils.MyLogger;
|
import com.gmail.artemis.the.gr8.playerstats.utils.MyLogger;
|
||||||
@ -30,19 +30,17 @@ public class ReloadThread extends Thread {
|
|||||||
private final BukkitAudiences adventure;
|
private final BukkitAudiences adventure;
|
||||||
private static ConfigHandler config;
|
private static ConfigHandler config;
|
||||||
private static MessageWriter messageWriter;
|
private static MessageWriter messageWriter;
|
||||||
private final Main plugin;
|
|
||||||
|
|
||||||
private final StatThread statThread;
|
private final StatThread statThread;
|
||||||
private final CommandSender sender;
|
private final CommandSender sender;
|
||||||
|
|
||||||
public ReloadThread(BukkitAudiences a, ConfigHandler c, MessageWriter m, Main p, int threshold, int ID, @Nullable StatThread s, @Nullable CommandSender se) {
|
public ReloadThread(BukkitAudiences a, ConfigHandler c, MessageWriter m, int threshold, int ID, @Nullable StatThread s, @Nullable CommandSender se) {
|
||||||
this.threshold = threshold;
|
this.threshold = threshold;
|
||||||
reloadThreadID = ID;
|
reloadThreadID = ID;
|
||||||
|
|
||||||
adventure = a;
|
adventure = a;
|
||||||
config = c;
|
config = c;
|
||||||
messageWriter = m;
|
messageWriter = m;
|
||||||
plugin = p;
|
|
||||||
|
|
||||||
statThread = s;
|
statThread = s;
|
||||||
sender = se;
|
sender = se;
|
||||||
@ -59,6 +57,10 @@ public class ReloadThread extends Thread {
|
|||||||
//if reload is triggered by /statreload (aka this thread does not have ID number 1)...
|
//if reload is triggered by /statreload (aka this thread does not have ID number 1)...
|
||||||
if (reloadThreadID != 1) {
|
if (reloadThreadID != 1) {
|
||||||
if (statThread != null && statThread.isAlive()) {
|
if (statThread != null && statThread.isAlive()) {
|
||||||
|
statThread.stopThread();
|
||||||
|
return;
|
||||||
|
|
||||||
|
/*
|
||||||
try {
|
try {
|
||||||
MyLogger.waitingForOtherThread(this.getName(), statThread.getName());
|
MyLogger.waitingForOtherThread(this.getName(), statThread.getName());
|
||||||
statThread.join();
|
statThread.join();
|
||||||
@ -66,8 +68,9 @@ public class ReloadThread extends Thread {
|
|||||||
MyLogger.logException(e, "ReloadThread", "run(), trying to join" + statThread.getName());
|
MyLogger.logException(e, "ReloadThread", "run(), trying to join" + statThread.getName());
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
plugin.getLogger().info("Reloading!");
|
MyLogger.logMsg("Reloading!", false);
|
||||||
if (config.reloadConfig()) {
|
if (config.reloadConfig()) {
|
||||||
boolean isBukkitConsole = sender instanceof ConsoleCommandSender && Bukkit.getName().equalsIgnoreCase("CraftBukkit");
|
boolean isBukkitConsole = sender instanceof ConsoleCommandSender && Bukkit.getName().equalsIgnoreCase("CraftBukkit");
|
||||||
|
|
||||||
@ -81,7 +84,7 @@ public class ReloadThread extends Thread {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MyLogger.logTimeTakenDefault("ReloadThread", ("loaded " + OfflinePlayerHandler.getOfflinePlayerCount() + " offline players"), time);
|
MyLogger.logTimeTaken("ReloadThread", ("loaded " + OfflinePlayerHandler.getOfflinePlayerCount() + " offline players"), time);
|
||||||
if (sender != null) {
|
if (sender != null) {
|
||||||
adventure.sender(sender).sendMessage(messageWriter.reloadedConfig(isBukkitConsole));
|
adventure.sender(sender).sendMessage(messageWriter.reloadedConfig(isBukkitConsole));
|
||||||
}
|
}
|
||||||
@ -92,7 +95,7 @@ public class ReloadThread extends Thread {
|
|||||||
try {
|
try {
|
||||||
OfflinePlayerHandler.updateOfflinePlayerList(getPlayerMap());
|
OfflinePlayerHandler.updateOfflinePlayerList(getPlayerMap());
|
||||||
ThreadManager.recordCalcTime(System.currentTimeMillis() - time);
|
ThreadManager.recordCalcTime(System.currentTimeMillis() - time);
|
||||||
MyLogger.logTimeTakenDefault("ReloadThread",
|
MyLogger.logTimeTaken("ReloadThread",
|
||||||
("loaded " + OfflinePlayerHandler.getOfflinePlayerCount() + " offline players"), time);
|
("loaded " + OfflinePlayerHandler.getOfflinePlayerCount() + " offline players"), time);
|
||||||
}
|
}
|
||||||
catch (ConcurrentModificationException e) {
|
catch (ConcurrentModificationException e) {
|
||||||
@ -107,29 +110,42 @@ public class ReloadThread extends Thread {
|
|||||||
OfflinePlayer[] offlinePlayers;
|
OfflinePlayer[] offlinePlayers;
|
||||||
if (config.whitelistOnly()) {
|
if (config.whitelistOnly()) {
|
||||||
offlinePlayers = Bukkit.getWhitelistedPlayers().toArray(OfflinePlayer[]::new);
|
offlinePlayers = Bukkit.getWhitelistedPlayers().toArray(OfflinePlayer[]::new);
|
||||||
MyLogger.logTimeTaken("ReloadThread", "retrieved whitelist", time);
|
MyLogger.logTimeTaken("ReloadThread", "retrieved whitelist", time, DebugLevel.MEDIUM);
|
||||||
}
|
}
|
||||||
else if (config.excludeBanned()) {
|
else if (config.excludeBanned()) {
|
||||||
Set<OfflinePlayer> bannedPlayers = Bukkit.getBannedPlayers();
|
Set<OfflinePlayer> bannedPlayers = Bukkit.getBannedPlayers();
|
||||||
offlinePlayers = Arrays.stream(Bukkit.getOfflinePlayers())
|
offlinePlayers = Arrays.stream(Bukkit.getOfflinePlayers())
|
||||||
.parallel()
|
.parallel()
|
||||||
.filter(offlinePlayer -> !bannedPlayers.contains(offlinePlayer)).toArray(OfflinePlayer[]::new);
|
.filter(offlinePlayer -> !bannedPlayers.contains(offlinePlayer)).toArray(OfflinePlayer[]::new);
|
||||||
MyLogger.logTimeTaken("ReloadThread", "retrieved banlist", time);
|
MyLogger.logTimeTaken("ReloadThread", "retrieved banlist", time, DebugLevel.MEDIUM);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
offlinePlayers = Bukkit.getOfflinePlayers();
|
offlinePlayers = Bukkit.getOfflinePlayers();
|
||||||
MyLogger.logTimeTaken("ReloadThread", "retrieved list of Offline Players", time);
|
MyLogger.logTimeTaken("ReloadThread", "retrieved list of Offline Players", time, DebugLevel.MEDIUM);
|
||||||
}
|
}
|
||||||
|
|
||||||
int size = offlinePlayers != null ? offlinePlayers.length : 16;
|
int size = offlinePlayers != null ? offlinePlayers.length : 16;
|
||||||
ConcurrentHashMap<String, UUID> playerMap = new ConcurrentHashMap<>(size);
|
ConcurrentHashMap<String, UUID> playerMap = new ConcurrentHashMap<>(size);
|
||||||
|
|
||||||
ReloadAction task = new ReloadAction(threshold, offlinePlayers, config.lastPlayedLimit(), playerMap);
|
ReloadAction task = new ReloadAction(threshold, offlinePlayers, config.getLastPlayedLimit(), playerMap);
|
||||||
MyLogger.actionCreated((offlinePlayers != null) ? offlinePlayers.length : 0);
|
MyLogger.actionCreated((offlinePlayers != null) ? offlinePlayers.length : 0);
|
||||||
|
|
||||||
ForkJoinPool.commonPool().invoke(task);
|
ForkJoinPool.commonPool().invoke(task);
|
||||||
MyLogger.actionFinished(1);
|
MyLogger.actionFinished(1);
|
||||||
|
|
||||||
return playerMap;
|
return generateFakeExtraPlayers(playerMap, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
//generate fake extra players for PlayerStats, by looping over the real offlinePlayers multiple times
|
||||||
|
private @NotNull ConcurrentHashMap<String, UUID> generateFakeExtraPlayers(@NotNull ConcurrentHashMap<String, UUID> realPlayers, int loops) {
|
||||||
|
if (loops == 0 || loops == 1) return realPlayers;
|
||||||
|
|
||||||
|
ConcurrentHashMap<String, UUID> newPlayerMap = new ConcurrentHashMap<>(realPlayers.size() * loops);
|
||||||
|
for (int i = 0; i < loops; i++) {
|
||||||
|
for (String key : realPlayers.keySet()) {
|
||||||
|
newPlayerMap.put(key + i, realPlayers.get(key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newPlayerMap;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -24,6 +24,7 @@ import java.util.stream.Collectors;
|
|||||||
|
|
||||||
public class StatThread extends Thread {
|
public class StatThread extends Thread {
|
||||||
|
|
||||||
|
private volatile boolean keepRunning = true;
|
||||||
private final int threshold;
|
private final int threshold;
|
||||||
|
|
||||||
private final StatRequest request;
|
private final StatRequest request;
|
||||||
@ -50,6 +51,12 @@ public class StatThread extends Thread {
|
|||||||
MyLogger.threadCreated(this.getName());
|
MyLogger.threadCreated(this.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void stopThread() {
|
||||||
|
MyLogger.logMsg("stopThread() is being executed by: " + Thread.currentThread().getName(), false);
|
||||||
|
keepRunning = false;
|
||||||
|
MyLogger.logMsg("StatThread has been forced to stop!", true);
|
||||||
|
}
|
||||||
|
|
||||||
//what the thread will do once started
|
//what the thread will do once started
|
||||||
@Override
|
@Override
|
||||||
public void run() throws IllegalStateException, NullPointerException {
|
public void run() throws IllegalStateException, NullPointerException {
|
||||||
@ -71,52 +78,48 @@ public class StatThread extends Thread {
|
|||||||
.sendMessage(messageWriter
|
.sendMessage(messageWriter
|
||||||
.stillReloading(isBukkitConsole));
|
.stillReloading(isBukkitConsole));
|
||||||
reloadThread.join();
|
reloadThread.join();
|
||||||
|
|
||||||
|
//TODO Add timeout after which it checks again and potentially aborts mission
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
plugin.getLogger().warning(e.toString());
|
MyLogger.logException(e, "StatThread", "Trying to join" + reloadThread.getName());
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Target selection = request.getSelection();
|
//TODO Evaluate if this really does something
|
||||||
|
while (keepRunning) {
|
||||||
if (selection == Target.TOP || selection == Target.SERVER) {
|
Target selection = request.getSelection();
|
||||||
if (ThreadManager.getLastRecordedCalcTime() > 20000) {
|
if (selection == Target.PLAYER) {
|
||||||
adventure.sender(sender).sendMessage(messageWriter.waitAMoment(true, isBukkitConsole));
|
|
||||||
}
|
|
||||||
else if (ThreadManager.getLastRecordedCalcTime() > 2000) {
|
|
||||||
adventure.sender(sender).sendMessage(messageWriter.waitAMoment(false, isBukkitConsole));
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (selection == Target.TOP) {
|
|
||||||
adventure.sender(sender).sendMessage(messageWriter.formatTopStats(getTopStats(), request));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
adventure.sender(sender).sendMessage(messageWriter.formatServerStat(getServerTotal(), request));
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (ConcurrentModificationException e) {
|
|
||||||
if (!isBukkitConsole) {
|
|
||||||
adventure.sender(sender).sendMessage(messageWriter.unknownError(false));
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
adventure.sender(sender).sendMessage(messageWriter.formatExceptions(e.toString(), isBukkitConsole));
|
|
||||||
MyLogger.logException(e, "StatThread", "run(), trying to calculate or format a top or server statistic");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (selection == Target.PLAYER) {
|
|
||||||
try {
|
|
||||||
adventure.sender(sender).sendMessage(
|
adventure.sender(sender).sendMessage(
|
||||||
messageWriter.formatPlayerStat(getIndividualStat(), request));
|
messageWriter.formatPlayerStat(getIndividualStat(), request));
|
||||||
|
|
||||||
} catch (UnsupportedOperationException | NullPointerException e) {
|
|
||||||
adventure.sender(sender).sendMessage(messageWriter.formatExceptions(e.toString(), isBukkitConsole));
|
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
if (ThreadManager.getLastRecordedCalcTime() > 2000) {
|
||||||
|
adventure.sender(sender).sendMessage(
|
||||||
|
messageWriter.waitAMoment(ThreadManager.getLastRecordedCalcTime() > 20000, isBukkitConsole));
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (selection == Target.TOP) {
|
||||||
|
adventure.sender(sender).sendMessage(
|
||||||
|
messageWriter.formatTopStats(getTopStats(), request));
|
||||||
|
} else {
|
||||||
|
adventure.sender(sender).sendMessage(
|
||||||
|
messageWriter.formatServerStat(getServerTotal(), request));
|
||||||
|
}
|
||||||
|
} catch (ConcurrentModificationException e) {
|
||||||
|
if (!isBukkitConsole) {
|
||||||
|
adventure.sender(sender).sendMessage(
|
||||||
|
messageWriter.unknownError(false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
MyLogger.logMsg("(" + Thread.currentThread().getName() + ") shutting down...", false);
|
||||||
|
adventure.sender(sender).sendMessage(messageWriter.interruptedRequest());
|
||||||
}
|
}
|
||||||
|
|
||||||
private LinkedHashMap<String, Integer> getTopStats() throws ConcurrentModificationException, NullPointerException {
|
private LinkedHashMap<String, Integer> getTopStats() throws ConcurrentModificationException {
|
||||||
return getAllStats().entrySet().stream()
|
return getAllStats().entrySet().stream()
|
||||||
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
|
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
|
||||||
.limit(config.getTopListMaxSize()).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
|
.limit(config.getTopListMaxSize()).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
|
||||||
@ -128,7 +131,7 @@ public class StatThread extends Thread {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//invokes a bunch of worker pool threads to divide and conquer (get the statistics for all players in the list)
|
//invokes a bunch of worker pool threads to divide and conquer (get the statistics for all players in the list)
|
||||||
private @NotNull ConcurrentHashMap<String, Integer> getAllStats() throws ConcurrentModificationException, NullPointerException {
|
private @NotNull ConcurrentHashMap<String, Integer> getAllStats() throws ConcurrentModificationException {
|
||||||
long time = System.currentTimeMillis();
|
long time = System.currentTimeMillis();
|
||||||
|
|
||||||
int size = OfflinePlayerHandler.getOfflinePlayerCount() != 0 ? (int) (OfflinePlayerHandler.getOfflinePlayerCount() * 1.05) : 16;
|
int size = OfflinePlayerHandler.getOfflinePlayerCount() != 0 ? (int) (OfflinePlayerHandler.getOfflinePlayerCount() * 1.05) : 16;
|
||||||
@ -142,20 +145,21 @@ public class StatThread extends Thread {
|
|||||||
try {
|
try {
|
||||||
commonPool.invoke(task);
|
commonPool.invoke(task);
|
||||||
} catch (ConcurrentModificationException e) {
|
} catch (ConcurrentModificationException e) {
|
||||||
plugin.getLogger().warning("The request could not be executed due to a ConcurrentModificationException. " +
|
MyLogger.logMsg("The request 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!");
|
"This likely happened because Bukkit hasn't fully initialized all player-data yet. Try again and it should be fine!", true);
|
||||||
throw new ConcurrentModificationException(e.toString());
|
throw new ConcurrentModificationException(e.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
MyLogger.actionFinished(2);
|
MyLogger.actionFinished(2);
|
||||||
ThreadManager.recordCalcTime(System.currentTimeMillis() - time);
|
ThreadManager.recordCalcTime(System.currentTimeMillis() - time);
|
||||||
MyLogger.logTimeTakenDefault("StatThread", "calculated all stats", time);
|
MyLogger.logTimeTaken("StatThread", "calculated all stats", time);
|
||||||
|
|
||||||
return playerStats;
|
return playerStats;
|
||||||
}
|
}
|
||||||
|
|
||||||
//gets the actual statistic data for an individual player
|
/** Gets the statistic data for an individual player. If somehow the player
|
||||||
private int getIndividualStat() throws UnsupportedOperationException, NullPointerException {
|
cannot be found, this returns 0.*/
|
||||||
|
private int getIndividualStat() {
|
||||||
OfflinePlayer player = OfflinePlayerHandler.getOfflinePlayer(request.getPlayerName());
|
OfflinePlayer player = OfflinePlayerHandler.getOfflinePlayer(request.getPlayerName());
|
||||||
if (player != null) {
|
if (player != null) {
|
||||||
switch (request.getStatistic().getType()) {
|
switch (request.getStatistic().getType()) {
|
||||||
@ -173,6 +177,6 @@ public class StatThread extends Thread {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new NullPointerException("The player you are trying to request either does not exist, or is not on the list for statistic lookups!");
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -50,29 +50,26 @@ public class TopStatAction extends RecursiveAction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void getStatsDirectly() throws UnsupportedOperationException {
|
private void getStatsDirectly() {
|
||||||
try {
|
Iterator<String> iterator = playerNames.iterator();
|
||||||
Iterator<String> iterator = playerNames.iterator();
|
if (iterator.hasNext()) {
|
||||||
if (iterator.hasNext()) {
|
do {
|
||||||
do {
|
String playerName = iterator.next();
|
||||||
String playerName = iterator.next();
|
MyLogger.actionRunning(Thread.currentThread().getName(), playerName, 2);
|
||||||
MyLogger.actionRunning(Thread.currentThread().getName(), playerName, 2);
|
OfflinePlayer player = OfflinePlayerHandler.getOfflinePlayer(playerName);
|
||||||
OfflinePlayer player = OfflinePlayerHandler.getOfflinePlayer(playerName);
|
if (player != null) {
|
||||||
if (player != null) {
|
int statistic = 0;
|
||||||
int statistic = 0;
|
switch (request.getStatistic().getType()) {
|
||||||
switch (request.getStatistic().getType()) {
|
case UNTYPED -> statistic = player.getStatistic(request.getStatistic());
|
||||||
case UNTYPED -> statistic = player.getStatistic(request.getStatistic());
|
case ENTITY -> statistic = player.getStatistic(request.getStatistic(), request.getEntity());
|
||||||
case ENTITY -> statistic = player.getStatistic(request.getStatistic(), request.getEntity());
|
case BLOCK -> statistic = player.getStatistic(request.getStatistic(), request.getBlock());
|
||||||
case BLOCK -> statistic = player.getStatistic(request.getStatistic(), request.getBlock());
|
case ITEM -> statistic = player.getStatistic(request.getStatistic(), request.getItem());
|
||||||
case ITEM -> statistic = player.getStatistic(request.getStatistic(), request.getItem());
|
|
||||||
}
|
|
||||||
if (statistic > 0) {
|
|
||||||
playerStats.put(playerName, statistic);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} while (iterator.hasNext());
|
if (statistic > 0) {
|
||||||
}
|
playerStats.put(playerName, statistic);
|
||||||
} catch (NoSuchElementException ignored) {
|
}
|
||||||
|
}
|
||||||
|
} while (iterator.hasNext());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -21,7 +21,7 @@ public class EnumHandler {
|
|||||||
private final static List<String> entitySubStatNames;
|
private final static List<String> entitySubStatNames;
|
||||||
private final static List<String> subStatNames;
|
private final static List<String> subStatNames;
|
||||||
|
|
||||||
static{
|
static {
|
||||||
blockNames = Arrays.stream(Material.values())
|
blockNames = Arrays.stream(Material.values())
|
||||||
.filter(Material::isBlock)
|
.filter(Material::isBlock)
|
||||||
.map(Material::toString)
|
.map(Material::toString)
|
||||||
|
@ -22,7 +22,7 @@ public class MyLogger {
|
|||||||
private static final AtomicInteger playersIndex;
|
private static final AtomicInteger playersIndex;
|
||||||
private static ConcurrentHashMap<String, Integer> threadNames;
|
private static ConcurrentHashMap<String, Integer> threadNames;
|
||||||
|
|
||||||
static{
|
static {
|
||||||
Plugin plugin = Bukkit.getPluginManager().getPlugin("PlayerStats");
|
Plugin plugin = Bukkit.getPluginManager().getPlugin("PlayerStats");
|
||||||
logger = (plugin != null) ? plugin.getLogger() : Bukkit.getLogger();
|
logger = (plugin != null) ? plugin.getLogger() : Bukkit.getLogger();
|
||||||
debugLevel = DebugLevel.LOW;
|
debugLevel = DebugLevel.LOW;
|
||||||
@ -35,17 +35,6 @@ public class MyLogger {
|
|||||||
private MyLogger() {
|
private MyLogger() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/** Accesses the playersIndex to up it by 1 and return its previous value. */
|
|
||||||
private static int nextPlayersIndex() {
|
|
||||||
return playersIndex.getAndIncrement();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns true if the playersIndex is 10, or any subsequent increment of 10. */
|
|
||||||
private static boolean incrementOfTen() {
|
|
||||||
return (playersIndex.get() == 10 || (playersIndex.get() > 10 && playersIndex.get() % 10 == 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Sets the desired debugging level.
|
/** Sets the desired debugging level.
|
||||||
<p>1 = low (only show unexpected errors)</p>
|
<p>1 = low (only show unexpected errors)</p>
|
||||||
<p>2 = medium (detail all encountered exceptions, log main tasks and show time taken)</p>
|
<p>2 = medium (detail all encountered exceptions, log main tasks and show time taken)</p>
|
||||||
@ -63,6 +52,30 @@ public class MyLogger {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void logMsg(String content, boolean logAsWarning) {
|
||||||
|
logMsg(content, DebugLevel.LOW, logAsWarning);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void logMsg(String content, DebugLevel logThreshold) {
|
||||||
|
logMsg(content, logThreshold, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void logMsg(String content, DebugLevel logThreshold, boolean logAsWarning) {
|
||||||
|
switch (logThreshold) {
|
||||||
|
case LOW -> log(content, logAsWarning);
|
||||||
|
case MEDIUM -> {
|
||||||
|
if (debugLevel != DebugLevel.LOW) {
|
||||||
|
log(content, logAsWarning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case HIGH -> {
|
||||||
|
if (debugLevel == DebugLevel.HIGH) {
|
||||||
|
log(content, logAsWarning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Log the encountered exception as a warning to console,
|
/** Log the encountered exception as a warning to console,
|
||||||
with some information about which class/method caught it
|
with some information about which class/method caught it
|
||||||
and with a printStackTrace if DebugLevel is HIGH.
|
and with a printStackTrace if DebugLevel is HIGH.
|
||||||
@ -171,21 +184,54 @@ public class MyLogger {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Output to console how long a certain task has taken if DebugLevel is MEDIUM or HIGH.
|
|
||||||
@param className Name of the class executing the task
|
|
||||||
@param methodName Name or description of the task
|
|
||||||
@param startTime Timestamp marking the beginning of the task */
|
|
||||||
public static void logTimeTaken(String className, String methodName, long startTime) {
|
|
||||||
if (debugLevel != DebugLevel.LOW) {
|
|
||||||
logger.info(className + " " + methodName + ": " + (System.currentTimeMillis() - startTime) + "ms");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Output to console how long a certain task has taken (regardless of DebugLevel).
|
/** Output to console how long a certain task has taken (regardless of DebugLevel).
|
||||||
@param className Name of the class executing the task
|
@param className Name of the class executing the task
|
||||||
@param methodName Name or description of the task
|
@param methodName Name or description of the task
|
||||||
@param startTime Timestamp marking the beginning of the task */
|
@param startTime Timestamp marking the beginning of the task */
|
||||||
public static void logTimeTakenDefault(String className, String methodName, long startTime) {
|
public static void logTimeTaken(String className, String methodName, long startTime) {
|
||||||
|
logTimeTaken(className, methodName, startTime, DebugLevel.LOW);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Output to console how long a certain task has taken if DebugLevel is equal to or higher than the specified threshold.
|
||||||
|
@param className Name of the class executing the task
|
||||||
|
@param methodName Name or description of the task
|
||||||
|
@param startTime Timestamp marking the beginning of the task
|
||||||
|
@param logThreshold the DebugLevel threshold */
|
||||||
|
public static void logTimeTaken(String className, String methodName, long startTime, DebugLevel logThreshold) {
|
||||||
|
switch (logThreshold) {
|
||||||
|
case LOW -> printTime(className, methodName, startTime);
|
||||||
|
case MEDIUM -> {
|
||||||
|
if (debugLevel != DebugLevel.LOW) {
|
||||||
|
printTime(className, methodName, startTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case HIGH -> {
|
||||||
|
if (debugLevel == DebugLevel.HIGH) {
|
||||||
|
printTime(className, methodName, startTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void log(String content, boolean logAsWarning) {
|
||||||
|
if (logAsWarning) {
|
||||||
|
logger.warning(content);
|
||||||
|
} else {
|
||||||
|
logger.info(content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void printTime(String className, String methodName, long startTime) {
|
||||||
logger.info(className + " " + methodName + ": " + (System.currentTimeMillis() - startTime) + "ms");
|
logger.info(className + " " + methodName + ": " + (System.currentTimeMillis() - startTime) + "ms");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Accesses the playersIndex to up it by 1 and return its previous value. */
|
||||||
|
private static int nextPlayersIndex() {
|
||||||
|
return playersIndex.getAndIncrement();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns true if the playersIndex is 10, or any subsequent increment of 10. */
|
||||||
|
private static boolean incrementOfTen() {
|
||||||
|
return (playersIndex.get() == 10 || (playersIndex.get() > 10 && playersIndex.get() % 10 == 0));
|
||||||
|
}
|
||||||
}
|
}
|
@ -12,7 +12,7 @@ public class OfflinePlayerHandler {
|
|||||||
private static ConcurrentHashMap<String, UUID> offlinePlayerUUIDs;
|
private static ConcurrentHashMap<String, UUID> offlinePlayerUUIDs;
|
||||||
private static ArrayList<String> playerNames;
|
private static ArrayList<String> playerNames;
|
||||||
|
|
||||||
static{
|
static {
|
||||||
offlinePlayerUUIDs = new ConcurrentHashMap<>();
|
offlinePlayerUUIDs = new ConcurrentHashMap<>();
|
||||||
playerNames = new ArrayList<>();
|
playerNames = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user