From 9a4fa183045f25366c4d8956a2efd5ff6d9875bb Mon Sep 17 00:00:00 2001 From: ichocomilk Date: Wed, 4 Feb 2026 01:26:39 -0300 Subject: [PATCH] Enhance placeholder translation performance and improve code clarity --- .../clip/placeholderapi/PlaceholderAPI.java | 41 +++-- .../manager/LocalExpansionManager.java | 2 +- .../replacer/CharsReplacer.java | 148 ++++++++++-------- 3 files changed, 106 insertions(+), 85 deletions(-) diff --git a/src/main/java/me/clip/placeholderapi/PlaceholderAPI.java b/src/main/java/me/clip/placeholderapi/PlaceholderAPI.java index 6f11ee9..f3386cb 100644 --- a/src/main/java/me/clip/placeholderapi/PlaceholderAPI.java +++ b/src/main/java/me/clip/placeholderapi/PlaceholderAPI.java @@ -20,12 +20,7 @@ package me.clip.placeholderapi; -import com.google.common.collect.ImmutableSet; - -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -85,7 +80,12 @@ public final class PlaceholderAPI { @NotNull public static List setPlaceholders(final OfflinePlayer player, @NotNull final List text) { - return text.stream().map(line -> setPlaceholders(player, line)).collect(Collectors.toList()); + final String[] parsed = new String[text.size()]; + int i = 0; + for (final String line : text) { + parsed[i++] = setPlaceholders(player, line); + } + return Arrays.asList(parsed); } /** @@ -140,8 +140,12 @@ public final class PlaceholderAPI { @NotNull public static List<@NotNull String> setBracketPlaceholders(final OfflinePlayer player, @NotNull final List<@NotNull String> text) { - return text.stream().map(line -> setBracketPlaceholders(player, line)) - .collect(Collectors.toList()); + final String[] parsed = new String[text.size()]; + int i = 0; + for (final String line : text) { + parsed[i++] = setBracketPlaceholders(player, line); + } + return Arrays.asList(parsed); } /** @@ -179,14 +183,14 @@ public final class PlaceholderAPI { * @param text Text to parse the placeholders in * @return The text containing the parsed relational placeholders */ - public static String setRelationalPlaceholders(Player one, Player two, String text) { + public static String setRelationalPlaceholders(final Player one, final Player two, @NotNull String text) { final Matcher matcher = RELATIONAL_PLACEHOLDER_PATTERN.matcher(text); while (matcher.find()) { final String format = matcher.group(2); - final int index = format.indexOf("_"); + final int index = format.indexOf('_'); - if (index <= 0 || index >= format.length()) { + if (index <= 0) { continue; } @@ -218,9 +222,13 @@ public final class PlaceholderAPI { * @param text text to parse the placeholder values to * @return The text containing the parsed relational placeholders */ - public static List setRelationalPlaceholders(Player one, Player two, List text) { - return text.stream().map(line -> setRelationalPlaceholders(one, two, line)) - .collect(Collectors.toList()); + public static List setRelationalPlaceholders(final Player one, final Player two, final @NotNull List text) { + final String[] parsed = new String[text.size()]; + int i = 0; + for (final String line : text) { + parsed[i++] = setRelationalPlaceholders(one, two, line); + } + return Arrays.asList(parsed); } /** @@ -241,8 +249,7 @@ public final class PlaceholderAPI { */ @NotNull public static Set getRegisteredIdentifiers() { - return ImmutableSet - .copyOf(PlaceholderAPIPlugin.getInstance().getLocalExpansionManager().getIdentifiers()); + return PlaceholderAPIPlugin.getInstance().getLocalExpansionManager().getIdentifiers(); } /** 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 ff54463..d703e01 100644 --- a/src/main/java/me/clip/placeholderapi/expansion/manager/LocalExpansionManager.java +++ b/src/main/java/me/clip/placeholderapi/expansion/manager/LocalExpansionManager.java @@ -114,7 +114,7 @@ public final class LocalExpansionManager implements Listener { @NotNull @Unmodifiable - public Collection getIdentifiers() { + public Set getIdentifiers() { expansionsLock.lock(); try { return ImmutableSet.copyOf(expansions.keySet()); diff --git a/src/main/java/me/clip/placeholderapi/replacer/CharsReplacer.java b/src/main/java/me/clip/placeholderapi/replacer/CharsReplacer.java index 8f0af63..052047b 100644 --- a/src/main/java/me/clip/placeholderapi/replacer/CharsReplacer.java +++ b/src/main/java/me/clip/placeholderapi/replacer/CharsReplacer.java @@ -24,7 +24,6 @@ import java.util.Locale; import java.util.function.Function; import me.clip.placeholderapi.expansion.PlaceholderExpansion; -import org.bukkit.ChatColor; import org.bukkit.OfflinePlayer; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -38,98 +37,113 @@ public final class CharsReplacer implements Replacer { this.closure = closure; } - + /** + * Translates placeholders within the provided text using a high-performance + * character-scanning approach. + * *

The method identifies placeholders delimited by the defined {@link Closure} + * (e.g., %identifier_params% or {identifier_params}). If a placeholder is + * successfully identified, the provided lookup function is used to fetch the + * corresponding {@link PlaceholderExpansion}.

+ * + * @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) { - final char[] chars = text.toCharArray(); - final StringBuilder builder = new StringBuilder(text.length()); + final char head = closure.head; + int startPlaceholder = text.indexOf(head); - final StringBuilder identifier = new StringBuilder(); - final StringBuilder parameters = new StringBuilder(); + if (startPlaceholder == -1) { + return text; + } - for (int i = 0; i < chars.length; i++) { - final char l = chars[i]; + final int length = text.length(); + final StringBuilder builder = new StringBuilder(length + (length >> 3)); + int cursor = 0; - if (l != closure.head || i + 1 >= chars.length) { - builder.append(l); - continue; + final char tail = closure.tail; + + loop: do { + // Append plain text preceding the placeholder + if (startPlaceholder > cursor) { + builder.append(text, cursor, startPlaceholder); } - boolean identified = false; - boolean invalid = true; - boolean hadSpace = false; + final int endPlaceholder = text.indexOf(tail, startPlaceholder + 1); - while (++i < chars.length) { - final char p = chars[i]; + if (endPlaceholder == -1) { + builder.append(text, startPlaceholder, length); + return builder.toString(); + } - if (p == ' ' && !identified) { - hadSpace = true; - break; - } - if (p == closure.tail) { - invalid = false; - break; + int underscoreIndex = -1; + + for (int i = startPlaceholder + 1; i < endPlaceholder; i++) { + final char current = text.charAt(i); + + if (current == ' ') { + // Invalid placeholder (contains space). + // Treat the opening symbol as literal text and search for the next one. + builder.append(head); + cursor = startPlaceholder + 1; + startPlaceholder = text.indexOf(head, cursor); + + // Safety check: If no more placeholders exist, break to finalize + if (startPlaceholder == -1) { + break loop; + } + continue loop; } - if (p == '_' && !identified) { - identified = true; - continue; - } - - if (identified) { - parameters.append(p); - } else { - identifier.append(p); + if (current == '_' && underscoreIndex == -1) { + underscoreIndex = i; } } - final String identifierString = identifier.toString(); - final String lowercaseIdentifierString = identifierString.toLowerCase(Locale.ROOT); - final String parametersString = parameters.toString(); + String identifier; + String parameters = ""; - identifier.setLength(0); - parameters.setLength(0); - - if (invalid) { - builder.append(closure.head).append(identifierString); - - if (identified) { - builder.append('_').append(parametersString); + if (underscoreIndex != -1) { + identifier = text.substring(startPlaceholder + 1, underscoreIndex); + if (underscoreIndex + 1 < endPlaceholder) { + parameters = text.substring(underscoreIndex + 1, endPlaceholder); } - - if (hadSpace) { - builder.append(' '); - } - continue; + } else { + identifier = text.substring(startPlaceholder + 1, endPlaceholder); } - final PlaceholderExpansion placeholder = lookup.apply(lowercaseIdentifierString); - if (placeholder == null) { - builder.append(closure.head).append(identifierString); + final PlaceholderExpansion expansion = lookup.apply(identifier.toLowerCase(Locale.ROOT)); + String replacement = null; - if (identified) { - builder.append('_'); - } - - builder.append(parametersString).append(closure.tail); - continue; + if (expansion != null) { + replacement = expansion.onRequest(player, parameters); } - final String replacement = placeholder.onRequest(player, parametersString); - if (replacement == null) { - builder.append(closure.head).append(identifierString); - - if (identified) { - builder.append('_'); + if (replacement != null) { + builder.append(replacement); + } else { + // Fallback: Restore original placeholder format + builder.append(head).append(identifier); + if (underscoreIndex != -1) { + builder.append('_').append(parameters); } - - builder.append(parametersString).append(closure.tail); - continue; + builder.append(tail); } - builder.append(replacement); + cursor = endPlaceholder + 1; + startPlaceholder = text.indexOf(head, cursor); + + } while (startPlaceholder != -1); + + if (cursor < length) { + builder.append(text, cursor, length); } return builder.toString();