diff --git a/src/main/java/me/clip/placeholderapi/replacer/CharsReplacer.java b/src/main/java/me/clip/placeholderapi/replacer/CharsReplacer.java
index 1d05ef4..7d87163 100644
--- a/src/main/java/me/clip/placeholderapi/replacer/CharsReplacer.java
+++ b/src/main/java/me/clip/placeholderapi/replacer/CharsReplacer.java
@@ -22,9 +22,7 @@ package me.clip.placeholderapi.replacer;
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 +36,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);
+ final char tail = closure.tail;
+
+ loop: do {
+ // Append plain text preceding the placeholder
+ if (startPlaceholder > cursor) {
+ builder.append(text, cursor, startPlaceholder);
+ }
+
+ final int endPlaceholder = text.indexOf(tail, startPlaceholder + 1);
+
+ if (endPlaceholder == -1) {
+ builder.append(text, startPlaceholder, length);
+ return builder.toString();
+ }
+
+ 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 (current == '_' && underscoreIndex == -1) {
+ underscoreIndex = i;
+ }
+ }
+
+ if (underscoreIndex == -1) {
+ builder.append(text, startPlaceholder, endPlaceholder + 1);
+ cursor = endPlaceholder + 1;
+ startPlaceholder = text.indexOf(head, cursor);
continue;
}
- boolean identified = false;
- boolean invalid = true;
- boolean hadSpace = false;
+ String identifier = text.substring(startPlaceholder + 1, underscoreIndex);
+ String parameters = "";
- while (++i < chars.length) {
- final char p = chars[i];
-
- if (p == ' ' && !identified) {
- hadSpace = true;
- break;
- }
- if (p == closure.tail) {
- invalid = false;
- break;
- }
-
- if (p == '_' && !identified) {
- identified = true;
- continue;
- }
-
- if (identified) {
- parameters.append(p);
- } else {
- identifier.append(p);
- }
+ if (underscoreIndex + 1 < endPlaceholder) {
+ parameters = text.substring(underscoreIndex + 1, endPlaceholder);
}
- final String identifierString = identifier.toString();
- final String lowercaseIdentifierString = identifierString.toLowerCase(Locale.ROOT);
- final String parametersString = parameters.toString();
+ final PlaceholderExpansion expansion = lookup.apply(identifier.toLowerCase(Locale.ROOT));
+ String replacement = null;
- identifier.setLength(0);
- parameters.setLength(0);
-
- if (invalid) {
- builder.append(closure.head).append(identifierString);
-
- if (identified) {
- builder.append('_').append(parametersString);
- }
-
- if (hadSpace) {
- builder.append(' ');
- }
- continue;
+ if (expansion != null) {
+ replacement = expansion.onRequest(player, parameters);
}
- final PlaceholderExpansion placeholder = lookup.apply(lowercaseIdentifierString);
- if (placeholder == null) {
- builder.append(closure.head).append(identifierString);
-
- if (identified) {
- builder.append('_');
- }
-
- builder.append(parametersString).append(closure.tail);
- continue;
+ if (replacement != null) {
+ builder.append(replacement);
+ } else {
+ // Fallback: Restore original placeholder format
+ builder.append(head).append(identifier);
+ builder.append('_').append(parameters);
+ builder.append(tail);
}
- final String replacement = placeholder.onRequest(player, parametersString);
- if (replacement == null) {
- builder.append(closure.head).append(identifierString);
+ cursor = endPlaceholder + 1;
+ startPlaceholder = text.indexOf(head, cursor);
- if (identified) {
- builder.append('_');
- }
+ } while (startPlaceholder != -1);
- builder.append(parametersString).append(closure.tail);
- continue;
- }
-
- builder.append(replacement);
+ if (cursor < length) {
+ builder.append(text, cursor, length);
}
return builder.toString();