diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 1798a47..7dc4766 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -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? 1. + +### Installed expansions +> Please list all expansions that are displayed when running `/papi list` + diff --git a/build.gradle b/build.gradle index 2fe52f8..de0dffe 100644 --- a/build.gradle +++ b/build.gradle @@ -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" diff --git a/src/main/java/me/clip/placeholderapi/PlaceholderAPIPlugin.java b/src/main/java/me/clip/placeholderapi/PlaceholderAPIPlugin.java index 6fe1e6c..32a2f74 100644 --- a/src/main/java/me/clip/placeholderapi/PlaceholderAPIPlugin.java +++ b/src/main/java/me/clip/placeholderapi/PlaceholderAPIPlugin.java @@ -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 map = new HashMap<>(); - Map hooks = PlaceholderAPI.getPlaceholders(); + metrics.addCustomChart(new Metrics.AdvancedPie("expansions_used", () -> { + final Map 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() diff --git a/src/main/java/me/clip/placeholderapi/PlaceholderHook.java b/src/main/java/me/clip/placeholderapi/PlaceholderHook.java index 881b85a..915f7ed 100644 --- a/src/main/java/me/clip/placeholderapi/PlaceholderHook.java +++ b/src/main/java/me/clip/placeholderapi/PlaceholderHook.java @@ -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 diff --git a/src/main/java/me/clip/placeholderapi/commands/PlaceholderCommandRouter.java b/src/main/java/me/clip/placeholderapi/commands/PlaceholderCommandRouter.java index 1bab006..8e0e26f 100644 --- a/src/main/java/me/clip/placeholderapi/commands/PlaceholderCommandRouter.java +++ b/src/main/java/me/clip/placeholderapi/commands/PlaceholderCommandRouter.java @@ -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 COMMANDS = ImmutableList.of(new CommandHelp(), new CommandInfo(), new CommandList(), + new CommandDump(), new CommandECloud(), new CommandParse(), new CommandReload(), diff --git a/src/main/java/me/clip/placeholderapi/commands/impl/cloud/CommandECloudExpansionList.java b/src/main/java/me/clip/placeholderapi/commands/impl/cloud/CommandECloudExpansionList.java index 8ab82f4..3244cb6 100644 --- a/src/main/java/me/clip/placeholderapi/commands/impl/cloud/CommandECloudExpansionList.java +++ b/src/main/java/me/clip/placeholderapi/commands/impl/cloud/CommandECloudExpansionList.java @@ -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) { diff --git a/src/main/java/me/clip/placeholderapi/commands/impl/cloud/CommandECloudUpdate.java b/src/main/java/me/clip/placeholderapi/commands/impl/cloud/CommandECloudUpdate.java index 5256a64..2e94c6b 100644 --- a/src/main/java/me/clip/placeholderapi/commands/impl/cloud/CommandECloudUpdate.java +++ b/src/main/java/me/clip/placeholderapi/commands/impl/cloud/CommandECloudUpdate.java @@ -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> downloadExpansions(@NotNull final PlaceholderAPIPlugin plugin, @NotNull final List expansions) + private static CompletableFuture>> downloadAndDiscover(@NotNull final List expansions, @NotNull final PlaceholderAPIPlugin plugin) { - final List> 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>> discoverExpansions(@NotNull final PlaceholderAPIPlugin plugin, @NotNull final List files) - { - final List>>> 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()); } } diff --git a/src/main/java/me/clip/placeholderapi/commands/impl/local/CommandDump.java b/src/main/java/me/clip/placeholderapi/commands/impl/local/CommandDump.java new file mode 100644 index 0000000..d66a160 --- /dev/null +++ b/src/main/java/me/clip/placeholderapi/commands/impl/local/CommandDump.java @@ -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 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 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> expansions = plugin.getLocalExpansionManager() + .getExpansions() + .stream() + .collect(Collectors.groupingBy(PlaceholderExpansion::getAuthor)); + + for (final Map.Entry> 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(); + } + +} diff --git a/src/main/java/me/clip/placeholderapi/commands/impl/local/CommandExpansionRegister.java b/src/main/java/me/clip/placeholderapi/commands/impl/local/CommandExpansionRegister.java index ec50fe0..f884c82 100644 --- a/src/main/java/me/clip/placeholderapi/commands/impl/local/CommandExpansionRegister.java +++ b/src/main/java/me/clip/placeholderapi/commands/impl/local/CommandExpansionRegister.java @@ -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 expansion = manager.register(classes.get(0)); - if (!expansion.isPresent()) - { - Msg.msg(sender, - "&cFailed to register expansion from &f" + params.get(0)); - return; - } + final Optional 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()); + }); } diff --git a/src/main/java/me/clip/placeholderapi/commands/impl/local/CommandExpansionUnregister.java b/src/main/java/me/clip/placeholderapi/commands/impl/local/CommandExpansionUnregister.java index 492c15a..6cd32ed 100644 --- a/src/main/java/me/clip/placeholderapi/commands/impl/local/CommandExpansionUnregister.java +++ b/src/main/java/me/clip/placeholderapi/commands/impl/local/CommandExpansionUnregister.java @@ -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"; diff --git a/src/main/java/me/clip/placeholderapi/configuration/ExpansionSort.java b/src/main/java/me/clip/placeholderapi/configuration/ExpansionSort.java new file mode 100644 index 0000000..b8306d7 --- /dev/null +++ b/src/main/java/me/clip/placeholderapi/configuration/ExpansionSort.java @@ -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 +{ + + NAME(Comparator.comparing(CloudExpansion::getName)), + AUTHOR(Comparator.comparing(CloudExpansion::getAuthor)), + LATEST(Comparator.comparing(CloudExpansion::getLastUpdate).reversed()); + + + @NotNull + private final Comparator comparator; + + ExpansionSort(@NotNull final Comparator comparator) + { + this.comparator = comparator; + } + + + @Override + public final int compare(final CloudExpansion expansion1, final CloudExpansion expansion2) + { + return comparator.compare(expansion1, expansion2); + } + +} diff --git a/src/main/java/me/clip/placeholderapi/configuration/PlaceholderAPIConfig.java b/src/main/java/me/clip/placeholderapi/configuration/PlaceholderAPIConfig.java index 8389306..7a8fbd5 100644 --- a/src/main/java/me/clip/placeholderapi/configuration/PlaceholderAPIConfig.java +++ b/src/main/java/me/clip/placeholderapi/configuration/PlaceholderAPIConfig.java @@ -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 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() { diff --git a/src/main/java/me/clip/placeholderapi/expansion/PlaceholderExpansion.java b/src/main/java/me/clip/placeholderapi/expansion/PlaceholderExpansion.java index 377a644..ec86eef 100644 --- a/src/main/java/me/clip/placeholderapi/expansion/PlaceholderExpansion.java +++ b/src/main/java/me/clip/placeholderapi/expansion/PlaceholderExpansion.java @@ -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 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 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 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 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; - } } diff --git a/src/main/java/me/clip/placeholderapi/expansion/manager/LocalExpansionManager.java b/src/main/java/me/clip/placeholderapi/expansion/manager/LocalExpansionManager.java index c61cdd7..e58317f 100644 --- a/src/main/java/me/clip/placeholderapi/expansion/manager/LocalExpansionManager.java +++ b/src/main/java/me/clip/placeholderapi/expansion/manager/LocalExpansionManager.java @@ -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 register(@NotNull final Class 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 register(@NotNull final Class 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>> findExpansionsOnDisk() + public CompletableFuture<@NotNull List<@NotNull Class>> 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>> findExpansionsInFile(@NotNull final File file) + public CompletableFuture<@Nullable Class> findExpansionInFile(@NotNull final File file) { return CompletableFuture.supplyAsync(() -> { try { - final List<@NotNull Class> 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()); } } diff --git a/src/main/java/me/clip/placeholderapi/replacer/CharsReplacer.java b/src/main/java/me/clip/placeholderapi/replacer/CharsReplacer.java index 5e4e089..4be9a2c 100644 --- a/src/main/java/me/clip/placeholderapi/replacer/CharsReplacer.java +++ b/src/main/java/me/clip/placeholderapi/replacer/CharsReplacer.java @@ -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 lookup) + public String apply(@NotNull final String text, @Nullable final OfflinePlayer player, @NotNull final Function 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); diff --git a/src/main/java/me/clip/placeholderapi/replacer/RegexReplacer.java b/src/main/java/me/clip/placeholderapi/replacer/RegexReplacer.java index 12ece67..44c9aa7 100644 --- a/src/main/java/me/clip/placeholderapi/replacer/RegexReplacer.java +++ b/src/main/java/me/clip/placeholderapi/replacer/RegexReplacer.java @@ -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 lookup) + public String apply(@NotNull final String text, @Nullable final OfflinePlayer player, @NotNull final Function 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()); diff --git a/src/main/java/me/clip/placeholderapi/replacer/Replacer.java b/src/main/java/me/clip/placeholderapi/replacer/Replacer.java index 97d605f..04b24b7 100644 --- a/src/main/java/me/clip/placeholderapi/replacer/Replacer.java +++ b/src/main/java/me/clip/placeholderapi/replacer/Replacer.java @@ -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 lookup); + String apply(@NotNull final String text, @Nullable final OfflinePlayer player, @NotNull final Function lookup); enum Closure diff --git a/src/main/java/me/clip/placeholderapi/util/FileUtil.java b/src/main/java/me/clip/placeholderapi/util/FileUtil.java index 7902176..ab5f3b5 100644 --- a/src/main/java/me/clip/placeholderapi/util/FileUtil.java +++ b/src/main/java/me/clip/placeholderapi/util/FileUtil.java @@ -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 List<@NotNull Class> getClasses(@NotNull final File folder, @NotNull final Class clazz) throws IOException, ClassNotFoundException + @Nullable + public static Class findClass(@NotNull final File file, @NotNull final Class clazz) throws IOException, ClassNotFoundException { - return getClasses(folder, clazz, null); - } - - @NotNull - public static List<@NotNull Class> getClasses(@NotNull final File folder, @NotNull final Class 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> list = new ArrayList<>(); - - for (final File file : jars) - { - gather(file.toURI().toURL(), clazz, list); - } - - return list; - } - - private static void gather(@NotNull final URL jar, @NotNull final Class clazz, @NotNull final List<@NotNull Class> 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; } } diff --git a/src/main/java/me/clip/placeholderapi/util/Futures.java b/src/main/java/me/clip/placeholderapi/util/Futures.java new file mode 100644 index 0000000..d689b56 --- /dev/null +++ b/src/main/java/me/clip/placeholderapi/util/Futures.java @@ -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 void onMainThread(@NotNull final Plugin plugin, @NotNull final CompletableFuture future, @NotNull final BiConsumer consumer) + { + future.whenComplete((value, exception) -> { + if (Bukkit.isPrimaryThread()) + { + consumer.accept(value, exception); + } + else + { + Bukkit.getScheduler().runTask(plugin, () -> consumer.accept(value, exception)); + } + }); + } + + + @NotNull + public static Collector, ?, CompletableFuture>> collector() + { + return Collectors.collectingAndThen(Collectors.toList(), Futures::of); + } + + + @NotNull + public static CompletableFuture> of(@NotNull final Stream> futures) + { + return of(futures.collect(Collectors.toList())); + } + + @NotNull + public static CompletableFuture> of(@NotNull final Collection> futures) + { + return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) + .thenApplyAsync($ -> awaitCompletion(futures)); + } + + @NotNull + private static List awaitCompletion(@NotNull final Collection> futures) + { + return futures.stream().map(CompletableFuture::join).collect(Collectors.toList()); + } + +} diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 1202dba..6fa0861 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -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' diff --git a/src/test/java/me/clip/placeholderapi/Values.java b/src/test/java/me/clip/placeholderapi/Values.java index 865b0dc..d0c31ce 100644 --- a/src/test/java/me/clip/placeholderapi/Values.java +++ b/src/test/java/me/clip/placeholderapi/Values.java @@ -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 PLACEHOLDERS = ImmutableMap.builder() - .put("player", new MockPlayerPlaceholderHook()) + ImmutableMap PLACEHOLDERS = ImmutableMap.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 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 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) diff --git a/src/test/java/me/clip/placeholderapi/replacer/ReplacerUnitTester.java b/src/test/java/me/clip/placeholderapi/replacer/ReplacerUnitTester.java index 6028b27..71fae9b 100644 --- a/src/test/java/me/clip/placeholderapi/replacer/ReplacerUnitTester.java +++ b/src/test/java/me/clip/placeholderapi/replacer/ReplacerUnitTester.java @@ -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