From cb5d6c08954d6dec7ca6308170eb952401e1b82f Mon Sep 17 00:00:00 2001 From: ichocomilk Date: Wed, 4 Feb 2026 00:47:58 -0300 Subject: [PATCH 1/7] Optimize containsPlaceholders --- .../clip/placeholderapi/PlaceholderAPI.java | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/main/java/me/clip/placeholderapi/PlaceholderAPI.java b/src/main/java/me/clip/placeholderapi/PlaceholderAPI.java index c05f814..6f11ee9 100644 --- a/src/main/java/me/clip/placeholderapi/PlaceholderAPI.java +++ b/src/main/java/me/clip/placeholderapi/PlaceholderAPI.java @@ -279,8 +279,15 @@ public final class PlaceholderAPI { * @param text String to check * @return true if String contains any matches to the normal placeholder pattern, false otherwise */ - public static boolean containsPlaceholders(String text) { - return text != null && PLACEHOLDER_PATTERN.matcher(text).find(); + public static boolean containsPlaceholders(final String text) { + if (text == null) { + return false; + } + final int firstPercent = text.indexOf('%'); + if (firstPercent == -1) { + return false; + } + return text.indexOf('%', firstPercent + 1) != -1; } /** @@ -290,8 +297,15 @@ public final class PlaceholderAPI { * @param text String to check * @return true if String contains any matches to the bracket placeholder pattern, false otherwise */ - public static boolean containsBracketPlaceholders(String text) { - return text != null && BRACKET_PLACEHOLDER_PATTERN.matcher(text).find(); + public static boolean containsBracketPlaceholders(final String text) { + if (text == null) { + return false; + } + final int firstPercent = text.indexOf('{'); + if (firstPercent == -1) { + return false; + } + return text.indexOf('}', firstPercent + 1) != -1; } // === Deprecated API === From 9a4fa183045f25366c4d8956a2efd5ff6d9875bb Mon Sep 17 00:00:00 2001 From: ichocomilk Date: Wed, 4 Feb 2026 01:26:39 -0300 Subject: [PATCH 2/7] 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(); From 0f35362a0c3d700d9fd7a6e115b9ebf6d92e3aa8 Mon Sep 17 00:00:00 2001 From: iChocoMilk Date: Wed, 4 Feb 2026 01:50:19 -0300 Subject: [PATCH 3/7] Fix PlaceholderAPI#containsBracketPlaceholders variable name --- src/main/java/me/clip/placeholderapi/PlaceholderAPI.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/me/clip/placeholderapi/PlaceholderAPI.java b/src/main/java/me/clip/placeholderapi/PlaceholderAPI.java index f3386cb..61d554b 100644 --- a/src/main/java/me/clip/placeholderapi/PlaceholderAPI.java +++ b/src/main/java/me/clip/placeholderapi/PlaceholderAPI.java @@ -308,11 +308,11 @@ public final class PlaceholderAPI { if (text == null) { return false; } - final int firstPercent = text.indexOf('{'); - if (firstPercent == -1) { + final int openBracket = text.indexOf('{'); + if (openBracket == -1) { return false; } - return text.indexOf('}', firstPercent + 1) != -1; + return text.indexOf('}', openBracket + 1) != -1; } // === Deprecated API === From fe15e4ed7a868ec8a2cf93fa2e676e5b836afb7b Mon Sep 17 00:00:00 2001 From: ichocomilk Date: Wed, 4 Feb 2026 19:49:54 -0300 Subject: [PATCH 4/7] Update Gradle dependencies and add benchmarks --- build.gradle.kts | 14 +- gradle/wrapper/gradle-wrapper.properties | 2 +- .../java/me/clip/placeholderapi/Values.java | 94 ++++++++++++ .../replacer/OldCharsReplacer.java | 136 ++++++++++++++++++ .../replacer/ReplacerBenchmarks.java | 66 +++++++++ .../replacer/ReplacerBenchmarks.java | 38 ----- 6 files changed, 305 insertions(+), 45 deletions(-) create mode 100644 src/jmh/java/me/clip/placeholderapi/Values.java create mode 100644 src/jmh/java/me/clip/placeholderapi/replacer/OldCharsReplacer.java create mode 100644 src/jmh/java/me/clip/placeholderapi/replacer/ReplacerBenchmarks.java delete mode 100644 src/test/java/me/clip/placeholderapi/replacer/ReplacerBenchmarks.java diff --git a/build.gradle.kts b/build.gradle.kts index c5cf45b..311eb54 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,7 +4,8 @@ plugins { `java-library` `maven-publish` // id("com.github.hierynomus.license") version "0.16.1" - id("io.github.goooler.shadow") version "8.1.7" + id("com.gradleup.shadow") version "9.3.1" + id("me.champeau.jmh") version "0.7.2" } group = "me.clip" @@ -41,12 +42,13 @@ dependencies { compileOnly("dev.folia:folia-api:1.21.11-R0.1-SNAPSHOT") compileOnlyApi("org.jetbrains:annotations:23.0.0") - testImplementation("org.openjdk.jmh:jmh-core:1.32") - testImplementation("org.openjdk.jmh:jmh-generator-annprocess:1.32") - testImplementation("org.junit.jupiter:junit-jupiter-engine:5.8.2") - testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.1") -} + jmh("org.openjdk.jmh:jmh-core:1.37") + jmh("org.openjdk.jmh:jmh-generator-annprocess:1.37") + jmhAnnotationProcessor("org.openjdk.jmh:jmh-generator-annprocess:1.37") + testImplementation("org.junit.jupiter:junit-jupiter:6.0.2") + testRuntimeOnly("org.junit.platform:junit-platform-launcher") +} java { sourceCompatibility = JavaVersion.VERSION_1_8 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2617362..aa28adb 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.0-bin.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/jmh/java/me/clip/placeholderapi/Values.java b/src/jmh/java/me/clip/placeholderapi/Values.java new file mode 100644 index 0000000..ff06e7d --- /dev/null +++ b/src/jmh/java/me/clip/placeholderapi/Values.java @@ -0,0 +1,94 @@ +/* + * This file is part of PlaceholderAPI + * + * PlaceholderAPI + * Copyright (c) 2015 - 2026 PlaceholderAPI Team + * + * PlaceholderAPI free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlaceholderAPI is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.clip.placeholderapi; + +import com.google.common.collect.ImmutableMap; +import me.clip.placeholderapi.expansion.PlaceholderExpansion; +import me.clip.placeholderapi.replacer.CharsReplacer; +import me.clip.placeholderapi.replacer.OldCharsReplacer; +import me.clip.placeholderapi.replacer.Replacer; +import org.bukkit.OfflinePlayer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public interface Values { + + String SMALL_TEXT = "My name is %player_name%"; + String LARGE_TEXT = "My name is %player_name% and my location is (%player_x%, %player_y%, %player_z%), this placeholder is invalid %server_name%"; + + ImmutableMap PLACEHOLDERS = ImmutableMap.builder() + .put("player", new MockPlayerPlaceholderExpansion()) + .build(); + + + Replacer CHARS_REPLACER = new CharsReplacer(Replacer.Closure.PERCENT); + Replacer OLD_CHARS_REPLACER = new OldCharsReplacer(Replacer.Closure.PERCENT); + + final class MockPlayerPlaceholderExpansion extends PlaceholderExpansion { + + public static final String PLAYER_X = "10"; + public static final String PLAYER_Y = "20"; + public static final String PLAYER_Z = "30"; + public static final String PLAYER_NAME = "Sxtanna"; + + + @NotNull + @Override + public String getIdentifier() { + return "player"; + } + + @NotNull + @Override + public String getAuthor() { + return "Sxtanna"; + } + + @NotNull + @Override + public String getVersion() { + return "1.0"; + } + + @Override + public String onRequest(@Nullable final OfflinePlayer player, @NotNull final String params) { + final String[] parts = params.split("_"); + if (parts.length == 0) { + return null; + } + + switch (parts[0]) { + case "name": + return PLAYER_NAME; + case "x": + return PLAYER_X; + case "y": + return PLAYER_Y; + case "z": + return PLAYER_Z; + } + + return null; + } + + } + +} diff --git a/src/jmh/java/me/clip/placeholderapi/replacer/OldCharsReplacer.java b/src/jmh/java/me/clip/placeholderapi/replacer/OldCharsReplacer.java new file mode 100644 index 0000000..2941f21 --- /dev/null +++ b/src/jmh/java/me/clip/placeholderapi/replacer/OldCharsReplacer.java @@ -0,0 +1,136 @@ +package me.clip.placeholderapi.replacer; + +/* + * This file is part of PlaceholderAPI + * + * PlaceholderAPI + * Copyright (c) 2015 - 2026 PlaceholderAPI Team + * + * PlaceholderAPI free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlaceholderAPI is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import java.util.Locale; +import java.util.function.Function; + +import me.clip.placeholderapi.expansion.PlaceholderExpansion; +import org.bukkit.OfflinePlayer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public final class OldCharsReplacer implements Replacer { + + @NotNull + private final Closure closure; + + public OldCharsReplacer(@NotNull final Closure closure) { + this.closure = closure; + } + + @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 StringBuilder identifier = new StringBuilder(); + final StringBuilder parameters = new StringBuilder(); + + for (int i = 0; i < chars.length; i++) { + final char l = chars[i]; + + if (l != closure.head || i + 1 >= chars.length) { + builder.append(l); + continue; + } + + boolean identified = false; + boolean invalid = true; + boolean hadSpace = false; + + 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 String identifierString = identifier.toString(); + final String lowercaseIdentifierString = identifierString.toLowerCase(Locale.ROOT); + final String parametersString = parameters.toString(); + + 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; + } + + 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; + } + + final String replacement = placeholder.onRequest(player, parametersString); + if (replacement == null) { + builder.append(closure.head).append(identifierString); + + if (identified) { + builder.append('_'); + } + + builder.append(parametersString).append(closure.tail); + continue; + } + + builder.append(replacement); + } + + return builder.toString(); + } + +} \ No newline at end of file diff --git a/src/jmh/java/me/clip/placeholderapi/replacer/ReplacerBenchmarks.java b/src/jmh/java/me/clip/placeholderapi/replacer/ReplacerBenchmarks.java new file mode 100644 index 0000000..7469f64 --- /dev/null +++ b/src/jmh/java/me/clip/placeholderapi/replacer/ReplacerBenchmarks.java @@ -0,0 +1,66 @@ +/* + * This file is part of PlaceholderAPI + * + * PlaceholderAPI + * Copyright (c) 2015 - 2026 PlaceholderAPI Team + * + * PlaceholderAPI free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PlaceholderAPI is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package me.clip.placeholderapi.replacer; + +import me.clip.placeholderapi.Values; +import me.clip.placeholderapi.expansion.PlaceholderExpansion; +import org.jetbrains.annotations.Nullable; +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.infra.Blackhole; + +import java.util.concurrent.TimeUnit; +import java.util.function.Function; + +@State(Scope.Benchmark) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@BenchmarkMode({Mode.AverageTime, Mode.Throughput}) +@Fork(value = 3, warmups = 1) +@Warmup(iterations = 5, time = 1) +@Measurement(iterations = 10, time = 1) +public class ReplacerBenchmarks { + + private Function expansionFunction; + + @Setup + public void setup() { + this.expansionFunction = Values.PLACEHOLDERS::get; + } + + @Benchmark + public void measureCharsReplacerSmallText(final Blackhole blackhole) { + blackhole.consume(Values.CHARS_REPLACER.apply(Values.SMALL_TEXT, null, expansionFunction)); + } + + @Benchmark + public void measureCharsReplacerLargeText(final Blackhole blackhole) { + blackhole.consume(Values.CHARS_REPLACER.apply(Values.LARGE_TEXT, null, expansionFunction)); + } + + @Benchmark + public void measureCharsReplacerSmallTextOld(final Blackhole blackhole) { + blackhole.consume(Values.OLD_CHARS_REPLACER.apply(Values.SMALL_TEXT, null, expansionFunction)); + } + + @Benchmark + public void measureCharsReplacerLargeTextOld(final Blackhole blackhole) { + blackhole.consume(Values.OLD_CHARS_REPLACER.apply(Values.LARGE_TEXT, null, expansionFunction)); + } +} \ No newline at end of file diff --git a/src/test/java/me/clip/placeholderapi/replacer/ReplacerBenchmarks.java b/src/test/java/me/clip/placeholderapi/replacer/ReplacerBenchmarks.java deleted file mode 100644 index 1283299..0000000 --- a/src/test/java/me/clip/placeholderapi/replacer/ReplacerBenchmarks.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * This file is part of PlaceholderAPI - * - * PlaceholderAPI - * Copyright (c) 2015 - 2026 PlaceholderAPI Team - * - * PlaceholderAPI free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * PlaceholderAPI is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package me.clip.placeholderapi.replacer; - -import me.clip.placeholderapi.Values; -import org.openjdk.jmh.annotations.Benchmark; - -public class ReplacerBenchmarks { - - @Benchmark - public void measureCharsReplacerSmallText() { - Values.CHARS_REPLACER.apply(Values.SMALL_TEXT, null, Values.PLACEHOLDERS::get); - } - - @Benchmark - public void measureCharsReplacerLargeText() { - Values.CHARS_REPLACER.apply(Values.LARGE_TEXT, null, Values.PLACEHOLDERS::get); - } - -} \ No newline at end of file From 469997e11438b1a2f7685a9e5672a64987c55ab5 Mon Sep 17 00:00:00 2001 From: ichocomilk Date: Wed, 4 Feb 2026 20:17:44 -0300 Subject: [PATCH 5/7] Refactor placeholder methods to use ArrayList for compatibility | I used immutable list by error :( --- .../clip/placeholderapi/PlaceholderAPI.java | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/main/java/me/clip/placeholderapi/PlaceholderAPI.java b/src/main/java/me/clip/placeholderapi/PlaceholderAPI.java index 61d554b..258cb92 100644 --- a/src/main/java/me/clip/placeholderapi/PlaceholderAPI.java +++ b/src/main/java/me/clip/placeholderapi/PlaceholderAPI.java @@ -80,12 +80,11 @@ public final class PlaceholderAPI { @NotNull public static List setPlaceholders(final OfflinePlayer player, @NotNull final List text) { - final String[] parsed = new String[text.size()]; - int i = 0; + final List result = new ArrayList<>(text.size()); for (final String line : text) { - parsed[i++] = setPlaceholders(player, line); + result.add(setPlaceholders(player, line)); } - return Arrays.asList(parsed); + return result; } /** @@ -140,12 +139,11 @@ public final class PlaceholderAPI { @NotNull public static List<@NotNull String> setBracketPlaceholders(final OfflinePlayer player, @NotNull final List<@NotNull String> text) { - final String[] parsed = new String[text.size()]; - int i = 0; + final List result = new ArrayList<>(text.size()); for (final String line : text) { - parsed[i++] = setBracketPlaceholders(player, line); + result.add(setBracketPlaceholders(player, line)); } - return Arrays.asList(parsed); + return result; } /** @@ -223,12 +221,11 @@ public final class PlaceholderAPI { * @return The text containing the parsed relational placeholders */ public static List setRelationalPlaceholders(final Player one, final Player two, final @NotNull List text) { - final String[] parsed = new String[text.size()]; - int i = 0; + final List result = new ArrayList<>(text.size()); for (final String line : text) { - parsed[i++] = setRelationalPlaceholders(one, two, line); + result.add(setRelationalPlaceholders(one, two, line)); } - return Arrays.asList(parsed); + return result; } /** From 407b9f5e54af52845e02ec11a7105275e10596b4 Mon Sep 17 00:00:00 2001 From: ichocomilk Date: Wed, 4 Feb 2026 20:22:01 -0300 Subject: [PATCH 6/7] Simplify getExpansion method by removing unnecessary locking --- .../expansion/manager/LocalExpansionManager.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) 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 d703e01..9cf638e 100644 --- a/src/main/java/me/clip/placeholderapi/expansion/manager/LocalExpansionManager.java +++ b/src/main/java/me/clip/placeholderapi/expansion/manager/LocalExpansionManager.java @@ -136,12 +136,7 @@ public final class LocalExpansionManager implements Listener { @Nullable public PlaceholderExpansion getExpansion(@NotNull final String identifier) { - expansionsLock.lock(); - try { - return expansions.get(identifier.toLowerCase(Locale.ROOT)); - } finally { - expansionsLock.unlock(); - } + return expansions.get(identifier.toLowerCase(Locale.ROOT)); } @NotNull From b085e49062b0f5c648176cf7c53334deded859f0 Mon Sep 17 00:00:00 2001 From: PiggyPiglet Date: Thu, 12 Feb 2026 21:00:57 +0800 Subject: [PATCH 7/7] Reimplement Blitz' replacer change for new replacer --- .../replacer/CharsReplacer.java | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/main/java/me/clip/placeholderapi/replacer/CharsReplacer.java b/src/main/java/me/clip/placeholderapi/replacer/CharsReplacer.java index e07ef00..008e4ca 100644 --- a/src/main/java/me/clip/placeholderapi/replacer/CharsReplacer.java +++ b/src/main/java/me/clip/placeholderapi/replacer/CharsReplacer.java @@ -106,16 +106,18 @@ public final class CharsReplacer implements Replacer { } } - String identifier; + if (underscoreIndex == -1) { + builder.append(text, startPlaceholder, endPlaceholder + 1); + cursor = endPlaceholder + 1; + startPlaceholder = text.indexOf(head, cursor); + continue; + } + + String identifier = text.substring(startPlaceholder + 1, underscoreIndex); String parameters = ""; - if (underscoreIndex != -1) { - identifier = text.substring(startPlaceholder + 1, underscoreIndex); - if (underscoreIndex + 1 < endPlaceholder) { - parameters = text.substring(underscoreIndex + 1, endPlaceholder); - } - } else { - identifier = text.substring(startPlaceholder + 1, endPlaceholder); + if (underscoreIndex + 1 < endPlaceholder) { + parameters = text.substring(underscoreIndex + 1, endPlaceholder); } final PlaceholderExpansion expansion = lookup.apply(identifier.toLowerCase(Locale.ROOT)); @@ -130,9 +132,7 @@ public final class CharsReplacer implements Replacer { } else { // Fallback: Restore original placeholder format builder.append(head).append(identifier); - if (underscoreIndex != -1) { - builder.append('_').append(parameters); - } + builder.append('_').append(parameters); builder.append(tail); }