diff --git a/src/main/java/me/clip/placeholderapi/replacer/CharsReplacer.java b/src/main/java/me/clip/placeholderapi/replacer/CharsReplacer.java index 008e4ca..1d05ef4 100644 --- a/src/main/java/me/clip/placeholderapi/replacer/CharsReplacer.java +++ b/src/main/java/me/clip/placeholderapi/replacer/CharsReplacer.java @@ -22,7 +22,9 @@ 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; @@ -36,116 +38,101 @@ 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 head = closure.head; - int startPlaceholder = text.indexOf(head); + final char[] chars = text.toCharArray(); + final StringBuilder builder = new StringBuilder(text.length()); - if (startPlaceholder == -1) { - return text; - } + final StringBuilder identifier = new StringBuilder(); + final StringBuilder parameters = new StringBuilder(); - final int length = text.length(); - final StringBuilder builder = new StringBuilder(length + (length >> 3)); - int cursor = 0; + for (int i = 0; i < chars.length; i++) { + final char l = chars[i]; - 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); + if (l != closure.head || i + 1 >= chars.length) { + builder.append(l); continue; } - String identifier = text.substring(startPlaceholder + 1, underscoreIndex); - String parameters = ""; + boolean identified = false; + boolean invalid = true; + boolean hadSpace = false; - if (underscoreIndex + 1 < endPlaceholder) { - parameters = text.substring(underscoreIndex + 1, endPlaceholder); + 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); + } } - final PlaceholderExpansion expansion = lookup.apply(identifier.toLowerCase(Locale.ROOT)); - String replacement = null; + final String identifierString = identifier.toString(); + final String lowercaseIdentifierString = identifierString.toLowerCase(Locale.ROOT); + final String parametersString = parameters.toString(); - if (expansion != null) { - replacement = expansion.onRequest(player, parameters); + 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 (replacement != null) { - builder.append(replacement); - } else { - // Fallback: Restore original placeholder format - builder.append(head).append(identifier); - builder.append('_').append(parameters); - builder.append(tail); + 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; } - cursor = endPlaceholder + 1; - startPlaceholder = text.indexOf(head, cursor); + final String replacement = placeholder.onRequest(player, parametersString); + if (replacement == null) { + builder.append(closure.head).append(identifierString); - } while (startPlaceholder != -1); + if (identified) { + builder.append('_'); + } - if (cursor < length) { - builder.append(text, cursor, length); + builder.append(parametersString).append(closure.tail); + continue; + } + + builder.append(replacement); } return builder.toString(); } -} +} \ No newline at end of file