API improvements

This commit is contained in:
filoghost 2020-10-17 16:22:53 +02:00
parent f415e4afe2
commit ec057ea2e7
22 changed files with 210 additions and 118 deletions

View File

@ -53,18 +53,24 @@ public class ChestCommandsAPI {
* Menus loaded by Chest Commands from the menus folder always display placeholders, including those registered * Menus loaded by Chest Commands from the menus folder always display placeholders, including those registered
* through this method. * through this method.
* <p> * <p>
* The identifier is automatically converted to the appropriate placeholder format. For example, given the * The identifier is used to compute which placeholder formats will invoke the replacer callback. For example, given
* identifier "test", the callback would be invoked to replace the the following placeholders: * the identifier "test", the callback would be invoked to replace the following placeholders (case insensitive):
* <ul> * <ul>
* <li>{test} * <li>{test}
* <li>{test: 123} * <li>{test: argument}
* <li>{test: hello world} * <li>{pluginName/test}
* <li>{pluginName/test: argument}
* </ul> * </ul>
* The plugin name is used as optional namespace, to distinguish two placeholders with the same identifier but
* registered by distinct plugins.
* <p>
* This replaces any currently registered placeholder with the same plugin and identifier.
* *
* @param plugin the plugin registering the placeholder * @param plugin the plugin registering the placeholder
* @param identifier the identifier of the placeholder, which can only contain letters, digits and * @param identifier the identifier of the placeholder, which can only contain letters, digits and
* underscores * underscores
* @param placeholderReplacer the callback that returns the displayed value * @param placeholderReplacer the callback that returns the displayed value
* @throws IllegalArgumentException if the identifier contains invalid characters
* @see PlaceholderReplacer#getReplacement(Player, String) * @see PlaceholderReplacer#getReplacement(Player, String)
* @since 1 * @since 1
*/ */
@ -74,12 +80,11 @@ public class ChestCommandsAPI {
BackendAPI.getImplementation().registerPlaceholder(plugin, identifier, placeholderReplacer); BackendAPI.getImplementation().registerPlaceholder(plugin, identifier, placeholderReplacer);
} }
/** /**
* Returns if a menu with a given file name exists and was loaded successfully by Chest Commands from the menus * Returns if a menu with a given file name exists and was loaded successfully by Chest Commands from the menus
* folder. * folder.
* *
* @param menuFileName the file name of the menu to check * @param menuFileName the file name of the menu to check, including the {@code .yml} file extension
* @return true if the menu exists, false otherwise * @return true if the menu exists, false otherwise
* @since 1 * @since 1
*/ */
@ -94,7 +99,7 @@ public class ChestCommandsAPI {
* <b>WARNING</b>: this method opens the menu without checking the permissions of the player. * <b>WARNING</b>: this method opens the menu without checking the permissions of the player.
* *
* @param player the player that will see the menu * @param player the player that will see the menu
* @param menuFileName the file name of the menu to open * @param menuFileName the file name of the menu to open, including the {@code .yml} file extension
* @return true if the menu was found and opened successfully, false otherwise * @return true if the menu was found and opened successfully, false otherwise
* @since 1 * @since 1
*/ */

View File

@ -8,30 +8,34 @@ package me.filoghost.chestcommands.api;
import me.filoghost.chestcommands.api.internal.BackendAPI; import me.filoghost.chestcommands.api.internal.BackendAPI;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
/** /**
* Menus are containers of {@link Icon}s that can be displayed to players as unmodifiable inventories. * Menus are containers of {@link Icon}s that can be displayed to players as unmodifiable inventories, organized as a
* grid with a number of rows and columns.
* <p> * <p>
* It is not recommended to implement this interface, use the provided constructor {@link Menu#create(Plugin, String, * This interface should not be implemented, use the provided constructor {@link Menu#create(Plugin, String, int)}. Any
* int)}. * custom implementation will not be handled by Chest Commands' event listener, which relies on internal details. New
* methods may also be added, making existing custom implementations incompatible.
* *
* @since 1 * @since 1
*/ */
@ApiStatus.NonExtendable
public interface Menu { public interface Menu {
/** /**
* Creates a new menu. * Creates a new menu.
* *
* @param owner the plugin creating the menu * @param plugin the plugin creating the menu
* @param title title of the menu that will be displayed in the inventory * @param title title of the menu that appears in the displayed inventory
* @param rowCount number of rows in the menu (the number of columns is always 9, currently) * @param rows number of rows in the menu (the number of columns is always 9, currently)
* @return the created menu * @return the created menu
* @since 1 * @since 1
*/ */
static @NotNull Menu create(@NotNull Plugin owner, @NotNull String title, int rowCount) { static @NotNull Menu create(@NotNull Plugin plugin, @NotNull String title, int rows) {
return BackendAPI.getImplementation().createMenu(owner, title, rowCount); return BackendAPI.getImplementation().createMenu(plugin, title, rows);
} }
/** /**
@ -40,6 +44,8 @@ public interface Menu {
* @param row the row position * @param row the row position
* @param column the column position * @param column the column position
* @param icon the new icon, null to remove the current one * @param icon the new icon, null to remove the current one
* @throws IndexOutOfBoundsException if the row or the column is outside the limits ({@code row < 0 || row >=
* getRows() || column < 0 || column >= getColumns()})
* @since 1 * @since 1
*/ */
void setIcon(int row, int column, @Nullable Icon icon); void setIcon(int row, int column, @Nullable Icon icon);
@ -50,39 +56,17 @@ public interface Menu {
* @param row the row position * @param row the row position
* @param column the column position * @param column the column position
* @return the icon at the give position, null if absent * @return the icon at the give position, null if absent
* @throws IndexOutOfBoundsException if the row or the column is outside the limits ({@code row < 0 || row >=
* getRows() || column < 0 || column >= getColumns()})
* @since 1 * @since 1
*/ */
@Nullable Icon getIcon(int row, int column); @Nullable Icon getIcon(int row, int column);
/**
* Returns the title of the displayed inventory.
*
* @return the title
* @since 1
*/
@NotNull String getTitle();
/**
* Returns the amount of rows of the displayed inventory.
*
* @return the amount of rows
* @since 1
*/
int getRowCount();
/**
* Returns the amount of columns of the displayed inventory.
*
* @return the amount of columns
* @since 1
*/
int getColumnCount();
/** /**
* Displays the menu to a player, creating a rendering of this menu and its icons. * Displays the menu to a player, creating a rendering of this menu and its icons.
* <p> * <p>
* If icons are added, removed or changed after the menu is displayed to a player, the view is not updated * If icons are added, removed or changed after the menu is displayed to a player, the view is not updated
* automatically and you may want to invoke {@link Menu#refreshOpenMenuViews()}. * automatically and you may want to invoke {@link Menu#refreshOpenViews()}.
* *
* @param player the player to which the menu will be displayed * @param player the player to which the menu will be displayed
* @return the newly created view for the player * @return the newly created view for the player
@ -97,10 +81,42 @@ public interface Menu {
* This method should be called after adding, removing or changing one or more icons to update the open menu views * This method should be called after adding, removing or changing one or more icons to update the open menu views
* of players. * of players.
* <p> * <p>
* This method invokes {@link MenuView#refresh()} on each open view created by this menu. * This method invokes {@link MenuView#refresh()} on each currently open view created by this menu.
* *
* @since 1 * @since 1
*/ */
void refreshOpenMenuViews(); void refreshOpenViews();
/**
* Returns the amount of rows of the displayed inventory.
*
* @return the amount of rows
* @since 1
*/
int getRows();
/**
* Returns the amount of columns of the displayed inventory.
*
* @return the amount of columns
* @since 1
*/
int getColumns();
/**
* Returns the title of the displayed inventory.
*
* @return the title
* @since 1
*/
@NotNull String getTitle();
/**
* Returns the plugin that created the menu.
*
* @return the plugin that created the menu
* @since 1
*/
Plugin getPlugin();
} }

View File

@ -29,7 +29,7 @@ public interface MenuView {
* player and you want to refresh the money placeholder instantly. * player and you want to refresh the money placeholder instantly.
* <p> * <p>
* Note that {@link ClickHandler} exposes the menu view being interacted with, so you don't need to refresh all the * Note that {@link ClickHandler} exposes the menu view being interacted with, so you don't need to refresh all the
* views of a menu through {@link Menu#refreshOpenMenuViews()}. * views of a menu through {@link Menu#refreshOpenViews()}.
* *
* @since 1 * @since 1
*/ */

View File

@ -27,6 +27,8 @@ public interface PlaceholderReplacer {
* <p> * <p>
* If this method returns null, the placeholder is not replaced. It is preferred to return a descriptive error * If this method returns null, the placeholder is not replaced. It is preferred to return a descriptive error
* message rather than returning null. * message rather than returning null.
* <p>
* <b>Warning</b>: this method should be performance efficient, as it may be invoked quite often.
* *
* @param player the player viewing the placeholder * @param player the player viewing the placeholder
* @param argument the argument inside the placeholder, if present * @param argument the argument inside the placeholder, if present

View File

@ -42,7 +42,7 @@ public abstract class BackendAPI {
public abstract boolean openPluginMenu(@NotNull Player player, @NotNull String menuFileName); public abstract boolean openPluginMenu(@NotNull Player player, @NotNull String menuFileName);
public abstract @NotNull Menu createMenu(@NotNull Plugin owner, @NotNull String title, int rows); public abstract @NotNull Menu createMenu(@NotNull Plugin plugin, @NotNull String title, int rows);
public abstract @NotNull ConfigurableIcon createConfigurableIcon(@NotNull Material material); public abstract @NotNull ConfigurableIcon createConfigurableIcon(@NotNull Material material);

View File

@ -75,7 +75,6 @@
<module name="EqualsAvoidNull"/> <module name="EqualsAvoidNull"/>
<module name="EqualsHashCode"/> <module name="EqualsHashCode"/>
<module name="FallThrough"/> <module name="FallThrough"/>
<module name="IllegalCatch"/>
<module name="IllegalThrows"/> <module name="IllegalThrows"/>
<module name="IllegalToken"/> <module name="IllegalToken"/>
<module name="IllegalType"/> <module name="IllegalType"/>

View File

@ -184,7 +184,7 @@ public class ChestCommands extends BaseJavaPlugin {
} }
public static Plugin getPluginInstance() { public static Plugin getInstance() {
return pluginInstance; return pluginInstance;
} }

View File

@ -53,8 +53,8 @@ public class DefaultBackendAPI extends BackendAPI {
} }
@Override @Override
public @NotNull Menu createMenu(@NotNull Plugin owner, @NotNull String title, int rows) { public @NotNull Menu createMenu(@NotNull Plugin plugin, @NotNull String title, int rows) {
return new APIMenu(owner, title, rows); return new APIMenu(plugin, title, rows);
} }
@Override @Override
@ -63,7 +63,9 @@ public class DefaultBackendAPI extends BackendAPI {
} }
@Override @Override
public void registerPlaceholder(@NotNull Plugin plugin, @NotNull String identifier, @NotNull PlaceholderReplacer placeholderReplacer) { public void registerPlaceholder(@NotNull Plugin plugin,
@NotNull String identifier,
@NotNull PlaceholderReplacer placeholderReplacer) {
PlaceholderManager.registerPluginPlaceholder(plugin, identifier, placeholderReplacer); PlaceholderManager.registerPluginPlaceholder(plugin, identifier, placeholderReplacer);
} }

View File

@ -31,7 +31,7 @@ public class OpenMenuAction implements Action {
* Delay the task, since this action is executed in ClickInventoryEvent * Delay the task, since this action is executed in ClickInventoryEvent
* and opening another inventory in the same moment is not a good idea. * and opening another inventory in the same moment is not a good idea.
*/ */
Bukkit.getScheduler().runTask(ChestCommands.getPluginInstance(), () -> { Bukkit.getScheduler().runTask(ChestCommands.getInstance(), () -> {
menu.openCheckingPermission(player); menu.openCheckingPermission(player);
}); });

View File

@ -42,7 +42,7 @@ public class CommandHandler extends MultiCommandManager {
@Override @Override
protected void sendNoArgsMessage(CommandSender sender, String rootCommandLabel) { protected void sendNoArgsMessage(CommandSender sender, String rootCommandLabel) {
sender.sendMessage(ChestCommands.CHAT_PREFIX); sender.sendMessage(ChestCommands.CHAT_PREFIX);
sender.sendMessage(ChatColor.GREEN + "Version: " + ChatColor.GRAY + ChestCommands.getPluginInstance().getDescription().getVersion()); sender.sendMessage(ChatColor.GREEN + "Version: " + ChatColor.GRAY + ChestCommands.getInstance().getDescription().getVersion());
sender.sendMessage(ChatColor.GREEN + "Developer: " + ChatColor.GRAY + "filoghost"); sender.sendMessage(ChatColor.GREEN + "Developer: " + ChatColor.GRAY + "filoghost");
sender.sendMessage(ChatColor.GREEN + "Commands: " + ChatColor.GRAY + "/" + rootCommandLabel + " help"); sender.sendMessage(ChatColor.GREEN + "Commands: " + ChatColor.GRAY + "/" + rootCommandLabel + " help");
} }

View File

@ -22,8 +22,8 @@ public enum BungeeCordHook implements PluginHook {
@Override @Override
public void setup() { public void setup() {
if (!Bukkit.getMessenger().isOutgoingChannelRegistered(ChestCommands.getPluginInstance(), BUNGEE_CORD_CHANNEL)) { if (!Bukkit.getMessenger().isOutgoingChannelRegistered(ChestCommands.getInstance(), BUNGEE_CORD_CHANNEL)) {
Bukkit.getMessenger().registerOutgoingPluginChannel(ChestCommands.getPluginInstance(), BUNGEE_CORD_CHANNEL); Bukkit.getMessenger().registerOutgoingPluginChannel(ChestCommands.getInstance(), BUNGEE_CORD_CHANNEL);
} }
} }
@ -50,7 +50,7 @@ public enum BungeeCordHook implements PluginHook {
throw new AssertionError(); throw new AssertionError();
} }
player.sendPluginMessage(ChestCommands.getPluginInstance(), BUNGEE_CORD_CHANNEL, byteArrayOutputStream.toByteArray()); player.sendPluginMessage(ChestCommands.getInstance(), BUNGEE_CORD_CHANNEL, byteArrayOutputStream.toByteArray());
} }
} }

View File

@ -14,9 +14,6 @@ import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
/**
* Represents a particular view of a menu.
*/
public class DefaultMenuView implements MenuView { public class DefaultMenuView implements MenuView {
private final BaseMenu menu; private final BaseMenu menu;
@ -26,7 +23,7 @@ public class DefaultMenuView implements MenuView {
public DefaultMenuView(@NotNull BaseMenu menu, @NotNull Player viewer) { public DefaultMenuView(@NotNull BaseMenu menu, @NotNull Player viewer) {
this.menu = menu; this.menu = menu;
this.viewer = viewer; this.viewer = viewer;
this.bukkitInventory = new InventoryGrid(new MenuInventoryHolder(this), menu.getRowCount(), menu.getTitle()); this.bukkitInventory = new InventoryGrid(new MenuInventoryHolder(this), menu.getRows(), menu.getTitle());
refresh(); refresh();
} }

View File

@ -8,9 +8,13 @@ package me.filoghost.chestcommands.listener;
import me.filoghost.chestcommands.ChestCommands; import me.filoghost.chestcommands.ChestCommands;
import me.filoghost.chestcommands.api.ClickResult; import me.filoghost.chestcommands.api.ClickResult;
import me.filoghost.chestcommands.api.Icon; import me.filoghost.chestcommands.api.Icon;
import me.filoghost.chestcommands.api.Menu;
import me.filoghost.chestcommands.config.Settings; import me.filoghost.chestcommands.config.Settings;
import me.filoghost.chestcommands.inventory.DefaultMenuView; import me.filoghost.chestcommands.inventory.DefaultMenuView;
import me.filoghost.chestcommands.logging.Errors;
import me.filoghost.chestcommands.menu.InternalMenu;
import me.filoghost.chestcommands.menu.MenuManager; import me.filoghost.chestcommands.menu.MenuManager;
import me.filoghost.fcommons.logging.Log;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
@ -75,13 +79,28 @@ public class InventoryListener implements Listener {
} }
// Only handle the click AFTER the event has finished // Only handle the click AFTER the event has finished
Bukkit.getScheduler().runTask(ChestCommands.getPluginInstance(), () -> { Bukkit.getScheduler().runTask(ChestCommands.getInstance(), () -> {
try {
ClickResult result = icon.onClick(menuView, clicker); ClickResult result = icon.onClick(menuView, clicker);
if (result == ClickResult.CLOSE) { if (result == ClickResult.CLOSE) {
clicker.closeInventory(); clicker.closeInventory();
} }
} catch (Throwable t) {
handleIconClickException(menuView.getMenu(), t);
}
}); });
} }
private void handleIconClickException(Menu menu, Throwable throwable) {
String menuDescription;
if (menu.getPlugin() == ChestCommands.getInstance()) {
menuDescription = "the menu \"" + Errors.formatPath(((InternalMenu) menu).getSourceFile()) + "\"";
} else {
menuDescription = "a menu created by the plugin \"" + menu.getPlugin().getName() + "\"";
}
Log.severe("Encountered exception while handling a click inside " + menuDescription, throwable);
}
} }

View File

@ -215,7 +215,7 @@ public class Errors {
} }
private static String formatPath(Path path) { public static String formatPath(Path path) {
return ConfigErrors.formatPath(ChestCommands.getDataFolderPath(), path); return ConfigErrors.formatPath(ChestCommands.getDataFolderPath(), path);
} }

View File

@ -11,16 +11,17 @@ import org.jetbrains.annotations.NotNull;
public class APIMenu extends BaseMenu { public class APIMenu extends BaseMenu {
private final Plugin owner; private final Plugin plugin;
public APIMenu(@NotNull Plugin owner, @NotNull String title, int rows) { public APIMenu(@NotNull Plugin plugin, @NotNull String title, int rows) {
super(title, rows); super(title, rows);
Preconditions.notNull(owner, "owner"); Preconditions.notNull(plugin, "plugin");
this.owner = owner; this.plugin = plugin;
} }
public @NotNull Plugin getOwner() { @Override
return owner; public Plugin getPlugin() {
return plugin;
} }
} }

View File

@ -42,12 +42,31 @@ public abstract class BaseMenu implements Menu {
} }
@Override @Override
public int getRowCount() { public @NotNull MenuView open(@NotNull Player player) {
Preconditions.notNull(player, "player");
DefaultMenuView menuView = new DefaultMenuView(this, player);
menuView.open();
return menuView;
}
@Override
public void refreshOpenViews() {
for (Player player : Bukkit.getOnlinePlayers()) {
DefaultMenuView menuView = MenuManager.getOpenMenuView(player);
if (menuView != null && menuView.getMenu() == this) {
menuView.refresh();
}
}
}
@Override
public int getRows() {
return icons.getRows(); return icons.getRows();
} }
@Override @Override
public int getColumnCount() { public int getColumns() {
return icons.getColumns(); return icons.getColumns();
} }
@ -60,22 +79,4 @@ public abstract class BaseMenu implements Menu {
return icons; return icons;
} }
@Override
public @NotNull MenuView open(@NotNull Player player) {
Preconditions.notNull(player, "player");
DefaultMenuView menuView = new DefaultMenuView(this, player);
menuView.open();
return menuView;
}
@Override
public void refreshOpenMenuViews() {
for (Player player : Bukkit.getOnlinePlayers()) {
DefaultMenuView menuView = MenuManager.getOpenMenuView(player);
if (menuView != null && menuView.getMenu() == this) {
menuView.refresh();
}
}
}
} }

View File

@ -6,6 +6,7 @@
package me.filoghost.chestcommands.menu; package me.filoghost.chestcommands.menu;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import me.filoghost.chestcommands.ChestCommands;
import me.filoghost.chestcommands.Permissions; import me.filoghost.chestcommands.Permissions;
import me.filoghost.chestcommands.action.Action; import me.filoghost.chestcommands.action.Action;
import me.filoghost.chestcommands.api.MenuView; import me.filoghost.chestcommands.api.MenuView;
@ -13,6 +14,7 @@ import me.filoghost.chestcommands.config.Lang;
import me.filoghost.fcommons.collection.CollectionUtils; import me.filoghost.fcommons.collection.CollectionUtils;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.nio.file.Path; import java.nio.file.Path;
@ -63,6 +65,11 @@ public class InternalMenu extends BaseMenu {
return super.open(player); return super.open(player);
} }
@Override
public Plugin getPlugin() {
return ChestCommands.getInstance();
}
public void openCheckingPermission(Player player) { public void openCheckingPermission(Player player) {
if (player.hasPermission(openPermission)) { if (player.hasPermission(openPermission)) {
open(player); open(player);

View File

@ -63,16 +63,16 @@ public class MenuParser {
int row = positionY.getPosition() - 1; int row = positionY.getPosition() - 1;
int column = positionX.getPosition() - 1; int column = positionX.getPosition() - 1;
if (row < 0 || row >= menu.getRowCount()) { if (row < 0 || row >= menu.getRows()) {
errorCollector.add( errorCollector.add(
Errors.Menu.invalidAttribute(iconSettings, AttributeType.POSITION_Y), Errors.Menu.invalidAttribute(iconSettings, AttributeType.POSITION_Y),
"it must be between 1 and " + menu.getRowCount()); "it must be between 1 and " + menu.getRows());
return; return;
} }
if (column < 0 || column >= menu.getColumnCount()) { if (column < 0 || column >= menu.getColumns()) {
errorCollector.add( errorCollector.add(
Errors.Menu.invalidAttribute(iconSettings, AttributeType.POSITION_X), Errors.Menu.invalidAttribute(iconSettings, AttributeType.POSITION_X),
"it must be between 1 and " + menu.getColumnCount()); "it must be between 1 and " + menu.getColumns());
return; return;
} }

View File

@ -0,0 +1,29 @@
/*
* Copyright (C) filoghost and contributors
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package me.filoghost.chestcommands.placeholder;
import me.filoghost.chestcommands.api.PlaceholderReplacer;
import org.bukkit.plugin.Plugin;
public class Placeholder {
private final Plugin plugin;
private final PlaceholderReplacer placeholderReplacer;
public Placeholder(Plugin plugin, PlaceholderReplacer placeholderReplacer) {
this.plugin = plugin;
this.placeholderReplacer = placeholderReplacer;
}
public Plugin getPlugin() {
return plugin;
}
public PlaceholderReplacer getReplacer() {
return placeholderReplacer;
}
}

View File

@ -10,6 +10,7 @@ import me.filoghost.chestcommands.hook.PlaceholderAPIHook;
import me.filoghost.chestcommands.placeholder.scanner.PlaceholderMatch; import me.filoghost.chestcommands.placeholder.scanner.PlaceholderMatch;
import me.filoghost.chestcommands.placeholder.scanner.PlaceholderScanner; import me.filoghost.chestcommands.placeholder.scanner.PlaceholderScanner;
import me.filoghost.fcommons.Preconditions; import me.filoghost.fcommons.Preconditions;
import me.filoghost.fcommons.logging.Log;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@ -21,14 +22,13 @@ public class PlaceholderManager {
private static final List<StaticPlaceholder> staticPlaceholders = new ArrayList<>(); private static final List<StaticPlaceholder> staticPlaceholders = new ArrayList<>();
private static final PlaceholderRegistry relativePlaceholderRegistry = new PlaceholderRegistry(); private static final PlaceholderRegistry relativePlaceholderRegistry = new PlaceholderRegistry();
private static final PlaceholderCache placeholderCache = new PlaceholderCache();
static { static {
for (DefaultPlaceholder placeholder : DefaultPlaceholder.values()) { for (DefaultPlaceholder placeholder : DefaultPlaceholder.values()) {
relativePlaceholderRegistry.registerInternalPlaceholder(placeholder.getIdentifier(), placeholder.getReplacer()); relativePlaceholderRegistry.registerInternalPlaceholder(placeholder.getIdentifier(), placeholder.getReplacer());
} }
} }
private static final PlaceholderCache placeholderCache = new PlaceholderCache();
public static boolean hasRelativePlaceholders(List<String> list) { public static boolean hasRelativePlaceholders(List<String> list) {
for (String element : list) { for (String element : list) {
if (hasRelativePlaceholders(element)) { if (hasRelativePlaceholders(element)) {
@ -61,18 +61,24 @@ public class PlaceholderManager {
} }
private static boolean isValidPlaceholder(PlaceholderMatch placeholderMatch) { private static boolean isValidPlaceholder(PlaceholderMatch placeholderMatch) {
return relativePlaceholderRegistry.getPlaceholderReplacer(placeholderMatch) != null; return relativePlaceholderRegistry.getPlaceholder(placeholderMatch) != null;
} }
private static @Nullable String getReplacement(PlaceholderMatch placeholderMatch, Player player) { private static @Nullable String getReplacement(PlaceholderMatch placeholderMatch, Player player) {
PlaceholderReplacer placeholderReplacer = relativePlaceholderRegistry.getPlaceholderReplacer(placeholderMatch); Placeholder placeholder = relativePlaceholderRegistry.getPlaceholder(placeholderMatch);
if (placeholderReplacer == null) { if (placeholder == null) {
return null; // Placeholder not found return null; // Placeholder not found
} }
return placeholderCache.computeIfAbsent(placeholderMatch, player, () -> { return placeholderCache.computeIfAbsent(placeholderMatch, player, () -> {
return placeholderReplacer.getReplacement(player, placeholderMatch.getArgument()); try {
return placeholder.getReplacer().getReplacement(player, placeholderMatch.getArgument());
} catch (Throwable t) {
Log.severe("Encountered exception while replacing the placeholder \"" + placeholderMatch
+ "\" registered by the plugin \"" + placeholder.getPlugin().getName() + "\"", t);
return "[ERROR]";
}
}); });
} }

View File

@ -5,47 +5,54 @@
*/ */
package me.filoghost.chestcommands.placeholder; package me.filoghost.chestcommands.placeholder;
import me.filoghost.chestcommands.ChestCommands;
import me.filoghost.chestcommands.api.PlaceholderReplacer; import me.filoghost.chestcommands.api.PlaceholderReplacer;
import me.filoghost.chestcommands.placeholder.scanner.PlaceholderMatch; import me.filoghost.chestcommands.placeholder.scanner.PlaceholderMatch;
import me.filoghost.fcommons.collection.CaseInsensitiveMap;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
public class PlaceholderRegistry { public class PlaceholderRegistry {
private final Map<String, PlaceholderReplacer> internalPlaceholders = new HashMap<>(); // <identifier, placeholder>
private final Map<String, Map<String, PlaceholderReplacer>> externalPlaceholders = new HashMap<>(); private final Map<String, Placeholder> internalPlaceholders = new CaseInsensitiveMap<>();
// <identifier, <pluginName, placeholder>>
private final Map<String, Map<String, Placeholder>> externalPlaceholders = new CaseInsensitiveMap<>();
public void registerInternalPlaceholder(String identifier, PlaceholderReplacer replacer) { public void registerInternalPlaceholder(String identifier, PlaceholderReplacer replacer) {
internalPlaceholders.put(identifier, replacer); internalPlaceholders.put(identifier, new Placeholder(ChestCommands.getInstance(), replacer));
} }
public void registerExternalPlaceholder(Plugin plugin, String identifier, PlaceholderReplacer placeholderReplacer) { public void registerExternalPlaceholder(Plugin plugin, String identifier, PlaceholderReplacer placeholderReplacer) {
externalPlaceholders externalPlaceholders
.computeIfAbsent(identifier, key -> new LinkedHashMap<>()) .computeIfAbsent(identifier, key -> new CaseInsensitiveMap<>(new LinkedHashMap<>()))
.put(plugin.getName(), placeholderReplacer); .put(plugin.getName(), new Placeholder(plugin, placeholderReplacer));
} }
public @Nullable PlaceholderReplacer getPlaceholderReplacer(PlaceholderMatch placeholderMatch) { public @Nullable Placeholder getPlaceholder(PlaceholderMatch placeholderMatch) {
String identifier = placeholderMatch.getIdentifier();
if (placeholderMatch.getPluginNamespace() == null) { if (placeholderMatch.getPluginNamespace() == null) {
PlaceholderReplacer internalReplacer = internalPlaceholders.get(placeholderMatch.getIdentifier()); Placeholder internalPlaceholder = internalPlaceholders.get(identifier);
if (internalReplacer != null) { if (internalPlaceholder != null) {
return internalReplacer; return internalPlaceholder;
} }
} }
Map<String, PlaceholderReplacer> externalReplacers = externalPlaceholders.get(placeholderMatch.getIdentifier()); Map<String, Placeholder> externalPlaceholdersByPlugin = externalPlaceholders.get(identifier);
// Find exact replacer if plugin name is specified // Find exact replacer if plugin name is specified
if (placeholderMatch.getPluginNamespace() != null) { if (placeholderMatch.getPluginNamespace() != null) {
return externalReplacers.get(placeholderMatch.getPluginNamespace()); return externalPlaceholdersByPlugin.get(placeholderMatch.getPluginNamespace());
} }
if (externalReplacers != null && !externalReplacers.isEmpty()) { // Otherwise try find the first one registered
return externalReplacers.values().iterator().next(); if (externalPlaceholdersByPlugin != null && !externalPlaceholdersByPlugin.isEmpty()) {
return externalPlaceholdersByPlugin.values().iterator().next();
} }
return null; return null;

View File

@ -35,9 +35,10 @@ public class PlaceholderMatch {
/* /*
* Valid formats: * Valid formats:
* {pluginName/placeholder: argument}
* {placeholder: argument}
* {placeholder} * {placeholder}
* {placeholder: argument}
* {pluginName/identifier}
* {pluginName/identifier: argument}
*/ */
public static PlaceholderMatch parse(String placeholderContent) { public static PlaceholderMatch parse(String placeholderContent) {
String explicitPluginName = null; String explicitPluginName = null;