Merge remote-tracking branch 'origin/master'

This commit is contained in:
PiggyPiglet 2020-07-27 15:23:11 +08:00
commit 9291184534
22 changed files with 750 additions and 478 deletions

View File

@ -38,3 +38,7 @@ Please also make sure that you use the [latest release][Spigot] or the latest [d
> What steps did you made, to get this bug?
<!-- Please write below this line to prevent formatting issues -->
1.
### Installed expansions
> Please list all expansions that are displayed when running `/papi list`
<!-- Please write below this line to prevent formatting issues -->

View File

@ -18,9 +18,9 @@ repositories {
}
dependencies {
implementation "com.google.code.gson:gson:2.8.5"
implementation "org.bstats:bstats-bukkit:1.5"
implementation "me.rayzr522:jsonmessage:1.2.1"
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'org.bstats:bstats-bukkit:1.5'
implementation 'me.rayzr522:jsonmessage:1.2.1'
compileOnly "org.spigotmc:spigot-api:1.16.1-R0.1-SNAPSHOT"
compileOnly "org.jetbrains:annotations:19.0.0"

View File

@ -181,40 +181,20 @@ public final class PlaceholderAPIPlugin extends JavaPlugin
private void setupMetrics()
{
final Metrics metrics = new Metrics(this);
metrics.addCustomChart(
new Metrics.SimplePie(
"using_expansion_cloud",
() -> getPlaceholderAPIConfig().isCloudEnabled() ? "yes" : "no"));
metrics.addCustomChart(new Metrics.SimplePie("using_expansion_cloud", () -> getPlaceholderAPIConfig().isCloudEnabled() ? "yes" : "no"));
metrics.addCustomChart(
new Metrics.SimplePie("using_spigot", () -> getServerVersion().isSpigot() ? "yes" : "no"));
metrics.addCustomChart(new Metrics.SimplePie("using_spigot", () -> getServerVersion().isSpigot() ? "yes" : "no"));
metrics.addCustomChart(
new Metrics.AdvancedPie(
"expansions_used",
() -> {
Map<String, Integer> map = new HashMap<>();
Map<String, PlaceholderHook> hooks = PlaceholderAPI.getPlaceholders();
metrics.addCustomChart(new Metrics.AdvancedPie("expansions_used", () -> {
final Map<String, Integer> values = new HashMap<>();
if (!hooks.isEmpty())
{
for (final PlaceholderExpansion expansion : getLocalExpansionManager().getExpansions())
{
values.put(expansion.getRequiredPlugin() == null ? expansion.getIdentifier() : expansion.getRequiredPlugin(), 1);
}
for (PlaceholderHook hook : hooks.values())
{
if (hook instanceof PlaceholderExpansion)
{
PlaceholderExpansion expansion = (PlaceholderExpansion) hook;
map.put(
expansion.getRequiredPlugin() == null
? expansion.getIdentifier()
: expansion.getRequiredPlugin(),
1);
}
}
}
return map;
}));
return values;
}));
}
private void setupExpansions()

View File

@ -25,6 +25,10 @@ import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @deprecated This class will be completely removed in the next release, please use {@link me.clip.placeholderapi.expansion.PlaceholderExpansion}
*/
@Deprecated
public abstract class PlaceholderHook
{
@ -35,8 +39,10 @@ public abstract class PlaceholderHook
* player
* @param params String passed to the hook to determine what value to return
* @return value for the requested player and params
* @deprecated This method will be completely removed, please use {@link me.clip.placeholderapi.expansion.PlaceholderExpansion#onRequest(OfflinePlayer, String)}
*/
@Nullable
@Deprecated
public String onRequest(@Nullable final OfflinePlayer player, @NotNull final String params)
{
if (player != null && player.isOnline())
@ -53,6 +59,8 @@ public abstract class PlaceholderHook
* @param player {@link Player} to request the placeholder value for, null if not needed for a player
* @param params String passed to the hook to determine what value to return
* @return value for the requested player and params
*
* @deprecated This method will be completely removed, please use {@link me.clip.placeholderapi.expansion.PlaceholderExpansion#onRequest(OfflinePlayer, String)}
*/
@Nullable
@Deprecated

View File

@ -24,7 +24,15 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import me.clip.placeholderapi.PlaceholderAPIPlugin;
import me.clip.placeholderapi.commands.impl.cloud.CommandECloud;
import me.clip.placeholderapi.commands.impl.local.*;
import me.clip.placeholderapi.commands.impl.local.CommandDump;
import me.clip.placeholderapi.commands.impl.local.CommandExpansionRegister;
import me.clip.placeholderapi.commands.impl.local.CommandExpansionUnregister;
import me.clip.placeholderapi.commands.impl.local.CommandHelp;
import me.clip.placeholderapi.commands.impl.local.CommandInfo;
import me.clip.placeholderapi.commands.impl.local.CommandList;
import me.clip.placeholderapi.commands.impl.local.CommandParse;
import me.clip.placeholderapi.commands.impl.local.CommandReload;
import me.clip.placeholderapi.commands.impl.local.CommandVersion;
import me.clip.placeholderapi.util.Msg;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
@ -43,6 +51,7 @@ public final class PlaceholderCommandRouter implements CommandExecutor, TabCompl
private static final List<PlaceholderCommand> COMMANDS = ImmutableList.of(new CommandHelp(),
new CommandInfo(),
new CommandList(),
new CommandDump(),
new CommandECloud(),
new CommandParse(),
new CommandReload(),

View File

@ -27,6 +27,7 @@ import com.google.common.collect.Sets;
import com.google.common.primitives.Ints;
import me.clip.placeholderapi.PlaceholderAPIPlugin;
import me.clip.placeholderapi.commands.PlaceholderCommand;
import me.clip.placeholderapi.configuration.ExpansionSort;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import me.clip.placeholderapi.expansion.cloud.CloudExpansion;
import me.clip.placeholderapi.util.Format;
@ -39,7 +40,14 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Unmodifiable;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.stream.Collectors;
@ -97,7 +105,7 @@ public final class CommandECloudExpansionList extends PlaceholderCommand
return;
}
expansions.sort(Comparator.comparing(CloudExpansion::getLastUpdate).reversed());
expansions.sort(plugin.getPlaceholderAPIConfig().getExpansionSort().orElse(ExpansionSort.LATEST));
if (!(sender instanceof Player) && params.size() < 2)
{

View File

@ -25,16 +25,16 @@ import me.clip.placeholderapi.PlaceholderAPIPlugin;
import me.clip.placeholderapi.commands.PlaceholderCommand;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import me.clip.placeholderapi.expansion.cloud.CloudExpansion;
import me.clip.placeholderapi.util.Futures;
import me.clip.placeholderapi.util.Msg;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Unmodifiable;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
@ -87,31 +87,31 @@ public final class CommandECloudUpdate extends PlaceholderCommand
Msg.msg(sender,
"&aUpdating expansions: " + expansions.stream().map(CloudExpansion::getName).collect(Collectors.joining("&7, &6", "&8[&6", "&8]&r")));
downloadExpansions(plugin, expansions)
.thenCompose(files -> discoverExpansions(plugin, files))
.whenComplete((classes, exception) -> {
if (exception != null)
{
Msg.msg(sender,
"&cFailed to update expansions: &e" + exception.getMessage());
return;
}
Msg.msg(sender,
"&aSuccessfully downloaded updates, registering new versions.");
Futures.onMainThread(plugin, downloadAndDiscover(expansions, plugin), (classes, exception) -> {
if (exception != null)
{
Msg.msg(sender,
"&cFailed to update expansions: &e" + exception.getMessage());
return;
}
Bukkit.getScheduler().runTask(plugin, () -> {
final String message = classes.stream()
.map(plugin.getLocalExpansionManager()::register)
.filter(Optional::isPresent)
.map(Optional::get)
.map(expansion -> " &a" + expansion.getName() + " &f" + expansion.getVersion())
.collect(Collectors.joining("\n"));
Msg.msg(sender,
"&7Registered expansions:",
message);
});
});
Msg.msg(sender,
"&aSuccessfully downloaded updates, registering new versions.");
final String message = classes.stream()
.filter(Objects::nonNull)
.map(plugin.getLocalExpansionManager()::register)
.filter(Optional::isPresent)
.map(Optional::get)
.map(expansion -> " &a" + expansion.getName() + " &f" + expansion.getVersion())
.collect(Collectors.joining("\n"));
Msg.msg(sender,
"&7Registered expansions:", message);
});
}
@Override
@ -134,22 +134,12 @@ public final class CommandECloudUpdate extends PlaceholderCommand
}
public static CompletableFuture<List<File>> downloadExpansions(@NotNull final PlaceholderAPIPlugin plugin, @NotNull final List<CloudExpansion> expansions)
private static CompletableFuture<List<@Nullable Class<? extends PlaceholderExpansion>>> downloadAndDiscover(@NotNull final List<CloudExpansion> expansions, @NotNull final PlaceholderAPIPlugin plugin)
{
final List<CompletableFuture<File>> futures = expansions.stream()
.map(expansion -> plugin.getCloudExpansionManager().downloadExpansion(expansion, expansion.getVersion()))
.collect(Collectors.toList());
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).thenApplyAsync(v -> futures.stream().map(CompletableFuture::join).collect(Collectors.toList()));
}
public static CompletableFuture<List<Class<? extends PlaceholderExpansion>>> discoverExpansions(@NotNull final PlaceholderAPIPlugin plugin, @NotNull final List<File> files)
{
final List<CompletableFuture<List<Class<? extends PlaceholderExpansion>>>> futures = files.stream()
.map(file -> plugin.getLocalExpansionManager().findExpansionsInFile(file))
.collect(Collectors.toList());
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).thenApplyAsync(v -> futures.stream().map(CompletableFuture::join).flatMap(Collection::stream).collect(Collectors.toList()));
return expansions.stream()
.map(expansion -> plugin.getCloudExpansionManager().downloadExpansion(expansion, expansion.getVersion()))
.map(future -> future.thenCompose(plugin.getLocalExpansionManager()::findExpansionInFile))
.collect(Futures.collector());
}
}

View File

@ -0,0 +1,179 @@
package me.clip.placeholderapi.commands.impl.local;
import com.google.common.io.CharStreams;
import com.google.gson.JsonParser;
import me.clip.placeholderapi.PlaceholderAPIPlugin;
import me.clip.placeholderapi.commands.PlaceholderCommand;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import me.clip.placeholderapi.util.Msg;
import org.bukkit.command.CommandSender;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Unmodifiable;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.logging.Level;
import java.util.stream.Collectors;
public final class CommandDump extends PlaceholderCommand
{
@NotNull
private static final String URL = "https://paste.helpch.at/";
@NotNull
private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG)
.withLocale(Locale.US)
.withZone(ZoneId.of("UTC"));
public CommandDump()
{
super("dump");
}
@Override
public void evaluate(@NotNull final PlaceholderAPIPlugin plugin, @NotNull final CommandSender sender, @NotNull final String alias, @NotNull @Unmodifiable final List<String> params)
{
postDump(makeDump(plugin)).whenComplete((key, exception) -> {
if (exception != null)
{
plugin.getLogger().log(Level.WARNING, "failed to post dump details", exception);
Msg.msg(sender,
"&cFailed to post dump details, check console.");
return;
}
Msg.msg(sender,
"&aSuccessfully posted dump: " + URL + key);
});
}
@NotNull
private CompletableFuture<String> postDump(@NotNull final String dump)
{
return CompletableFuture.supplyAsync(() -> {
try
{
final HttpURLConnection connection = ((HttpURLConnection) new URL(URL + "documents").openConnection());
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "text/plain; charset=utf-8");
connection.setDoOutput(true);
connection.connect();
try (final OutputStream stream = connection.getOutputStream())
{
stream.write(dump.getBytes(StandardCharsets.UTF_8));
}
try (final InputStream stream = connection.getInputStream())
{
//noinspection UnstableApiUsage
final String json = CharStreams.toString(new InputStreamReader(stream, StandardCharsets.UTF_8));
return JsonParser.parseString(json).getAsJsonObject().get("key").getAsString();
}
}
catch (final IOException ex)
{
throw new CompletionException(ex);
}
});
}
@NotNull
private String makeDump(@NotNull final PlaceholderAPIPlugin plugin)
{
final StringBuilder builder = new StringBuilder();
builder.append("Generated: ")
.append(DATE_FORMAT.format(Instant.now()))
.append("\n\n");
builder.append("PlaceholderAPI: ")
.append(plugin.getDescription().getVersion())
.append("\n\n");
builder.append("Expansions Registered:")
.append('\n');
final Map<String, List<PlaceholderExpansion>> expansions = plugin.getLocalExpansionManager()
.getExpansions()
.stream()
.collect(Collectors.groupingBy(PlaceholderExpansion::getAuthor));
for (final Map.Entry<String, List<PlaceholderExpansion>> expansionsByAuthor : expansions.entrySet())
{
builder.append(" ")
.append(expansionsByAuthor.getKey())
.append(": ")
.append('\n');
for (final PlaceholderExpansion expansion : expansionsByAuthor.getValue())
{
builder.append(" ")
.append(expansion.getName())
.append(':')
.append(expansion.getVersion())
.append('\n');
}
}
builder.append('\n');
builder.append("Expansions Directory:")
.append('\n');
final String[] jars = plugin.getLocalExpansionManager()
.getExpansionsFolder()
.list((dir, name) -> name.toLowerCase().endsWith(".jar"));
for (final String jar : jars)
{
builder.append(" ")
.append(jar)
.append('\n');
}
builder.append('\n');
builder.append("Server Info: ")
.append(plugin.getServer().getBukkitVersion())
.append('/')
.append(plugin.getServer().getVersion())
.append("\n\n");
builder.append("Plugin Info:")
.append('\n');
for (final Plugin other : plugin.getServer().getPluginManager().getPlugins())
{
builder.append(" ")
.append(other.getName())
.append(": ")
.append(other.getDescription().getVersion())
.append('\n');
}
return builder.toString();
}
}

View File

@ -24,8 +24,8 @@ import me.clip.placeholderapi.PlaceholderAPIPlugin;
import me.clip.placeholderapi.commands.PlaceholderCommand;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import me.clip.placeholderapi.expansion.manager.LocalExpansionManager;
import me.clip.placeholderapi.util.Futures;
import me.clip.placeholderapi.util.Msg;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Unmodifiable;
@ -65,7 +65,7 @@ public final class CommandExpansionRegister extends PlaceholderCommand
return;
}
manager.findExpansionsInFile(file).whenComplete((classes, exception) -> {
Futures.onMainThread(plugin, manager.findExpansionInFile(file), (clazz, exception) -> {
if (exception != null)
{
Msg.msg(sender,
@ -75,25 +75,25 @@ public final class CommandExpansionRegister extends PlaceholderCommand
return;
}
if (classes.isEmpty())
if (clazz == null)
{
Msg.msg(sender,
"&cNo expansion class found in file: &f" + file);
return;
}
Bukkit.getScheduler().runTask(plugin, () -> {
final Optional<PlaceholderExpansion> expansion = manager.register(classes.get(0));
if (!expansion.isPresent())
{
Msg.msg(sender,
"&cFailed to register expansion from &f" + params.get(0));
return;
}
final Optional<PlaceholderExpansion> expansion = manager.register(clazz);
if (!expansion.isPresent())
{
Msg.msg(sender,
"&aSuccessfully registered expansion: &f" + expansion.get().getName());
});
"&cFailed to register expansion from &f" + params.get(0));
return;
}
Msg.msg(sender,
"&aSuccessfully registered expansion: &f" + expansion.get().getName());
});
}

View File

@ -59,7 +59,7 @@ public final class CommandExpansionUnregister extends PlaceholderCommand
}
final String message = !plugin.getLocalExpansionManager().unregister(expansion.get()) ?
final String message = !expansion.get().unregister() ?
"&cFailed to unregister expansion: &f" :
"&aSuccessfully unregistered expansion: &f";

View File

@ -0,0 +1,31 @@
package me.clip.placeholderapi.configuration;
import me.clip.placeholderapi.expansion.cloud.CloudExpansion;
import org.jetbrains.annotations.NotNull;
import java.util.Comparator;
public enum ExpansionSort implements Comparator<CloudExpansion>
{
NAME(Comparator.comparing(CloudExpansion::getName)),
AUTHOR(Comparator.comparing(CloudExpansion::getAuthor)),
LATEST(Comparator.comparing(CloudExpansion::getLastUpdate).reversed());
@NotNull
private final Comparator<CloudExpansion> comparator;
ExpansionSort(@NotNull final Comparator<CloudExpansion> comparator)
{
this.comparator = comparator;
}
@Override
public final int compare(final CloudExpansion expansion1, final CloudExpansion expansion2)
{
return comparator.compare(expansion1, expansion2);
}
}

View File

@ -23,6 +23,8 @@ package me.clip.placeholderapi.configuration;
import me.clip.placeholderapi.PlaceholderAPIPlugin;
import org.jetbrains.annotations.NotNull;
import java.util.Optional;
public final class PlaceholderAPIConfig
{
@ -64,6 +66,22 @@ public final class PlaceholderAPIConfig
}
public Optional<ExpansionSort> getExpansionSort()
{
final String option = plugin.getConfig().getString("cloud_sorting", ExpansionSort.LATEST.name());
try
{
//noinspection ConstantConditions (bad spigot annotation)
return Optional.of(ExpansionSort.valueOf(option.toUpperCase()));
}
catch (final IllegalArgumentException ignored)
{
return Optional.empty();
}
}
@NotNull
public String dateFormat()
{

View File

@ -20,180 +20,278 @@
package me.clip.placeholderapi.expansion;
import me.clip.placeholderapi.PlaceholderAPI;
import me.clip.placeholderapi.PlaceholderAPIPlugin;
import me.clip.placeholderapi.PlaceholderHook;
import org.apache.commons.lang.Validate;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.configuration.ConfigurationSection;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
public abstract class PlaceholderExpansion extends PlaceholderHook {
public abstract class PlaceholderExpansion extends PlaceholderHook
{
/**
* The name of this expansion
*
* @return {@link #getIdentifier()} by default, name of this expansion if specified
*/
public String getName() {
return getIdentifier();
}
/**
* The placeholder identifier of this expansion
*
* @return placeholder identifier that is associated with this expansion
*/
@NotNull
public abstract String getIdentifier();
/**
* The placeholder identifier of this expansion
*
* @return placeholder identifier that is associated with this expansion
*/
public abstract String getIdentifier();
/**
* The author of this expansion
*
* @return name of the author for this expansion
*/
@NotNull
public abstract String getAuthor();
/**
* The author of this expansion
*
* @return name of the author for this expansion
*/
public abstract String getAuthor();
/**
* The version of this expansion
*
* @return current version of this expansion
*/
@NotNull
public abstract String getVersion();
/**
* The version of this expansion
*
* @return current version of this expansion
*/
public abstract String getVersion();
/**
* The name of the plugin that this expansion hooks into. by default will return the deprecated
* {@link #getPlugin()} method
*
* @return plugin name that this expansion requires to function
*/
public String getRequiredPlugin() {
return getPlugin();
}
/**
* The placeholders associated with this expansion
*
* @return placeholder list that this expansion provides
*/
public List<String> getPlaceholders() {
return null;
}
/**
* Expansions that do not use the ecloud and instead register from the dependency should set this
* to true to ensure that your placeholder expansion is not unregistered when the papi reload
* command is used
*
* @return if this expansion should persist through placeholder reloads
*/
public boolean persist() {
return false;
}
/**
* Check if this placeholder identifier has already been registered
*
* @return true if the identifier for this expansion is already registered
*/
public boolean isRegistered() {
Validate.notNull(getIdentifier(), "Placeholder identifier can not be null!");
return PlaceholderAPI.isRegistered(getIdentifier());
}
/**
* If any requirements need to be checked before this expansion should register, you can check
* them here
*
* @return true if this hook meets all the requirements to register
*/
public boolean canRegister() {
return getRequiredPlugin() == null || Bukkit.getPluginManager().getPlugin(getRequiredPlugin()) != null;
}
/**
* Attempt to register this PlaceholderExpansion
*
* @return true if this expansion is now registered with PlaceholderAPI
*/
public boolean register() {
Validate.notNull(getIdentifier(), "Placeholder identifier can not be null!");
return canRegister() && getPlaceholderAPI().getLocalExpansionManager().register(this);
}
/**
* Quick getter for the {@link PlaceholderAPIPlugin} instance
*
* @return {@link PlaceholderAPIPlugin} instance
*/
public PlaceholderAPIPlugin getPlaceholderAPI() {
return PlaceholderAPIPlugin.getInstance();
}
public String getString(String path, String def) {
return getPlaceholderAPI().getConfig()
.getString("expansions." + getIdentifier() + "." + path, def);
}
public int getInt(String path, int def) {
return getPlaceholderAPI().getConfig()
.getInt("expansions." + getIdentifier() + "." + path, def);
}
public long getLong(String path, long def) {
return getPlaceholderAPI().getConfig()
.getLong("expansions." + getIdentifier() + "." + path, def);
}
public double getDouble(String path, double def) {
return getPlaceholderAPI().getConfig()
.getDouble("expansions." + getIdentifier() + "." + path, def);
}
public List<String> getStringList(String path) {
return getPlaceholderAPI().getConfig()
.getStringList("expansions." + getIdentifier() + "." + path);
}
public Object get(String path, Object def) {
return getPlaceholderAPI().getConfig().get("expansions." + getIdentifier() + "." + path, def);
}
public ConfigurationSection getConfigSection(String path) {
return getPlaceholderAPI().getConfig()
.getConfigurationSection("expansions." + getIdentifier() + "." + path);
}
public ConfigurationSection getConfigSection() {
return getPlaceholderAPI().getConfig().getConfigurationSection("expansions." + getIdentifier());
}
public boolean configurationContains(String path) {
return getPlaceholderAPI().getConfig().contains("expansions." + getIdentifier() + "." + path);
}
@Nullable
@Override /* override for now >:) */
public String onRequest(@Nullable final OfflinePlayer player, @NotNull final String params)
{
return super.onRequest(player, params);
}
/**
* @deprecated As of versions greater than 2.8.7, use {@link #getRequiredPlugin()}
*/
@Deprecated
public String getPlugin() {
return null;
}
/**
* The name of this expansion
*
* @return {@link #getIdentifier()} by default, name of this expansion if specified
*/
@NotNull
public String getName()
{
return getIdentifier();
}
/**
* @deprecated As of versions greater than 2.8.7, use the expansion cloud to show a description
*/
@Deprecated
public String getDescription() {
return null;
}
/**
* The name of the plugin that this expansion hooks into. by default will null
*
* @return plugin name that this expansion requires to function
*/
@Nullable
public String getRequiredPlugin()
{
return getPlugin();
}
/**
* The placeholders associated with this expansion
*
* @return placeholder list that this expansion provides
*/
@NotNull
public List<String> getPlaceholders()
{
return Collections.emptyList();
}
/**
* Expansions that do not use the ecloud and instead register from the dependency should set this
* to true to ensure that your placeholder expansion is not unregistered when the papi reload
* command is used
*
* @return if this expansion should persist through placeholder reloads
*/
public boolean persist()
{
return false;
}
/**
* Check if this placeholder identifier has already been registered
*
* @return true if the identifier for this expansion is already registered
*/
public final boolean isRegistered()
{
return getPlaceholderAPI().getLocalExpansionManager().findExpansionByIdentifier(getIdentifier()).map(it -> it.equals(this)).orElse(false);
}
/**
* If any requirements need to be checked before this expansion should register, you can check
* them here
*
* @return true if this hook meets all the requirements to register
*/
public boolean canRegister()
{
return getRequiredPlugin() == null || Bukkit.getPluginManager().getPlugin(getRequiredPlugin()) != null;
}
/**
* Attempt to register this PlaceholderExpansion
*
* @return true if this expansion is now registered with PlaceholderAPI
* @deprecated This is going to be final in the future, startup and shutdown logic will have their own methods soon.
*/
@Deprecated
public boolean register()
{
return canRegister() && getPlaceholderAPI().getLocalExpansionManager().register(this);
}
/**
* Attempt to unregister this PlaceholderExpansion
*
* @return true if this expansion is now unregistered with PlaceholderAPI
*/
public final boolean unregister()
{
return getPlaceholderAPI().getLocalExpansionManager().unregister(this);
}
/**
* Quick getter for the {@link PlaceholderAPIPlugin} instance
*
* @return {@link PlaceholderAPIPlugin} instance
*/
@NotNull
public final PlaceholderAPIPlugin getPlaceholderAPI()
{
return PlaceholderAPIPlugin.getInstance();
}
// === Configuration ===
@Nullable
public final ConfigurationSection getConfigSection()
{
return getPlaceholderAPI().getConfig().getConfigurationSection("expansions." + getIdentifier());
}
@Nullable
public final ConfigurationSection getConfigSection(@NotNull final String path)
{
final ConfigurationSection section = getConfigSection();
return section == null ? null : section.getConfigurationSection(path);
}
@Nullable
@Contract("_, !null -> !null")
public final Object get(@NotNull final String path, final Object def)
{
final ConfigurationSection section = getConfigSection();
return section == null ? def : section.get(path, def);
}
public final int getInt(@NotNull final String path, final int def)
{
final ConfigurationSection section = getConfigSection();
return section == null ? def : section.getInt(path, def);
}
public final long getLong(@NotNull final String path, final long def)
{
final ConfigurationSection section = getConfigSection();
return section == null ? def : section.getLong(path, def);
}
public final double getDouble(@NotNull final String path, final double def)
{
final ConfigurationSection section = getConfigSection();
return section == null ? def : section.getDouble(path, def);
}
@Nullable
@Contract("_, !null -> !null")
public final String getString(@NotNull final String path, @Nullable final String def)
{
final ConfigurationSection section = getConfigSection();
return section == null ? def : section.getString(path, def);
}
@NotNull
public final List<String> getStringList(@NotNull final String path)
{
final ConfigurationSection section = getConfigSection();
return section == null ? Collections.emptyList() : section.getStringList(path);
}
public final boolean configurationContains(@NotNull final String path)
{
final ConfigurationSection section = getConfigSection();
return section != null && section.contains(path);
}
@Override
public final boolean equals(final Object o)
{
if (this == o)
{
return true;
}
if (!(o instanceof PlaceholderExpansion))
{
return false;
}
final PlaceholderExpansion expansion = (PlaceholderExpansion) o;
return getIdentifier().equals(expansion.getIdentifier()) &&
getAuthor().equals(expansion.getAuthor()) &&
getVersion().equals(expansion.getVersion());
}
@Override
public final int hashCode()
{
return Objects.hash(getIdentifier(), getAuthor(), getVersion());
}
@Override
public final String toString()
{
return String.format("PlaceholderExpansion[name: '%s', author: '%s', version: '%s']", getName(), getAuthor(), getVersion());
}
// === Deprecated API ===
/**
* @deprecated As of versions greater than 2.8.7, use {@link #getRequiredPlugin()}
*/
@Deprecated
public String getPlugin()
{
return null;
}
/**
* @deprecated As of versions greater than 2.8.7, use the expansion cloud to show a description
*/
@Deprecated
public String getDescription()
{
return null;
}
/**
* @deprecated As of versions greater than 2.8.7, use the expansion cloud to display a link
*/
@Deprecated
public String getLink()
{
return null;
}
/**
* @deprecated As of versions greater than 2.8.7, use the expansion cloud to display a link
*/
@Deprecated
public String getLink() {
return null;
}
}

View File

@ -28,6 +28,7 @@ import me.clip.placeholderapi.events.ExpansionUnregisterEvent;
import me.clip.placeholderapi.expansion.*;
import me.clip.placeholderapi.expansion.cloud.CloudExpansion;
import me.clip.placeholderapi.util.FileUtil;
import me.clip.placeholderapi.util.Futures;
import me.clip.placeholderapi.util.Msg;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
@ -43,8 +44,12 @@ import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Unmodifiable;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.logging.Level;
@ -134,6 +139,27 @@ public final class LocalExpansionManager implements Listener
}
public Optional<PlaceholderExpansion> register(@NotNull final Class<? extends PlaceholderExpansion> clazz)
{
try
{
final PlaceholderExpansion expansion = createExpansionInstance(clazz);
if (expansion == null || !expansion.register())
{
return Optional.empty();
}
return Optional.of(expansion);
}
catch (final LinkageError ex)
{
plugin.getLogger().severe("expansion class " + clazz.getSimpleName() + " is outdated: \n" +
"Failed to load due to a [" + ex.getClass().getSimpleName() + "], attempted to use " + ex.getMessage());
}
return Optional.empty();
}
/**
* Do not call this method yourself, use {@link PlaceholderExpansion#register()}
*/
@ -198,7 +224,7 @@ public final class LocalExpansionManager implements Listener
}
final PlaceholderExpansion removed = expansions.get(expansion.getIdentifier());
if (removed != null && !unregister(removed))
if (removed != null && !removed.unregister())
{
return false;
}
@ -238,27 +264,9 @@ public final class LocalExpansionManager implements Listener
return true;
}
public Optional<PlaceholderExpansion> register(@NotNull final Class<? extends PlaceholderExpansion> clazz)
{
try
{
final PlaceholderExpansion expansion = createExpansionInstance(clazz);
if (expansion == null || !expansion.register())
{
return Optional.empty();
}
return Optional.of(expansion);
}
catch (final LinkageError ex)
{
plugin.getLogger().severe("expansion class " + clazz.getSimpleName() + " is outdated: \n" +
"Failed to load due to a [" + ex.getClass().getSimpleName() + "], attempted to use " + ex.getMessage());
}
return Optional.empty();
}
/**
* Do not call this method yourself, use {@link PlaceholderExpansion#unregister()}
*/
public boolean unregister(@NotNull final PlaceholderExpansion expansion)
{
if (expansions.remove(expansion.getIdentifier()) == null)
@ -301,18 +309,17 @@ public final class LocalExpansionManager implements Listener
{
plugin.getLogger().info("Placeholder expansion registration initializing...");
findExpansionsOnDisk().whenCompleteAsync((classes, exception) -> {
Futures.onMainThread(plugin, findExpansionsOnDisk(), (classes, exception) -> {
if (exception != null)
{
plugin.getLogger().log(Level.SEVERE, "failed to load class files of expansions", exception);
return;
}
Bukkit.getScheduler().runTask(plugin, () -> {
final long registered = classes.stream().map(this::register).filter(Optional::isPresent).count();
Msg.msg(sender,
registered == 0 ? "&6No expansions were registered!" : registered + "&a placeholder hooks successfully registered!");
});
final long registered = classes.stream().map(this::register).filter(Optional::isPresent).count();
Msg.msg(sender,
registered == 0 ? "&6No expansions were registered!" : registered + "&a placeholder hooks successfully registered!");
});
}
@ -325,39 +332,32 @@ public final class LocalExpansionManager implements Listener
continue;
}
unregister(expansion);
expansion.unregister();
}
}
@NotNull
public CompletableFuture<List<Class<? extends PlaceholderExpansion>>> findExpansionsOnDisk()
public CompletableFuture<@NotNull List<@NotNull Class<? extends PlaceholderExpansion>>> findExpansionsOnDisk()
{
return CompletableFuture.supplyAsync(() -> {
try
{
return FileUtil.getClasses(getExpansionsFolder(), PlaceholderExpansion.class);
}
catch (final IOException | ClassNotFoundException ex)
{
throw new CompletionException(ex);
}
});
return Arrays.stream(folder.listFiles((dir, name) -> name.endsWith(".jar")))
.map(this::findExpansionInFile)
.collect(Futures.collector());
}
@NotNull
public CompletableFuture<List<Class<? extends PlaceholderExpansion>>> findExpansionsInFile(@NotNull final File file)
public CompletableFuture<@Nullable Class<? extends PlaceholderExpansion>> findExpansionInFile(@NotNull final File file)
{
return CompletableFuture.supplyAsync(() -> {
try
{
final List<@NotNull Class<? extends PlaceholderExpansion>> classes = FileUtil.getClasses(getExpansionsFolder(), PlaceholderExpansion.class, file.getName());
if (classes.size() > 1)
{
throw new IllegalStateException("multiple expansion classes in one file!");
}
return classes;
return FileUtil.findClass(file, PlaceholderExpansion.class);
}
catch (final VerifyError ex)
{
plugin.getLogger().severe("expansion file " + file.getName() + " is outdated: \n" +
"Failed to load due to a [" + ex.getClass().getSimpleName() + "], attempted to use" + ex.getMessage().substring(ex.getMessage().lastIndexOf(' ')));
return null;
}
catch (final Exception ex)
{
@ -376,6 +376,11 @@ public final class LocalExpansionManager implements Listener
}
catch (final Exception ex)
{
if (ex.getCause() instanceof LinkageError)
{
throw ((LinkageError) ex.getCause());
}
plugin.getLogger().log(Level.SEVERE, "Failed to load placeholder expansion from class: " + clazz.getName(), ex);
return null;
}
@ -412,7 +417,7 @@ public final class LocalExpansionManager implements Listener
continue;
}
unregister(expansion);
expansion.unregister();
plugin.getLogger().info("Unregistered placeholder expansion: " + expansion.getName());
}
}

View File

@ -20,7 +20,7 @@
package me.clip.placeholderapi.replacer;
import me.clip.placeholderapi.PlaceholderHook;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import org.bukkit.ChatColor;
import org.bukkit.OfflinePlayer;
import org.jetbrains.annotations.NotNull;
@ -40,8 +40,9 @@ public final class CharsReplacer implements Replacer
}
@NotNull
@Override
public @NotNull String apply(@NotNull final String text, @Nullable final OfflinePlayer player, @NotNull final Function<String, @Nullable PlaceholderHook> lookup)
public String apply(@NotNull final String text, @Nullable final OfflinePlayer player, @NotNull final Function<String, @Nullable PlaceholderExpansion> lookup)
{
final char[] chars = text.toCharArray();
final StringBuilder builder = new StringBuilder(text.length());
@ -162,7 +163,7 @@ public final class CharsReplacer implements Replacer
continue;
}
final PlaceholderHook placeholder = lookup.apply(identifierString);
final PlaceholderExpansion placeholder = lookup.apply(identifierString);
if (placeholder == null)
{
builder.append(closure.head).append(identifierString);

View File

@ -20,7 +20,7 @@
package me.clip.placeholderapi.replacer;
import me.clip.placeholderapi.PlaceholderHook;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import org.bukkit.ChatColor;
import org.bukkit.OfflinePlayer;
import org.jetbrains.annotations.NotNull;
@ -42,8 +42,9 @@ public final class RegexReplacer implements Replacer
}
@NotNull
@Override
public @NotNull String apply(@NotNull final String text, @Nullable final OfflinePlayer player, @NotNull final Function<String, @Nullable PlaceholderHook> lookup)
public String apply(@NotNull final String text, @Nullable final OfflinePlayer player, @NotNull final Function<String, @Nullable PlaceholderExpansion> lookup)
{
final Matcher matcher = pattern.matcher(text);
if (!matcher.find())
@ -58,13 +59,13 @@ public final class RegexReplacer implements Replacer
final String identifier = matcher.group("identifier");
final String parameters = matcher.group("parameters");
final PlaceholderHook hook = lookup.apply(identifier);
if (hook == null)
final PlaceholderExpansion expansion = lookup.apply(identifier);
if (expansion == null)
{
continue;
}
final String requested = hook.onRequest(player, parameters);
final String requested = expansion.onRequest(player, parameters);
matcher.appendReplacement(builder, requested != null ? requested : matcher.group(0));
}
while (matcher.find());

View File

@ -20,7 +20,7 @@
package me.clip.placeholderapi.replacer;
import me.clip.placeholderapi.PlaceholderHook;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import org.bukkit.OfflinePlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@ -31,7 +31,7 @@ public interface Replacer
{
@NotNull
String apply(@NotNull final String text, @Nullable final OfflinePlayer player, @NotNull final Function<String, @Nullable PlaceholderHook> lookup);
String apply(@NotNull final String text, @Nullable final OfflinePlayer player, @NotNull final Function<String, @Nullable PlaceholderExpansion> lookup);
enum Closure

View File

@ -27,47 +27,22 @@ import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
public class FileUtil
{
@NotNull
public static <T> List<@NotNull Class<? extends T>> getClasses(@NotNull final File folder, @NotNull final Class<T> clazz) throws IOException, ClassNotFoundException
@Nullable
public static <T> Class<? extends T> findClass(@NotNull final File file, @NotNull final Class<T> clazz) throws IOException, ClassNotFoundException
{
return getClasses(folder, clazz, null);
}
@NotNull
public static <T> List<@NotNull Class<? extends T>> getClasses(@NotNull final File folder, @NotNull final Class<T> clazz, @Nullable final String target) throws IOException, ClassNotFoundException
{
if (!folder.exists())
if (!file.exists())
{
return Collections.emptyList();
return null;
}
final File[] jars = folder.listFiles((dir, name) -> name.endsWith(".jar") && (target == null || name.replace(".jar", "").equalsIgnoreCase(target.replace(".jar", ""))));
if (jars == null)
{
return Collections.emptyList();
}
final URL jar = file.toURI().toURL();
final List<@NotNull Class<? extends T>> list = new ArrayList<>();
for (final File file : jars)
{
gather(file.toURI().toURL(), clazz, list);
}
return list;
}
private static <T> void gather(@NotNull final URL jar, @NotNull final Class<T> clazz, @NotNull final List<@NotNull Class<? extends T>> list) throws IOException, ClassNotFoundException
{
try (final URLClassLoader loader = new URLClassLoader(new URL[]{jar}, clazz.getClassLoader()); final JarInputStream stream = new JarInputStream(jar.openStream()))
{
JarEntry entry;
@ -84,7 +59,7 @@ public class FileUtil
final Class<?> loaded = loader.loadClass(name.substring(0, name.lastIndexOf('.')).replace('/', '.'));
if (clazz.isAssignableFrom(loaded))
{
list.add(loaded.asSubclass(clazz));
return loaded.asSubclass(clazz);
}
}
catch (final NoClassDefFoundError ignored)
@ -92,6 +67,8 @@ public class FileUtil
}
}
}
return null;
}
}

View File

@ -0,0 +1,63 @@
package me.clip.placeholderapi.util;
import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiConsumer;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public final class Futures
{
private Futures()
{}
public static <T> void onMainThread(@NotNull final Plugin plugin, @NotNull final CompletableFuture<T> future, @NotNull final BiConsumer<T, Throwable> consumer)
{
future.whenComplete((value, exception) -> {
if (Bukkit.isPrimaryThread())
{
consumer.accept(value, exception);
}
else
{
Bukkit.getScheduler().runTask(plugin, () -> consumer.accept(value, exception));
}
});
}
@NotNull
public static <T> Collector<CompletableFuture<T>, ?, CompletableFuture<List<T>>> collector()
{
return Collectors.collectingAndThen(Collectors.toList(), Futures::of);
}
@NotNull
public static <T> CompletableFuture<List<T>> of(@NotNull final Stream<CompletableFuture<T>> futures)
{
return of(futures.collect(Collectors.toList()));
}
@NotNull
public static <T> CompletableFuture<List<T>> of(@NotNull final Collection<CompletableFuture<T>> futures)
{
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.thenApplyAsync($ -> awaitCompletion(futures));
}
@NotNull
private static <T> List<T> awaitCompletion(@NotNull final Collection<CompletableFuture<T>> futures)
{
return futures.stream().map(CompletableFuture::join).collect(Collectors.toList());
}
}

View File

@ -10,6 +10,7 @@
# Download placeholders: /papi ecloud
check_updates: true
cloud_enabled: true
cloud_sorting: "name"
cloud_allow_unverified_expansions: false
boolean:
'true': 'yes'

View File

@ -21,165 +21,61 @@
package me.clip.placeholderapi;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import me.clip.placeholderapi.replacer.CharsReplacer;
import me.clip.placeholderapi.replacer.RegexReplacer;
import me.clip.placeholderapi.replacer.Replacer;
import org.bukkit.ChatColor;
import org.bukkit.OfflinePlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Set;
import java.util.function.Function;
public interface Values
{
String SMALL_TEXT = "My name is %player_name%";
String LARGE_TEXT = "My name is %player_name% and my location is (%player_x%, %player_y%, %player_z%), this placeholder is invalid %server_name%";
ImmutableMap<String, PlaceholderHook> PLACEHOLDERS = ImmutableMap.<String, PlaceholderHook>builder()
.put("player", new MockPlayerPlaceholderHook())
ImmutableMap<String, PlaceholderExpansion> PLACEHOLDERS = ImmutableMap.<String, PlaceholderExpansion>builder()
.put("player", new MockPlayerPlaceholderExpansion())
.build();
Replacer CHARS_REPLACER = new CharsReplacer(Replacer.Closure.PERCENT);
Replacer REGEX_REPLACER = new RegexReplacer(Replacer.Closure.PERCENT);
Replacer TESTS_REPLACER = new Replacer()
{
private final Set<Character> COLOR_CODES = ImmutableSet.of('0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'a', 'b', 'c', 'd', 'e', 'f', 'k', 'l', 'm', 'o', 'r', 'x');
private final boolean colorize = true;
private final Replacer.Closure closure = Replacer.Closure.PERCENT;
@Override
public @NotNull String apply(final @NotNull String text, final @Nullable OfflinePlayer player, final @NotNull Function<String, @Nullable PlaceholderHook> lookup)
{
char[] chars = text.toCharArray();
StringBuilder builder = new StringBuilder(chars.length);
// This won't cause memory leaks. It's inside a method. And we want to use setLength instead of
// creating a new string builder to use the maximum capacity and avoid initializing new objects.
StringBuilder identifier = new StringBuilder(50);
PlaceholderHook handler = null;
// Stages:
// Stage -1: Look for the color code in the next character.
// Stage 0: No closures detected, or the detected identifier is invalid. We're going forward while appending the characters normally.
// Stage 1: The closure has been detected, looking for the placeholder identifier...
// Stage 2: Detected the identifier and the parameter. Translating the placeholder...
int stage = 0;
for (char ch : chars)
{
if (stage == -1 && COLOR_CODES.contains(ch))
{
builder.append(ChatColor.COLOR_CHAR).append(ch);
stage = 0;
continue;
}
// Check if the placeholder starts or ends.
if (ch == closure.head || ch == closure.tail)
{
// If the placeholder ends.
if (stage == 2)
{
String parameter = identifier.toString();
String translated = handler.onRequest(player, parameter);
if (translated == null)
{
String name = "";
builder.append(closure.head).append(name).append('_').append(parameter).append(closure.tail);
}
else
{
builder.append(translated);
}
identifier.setLength(0);
stage = 0;
continue;
}
else if (stage == 1)
{ // If it just started | Double closures | If it's still hasn't detected the indentifier, reset.
builder.append(closure.head).append(identifier);
}
identifier.setLength(0);
stage = 1;
continue;
}
// Placeholder identifier started.
if (stage == 1)
{
// Compare the current character with the idenfitier's.
// We reached the end of our identifier.
if (ch == '_')
{
handler = lookup.apply(identifier.toString());
if (handler == null)
{
builder.append(closure.head).append(identifier).append('_');
stage = 0;
}
else
{
identifier.setLength(0);
stage = 2;
}
continue;
}
// Keep building the identifier name.
identifier.append(ch);
continue;
}
// Building the placeholder parameter.
if (stage == 2)
{
identifier.append(ch);
continue;
}
// Nothing placeholder related was found.
if (colorize && ch == '&')
{
stage = -1;
continue;
}
builder.append(ch);
}
if (identifier != null)
{
if (stage > 0)
{
builder.append(closure.tail);
}
builder.append(identifier);
}
return builder.toString();
}
};
final class MockPlayerPlaceholderHook extends PlaceholderHook
final class MockPlayerPlaceholderExpansion extends PlaceholderExpansion
{
public static final String PLAYER_X = String.valueOf(10);
public static final String PLAYER_Y = String.valueOf(20);
public static final String PLAYER_Z = String.valueOf(30);
public static final String PLAYER_X = "10";
public static final String PLAYER_Y = "20";
public static final String PLAYER_Z = "30";
public static final String PLAYER_NAME = "Sxtanna";
@NotNull
@Override
public String onRequest(final OfflinePlayer player, final String params)
public String getIdentifier()
{
return "player";
}
@NotNull
@Override
public String getAuthor()
{
return "Sxtanna";
}
@NotNull
@Override
public String getVersion()
{
return "1.0";
}
@Override
public String onRequest(@Nullable final OfflinePlayer player, @NotNull final String params)
{
final String[] parts = params.split("_");
if (parts.length == 0)

View File

@ -23,7 +23,10 @@ package me.clip.placeholderapi.replacer;
import me.clip.placeholderapi.Values;
import org.junit.jupiter.api.Test;
import static me.clip.placeholderapi.Values.MockPlayerPlaceholderHook.*;
import static me.clip.placeholderapi.Values.MockPlayerPlaceholderExpansion.PLAYER_NAME;
import static me.clip.placeholderapi.Values.MockPlayerPlaceholderExpansion.PLAYER_X;
import static me.clip.placeholderapi.Values.MockPlayerPlaceholderExpansion.PLAYER_Y;
import static me.clip.placeholderapi.Values.MockPlayerPlaceholderExpansion.PLAYER_Z;
import static org.junit.jupiter.api.Assertions.assertEquals;
public final class ReplacerUnitTester