From 144eca2af511db7c17b318af61b62fb283ba80cc Mon Sep 17 00:00:00 2001 From: PiggyPiglet Date: Wed, 1 Jul 2026 21:45:23 +0800 Subject: [PATCH] rough prototype --- .../clip/placeholderapi/PlaceholderAPI.java | 8 +- .../clip/placeholderapi/PlaceholderHook.java | 32 ++++++++ .../expansion/PlaceholderContext.java | 33 ++++++++ .../expansion/PlaceholderExpansion.java | 5 ++ .../placeholderapi/expansion/TypeHandler.java | 15 ++++ .../manager/LocalExpansionManager.java | 78 +++++++++++++++++-- .../replacer/CharsReplacer.java | 30 +++---- .../placeholderapi/replacer/Replacer.java | 20 ++++- 8 files changed, 195 insertions(+), 26 deletions(-) create mode 100644 src/main/java/me/clip/placeholderapi/expansion/PlaceholderContext.java create mode 100644 src/main/java/me/clip/placeholderapi/expansion/TypeHandler.java diff --git a/src/main/java/me/clip/placeholderapi/PlaceholderAPI.java b/src/main/java/me/clip/placeholderapi/PlaceholderAPI.java index 258cb92..6d8a87d 100644 --- a/src/main/java/me/clip/placeholderapi/PlaceholderAPI.java +++ b/src/main/java/me/clip/placeholderapi/PlaceholderAPI.java @@ -65,8 +65,8 @@ public final class PlaceholderAPI { @NotNull public static String setPlaceholders(final OfflinePlayer player, @NotNull final String text) { - return REPLACER_PERCENT.apply(text, player, - PlaceholderAPIPlugin.getInstance().getLocalExpansionManager()::getExpansion); + final LocalExpansionManager manager = PlaceholderAPIPlugin.getInstance().getLocalExpansionManager(); + return REPLACER_PERCENT.apply(text, player, manager::getExpansion, manager::resolvePlaceholderValue); } /** @@ -124,8 +124,8 @@ public final class PlaceholderAPI { @NotNull public static String setBracketPlaceholders(final OfflinePlayer player, @NotNull final String text) { - return REPLACER_BRACKET.apply(text, player, - PlaceholderAPIPlugin.getInstance().getLocalExpansionManager()::getExpansion); + final LocalExpansionManager manager = PlaceholderAPIPlugin.getInstance().getLocalExpansionManager(); + return REPLACER_BRACKET.apply(text, player, manager::getExpansion, manager::resolvePlaceholderValue); } /** diff --git a/src/main/java/me/clip/placeholderapi/PlaceholderHook.java b/src/main/java/me/clip/placeholderapi/PlaceholderHook.java index 655553b..db7fed4 100644 --- a/src/main/java/me/clip/placeholderapi/PlaceholderHook.java +++ b/src/main/java/me/clip/placeholderapi/PlaceholderHook.java @@ -35,8 +35,40 @@ public abstract class PlaceholderHook { return onPlaceholderRequest(null, params); } + /** + * Override this method when a placeholder should return a non-string value for type handling. + *
The default delegates to {@link #onPlaceholderTypeHandledRequest(Player, String)} with an online + * {@link Player}, matching the behavior of {@link #onRequest(OfflinePlayer, String)}. + * + * @param player Player to parse the placeholder against + * @param params Placeholder parameters + * @return Object to resolve through type handlers, or null to leave the placeholder unchanged + */ + @Nullable + public Object onTypeHandledRequest(final OfflinePlayer player, @NotNull final String params) { + if (player != null && player.isOnline()) { + return onPlaceholderTypeHandledRequest(player.getPlayer(), params); + } + + return onPlaceholderTypeHandledRequest(null, params); + } + @Nullable public String onPlaceholderRequest(final Player player, @NotNull final String params) { return null; } + + /** + * Override this method when a player placeholder should return a non-string value for type handling. + *
The default returns null so legacy string placeholders are only resolved through + * {@link #onRequest(OfflinePlayer, String)}. + * + * @param player Online player to parse the placeholder against + * @param params Placeholder parameters + * @return Object to resolve through type handlers, or null to leave the placeholder unchanged + */ + @Nullable + public Object onPlaceholderTypeHandledRequest(final Player player, @NotNull final String params) { + return null; + } } diff --git a/src/main/java/me/clip/placeholderapi/expansion/PlaceholderContext.java b/src/main/java/me/clip/placeholderapi/expansion/PlaceholderContext.java new file mode 100644 index 0000000..45d413e --- /dev/null +++ b/src/main/java/me/clip/placeholderapi/expansion/PlaceholderContext.java @@ -0,0 +1,33 @@ +package me.clip.placeholderapi.expansion; + +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public final class PlaceholderContext { + @Nullable private final Player player; + @NotNull private final T type; + @NotNull private final String args; + + public PlaceholderContext(@Nullable final Player player, @NotNull final T type, + @NotNull final String args) { + this.player = player; + this.type = type; + this.args = args; + } + + @Nullable + public Player player() { + return player; + } + + @NotNull + public T type() { + return type; + } + + @NotNull + public String args() { + return args; + } +} diff --git a/src/main/java/me/clip/placeholderapi/expansion/PlaceholderExpansion.java b/src/main/java/me/clip/placeholderapi/expansion/PlaceholderExpansion.java index 0d90b89..d0110cd 100644 --- a/src/main/java/me/clip/placeholderapi/expansion/PlaceholderExpansion.java +++ b/src/main/java/me/clip/placeholderapi/expansion/PlaceholderExpansion.java @@ -20,6 +20,7 @@ package me.clip.placeholderapi.expansion; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.logging.Level; @@ -140,6 +141,10 @@ public abstract class PlaceholderExpansion extends PlaceholderHook { || Bukkit.getPluginManager().getPlugin(getRequiredPlugin()) != null; } + public Collection> provideTypeHandlers() { + return Collections.emptyList(); + } + /** * Attempt to register this PlaceholderExpansion * diff --git a/src/main/java/me/clip/placeholderapi/expansion/TypeHandler.java b/src/main/java/me/clip/placeholderapi/expansion/TypeHandler.java new file mode 100644 index 0000000..5cc4d0d --- /dev/null +++ b/src/main/java/me/clip/placeholderapi/expansion/TypeHandler.java @@ -0,0 +1,15 @@ +package me.clip.placeholderapi.expansion; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public interface TypeHandler { + Class typeClass(); + + default boolean includeDerivatives() { + return true; + } + + @Nullable + String onRequest(@NotNull final PlaceholderContext context); +} 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 6f4d0f8..5fa74a1 100644 --- a/src/main/java/me/clip/placeholderapi/expansion/manager/LocalExpansionManager.java +++ b/src/main/java/me/clip/placeholderapi/expansion/manager/LocalExpansionManager.java @@ -44,19 +44,16 @@ import me.clip.placeholderapi.PlaceholderAPIPlugin; import me.clip.placeholderapi.events.ExpansionRegisterEvent; import me.clip.placeholderapi.events.ExpansionUnregisterEvent; import me.clip.placeholderapi.events.ExpansionsLoadedEvent; -import me.clip.placeholderapi.expansion.Cacheable; -import me.clip.placeholderapi.expansion.Cleanable; -import me.clip.placeholderapi.expansion.Configurable; -import me.clip.placeholderapi.expansion.PlaceholderExpansion; -import me.clip.placeholderapi.expansion.Taskable; -import me.clip.placeholderapi.expansion.VersionSpecific; +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.OfflinePlayer; import org.bukkit.command.CommandSender; import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.HandlerList; @@ -86,6 +83,7 @@ public final class LocalExpansionManager implements Listener { @NotNull private final Map expansions = new ConcurrentHashMap<>(); + private final Map, TypeHandler> typeHandlers = new ConcurrentHashMap<>(); private final ReentrantLock expansionsLock = new ReentrantLock(); @@ -208,6 +206,64 @@ public final class LocalExpansionManager implements Listener { return Optional.empty(); } + @Nullable + public String resolvePlaceholderValue(@NotNull final PlaceholderExpansion expansion, @Nullable final OfflinePlayer player, + @NotNull final String args) { + Object value = expansion.onRequest(player, args); + + if (value != null) { + return (String) value; + } + + value = expansion.onTypeHandledRequest(player, args); + + if (value instanceof String) { + return (String) value; + } + + if (value != null) { + return resolveTypeHandler(value, player, args); + } + + return null; + } + + @Nullable + public String resolveTypeHandler(@NotNull final Object value, @Nullable final OfflinePlayer player, + @NotNull final String args) { + TypeHandler handler = null; + + for (final TypeHandler typeHandler : typeHandlers.values()) { + if (typeHandler.typeClass() == value.getClass()) { + handler = typeHandler; + } + } + + if (handler == null) { + for (final TypeHandler typeHandler : typeHandlers.values()) { + if (typeHandler.includeDerivatives() && typeHandler.typeClass().isAssignableFrom(value.getClass())) { + handler = typeHandler; + } + } + } + + if (handler == null) { + Msg.warn("Failed to resolve type handler for placeholder %s and type %s", args, value.getClass().getSimpleName()); + return null; + } + + return handler.onRequest(new PlaceholderContext<>(player(player), value, args)); + } + + @Nullable + private Player player(@Nullable final OfflinePlayer player) { + if (player == null || !player.isOnline()) { + return null; + } + + return player.getPlayer(); + } + /** * Attempt to register a {@link PlaceholderExpansion} * @@ -228,6 +284,16 @@ public final class LocalExpansionManager implements Listener { return false; } + for (final TypeHandler typeHandler : expansion.provideTypeHandlers()) { + if (typeHandler.typeClass().isAssignableFrom(String.class)) { + Msg.warn("Failed to load type handler %s from expansion %s as there is already a type handler for this type.", + expansion.getIdentifier(), typeHandler.typeClass().getSimpleName()); + continue; + } + + typeHandlers.putIfAbsent(typeHandler.typeClass(), typeHandler); + } + if (expansion instanceof Configurable) { Map defaults = ((Configurable) expansion).getDefaults(); String pre = "expansions." + identifier + "."; diff --git a/src/main/java/me/clip/placeholderapi/replacer/CharsReplacer.java b/src/main/java/me/clip/placeholderapi/replacer/CharsReplacer.java index 5edc145..8530e2b 100644 --- a/src/main/java/me/clip/placeholderapi/replacer/CharsReplacer.java +++ b/src/main/java/me/clip/placeholderapi/replacer/CharsReplacer.java @@ -47,15 +47,17 @@ public final class CharsReplacer implements Replacer { * @param text The raw text containing potential placeholders to be replaced. * @param player The {@link OfflinePlayer} to contextually parse the placeholders against. * May be {@code null} if no player context is available. - * @param lookup A function that maps a lowercase identifier string to a registered - * {@link PlaceholderExpansion}. - * @return A string with all valid placeholders replaced by their respective values. - * Returns the original text if no placeholders are found. - */ - @NotNull - @Override - public String apply(@NotNull final String text, @Nullable final OfflinePlayer player, - @NotNull final Function lookup) { + * @param lookup A function that maps a lowercase identifier string to a registered + * {@link PlaceholderExpansion}. + * @param resolver Resolves a registered {@link PlaceholderExpansion} to its replacement value. + * @return A string with all valid placeholders replaced by their respective values. + * Returns the original text if no placeholders are found. + */ + @NotNull + @Override + public String apply(@NotNull final String text, @Nullable final OfflinePlayer player, + @NotNull final Function lookup, + @NotNull final PlaceholderResolver resolver) { final char head = closure.head; int startPlaceholder = text.indexOf(head); @@ -122,10 +124,10 @@ public final class CharsReplacer implements Replacer { final PlaceholderExpansion expansion = lookup.apply(identifier.toLowerCase(Locale.ROOT)); String replacement = null; - - if (expansion != null) { - replacement = expansion.onRequest(player, parameters); - } + + if (expansion != null) { + replacement = resolver.resolve(expansion, player, parameters); + } if (replacement != null) { builder.append(replacement); @@ -148,4 +150,4 @@ public final class CharsReplacer implements Replacer { return builder.toString(); } -} \ No newline at end of file +} diff --git a/src/main/java/me/clip/placeholderapi/replacer/Replacer.java b/src/main/java/me/clip/placeholderapi/replacer/Replacer.java index 417df30..510637c 100644 --- a/src/main/java/me/clip/placeholderapi/replacer/Replacer.java +++ b/src/main/java/me/clip/placeholderapi/replacer/Replacer.java @@ -30,9 +30,25 @@ import org.jetbrains.annotations.Nullable; public interface Replacer { @NotNull - String apply(@NotNull final String text, @Nullable final OfflinePlayer player, - @NotNull final Function lookup); + default String apply(@NotNull final String text, @Nullable final OfflinePlayer player, + @NotNull final Function lookup) { + return apply(text, player, lookup, PlaceholderExpansion::onRequest); + } + @NotNull + String apply(@NotNull final String text, @Nullable final OfflinePlayer player, + @NotNull final Function lookup, + @NotNull final PlaceholderResolver resolver); + + @FunctionalInterface + interface PlaceholderResolver { + + @Nullable + String resolve(@NotNull final PlaceholderExpansion expansion, + @Nullable final OfflinePlayer player, + @NotNull final String params); + + } enum Closure { BRACKET('{', '}'),