Moved all arg-analyzing to StatCommand and got rid of InternalStatRequest (#114)

This commit is contained in:
Artemis-the-gr8 2022-10-15 17:34:52 +02:00
parent b1c015e156
commit 03efe136b0
4 changed files with 154 additions and 265 deletions

View File

@ -56,7 +56,7 @@ public final class Main extends JavaPlugin {
//register all commands and the tabCompleter //register all commands and the tabCompleter
PluginCommand statcmd = this.getCommand("statistic"); PluginCommand statcmd = this.getCommand("statistic");
if (statcmd != null) { if (statcmd != null) {
statcmd.setExecutor(new StatCommand(outputManager, threadManager)); statcmd.setExecutor(new StatCommand(outputManager, threadManager, config, offlinePlayerHandler, enumHandler));
statcmd.setTabCompleter(new TabCompleter(enumHandler, offlinePlayerHandler)); statcmd.setTabCompleter(new TabCompleter(enumHandler, offlinePlayerHandler));
} }
PluginCommand reloadcmd = this.getCommand("statisticreload"); PluginCommand reloadcmd = this.getCommand("statisticreload");
@ -80,17 +80,6 @@ public final class Main extends JavaPlugin {
this.getLogger().info("Disabled PlayerStats!"); this.getLogger().info("Disabled PlayerStats!");
} }
/**
* @return Adventure's BukkitAudiences object
* @throws IllegalStateException if PlayerStats is not enabled
*/
public static @NotNull BukkitAudiences getAdventure() throws IllegalStateException {
if (adventure == null) {
throw new IllegalStateException("Tried to access Adventure without PlayerStats being enabled!");
}
return adventure;
}
/** /**
* *
* @return the JavaPlugin instance associated with PlayerStats * @return the JavaPlugin instance associated with PlayerStats
@ -117,19 +106,9 @@ public final class Main extends JavaPlugin {
return requestProcessor; return requestProcessor;
} }
/** public static @NotNull OfflinePlayerHandler getOfflinePlayerHandler() throws IllegalStateException {
* @return PlayerStats' ConfigHandler
*/
public static @NotNull ConfigHandler getConfigHandler() {
if (config == null) {
config = new ConfigHandler();
}
return config;
}
public static @NotNull OfflinePlayerHandler getOfflinePlayerHandler() {
if (offlinePlayerHandler == null) { if (offlinePlayerHandler == null) {
offlinePlayerHandler = new OfflinePlayerHandler(getConfigHandler()); throw new IllegalStateException("PlayerStats does not seem to be loaded!");
} }
return offlinePlayerHandler; return offlinePlayerHandler;
} }
@ -141,17 +120,6 @@ public final class Main extends JavaPlugin {
return languageKeyHandler; return languageKeyHandler;
} }
/**
* Gets the EnumHandler. If there is no EnumHandler, one will be created.
* @return PlayerStat's EnumHandler
*/
public static @NotNull EnumHandler getEnumHandler() {
if (enumHandler == null) {
enumHandler = new EnumHandler();
}
return enumHandler;
}
private void initializeMainClasses() { private void initializeMainClasses() {
pluginInstance = this; pluginInstance = this;
adventure = BukkitAudiences.create(this); adventure = BukkitAudiences.create(this);

View File

@ -1,38 +1,47 @@
package com.artemis.the.gr8.playerstats.commands; package com.artemis.the.gr8.playerstats.commands;
import com.artemis.the.gr8.playerstats.ThreadManager; import com.artemis.the.gr8.playerstats.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; import com.artemis.the.gr8.playerstats.enums.StandardMessage;
import com.artemis.the.gr8.playerstats.enums.Target; import com.artemis.the.gr8.playerstats.enums.Target;
import com.artemis.the.gr8.playerstats.msg.OutputManager; import com.artemis.the.gr8.playerstats.msg.OutputManager;
import com.artemis.the.gr8.playerstats.statistic.InternalStatRequest; import com.artemis.the.gr8.playerstats.statistic.*;
import com.artemis.the.gr8.playerstats.statistic.PlayerStatRequest;
import com.artemis.the.gr8.playerstats.statistic.StatRequest;
import com.artemis.the.gr8.playerstats.utils.EnumHandler; import com.artemis.the.gr8.playerstats.utils.EnumHandler;
import com.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler; import com.artemis.the.gr8.playerstats.utils.OfflinePlayerHandler;
import net.kyori.adventure.text.TextComponent; import org.bukkit.Material;
import org.bukkit.Statistic; import org.bukkit.Statistic;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
public class StatCommand implements CommandExecutor { public class StatCommand implements CommandExecutor {
private static final Pattern pattern = Pattern.compile("top|server|me|player");
private static ThreadManager threadManager; private static ThreadManager threadManager;
private static OutputManager outputManager; private static OutputManager outputManager;
private OfflinePlayerHandler offlinePlayerHandler; private static ConfigHandler config;
private EnumHandler enumHandler; private final OfflinePlayerHandler offlinePlayerHandler;
private final EnumHandler enumHandler;
public StatCommand(OutputManager m, ThreadManager t) { public StatCommand(OutputManager m, ThreadManager t, ConfigHandler c, OfflinePlayerHandler o, EnumHandler e) {
threadManager = t; threadManager = t;
outputManager = m; outputManager = m;
config = c;
offlinePlayerHandler = o;
enumHandler = e;
} }
@Override @Override
@ -45,44 +54,138 @@ public class StatCommand implements CommandExecutor {
outputManager.sendExamples(sender); outputManager.sendExamples(sender);
} }
else { else {
StatRequest<TextComponent> request = new InternalStatRequest(sender, args); ArgProcessor processor = new ArgProcessor(sender, args);
if (request.isValid()) { if (processor.request != null) {
threadManager.startStatThread(request); threadManager.startStatThread(processor.request);
} else { } else {
sendFeedback(sender, request); sendFeedback(sender, processor);
return false; return false;
} }
} }
return true; return true;
} }
/**
* Analyzes the provided args and sends an appropriate
* feedback message to the CommandSender that called the
* stat command. The following is checked:
* <ul>
* <li>Is a <code>statistic</code> set?
* <li>Is a <code>subStatEntry</code> needed, and if so,
* is a corresponding Material/EntityType present?
* <li>If the <code>target</code> is Player, is a valid
* <code>playerName</code> provided?
* </ul>
*
* @param sender the CommandSender to send feedback to
* @param processor the ArgProcessor object that holds
* the analyzed args
*/
private void sendFeedback(CommandSender sender, @NotNull ArgProcessor processor) {
if (processor.statistic == null) {
outputManager.sendFeedbackMsg(sender, StandardMessage.MISSING_STAT_NAME);
}
else if (processor.target == Target.PLAYER && processor.playerName == null) {
outputManager.sendFeedbackMsg(sender, StandardMessage.MISSING_PLAYER_NAME);
}
else {
Statistic.Type type = processor.statistic.getType();
if (type != Statistic.Type.UNTYPED && processor.subStatName == null) {
outputManager.sendFeedbackMsgMissingSubStat(sender, type);
} else {
outputManager.sendFeedbackMsgWrongSubStat(sender, type, processor.subStatName);
}
}
}
private final class ArgProcessor { private final class ArgProcessor {
private String[] argsToProcess; private String[] argsToProcess;
private Statistic statistic; private Statistic statistic;
private String subStatistic; private String subStatName;
private Target target;
private String playerName;
private StatRequest<?> request;
private ArgProcessor(CommandSender sender, String[] args) { private ArgProcessor(CommandSender sender, String[] args) {
argsToProcess = args; argsToProcess = args;
process(sender);
extractStatistic();
extractSubStatistic();
extractTarget(sender);
combineProcessedArgsIntoRequest();
} }
private StatRequest<?> process(CommandSender sender) { private void combineProcessedArgsIntoRequest() {
Pattern pattern = Pattern.compile("top|server|me|player"); if (statistic == null ||
extractStatistic(); target == Target.PLAYER && playerName == null) {
return;
}
String playerName = tryToFindPlayerName(argsToProcess); RequestGenerator<?> requestGenerator =
switch (target) {
case PLAYER -> new PlayerStatRequest(playerName);
case SERVER -> new ServerStatRequest();
case TOP -> new TopStatRequest(config.getTopListMaxSize());
};
switch (statistic.getType()) {
case UNTYPED -> request = requestGenerator.untyped(statistic);
case BLOCK -> {
Material block = EnumHandler.getBlockEnum(subStatName);
if (block != null) {
request = requestGenerator.blockOrItemType(statistic, block);
}
}
case ITEM -> {
Material item = EnumHandler.getItemEnum(subStatName);
if (item != null) {
request = requestGenerator.blockOrItemType(statistic, item);
}
}
case ENTITY -> {
EntityType entity = EnumHandler.getEntityEnum(subStatName);
if (entity != null) {
request = requestGenerator.entityType(statistic, entity);
}
}
}
}
private void extractTarget(CommandSender sender) {
String targetArg = null;
for (String arg : argsToProcess) { for (String arg : argsToProcess) {
Matcher matcher = pattern.matcher(arg); Matcher matcher = pattern.matcher(arg);
if (matcher.find()) { if (matcher.find()) {
switch (matcher.group()) { targetArg = matcher.group();
switch (targetArg) {
case "me" -> {
if (sender instanceof Player) {
target = Target.PLAYER;
playerName = sender.getName();
} else {
target = Target.SERVER;
}
}
case "player" -> { case "player" -> {
if (playerName != null || containsPlayerTwice(argsToProcess)) { target = Target.PLAYER;
new PlayerStatRequest(playerName); playerName = tryToFindPlayerName(argsToProcess);
} }
case "server" -> target = Target.SERVER;
case "top" -> target = Target.TOP;
}
argsToProcess = removeArg(targetArg);
break;
} }
} }
if (targetArg == null) {
String playerName = tryToFindPlayerName(argsToProcess);
if (playerName != null) {
target = Target.PLAYER;
this.playerName = playerName;
} else {
target = Target.TOP;
} }
} }
} }
@ -97,7 +200,7 @@ public class StatCommand implements CommandExecutor {
} }
if (statName != null) { if (statName != null) {
statistic = EnumHandler.getStatEnum(statName); statistic = EnumHandler.getStatEnum(statName);
argsToProcess = removeArg(argsToProcess, statName); argsToProcess = removeArg(statName);
} }
} }
@ -108,9 +211,29 @@ public class StatCommand implements CommandExecutor {
return; return;
} }
for (String arg : argsToProcess) { String subStatName = null;
List<String> subStats = Arrays.stream(argsToProcess)
.filter(enumHandler::isSubStatEntry)
.toList();
if (subStats.isEmpty()) {
return;
} }
else if (subStats.size() == 1) {
subStatName = subStats.get(0);
}
else {
for (String arg : subStats) {
if (!arg.equalsIgnoreCase("player")) {
subStatName = arg;
break;
}
}
if (subStatName == null) {
subStatName = "player";
}
}
this.subStatName = subStatName;
argsToProcess = removeArg(subStatName);
} }
@Contract(pure = true) @Contract(pure = true)
@ -123,50 +246,10 @@ public class StatCommand implements CommandExecutor {
return null; return null;
} }
private boolean containsPlayerTwice(String[] args) { private String[] removeArg(String argToRemove) {
return Arrays.stream(args) ArrayList<String> currentArgs = new ArrayList<>(Arrays.asList(argsToProcess));
.filter(arg -> arg.equalsIgnoreCase("player"))
.toList()
.size() >= 2;
}
private String[] removeArg(@NotNull String[] args, String argToRemove) {
ArrayList<String> currentArgs = new ArrayList<>(Arrays.asList(args));
currentArgs.remove(argToRemove); currentArgs.remove(argToRemove);
return currentArgs.toArray(String[]::new); return currentArgs.toArray(String[]::new);
} }
}
/**
* If a given {@link StatRequest} object does not result in a valid
* statistic look-up, this will send a feedback message to the CommandSender
* that made the request. The following is checked:
* <ul>
* <li>Is a <code>statistic</code> set?
* <li>Is a <code>subStatEntry</code> needed, and if so, is a corresponding Material/EntityType present?
* <li>If the <code>target</code> is Player, is a valid <code>playerName</code> provided?
* </ul>
*
* @param sender the CommandSender to send feedback to
* @param request the StatRequest to give feedback on
*/
private void sendFeedback(CommandSender sender, StatRequest<?> request) {
StatRequest.Settings settings = request.getSettings();
if (settings.getStatistic() == null) {
outputManager.sendFeedbackMsg(sender, StandardMessage.MISSING_STAT_NAME);
}
else if (settings.getTarget() == Target.PLAYER && settings.getPlayerName() == null) {
outputManager.sendFeedbackMsg(sender, StandardMessage.MISSING_PLAYER_NAME);
}
else {
Statistic.Type type = settings.getStatistic().getType();
if (type != Statistic.Type.UNTYPED && settings.getSubStatEntryName() == null) {
outputManager.sendFeedbackMsgMissingSubStat(sender, type);
} else {
outputManager.sendFeedbackMsgWrongSubStat(sender, type, settings.getSubStatEntryName());
}
}
} }
} }

View File

@ -1,160 +0,0 @@
package com.artemis.the.gr8.playerstats.statistic;
import com.artemis.the.gr8.playerstats.Main;
import com.artemis.the.gr8.playerstats.config.ConfigHandler;
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 net.kyori.adventure.text.TextComponent;
import org.bukkit.Material;
import org.bukkit.Statistic;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public final class InternalStatRequest extends StatRequest<TextComponent> {
private final ConfigHandler config;
private final OfflinePlayerHandler offlinePlayerHandler;
private final EnumHandler enumHandler;
private final Pattern targetPattern;
public InternalStatRequest(CommandSender sender, String[] args) {
super(sender);
config = Main.getConfigHandler();
offlinePlayerHandler = Main.getOfflinePlayerHandler();
enumHandler = Main.getEnumHandler();
targetPattern = Pattern.compile("top|server|me|player");
processArgs(sender, args);
}
@Override
public @NotNull StatResult<TextComponent> execute() {
return Main.getRequestProcessor().getInternalResult(super.getSettings());
}
private void processArgs(CommandSender sender, String[] args) {
MyLogger.logWarning("processArgs: " + Arrays.toString(args));
String[] argsMinusTarget = extractAndStoreTarget(sender, args);
MyLogger.logWarning("processArgs minus target: " + Arrays.toString(argsMinusTarget));
findStatAndSubStat(argsMinusTarget);
}
private String[] extractAndStoreTarget(CommandSender sender, @NotNull String[] leftoverArgs) {
String playerName = tryToFindPlayerName(leftoverArgs);
for (String arg : leftoverArgs) {
Matcher targetMatcher = targetPattern.matcher(arg);
if (targetMatcher.find()) {
switch (targetMatcher.group()) {
case "player" -> {
if (playerName == null) {
continue;
}
else {
super.getSettings().configureForPlayer(playerName);
String[] extractedPlayerName = removeArg(leftoverArgs, playerName);
return removeArg(extractedPlayerName, arg);
}
}
case "me" -> {
if (sender instanceof Player) {
super.getSettings().configureForPlayer(sender.getName());
} else {
super.getSettings().configureForServer();
}
}
case "server" -> super.getSettings().configureForServer();
case "top" -> super.getSettings().configureForTop(config.getTopListMaxSize());
}
return removeArg(leftoverArgs, arg);
}
}
//if no target is found, but there is a playerName, assume target = Target.PLAYER
if (playerName != null) {
super.getSettings().configureForPlayer(playerName);
return removeArg(leftoverArgs, playerName);
}
//otherwise, assume target = Target.TOP
super.getSettings().configureForTop(config.getTopListMaxSize());
return leftoverArgs;
}
private void findStatAndSubStat(@NotNull String[] leftoverArgs) {
MyLogger.logWarning("findStatAndSubStat: " + Arrays.toString(leftoverArgs));
for (String arg : leftoverArgs) {
if (enumHandler.isStatistic(arg)) {
MyLogger.logWarning("statistic found: " + arg);
Statistic stat = EnumHandler.getStatEnum(arg);
String[] argsWithoutStat = removeArg(leftoverArgs, arg);
findAndStoreSubStat(argsWithoutStat, stat);
}
}
}
private void findAndStoreSubStat(String[] leftoverArgs, Statistic statistic) {
MyLogger.logWarning("findAndStoreSubStat: " + Arrays.toString(leftoverArgs));
if (statistic == null) {
return;
}
else if (leftoverArgs.length == 0) {
super.getSettings().configureUntyped(statistic);
return;
}
for (String arg : leftoverArgs) {
if (enumHandler.isSubStatEntry(arg)) {
switch (statistic.getType()) {
case UNTYPED -> super.getSettings().configureUntyped(statistic);
case ITEM -> {
Material item = EnumHandler.getItemEnum(arg);
if (item != null) {
super.getSettings().configureBlockOrItemType(statistic, item);
}
}
case BLOCK -> {
Material block = EnumHandler.getBlockEnum(arg);
if (block != null) {
super.getSettings().configureBlockOrItemType(statistic, block);
}
}
case ENTITY -> {
EntityType entityType = EnumHandler.getEntityEnum(arg);
if (entityType != null) {
super.getSettings().configureEntityType(statistic, entityType);
}
}
}
break;
}
}
}
@Contract(pure = true)
private @Nullable String tryToFindPlayerName(@NotNull String[] args) {
for (String arg : args) {
if (offlinePlayerHandler.isRelevantPlayer(arg)) {
return arg;
}
}
return null;
}
private String[] removeArg(@NotNull String[] args, String argToRemove) {
ArrayList<String> currentArgs = new ArrayList<>(Arrays.asList(args));
currentArgs.remove(argToRemove);
return currentArgs.toArray(String[]::new);
}
}

View File

@ -29,10 +29,8 @@ import net.kyori.adventure.text.TextComponent;
* By default, the resulting message is a {@link TextComponent}, which can be * By default, the resulting message is a {@link TextComponent}, which can be
* sent directly to a Minecraft client or console with the Adventure library. * sent directly to a Minecraft client or console with the Adventure library.
* To send a Component, you need to get a {@link BukkitAudiences} object, * To send a Component, you need to get a {@link BukkitAudiences} object,
* and use that to send the desired Component. Normally you would have to add * and use that to send the desired Component. Information on how to get
* Adventure as a dependency to your project, but since the library is included * and use the BukkitAudiences object can be found on
* in PlayerStats, you can access it through the PlayerStatsImpl. Information
* on how to get and use the BukkitAudiences object can be found on
* <a href="https://docs.adventure.kyori.net/platform/bukkit.html">Adventure's website</a>. * <a href="https://docs.adventure.kyori.net/platform/bukkit.html">Adventure's website</a>.
* *
* <p>You can also use the provided {@link #formattedString ()} method to get the * <p>You can also use the provided {@link #formattedString ()} method to get the