Separated the API into 3 different interfaces: RequestGenerator, StatCalculator, and StatFormatter

This commit is contained in:
Artemis-the-gr8 2022-07-27 23:47:53 +02:00
parent 81acf6b308
commit 3a542ec72a
12 changed files with 212 additions and 242 deletions

View File

@ -1,34 +1,19 @@
package com.gmail.artemis.the.gr8.playerstats.api;
import com.gmail.artemis.the.gr8.playerstats.Main;
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
import net.kyori.adventure.text.*;
import org.bukkit.Material;
import org.bukkit.Statistic;
import org.bukkit.entity.EntityType;
import com.gmail.artemis.the.gr8.playerstats.enums.Target;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
/** This is the outgoing API that you can use to access the core functionality of PlayerStats.
To work with it, you need to call PlayerStats.{@link #getAPI()} to get an instance of {@link PlayerStatsAPI}.
You can then use this object to access any of the further methods.
To work with it, you need to call PlayerStats.{@link #getAPI()} to get an instance of
{@link PlayerStatsAPI}. You can then use this object to access any of the further methods.
<br>
<br>Since calculating a top or server statistics can take some time, it is recommended to call all the
getServerStat() or getTopStats() methods asynchronously. Otherwise, the main Thread will have
to wait until all calculations are done, and this might cause some lag spikes on the server.
<br>
<br>The result of the methods in PlayerStats' API are returned in the form of a 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. Normally you would
have to add the library as a dependency, but since the library is included in PlayerStats, you can
access it directly. 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>.
<br>
<br>Alternatively, you can also turn your TextComponent into a plain String with
{@link #statResultComponentToString(TextComponent)}. Don't use Adventure's method .content()
on your statResult to do this - because of the way the TextComponent is built by PlayerStats,
you won't be able to get the full content that way. </p>*/
<br>Since calculating a top or server statistics can take some time, I strongly
encourage you to call all the getServerStat() and getTopStats() methods from the
{@link StatCalculator} asynchronously. Otherwise, the main Thread will have to wait
until all calculations are done, and this might severely impact server performance.
*/
public interface PlayerStats {
/** Returns an instance of the {@link PlayerStatsAPI}.
@ -38,68 +23,15 @@ public interface PlayerStats {
return Main.getPlayerStatsAPI();
}
String statResultComponentToString(TextComponent component);
/** The {@link StatCalculator} is responsible for getting, calculating and/or ordering raw numbers.
It gets its data from the vanilla statistic files (stored by the server). It can return three kinds of data,
depending on the chosen {@link Target}:
<br>- int (for {@link Target#PLAYER})
<br>- long (for {@link Target#SERVER})
<br>- LinkedHashMap[String player-name, Integer number] (for {@link Target#TOP})*/
StatCalculator statCalculator();
/** Get a formatted player-statistic of Statistic.Type Untyped.
@return a TextComponent with the following parts:
<br>[player-name]: [number] [stat-name]
@throws NullPointerException if statistic or playerName is null*/
TextComponent getPlayerStat(@NotNull Statistic statistic, @NotNull String playerName) throws NullPointerException;
RequestGenerator requestGenerator();
/** Get a formatted player-statistic of Statistic.Type Block or Item.
@return a TextComponent with the following parts:
<br>[player-name]: [number] [stat-name] [sub-stat-name]
@throws NullPointerException if statistic, material or playerName is null*/
TextComponent getPlayerStat(@NotNull Statistic statistic, @NotNull Material material, @NotNull String playerName) throws NullPointerException;
/** Get a formatted player-statistic of Statistic.Type Entity.
@return a TextComponent with the following parts:
<br>[player-name]: [number] [stat-name] [sub-stat-name]
@throws NullPointerException if statistic, entity or playerName is null*/
TextComponent getPlayerStat(@NotNull Statistic statistic, @NotNull EntityType entity, @NotNull String playerName) throws NullPointerException;
/** Get a formatted server-statistic of Statistic.Type Untyped. Don't call this from the main Thread (see class description)!
@return a TextComponent with the following parts:
<br>[Total on] [server-name]: [number] [stat-name]
@throws NullPointerException if statistic is null*/
TextComponent getServerStat(@NotNull Statistic statistic) throws NullPointerException;
/** Get a formatted server-statistic of Statistic.Type Block or Item. Don't call this from the main Thread (see class description)!
@return a TextComponent with the following parts:
<br>[Total on] [server-name]: [number] [stat-name] [sub-stat-name]
@throws NullPointerException if statistic or material is null*/
TextComponent getServerStat(@NotNull Statistic statistic, @NotNull Material material) throws NullPointerException;
/** Get a formatted server-statistic of Statistic.Type Entity. Don't call this from the main Thread (see class description)!
@return a TextComponent with the following parts:
<br>[Total on] [server-name]: [number] [stat-name] [sub-stat-name]
@throws NullPointerException if statistic or entity is null*/
TextComponent getServerStat(@NotNull Statistic statistic, @NotNull EntityType entity) throws NullPointerException;
/** Get a formatted top-statistic of Statistic.Type Untyped. Don't call this from the main Thread (see class description)!
@return a TextComponent with the following parts:
<br>[PlayerStats] [Top 10] [stat-name]
<br> [1.] [player-name] [number]
<br> [2.] [player-name] [number]
<br> [3.] etc...
@throws NullPointerException if statistic is null*/
TextComponent getTopStats(@NotNull Statistic statistic) throws NullPointerException;
/** Get a formatted top-statistic of Statistic.Type Block or Item. Don't call this from the main Thread (see class description)!
@return a TextComponent with the following parts:
<br>[PlayerStats] [Top 10] [stat-name] [sub-stat-name]
<br> [1.] [player-name] [number]
<br> [2.] [player-name] [number]
<br> [3.] etc...
@throws NullPointerException if statistic or material is null*/
TextComponent getTopStats(@NotNull Statistic statistic, @NotNull Material material) throws NullPointerException;
/** Get a formatted top-statistic of Statistic.Type Entity. Don't call this from the main Thread (see class description)!
@return a TextComponent with the following parts:
<br>[PlayerStats] [Top 10] [stat-name] [sub-stat-name]
<br> [1.] [player-name] [number]
<br> [2.] [player-name] [number]
<br> [3.] etc...
@throws NullPointerException if statistic or entity is null*/
TextComponent getTopStats(@NotNull Statistic statistic, @NotNull EntityType entity) throws NullPointerException;
StatFormatter statFormatter();
}

View File

@ -1,19 +1,7 @@
package com.gmail.artemis.the.gr8.playerstats.api;
import com.gmail.artemis.the.gr8.playerstats.enums.Target;
import com.gmail.artemis.the.gr8.playerstats.models.StatRequest;
import com.gmail.artemis.the.gr8.playerstats.statistic.RequestManager;
import com.gmail.artemis.the.gr8.playerstats.statistic.StatManager;
import com.gmail.artemis.the.gr8.playerstats.utils.MyLogger;
import net.kyori.adventure.text.TextComponent;
import org.bukkit.Material;
import org.bukkit.Statistic;
import org.bukkit.entity.EntityType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.LinkedHashMap;
import static org.jetbrains.annotations.ApiStatus.Internal;
@ -32,76 +20,17 @@ public final class PlayerStatsAPI implements PlayerStats {
}
@Override
public String statResultComponentToString(TextComponent component) {
return statFormatter.statResultComponentToString(component);
public StatCalculator statCalculator() {
return statManager;
}
@Override
public TextComponent getPlayerStat(@NotNull Statistic statistic, @NotNull String playerName) throws NullPointerException {
return getFormattedStatistic(Target.PLAYER, statistic, null, null, playerName);
public RequestGenerator requestGenerator() {
return requestManager;
}
@Override
public TextComponent getPlayerStat(@NotNull Statistic statistic, @NotNull Material material, @NotNull String playerName) throws NullPointerException {
return getFormattedStatistic(Target.PLAYER, statistic, material, null, playerName);
}
@Override
public TextComponent getPlayerStat(@NotNull Statistic statistic, @NotNull EntityType entity, @NotNull String playerName) throws NullPointerException {
return getFormattedStatistic(Target.PLAYER, statistic, null, entity, playerName);
}
@Override
public TextComponent getServerStat(@NotNull Statistic statistic) throws NullPointerException {
return getFormattedStatistic(Target.SERVER, statistic, null, null, null);
}
@Override
public TextComponent getServerStat(@NotNull Statistic statistic, @NotNull Material material) throws NullPointerException {
return getFormattedStatistic(Target.SERVER, statistic, material, null, null);
}
@Override
public TextComponent getServerStat(@NotNull Statistic statistic, @NotNull EntityType entity) throws NullPointerException {
return getFormattedStatistic(Target.SERVER, statistic, null, entity, null);
}
@Override
public TextComponent getTopStats(@NotNull Statistic statistic) throws NullPointerException {
return getFormattedStatistic(Target.TOP, statistic, null, null, null);
}
@Override
public TextComponent getTopStats(@NotNull Statistic statistic, @NotNull Material material) throws NullPointerException {
return getFormattedStatistic(Target.TOP, statistic, material, null, null);
}
@Override
public TextComponent getTopStats(@NotNull Statistic statistic, @NotNull EntityType entity) throws NullPointerException {
return getFormattedStatistic(Target.TOP, statistic, null, entity, null);
}
private TextComponent getFormattedStatistic(@NotNull Target selection, @NotNull Statistic statistic,
@Nullable Material material, @Nullable EntityType entity, @Nullable String playerName) throws NullPointerException {
StatRequest request = requestManager.generateRequest(selection, statistic, material, entity, playerName);
if (requestManager.validateAPIRequest(request)) {
MyLogger.logMsg("API is being called! We are calculating a " + selection + "for " + statistic);
switch (selection) {
case PLAYER -> {
int stat = statManager.getPlayerStat(request);
return statFormatter.formatPlayerStat(request, stat);
}
case SERVER -> {
long stat = statManager.getServerStat(request);
return statFormatter.formatServerStat(request, stat);
}
case TOP -> {
LinkedHashMap<String, Integer> stats = statManager.getTopStats(request);
return statFormatter.formatTopStat(request, stats);
}
}
}
throw new NullPointerException("Not enough parameters are present for a valid statistic look-up! " +
"Make sure you include a block/item/entity if Statistic.Type is not Untyped, and include a playerName if you want a player-statistic");
public StatFormatter statFormatter() {
return statFormatter;
}
}

View File

@ -1,29 +1,32 @@
package com.gmail.artemis.the.gr8.playerstats.api;
import com.gmail.artemis.the.gr8.playerstats.enums.Target;
import com.gmail.artemis.the.gr8.playerstats.models.StatRequest;
import org.bukkit.Material;
import org.bukkit.Statistic;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.EntityType;
import org.jetbrains.annotations.NotNull;
/** The {@link RequestGenerator} can turn some user input, such as a String
(for example: "stat animals_bred") into a specific {@link StatRequest} that holds
all the information {@link PlayerStatsAPI} needs to work with.
This StatRequest is then used by the {@link StatCalculator} to get the desired statistic data.*/
/** Turns user input into a valid {@link StatRequest}. This StatRequest should hold all
the information PlayerStats needs to work with, and is used by the {@link StatCalculator}
to get the desired statistic data.*/
public interface RequestGenerator {
/** This will create a {@link StatRequest} from the provided args, with the requesting Player (or Console)
as CommandSender. This CommandSender will receive feedback messages if the SimpleStatRequest could not be created.
as CommandSender. This CommandSender will receive feedback messages if the StatRequest could not be created.
@param args an Array of args such as a CommandSender would put in Minecraft chat:
<p>- a stat-name (example: "mine_block")</p>
<p>- if applicable, a sub-stat-name (example: diorite)(</p>
<p>- a target for this lookup: can be "top", "server", "player" (or "me" to indicate the current CommandSender)</p>
<p>- if "player" was chosen, include a player-name</p>
@param sender the CommandSender that requested this specific statistic
@return the generated StatRequest
@throws IllegalArgumentException if the args do not result in a valid statistic look-up*/
StatRequest generateRequest(CommandSender sender, String[] args);
StatRequest generateRequest(@NotNull Target selection, @NotNull Statistic statistic, Material material, EntityType entity, String playerName);
StatRequest createPlayerStatRequest(String playerName, Statistic statistic, Material material, EntityType entity);
StatRequest createServerStatRequest(Statistic statistic, Material material, EntityType entityType);
StatRequest createTopStatRequest(Statistic statistic, Material material, EntityType entityType);
}

View File

@ -5,12 +5,16 @@ import com.gmail.artemis.the.gr8.playerstats.models.StatRequest;
import java.util.LinkedHashMap;
/** The {@link StatCalculator} represents the actual statistic-getting magic that happens once a valid
{@link StatRequest} has been obtained. It takes a valid StatRequest, and returns (a map of) numbers. */
{@link StatRequest} is passed to it. It takes a valid StatRequest, and returns (a map of) numbers.
For more information on how to create a valid StatRequest, see the class description for {@link StatRequest}.*/
public interface StatCalculator {
/** Returns the requested Statistic*/
int getPlayerStat(StatRequest request);
/** Don't call from main Thread!*/
long getServerStat(StatRequest request);
/** Don't call from main Thread!*/
LinkedHashMap<String, Integer> getTopStats(StatRequest request);
}

View File

@ -1,25 +1,47 @@
package com.gmail.artemis.the.gr8.playerstats.api;
import com.gmail.artemis.the.gr8.playerstats.models.StatRequest;
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
import net.kyori.adventure.text.*;
import java.util.LinkedHashMap;
/** The {@link StatFormatter} defines what the output of any given statistic look-up should be.
<br></br>
<p>The Formatter takes a {@link StatRequest} and the result of {@link StatCalculator} calculations, and transforms the
request object and raw numbers into a pretty message (TextComponent) with all the relevant information in it.
This output is ready to be sent to a Player or Console, or can be turned into a String representation if necessary.*/
/** The {@link StatFormatter} formats raw numbers into pretty messages.
This Formatter takes a {@link StatRequest} and combines it with the raw number(s)
returned by the {@link StatCalculator}, and transforms those into a pretty message
(by default a TextComponent) with all the relevant information in it.
<br>
<br>The output is ready to be sent to a Minecraft client or console with the Adventure library.
To send a Component, you need to get a {@link BukkitAudiences} object. Normally you would
have to add the library as a dependency, but since the library is included in PlayerStats, you can
access it directly. 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>.
<br>
<br>Alternatively, you can also turn your TextComponent into a plain String with
{@link #statResultComponentToString(TextComponent)}. Don't use Adventure's method .content()
on your statResult to do this - because of the way the TextComponent is built by PlayerStats,
you won't be able to get the full content that way.*/
public interface StatFormatter {
/** Turns a TextComponent into its String representation. If you don't want to work with
Adventure's TextComponents, you can call this method to turn any stat-result into a String.
@return a String representation of this TextComponent, without hover/click events, but with color, style and formatting */
@return a String representation of this TextComponent, without hover/click events,
but with color, style and formatting. TranslatableComponents will be turned into
plain English.*/
String statResultComponentToString(TextComponent statResult);
/** @return a TextComponent with the following parts:
<br>[player-name]: [number] [stat-name] {sub-stat-name}*/
TextComponent formatPlayerStat(StatRequest request, int playerStat);
/** @return a TextComponent with the following parts:
<br>[Total on] [server-name]: [number] [stat-name] [sub-stat-name]*/
TextComponent formatServerStat(StatRequest request, long serverStat);
/** @return a TextComponent with the following parts:
<br>[PlayerStats] [Top 10] [stat-name] [sub-stat-name]
<br> [1.] [player-name] [number]
<br> [2.] [player-name] [number]
<br> [3.] etc...*/
TextComponent formatTopStat(StatRequest request, LinkedHashMap<String, Integer> topStats);
}

View File

@ -1,40 +1,70 @@
package com.gmail.artemis.the.gr8.playerstats.models;
import com.gmail.artemis.the.gr8.playerstats.enums.Target;
import com.gmail.artemis.the.gr8.playerstats.api.RequestGenerator;
import org.bukkit.Material;
import org.bukkit.Statistic;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.entity.EntityType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/** The Object PlayerStats uses to calculate the appropriate statistic.
It is generated from the args provided by a CommandSender when /stat is called,
and always contains this CommandSender. By default, {@link #getSelection()}
will return {@link Target#TOP}, unless another selection is specified in the args.*/
public final class StatRequest {
/** A StatRequest is the object PlayerStats uses to calculate and format the requested statistic.
This object can be generated from two different sources:
<br>- Internally: by PlayerStats itself when /stat is called, using the args provided by the CommandSender.
<br>- Externally: through the API methods provided by the {@link RequestGenerator} interface.
<br>
<br>For this StatRequest to be valid, it needs the following values:
<ul>
<li> a {@link Statistic} <code>statistic</code> </li>
<li> if this Statistic is not of {@link Statistic.Type} Untyped, a <code>subStatEntryName</code> needs to be set,
together with one of the following values:
<br>- for Type.Block: a {@link Material} <code>blockMaterial</code>
<br>- for Type.Item: a {@link Material} <code>itemMaterial</code>
<br>- for Type.Entity: an {@link EntityType} <code>entityType</code>
<li> a {@link Target} <code>target</code> (automatically set for all API-requests)
<li> if the <code>target</code> is Target.Player, a <code>playerName</code> needs to be added
</ul>*/
public class StatRequest {
private final CommandSender sender;
private boolean isAPIRequest;
private Statistic statistic;
private String playerName;
private Target selection;
private Target target;
private String subStatEntry;
private String subStatEntryName;
private EntityType entity;
private Material block;
private Material item;
private boolean playerFlag;
//make a SimpleStatRequest for a given CommandSender with some default values
/** Create a new {@link StatRequest} with default values:
<br>- CommandSender sender (provided)
<br>- Target <code>target</code> = {@link Target#TOP}
<br>- boolean <code>playerFlag</code> = false
<br>- boolean <code>isAPIRequest</code> = false
@param sender the CommandSender who prompted this RequestGenerator
*/
public StatRequest(@NotNull CommandSender sender) {
this(sender, false);
}
/** Create a new {@link StatRequest} with default values:
<br>- CommandSender sender (provided)
<br>- Target target = {@link Target#TOP}
<br>- boolean playerFlag = false
<br>- boolean isAPIRequest (provided)
@param sender the CommandSender who prompted this RequestGenerator
@param isAPIRequest whether this RequestGenerator is coming through the API or the onCommand
*/
public StatRequest(@NotNull CommandSender sender, boolean isAPIRequest) {
this.sender = sender;
this.isAPIRequest = isAPIRequest;
selection = Target.TOP;
target = Target.TOP;
playerFlag = false;
}
@ -54,23 +84,30 @@ public final class StatRequest {
return sender instanceof ConsoleCommandSender;
}
/** Set a {@link Statistic} for this StatRequest.*/
public void setStatistic(Statistic statistic) {
this.statistic = statistic;
}
/** Returns the set enum constant Statistic, or null if none was set. */
/** If a {@link Statistic} was set, this will return it.
@return the <code>statistic</code> for this RequestGenerator*/
public Statistic getStatistic() {
return statistic;
}
/** Sets the subStatEntry, and automatically tries to get the corresponding item/block/entity if there is a valid statType present.
If the subStatEntry is set to null, any present item/block/entity is set to null again. */
public void setSubStatEntry(String subStatEntry) {
this.subStatEntry = subStatEntry;
/** Sets the <code>subStatEntryName</code> (a block-, item- or entity-name). */
public void setSubStatEntryName(String subStatEntry) {
this.subStatEntryName = subStatEntry;
}
public String getSubStatEntry() {
return subStatEntry;
/** If a {@link Statistic} is set, and this Statistic is of Type Block, Item or Entity,
this will return the name of said block, item or entity
(in the way .toString would for the given enum constant).
@return the <code>subStatEntryName</code>*/
public @Nullable String getSubStatEntryName() {
return subStatEntryName;
}
public void setPlayerName(String playerName) {
@ -86,18 +123,24 @@ public final class StatRequest {
this.playerFlag = playerFlag;
}
/** The "player" arg is a special case, because it could either be a valid subStatEntry, or indicate that the lookup action should target a specific player.
This is why the playerFlag exists - if this is true, and playerName is null, subStatEntry should be set to "player". */
/** For internal use. The "player" arg is a special case, because it could either be
a valid <code>subStatEntry</code>, or indicate that the lookup action should target
a specific player. This is why the <code>playerFlag</code> exists - if this flag true,
and <code>playerName</code> is null, the <code>subStatEntry</code> should be set to "player". */
public boolean getPlayerFlag() {
return playerFlag;
}
public void setSelection(Target selection) {
this.selection = selection;
public void setTarget(Target target) {
this.target = target;
}
public @NotNull Target getSelection() {
return selection;
/** Returns the {@link Target} for this StatRequest.
If no Target is explicitly set, this will return {@link Target#TOP}.
All static factory methods that create a {@link StatRequest} set the
appropriate Target for themselves, so there is no need to manually set the Target.*/
public @NotNull Target getTarget() {
return target;
}
public void setEntity(EntityType entity) {

View File

@ -177,7 +177,7 @@ public class MessageBuilder {
.append(getStatNumberComponent(request.getStatistic(), stat, Target.PLAYER, request.isConsoleSender()))
.append(space())
.append(getStatNameComponent(request))
.append(getStatUnitComponent(request.getStatistic(), request.getSelection(), request.isConsoleSender())) //space is provided by statUnitComponent
.append(getStatUnitComponent(request.getStatistic(), request.getTarget(), request.isConsoleSender())) //space is provided by statUnitComponent
.build();
return getFormattingFunction(playerStat, Target.PLAYER);
@ -197,7 +197,7 @@ public class MessageBuilder {
.append(getStatNumberComponent(request.getStatistic(), stat, Target.SERVER, request.isConsoleSender()))
.append(space())
.append(getStatNameComponent(request))
.append(getStatUnitComponent(request.getStatistic(), request.getSelection(), request.isConsoleSender())) //space is provided by statUnit
.append(getStatUnitComponent(request.getStatistic(), request.getTarget(), request.isConsoleSender())) //space is provided by statUnit
.build();
return getFormattingFunction(serverStat, Target.SERVER);
@ -308,7 +308,7 @@ public class MessageBuilder {
.append(componentFactory.title(config.getTopStatsTitle(), Target.TOP)).append(space())
.append(componentFactory.titleNumber(statListSize)).append(space())
.append(getStatNameComponent(request)) //space is provided by statUnitComponent
.append(getStatUnitComponent(request.getStatistic(), request.getSelection(), request.isConsoleSender()))
.append(getStatUnitComponent(request.getStatistic(), request.getTarget(), request.isConsoleSender()))
.build();
}
@ -353,7 +353,7 @@ public class MessageBuilder {
private TextComponent getStatNameComponent(StatRequest request) {
if (config.useTranslatableComponents()) {
String statKey = languageKeyHandler.getStatKey(request.getStatistic());
String subStatKey = request.getSubStatEntry();
String subStatKey = request.getSubStatEntryName();
if (subStatKey != null) {
switch (request.getStatistic().getType()) {
case BLOCK -> subStatKey = languageKeyHandler.getBlockKey(request.getBlock());
@ -363,13 +363,13 @@ public class MessageBuilder {
}
}
}
return componentFactory.statAndSubStatNameTranslatable(statKey, subStatKey, request.getSelection());
return componentFactory.statAndSubStatNameTranslatable(statKey, subStatKey, request.getTarget());
}
else {
return componentFactory.statAndSubStatName(
StringUtils.prettify(request.getStatistic().toString()),
StringUtils.prettify(request.getSubStatEntry()),
request.getSelection());
StringUtils.prettify(request.getSubStatEntryName()),
request.getTarget());
}
}

View File

@ -10,7 +10,7 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.RecursiveAction;
/** The action that is executed when a reload-command is triggered. */
public final class ReloadAction extends RecursiveAction {
final class ReloadAction extends RecursiveAction {
private static int threshold;

View File

@ -16,18 +16,31 @@ import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
public class RequestManager implements RequestGenerator {
public final class RequestManager extends StatRequest implements RequestGenerator {
private final EnumHandler enumHandler;
private final OfflinePlayerHandler offlinePlayerHandler;
private static OutputManager outputManager;
public RequestManager(EnumHandler enumHandler, OfflinePlayerHandler offlinePlayerHandler, OutputManager outputManager) {
super(Bukkit.getConsoleSender());
this.enumHandler = enumHandler;
this.offlinePlayerHandler = offlinePlayerHandler;
RequestManager.outputManager = outputManager;
}
/** This will create a {@link StatRequest} from the provided args, with the requesting Player (or Console)
as CommandSender. This CommandSender will receive feedback messages if the StatRequest could not be created.
@param args an Array of args such as a CommandSender would put in Minecraft chat:
<p>- a <code>statName</code> (example: "mine_block")</p>
<p>- if applicable, a <code>subStatEntryName</code> (example: diorite)(</p>
<p>- a <code>target</code> for this lookup: can be "top", "server", "player" (or "me" to indicate the current CommandSender)</p>
<p>- if "player" was chosen, include a <code>playerName</code></p>
@param sender the CommandSender that requested this specific statistic
@return the generated StatRequest
*/
public StatRequest generateRequest(CommandSender sender, String[] args) {
StatRequest request = new StatRequest(sender);
for (String arg : args) {
@ -41,28 +54,28 @@ public class RequestManager implements RequestGenerator {
request.setPlayerFlag(true);
}
else {
if (request.getSubStatEntry() == null) request.setSubStatEntry(arg);
if (request.getSubStatEntryName() == null) request.setSubStatEntryName(arg);
}
}
//check for selection
else if (arg.equalsIgnoreCase("top")) {
request.setSelection(Target.TOP);
request.setTarget(Target.TOP);
}
else if (arg.equalsIgnoreCase("server")) {
request.setSelection(Target.SERVER);
request.setTarget(Target.SERVER);
}
else if (arg.equalsIgnoreCase("me")) {
if (sender instanceof Player) {
request.setPlayerName(sender.getName());
request.setSelection(Target.PLAYER);
request.setTarget(Target.PLAYER);
}
else if (sender instanceof ConsoleCommandSender) {
request.setSelection(Target.SERVER);
request.setTarget(Target.SERVER);
}
}
else if (offlinePlayerHandler.isRelevantPlayer(arg) && request.getPlayerName() == null) {
request.setPlayerName(arg);
request.setSelection(Target.PLAYER);
request.setTarget(Target.PLAYER);
}
else if (arg.equalsIgnoreCase("api")) {
request.setAPIRequest();
@ -72,23 +85,38 @@ public class RequestManager implements RequestGenerator {
return request;
}
/** This method will generate a {@link StatRequest} for a stat-request arriving through the API.*/
public StatRequest generateRequest(@NotNull Target selection, @NotNull Statistic statistic, Material material, EntityType entity, String playerName) {
@Override
public StatRequest createPlayerStatRequest(String playerName, Statistic statistic, Material material, EntityType entity) {
return generateRequest(Target.PLAYER, statistic, material, entity, playerName);
}
@Override
public StatRequest createServerStatRequest(Statistic statistic, Material material, EntityType entityType) {
return generateRequest(Target.SERVER, statistic, material, entityType, null);
}
@Override
public StatRequest createTopStatRequest(Statistic statistic, Material material, EntityType entityType) {
return generateRequest(Target.TOP, statistic, material, entityType, null);
}
/** This method will generate a {@link StatRequest} for a request arriving through the API.*/
private StatRequest generateRequest(@NotNull Target selection, @NotNull Statistic statistic, Material material, EntityType entity, String playerName) {
StatRequest request = new StatRequest(Bukkit.getConsoleSender(), true);
request.setSelection(selection);
request.setTarget(selection);
request.setStatistic(statistic);
switch (statistic.getType()) {
case BLOCK -> {
request.setBlock(material);
request.setSubStatEntry(material.toString());
request.setSubStatEntryName(material.toString());
}
case ITEM -> {
request.setItem(material);
request.setSubStatEntry(material.toString());
request.setSubStatEntryName(material.toString());
}
case ENTITY -> {
request.setEntity(entity);
request.setSubStatEntry(entity.toString());
request.setSubStatEntryName(entity.toString());
}
}
if (selection == Target.PLAYER) request.setPlayerName(playerName);
@ -97,22 +125,30 @@ public class RequestManager implements RequestGenerator {
/** Checks if a given {@link StatRequest} would result in a valid statistic look-up,
and sends a feedback message to the CommandSender that prompted the request if it is invalid.
<p>The following is checked:
<br>1. Is a Statistic set?</br>
<br>2. Is a sub-Statistic needed, and if so, is a corresponding Material/EntityType present?</br>
<br>3. If the target is PLAYER, is a valid PlayerName provided? </br>
@return true if the StatRequest is valid, and false otherwise. */
<br> 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 request the StatRequest to check
@return true if the StatRequest is valid, and false otherwise.
*/
public boolean validateRequest(StatRequest request) {
return validateRequestAndSendMessage(request, request.getCommandSender());
}
/** Checks if a given {@link StatRequest} would result in a valid statistic look-up,
and sends a feedback message in the server console if it is invalid.
<p>The following is checked:
<br>1. Is a Statistic set?</br>
<br>2. Is a sub-Statistic needed, and if so, is a corresponding Material/EntityType present?</br>
<br>3. If the target is PLAYER, is a valid PlayerName provided? </br>
@return true if the StatRequest is valid, and false otherwise. */
<br> 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 request the StatRequest to check
@return true if the StatRequest is valid, and false otherwise.
*/
public boolean validateAPIRequest(StatRequest request) {
return validateRequestAndSendMessage(request, Bukkit.getConsoleSender());
}
@ -123,15 +159,15 @@ public class RequestManager implements RequestGenerator {
return false;
}
Statistic.Type type = request.getStatistic().getType();
if (request.getSubStatEntry() == null && type != Statistic.Type.UNTYPED) {
if (request.getSubStatEntryName() == null && type != Statistic.Type.UNTYPED) {
outputManager.sendFeedbackMsgMissingSubStat(sender, type);
return false;
}
else if (!hasMatchingSubStat(request)) {
outputManager.sendFeedbackMsgWrongSubStat(sender, type, request.getSubStatEntry());
outputManager.sendFeedbackMsgWrongSubStat(sender, type, request.getSubStatEntryName());
return false;
}
else if (request.getSelection() == Target.PLAYER && request.getPlayerName() == null) {
else if (request.getTarget() == Target.PLAYER && request.getPlayerName() == null) {
outputManager.sendFeedbackMsg(sender, StandardMessage.MISSING_PLAYER_NAME);
return false;
}
@ -148,15 +184,15 @@ public class RequestManager implements RequestGenerator {
Statistic.Type type = request.getStatistic().getType();
if (request.getPlayerFlag()) { //unpack the playerFlag
if (type == Statistic.Type.ENTITY && request.getSubStatEntry() == null) {
request.setSubStatEntry("player");
if (type == Statistic.Type.ENTITY && request.getSubStatEntryName() == null) {
request.setSubStatEntryName("player");
}
else {
request.setSelection(Target.PLAYER);
request.setTarget(Target.PLAYER);
}
}
String subStatEntry = request.getSubStatEntry();
String subStatEntry = request.getSubStatEntryName();
switch (type) { //attempt to convert relevant subStatEntries into their corresponding Enum Constant
case BLOCK -> {
Material block = EnumHandler.getBlockEnum(subStatEntry);
@ -171,7 +207,7 @@ public class RequestManager implements RequestGenerator {
if (item != null) request.setItem(item);
}
case UNTYPED -> { //remove unnecessary subStatEntries
if (subStatEntry != null) request.setSubStatEntry(null);
if (subStatEntry != null) request.setSubStatEntryName(null);
}
}
}

View File

@ -12,7 +12,7 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.RecursiveTask;
/** The action that is executed when a stat-command is triggered. */
public final class StatAction extends RecursiveTask<ConcurrentHashMap<String, Integer>> {
final class StatAction extends RecursiveTask<ConcurrentHashMap<String, Integer>> {
private static int threshold;

View File

@ -14,7 +14,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.*;
/** The Thread that is in charge of getting and calculating statistics.*/
public class StatThread extends Thread {
public final class StatThread extends Thread {
private static OutputManager outputManager;
private static StatManager statManager;
@ -57,7 +57,7 @@ public class StatThread extends Thread {
outputManager.sendFeedbackMsgWaitAMoment(request.getCommandSender(), lastCalc > 20000);
}
Target selection = request.getSelection();
Target selection = request.getTarget();
try {
TextComponent statResult = switch (selection) {
case PLAYER -> outputManager.formatPlayerStat(request, statManager.getPlayerStat(request));
@ -65,7 +65,7 @@ public class StatThread extends Thread {
case SERVER -> outputManager.formatServerStat(request, statManager.getServerStat(request));
};
if (request.isAPIRequest()) {
String msg = PlayerStats.getAPI().statResultComponentToString(statResult);
String msg = PlayerStats.getAPI().statFormatter().statResultComponentToString(statResult);
request.getCommandSender().sendMessage(msg);
}
else {

View File

@ -56,6 +56,7 @@ public final class OfflinePlayerHandler {
return Bukkit.getOfflinePlayer(offlinePlayerUUIDs.get(playerName));
}
else {
MyLogger.logMsg("Cannot calculate statistics for player-name: " + playerName, true);
return null;
}
}