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
PluginCommand statcmd = this.getCommand("statistic");
if (statcmd != null) {
statcmd.setExecutor(new StatCommand(outputManager, threadManager));
statcmd.setExecutor(new StatCommand(outputManager, threadManager, config, offlinePlayerHandler, enumHandler));
statcmd.setTabCompleter(new TabCompleter(enumHandler, offlinePlayerHandler));
}
PluginCommand reloadcmd = this.getCommand("statisticreload");
@ -80,17 +80,6 @@ public final class Main extends JavaPlugin {
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
@ -117,19 +106,9 @@ public final class Main extends JavaPlugin {
return requestProcessor;
}
/**
* @return PlayerStats' ConfigHandler
*/
public static @NotNull ConfigHandler getConfigHandler() {
if (config == null) {
config = new ConfigHandler();
}
return config;
}
public static @NotNull OfflinePlayerHandler getOfflinePlayerHandler() {
public static @NotNull OfflinePlayerHandler getOfflinePlayerHandler() throws IllegalStateException {
if (offlinePlayerHandler == null) {
offlinePlayerHandler = new OfflinePlayerHandler(getConfigHandler());
throw new IllegalStateException("PlayerStats does not seem to be loaded!");
}
return offlinePlayerHandler;
}
@ -141,17 +120,6 @@ public final class Main extends JavaPlugin {
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() {
pluginInstance = this;
adventure = BukkitAudiences.create(this);

View File

@ -1,38 +1,47 @@
package com.artemis.the.gr8.playerstats.commands;
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.Target;
import com.artemis.the.gr8.playerstats.msg.OutputManager;
import com.artemis.the.gr8.playerstats.statistic.InternalStatRequest;
import com.artemis.the.gr8.playerstats.statistic.PlayerStatRequest;
import com.artemis.the.gr8.playerstats.statistic.StatRequest;
import com.artemis.the.gr8.playerstats.statistic.*;
import com.artemis.the.gr8.playerstats.utils.EnumHandler;
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.Command;
import org.bukkit.command.CommandExecutor;
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.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class StatCommand implements CommandExecutor {
private static final Pattern pattern = Pattern.compile("top|server|me|player");
private static ThreadManager threadManager;
private static OutputManager outputManager;
private OfflinePlayerHandler offlinePlayerHandler;
private EnumHandler enumHandler;
private static ConfigHandler config;
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;
outputManager = m;
config = c;
offlinePlayerHandler = o;
enumHandler = e;
}
@Override
@ -45,44 +54,138 @@ public class StatCommand implements CommandExecutor {
outputManager.sendExamples(sender);
}
else {
StatRequest<TextComponent> request = new InternalStatRequest(sender, args);
if (request.isValid()) {
threadManager.startStatThread(request);
ArgProcessor processor = new ArgProcessor(sender, args);
if (processor.request != null) {
threadManager.startStatThread(processor.request);
} else {
sendFeedback(sender, request);
sendFeedback(sender, processor);
return false;
}
}
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 String[] argsToProcess;
private Statistic statistic;
private String subStatistic;
private String subStatName;
private Target target;
private String playerName;
private StatRequest<?> request;
private ArgProcessor(CommandSender sender, String[] args) {
argsToProcess = args;
process(sender);
extractStatistic();
extractSubStatistic();
extractTarget(sender);
combineProcessedArgsIntoRequest();
}
private StatRequest<?> process(CommandSender sender) {
Pattern pattern = Pattern.compile("top|server|me|player");
extractStatistic();
private void combineProcessedArgsIntoRequest() {
if (statistic == null ||
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) {
Matcher matcher = pattern.matcher(arg);
if (matcher.find()) {
switch (matcher.group()) {
case "player" -> {
if (playerName != null || containsPlayerTwice(argsToProcess)) {
new PlayerStatRequest(playerName);
targetArg = matcher.group();
switch (targetArg) {
case "me" -> {
if (sender instanceof Player) {
target = Target.PLAYER;
playerName = sender.getName();
} else {
target = Target.SERVER;
}
}
case "player" -> {
target = Target.PLAYER;
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) {
statistic = EnumHandler.getStatEnum(statName);
argsToProcess = removeArg(argsToProcess, statName);
argsToProcess = removeArg(statName);
}
}
@ -108,9 +211,29 @@ public class StatCommand implements CommandExecutor {
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)
@ -123,50 +246,10 @@ public class StatCommand implements CommandExecutor {
return null;
}
private boolean containsPlayerTwice(String[] args) {
return Arrays.stream(args)
.filter(arg -> arg.equalsIgnoreCase("player"))
.toList()
.size() >= 2;
}
private String[] removeArg(@NotNull String[] args, String argToRemove) {
ArrayList<String> currentArgs = new ArrayList<>(Arrays.asList(args));
private String[] removeArg(String argToRemove) {
ArrayList<String> currentArgs = new ArrayList<>(Arrays.asList(argsToProcess));
currentArgs.remove(argToRemove);
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
* sent directly to a Minecraft client or console with the Adventure library.
* 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
* Adventure as a dependency to your project, but since the library is included
* in PlayerStats, you can access it through the PlayerStatsImpl. Information
* on how to get and use the BukkitAudiences object can be found on
* and use that to send the desired Component. 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>.
*
* <p>You can also use the provided {@link #formattedString ()} method to get the