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