Enhance placeholder translation performance and improve code clarity

This commit is contained in:
ichocomilk
2026-02-04 01:26:39 -03:00
parent cb5d6c0895
commit 9a4fa18304
3 changed files with 106 additions and 85 deletions

View File

@@ -20,12 +20,7 @@
package me.clip.placeholderapi; package me.clip.placeholderapi;
import com.google.common.collect.ImmutableSet; import java.util.*;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -85,7 +80,12 @@ public final class PlaceholderAPI {
@NotNull @NotNull
public static List<String> setPlaceholders(final OfflinePlayer player, public static List<String> setPlaceholders(final OfflinePlayer player,
@NotNull final List<String> text) { @NotNull final List<String> 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 @NotNull
public static List<@NotNull String> setBracketPlaceholders(final OfflinePlayer player, public static List<@NotNull String> setBracketPlaceholders(final OfflinePlayer player,
@NotNull final List<@NotNull String> text) { @NotNull final List<@NotNull String> text) {
return text.stream().map(line -> setBracketPlaceholders(player, line)) final String[] parsed = new String[text.size()];
.collect(Collectors.toList()); 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 * @param text Text to parse the placeholders in
* @return The text containing the parsed relational placeholders * @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); final Matcher matcher = RELATIONAL_PLACEHOLDER_PATTERN.matcher(text);
while (matcher.find()) { while (matcher.find()) {
final String format = matcher.group(2); 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; continue;
} }
@@ -218,9 +222,13 @@ public final class PlaceholderAPI {
* @param text text to parse the placeholder values to * @param text text to parse the placeholder values to
* @return The text containing the parsed relational placeholders * @return The text containing the parsed relational placeholders
*/ */
public static List<String> setRelationalPlaceholders(Player one, Player two, List<String> text) { public static List<String> setRelationalPlaceholders(final Player one, final Player two, final @NotNull List<String> text) {
return text.stream().map(line -> setRelationalPlaceholders(one, two, line)) final String[] parsed = new String[text.size()];
.collect(Collectors.toList()); 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 @NotNull
public static Set<String> getRegisteredIdentifiers() { public static Set<String> getRegisteredIdentifiers() {
return ImmutableSet return PlaceholderAPIPlugin.getInstance().getLocalExpansionManager().getIdentifiers();
.copyOf(PlaceholderAPIPlugin.getInstance().getLocalExpansionManager().getIdentifiers());
} }
/** /**

View File

@@ -114,7 +114,7 @@ public final class LocalExpansionManager implements Listener {
@NotNull @NotNull
@Unmodifiable @Unmodifiable
public Collection<String> getIdentifiers() { public Set<String> getIdentifiers() {
expansionsLock.lock(); expansionsLock.lock();
try { try {
return ImmutableSet.copyOf(expansions.keySet()); return ImmutableSet.copyOf(expansions.keySet());

View File

@@ -24,7 +24,6 @@ import java.util.Locale;
import java.util.function.Function; import java.util.function.Function;
import me.clip.placeholderapi.expansion.PlaceholderExpansion; import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import org.bukkit.ChatColor;
import org.bukkit.OfflinePlayer; import org.bukkit.OfflinePlayer;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -38,98 +37,113 @@ public final class CharsReplacer implements Replacer {
this.closure = closure; this.closure = closure;
} }
/**
* Translates placeholders within the provided text using a high-performance
* character-scanning approach.
* * <p>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}.</p>
*
* @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 @NotNull
@Override @Override
public String apply(@NotNull final String text, @Nullable final OfflinePlayer player, public String apply(@NotNull final String text, @Nullable final OfflinePlayer player,
@NotNull final Function<String, @Nullable PlaceholderExpansion> lookup) { @NotNull final Function<String, @Nullable PlaceholderExpansion> lookup) {
final char[] chars = text.toCharArray(); final char head = closure.head;
final StringBuilder builder = new StringBuilder(text.length()); int startPlaceholder = text.indexOf(head);
final StringBuilder identifier = new StringBuilder(); if (startPlaceholder == -1) {
final StringBuilder parameters = new StringBuilder(); return text;
}
for (int i = 0; i < chars.length; i++) { final int length = text.length();
final char l = chars[i]; final StringBuilder builder = new StringBuilder(length + (length >> 3));
int cursor = 0;
if (l != closure.head || i + 1 >= chars.length) { final char tail = closure.tail;
builder.append(l);
continue; loop: do {
// Append plain text preceding the placeholder
if (startPlaceholder > cursor) {
builder.append(text, cursor, startPlaceholder);
} }
boolean identified = false; final int endPlaceholder = text.indexOf(tail, startPlaceholder + 1);
boolean invalid = true;
boolean hadSpace = false;
while (++i < chars.length) { if (endPlaceholder == -1) {
final char p = chars[i]; builder.append(text, startPlaceholder, length);
return builder.toString();
}
if (p == ' ' && !identified) { int underscoreIndex = -1;
hadSpace = true;
break; for (int i = startPlaceholder + 1; i < endPlaceholder; i++) {
} final char current = text.charAt(i);
if (p == closure.tail) {
invalid = false; if (current == ' ') {
break; // 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) { if (current == '_' && underscoreIndex == -1) {
identified = true; underscoreIndex = i;
continue;
}
if (identified) {
parameters.append(p);
} else {
identifier.append(p);
} }
} }
final String identifierString = identifier.toString(); String identifier;
final String lowercaseIdentifierString = identifierString.toLowerCase(Locale.ROOT); String parameters = "";
final String parametersString = parameters.toString();
identifier.setLength(0); if (underscoreIndex != -1) {
parameters.setLength(0); identifier = text.substring(startPlaceholder + 1, underscoreIndex);
if (underscoreIndex + 1 < endPlaceholder) {
if (invalid) { parameters = text.substring(underscoreIndex + 1, endPlaceholder);
builder.append(closure.head).append(identifierString);
if (identified) {
builder.append('_').append(parametersString);
} }
} else {
if (hadSpace) { identifier = text.substring(startPlaceholder + 1, endPlaceholder);
builder.append(' ');
}
continue;
} }
final PlaceholderExpansion placeholder = lookup.apply(lowercaseIdentifierString); final PlaceholderExpansion expansion = lookup.apply(identifier.toLowerCase(Locale.ROOT));
if (placeholder == null) { String replacement = null;
builder.append(closure.head).append(identifierString);
if (identified) { if (expansion != null) {
builder.append('_'); replacement = expansion.onRequest(player, parameters);
}
builder.append(parametersString).append(closure.tail);
continue;
} }
final String replacement = placeholder.onRequest(player, parametersString); if (replacement != null) {
if (replacement == null) { builder.append(replacement);
builder.append(closure.head).append(identifierString); } else {
// Fallback: Restore original placeholder format
if (identified) { builder.append(head).append(identifier);
builder.append('_'); if (underscoreIndex != -1) {
builder.append('_').append(parameters);
} }
builder.append(tail);
builder.append(parametersString).append(closure.tail);
continue;
} }
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(); return builder.toString();