mirror of
https://github.com/PlaceholderAPI/PlaceholderAPI
synced 2026-02-27 18:21:12 +01:00
Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
feb4eff237 | ||
|
|
7286d8849b | ||
|
|
1807b92505 | ||
|
|
3c82c6cdd7 | ||
|
|
17ad663257 | ||
|
|
984ec7f7b9 | ||
|
|
c8f73a5940 | ||
|
|
81675c1fd6 | ||
|
|
69cef6f72b | ||
|
|
ca2bd9f19a | ||
|
|
b331145c15 | ||
|
|
b085e49062 | ||
|
|
43f6a517af | ||
|
|
59769c2d93 | ||
|
|
354aebe8df | ||
|
|
566beb48c5 | ||
|
|
64b3d6fa68 | ||
|
|
975b1ac3ca | ||
|
|
7b8550c3f1 | ||
|
|
f267887b5e | ||
|
|
e187eb6402 | ||
|
|
0d1a356e0f | ||
|
|
407b9f5e54 | ||
|
|
469997e114 | ||
|
|
8185b7bfe9 | ||
|
|
fe15e4ed7a | ||
|
|
0f35362a0c | ||
|
|
9a4fa18304 | ||
|
|
cb5d6c0895 | ||
|
|
3684e32ac0 |
12
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
12
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -18,13 +18,13 @@ body:
|
||||
label: Confirmation
|
||||
description: Please make sure to have followed the following checks.
|
||||
options:
|
||||
- label: My issue isn't already found on the Issue tracker.
|
||||
- label: "My issue isn't already found on the Issue tracker."
|
||||
required: true
|
||||
- label: My issue is about **PlaceholderAPI** and not any expansion or external plugin
|
||||
- label: "My issue is about **PlaceholderAPI** and not any expansion or external plugin."
|
||||
required: true
|
||||
- label: The issue isn't already fixed in a Spigot Release or Development Build.
|
||||
- label: "The issue isn't already fixed in a Spigot Release or Development Build."
|
||||
required: true
|
||||
- label: The [Common Issues](https://github.com/PlaceholderAPI/PlaceholderAPI/wiki/Common-Issues) page doesn't mention this issue.
|
||||
- label: "The [Common Issues](https://wiki.placeholderapi.com/common-issues/) page doesn't mention this issue."
|
||||
required: true
|
||||
- type: dropdown
|
||||
attributes:
|
||||
@@ -85,6 +85,8 @@ body:
|
||||
description: |-
|
||||
Get the latest content of your `latest.log` file an upload it to https://paste.helpch.at
|
||||
Take the generated URL and paste it into this field.
|
||||
|
||||
**Always provide the full `latest.log` and not just parts of it or just the error (if any)!**
|
||||
placeholder: "https://paste.helpch.at/latest.log"
|
||||
- type: input
|
||||
id: "error"
|
||||
@@ -99,5 +101,5 @@ body:
|
||||
description: |-
|
||||
Add any extra info you think is nessesary for this Bug report.
|
||||
- If you selected `API Bug` will you need to include code-examples here to reproduce the issue.
|
||||
- If you selected `Plugin/Server Incompatability` should you include extra Server info such as a Timings or Spark-Report or info about the plugin in question.
|
||||
- If you selected `Plugin/Server Incompatability` should you include extra Server info such as Spark-Report or info about the plugin in question.
|
||||
placeholder: "Put any extra info you like into this field..."
|
||||
|
||||
55
.github/workflows/pr_wiki_validation.yml
vendored
Normal file
55
.github/workflows/pr_wiki_validation.yml
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
#
|
||||
# Validates Pull requests targeting the wiki branch
|
||||
# to ensure that the site can be build successfully
|
||||
# without broken links, navigation, etc.
|
||||
#
|
||||
name: "Validate Wiki Build"
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- synchronize
|
||||
paths-ignore:
|
||||
- "README.md"
|
||||
branches:
|
||||
- wiki
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
|
||||
env:
|
||||
RUN_URL: "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_number }}"
|
||||
|
||||
jobs:
|
||||
buildWiki:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: "Checkout Repository"
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: "${{ github.event.pull_request.head.sha }}"
|
||||
- name: "Setup Python 3.x"
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.x
|
||||
- name: "Install dependencies"
|
||||
run: "python -m pip install mkdocs-material"
|
||||
- name: "Build Site"
|
||||
run: "mkdocs build --strict"
|
||||
- name: "Create Comment"
|
||||
uses: peter-evans/create-or-update-comment@v5
|
||||
if: ${{ failure() }}
|
||||
with:
|
||||
body: |-
|
||||
## Wiki Build failure
|
||||
|
||||
Something went wrong while creating a test-build of the Wiki for this Pull request.
|
||||
Please check the [Workflow Logs](${{ env.RUN_URL }}) for any errors.
|
||||
issue-number: "${{ github.event.pull_request.number }}"
|
||||
token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
edit-mode: replace
|
||||
@@ -4,11 +4,12 @@ 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"
|
||||
version = "2.12.2"
|
||||
version = "2.12.3-DEV-${System.getProperty("BUILD_NUMBER")}"
|
||||
|
||||
description = "An awesome placeholder provider!"
|
||||
|
||||
@@ -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
|
||||
|
||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -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
|
||||
|
||||
94
src/jmh/java/me/clip/placeholderapi/Values.java
Normal file
94
src/jmh/java/me/clip/placeholderapi/Values.java
Normal file
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<String, PlaceholderExpansion> PLACEHOLDERS = ImmutableMap.<String, PlaceholderExpansion>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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<String, @Nullable PlaceholderExpansion> 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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<String, @Nullable PlaceholderExpansion> 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));
|
||||
}
|
||||
}
|
||||
@@ -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,11 @@ public final class PlaceholderAPI {
|
||||
@NotNull
|
||||
public static List<String> setPlaceholders(final OfflinePlayer player,
|
||||
@NotNull final List<String> text) {
|
||||
return text.stream().map(line -> setPlaceholders(player, line)).collect(Collectors.toList());
|
||||
final List<String> result = new ArrayList<>(text.size());
|
||||
for (final String line : text) {
|
||||
result.add(setPlaceholders(player, line));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -140,8 +139,11 @@ 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 List<String> result = new ArrayList<>(text.size());
|
||||
for (final String line : text) {
|
||||
result.add(setBracketPlaceholders(player, line));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -179,14 +181,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 +220,12 @@ public final class PlaceholderAPI {
|
||||
* @param text text to parse the placeholder values to
|
||||
* @return The text containing the parsed relational placeholders
|
||||
*/
|
||||
public static List<String> setRelationalPlaceholders(Player one, Player two, List<String> text) {
|
||||
return text.stream().map(line -> setRelationalPlaceholders(one, two, line))
|
||||
.collect(Collectors.toList());
|
||||
public static List<String> setRelationalPlaceholders(final Player one, final Player two, final @NotNull List<String> text) {
|
||||
final List<String> result = new ArrayList<>(text.size());
|
||||
for (final String line : text) {
|
||||
result.add(setRelationalPlaceholders(one, two, line));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -241,8 +246,7 @@ public final class PlaceholderAPI {
|
||||
*/
|
||||
@NotNull
|
||||
public static Set<String> getRegisteredIdentifiers() {
|
||||
return ImmutableSet
|
||||
.copyOf(PlaceholderAPIPlugin.getInstance().getLocalExpansionManager().getIdentifiers());
|
||||
return PlaceholderAPIPlugin.getInstance().getLocalExpansionManager().getIdentifiers();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -279,8 +283,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 +301,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 openBracket = text.indexOf('{');
|
||||
if (openBracket == -1) {
|
||||
return false;
|
||||
}
|
||||
return text.indexOf('}', openBracket + 1) != -1;
|
||||
}
|
||||
|
||||
// === Deprecated API ===
|
||||
|
||||
@@ -26,12 +26,9 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.*;
|
||||
import java.lang.reflect.Type;
|
||||
import java.net.URL;
|
||||
import java.net.UnknownHostException;
|
||||
import java.net.*;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@@ -61,6 +58,24 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Unmodifiable;
|
||||
|
||||
public final class CloudExpansionManager {
|
||||
@NotNull
|
||||
public static final String USER_AGENT;
|
||||
|
||||
static {
|
||||
String userAgent;
|
||||
|
||||
try (final InputStream in = PlaceholderAPIPlugin.class.getResourceAsStream("/user-agent.txt")) {
|
||||
if (in == null) {
|
||||
userAgent = "PlaceholderAPI-Bukkit-null";
|
||||
} else {
|
||||
userAgent = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)).readLine();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
userAgent = "PlaceholderAPI-Bukkit-null";
|
||||
}
|
||||
|
||||
USER_AGENT = userAgent;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static final String API_URL = "https://ecloud.placeholderapi.com/api/v3/?platform=bukkit";
|
||||
@@ -185,8 +200,16 @@ public final class CloudExpansionManager {
|
||||
// a defence tactic! use ConcurrentHashMap instead of normal HashMap
|
||||
Map<String, CloudExpansion> values = new ConcurrentHashMap<>();
|
||||
try {
|
||||
//noinspection UnstableApiUsage
|
||||
String json = Resources.toString(new URL(API_URL), StandardCharsets.UTF_8);
|
||||
final URI uri = new URI(API_URL);
|
||||
final URLConnection connection = uri.toURL().openConnection();
|
||||
connection.setRequestProperty("User-Agent", USER_AGENT);
|
||||
final String json;
|
||||
|
||||
try (final InputStream in = connection.getInputStream()) {
|
||||
final BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
|
||||
json = reader.lines().collect(Collectors.joining(System.lineSeparator()));
|
||||
}
|
||||
|
||||
values.putAll(GSON.fromJson(json, TYPE));
|
||||
|
||||
List<String> toRemove = new ArrayList<>();
|
||||
@@ -260,10 +283,14 @@ public final class CloudExpansionManager {
|
||||
"Expansion-" + toIndexName(expansion) + ".jar");
|
||||
|
||||
final CompletableFuture<File> download = CompletableFuture.supplyAsync(() -> {
|
||||
try (final ReadableByteChannel source = Channels.newChannel(new URL(version.getUrl())
|
||||
.openStream()); final FileOutputStream target = new FileOutputStream(file)) {
|
||||
target.getChannel().transferFrom(source, 0, Long.MAX_VALUE);
|
||||
} catch (final IOException ex) {
|
||||
try {
|
||||
final URLConnection connection = new URI(version.getUrl()).toURL().openConnection();
|
||||
connection.setRequestProperty("User-Agent", USER_AGENT);
|
||||
|
||||
try (final ReadableByteChannel source = Channels.newChannel(connection.getInputStream()); final FileOutputStream target = new FileOutputStream(file)) {
|
||||
target.getChannel().transferFrom(source, 0, Long.MAX_VALUE);
|
||||
}
|
||||
} catch (final IOException | URISyntaxException ex) {
|
||||
throw new CompletionException(ex);
|
||||
}
|
||||
return file;
|
||||
|
||||
@@ -114,7 +114,7 @@ public final class LocalExpansionManager implements Listener {
|
||||
|
||||
@NotNull
|
||||
@Unmodifiable
|
||||
public Collection<String> getIdentifiers() {
|
||||
public Set<String> getIdentifiers() {
|
||||
expansionsLock.lock();
|
||||
try {
|
||||
return ImmutableSet.copyOf(expansions.keySet());
|
||||
@@ -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
|
||||
|
||||
@@ -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,101 +36,116 @@ public final class CharsReplacer implements Replacer {
|
||||
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
|
||||
@Override
|
||||
public String apply(@NotNull final String text, @Nullable final OfflinePlayer player,
|
||||
@NotNull final Function<String, @Nullable PlaceholderExpansion> 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 == ' ' && underscoreIndex == -1) {
|
||||
// Invalid placeholder (contains space before _).
|
||||
// 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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,15 @@ import com.google.common.hash.Hashing;
|
||||
import com.google.common.io.Files;
|
||||
import com.google.common.io.Resources;
|
||||
import me.clip.placeholderapi.PlaceholderAPIPlugin;
|
||||
import me.clip.placeholderapi.expansion.manager.CloudExpansionManager;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
@@ -47,7 +52,10 @@ public final class ExpansionSafetyCheck {
|
||||
final Set<String> knownMaliciousExpansions;
|
||||
|
||||
try {
|
||||
final String hashes = Resources.toString(new URL("https://check.placeholderapi.com"), StandardCharsets.UTF_8);
|
||||
final URLConnection connection = new URI("https://check.placeholderapi.com").toURL().openConnection();
|
||||
connection.setRequestProperty("User-Agent", CloudExpansionManager.USER_AGENT);
|
||||
final String hashes = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))
|
||||
.lines().collect(Collectors.joining(System.lineSeparator()));
|
||||
knownMaliciousExpansions = Arrays.stream(hashes.split("\n")).collect(Collectors.toSet());
|
||||
} catch (Exception e) {
|
||||
main.getLogger().log(Level.SEVERE, "Failed to download anti malware hash check list from https://check.placeholderapi.com", e);
|
||||
@@ -55,9 +63,18 @@ public final class ExpansionSafetyCheck {
|
||||
}
|
||||
|
||||
final Set<String> maliciousPaths = new HashSet<>();
|
||||
final File[] files = expansionsFolder.listFiles();
|
||||
|
||||
for (File file : expansionsFolder.listFiles()) {
|
||||
if (files == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (File file : files) {
|
||||
try {
|
||||
if (!file.isFile()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final String hash = Hashing.sha256().hashBytes(Files.asByteSource(file).read()).toString();
|
||||
|
||||
if (knownMaliciousExpansions.contains(hash)) {
|
||||
|
||||
1
src/main/resources/user-agent.txt
Normal file
1
src/main/resources/user-agent.txt
Normal file
@@ -0,0 +1 @@
|
||||
PlaceholderAPI-Bukkit-${version}
|
||||
@@ -25,11 +25,14 @@ import me.clip.placeholderapi.replacer.ExactReplacer;
|
||||
import me.clip.placeholderapi.replacer.RelationalExactReplacer;
|
||||
import me.clip.placeholderapi.replacer.Replacer;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.ComponentLike;
|
||||
import org.bukkit.OfflinePlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static me.clip.placeholderapi.PlaceholderAPI.RELATIONAL_PLACEHOLDER_PATTERN;
|
||||
@@ -43,122 +46,236 @@ public final class PAPIComponents {
|
||||
* Translates all placeholders into their corresponding values.
|
||||
* <br>The pattern of a valid placeholder is {@literal %<identifier>_<params>%}.
|
||||
*
|
||||
* @param player Player to parse the placeholders against
|
||||
* @param player Player to parse the placeholders against
|
||||
* @param component Component to set the placeholder values in
|
||||
* @return Component containing all translated placeholders
|
||||
*/
|
||||
@NotNull
|
||||
public static Component setPlaceholders(final OfflinePlayer player, @NotNull final Component component) {
|
||||
if (PlaceholderAPIPlugin.getInstance().getPlaceholderAPIConfig().useAdventureProvidedReplacer()) {
|
||||
return component.replaceText(config -> config.match(PlaceholderAPI.PLACEHOLDER_PATTERN).replacement((result, builder) ->
|
||||
builder.content(PERCENT_EXACT_REPLACER.apply(result.group(), player, PlaceholderAPIPlugin.getInstance().getLocalExpansionManager()::getExpansion))));
|
||||
}
|
||||
|
||||
return ComponentReplacer.replace(component, str -> PlaceholderAPI.setPlaceholders(player, str));
|
||||
return setPlaceholders(player, component, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates all placeholders into their corresponding values.
|
||||
* <br>The pattern of a valid placeholder is {@literal %<identifier>_<params>%}.
|
||||
*
|
||||
* @param player Player to parse the placeholders against
|
||||
* @param player Player to parse the placeholders against
|
||||
* @param component Component to set the placeholder values in
|
||||
* @param deserializer Optional function to serialize parsed placeholder values into ComponentLike
|
||||
* @return Component containing all translated placeholders
|
||||
*/
|
||||
@NotNull
|
||||
public static Component setPlaceholders(final OfflinePlayer player, @NotNull final Component component, @Nullable Function<String, ComponentLike> deserializer) {
|
||||
if (PlaceholderAPIPlugin.getInstance().getPlaceholderAPIConfig().useAdventureProvidedReplacer()) {
|
||||
return component.replaceText(config -> config.match(PlaceholderAPI.PLACEHOLDER_PATTERN).replacement((result, builder) -> {
|
||||
String parsed = PERCENT_EXACT_REPLACER.apply(result.group(), player, PlaceholderAPIPlugin.getInstance().getLocalExpansionManager()::getExpansion);
|
||||
return deserializer == null ? builder.content(parsed) : deserializer.apply(parsed);
|
||||
}));
|
||||
}
|
||||
|
||||
return ComponentReplacer.replace(component, str -> PlaceholderAPI.setPlaceholders(player, str), deserializer == null ? null : s -> deserializer.apply(s).asComponent());
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates all placeholders into their corresponding values.
|
||||
* <br>The pattern of a valid placeholder is {@literal %<identifier>_<params>%}.
|
||||
*
|
||||
* @param player Player to parse the placeholders against
|
||||
* @param components List of Components to set the placeholder values in
|
||||
* @return List of Components containing all translated placeholders
|
||||
*/
|
||||
@NotNull
|
||||
public static List<Component> setPlaceholders(final OfflinePlayer player, @NotNull final List<Component> components) {
|
||||
return components.stream().map(component -> setPlaceholders(player, component)).collect(Collectors.toList());
|
||||
return setPlaceholders(player, components, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates all placeholders into their corresponding values.
|
||||
* <br>The pattern of a valid placeholder is {@literal %<identifier>_<params>%}.
|
||||
*
|
||||
* @param player Player to parse the placeholders against
|
||||
* @param player Player to parse the placeholders against
|
||||
* @param components List of Components to set the placeholder values in
|
||||
* @param deserializer Optional function to serialize parsed placeholder values into ComponentLike
|
||||
* @return List of Components containing all translated placeholders
|
||||
*/
|
||||
@NotNull
|
||||
public static List<Component> setPlaceholders(final OfflinePlayer player, @NotNull final List<Component> components, @Nullable Function<String, ComponentLike> deserializer) {
|
||||
return components.stream().map(component -> setPlaceholders(player, component, deserializer == null ? null : s -> deserializer.apply(s).asComponent())).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates all placeholders into their corresponding values.
|
||||
* <br>The pattern of a valid placeholder is {@literal %<identifier>_<params>%}.
|
||||
*
|
||||
* @param player Player to parse the placeholders against
|
||||
* @param component Component to set the placeholder values in
|
||||
* @return Component containing all translated placeholders
|
||||
*/
|
||||
@NotNull
|
||||
public static Component setPlaceholders(final Player player, @NotNull final Component component) {
|
||||
return setPlaceholders((OfflinePlayer) player, component);
|
||||
return setPlaceholders(player, component, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates all placeholders into their corresponding values.
|
||||
* <br>The pattern of a valid placeholder is {@literal %<identifier>_<params>%}.
|
||||
*
|
||||
* @param player Player to parse the placeholders against
|
||||
* @param player Player to parse the placeholders against
|
||||
* @param component Component to set the placeholder values in
|
||||
* @param deserializer Optional function to serialize parsed placeholder values into ComponentLike
|
||||
* @return Component containing all translated placeholders
|
||||
*/
|
||||
@NotNull
|
||||
public static Component setPlaceholders(final Player player, @NotNull final Component component, @Nullable Function<String, ComponentLike> deserializer) {
|
||||
return setPlaceholders((OfflinePlayer) player, component, deserializer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates all placeholders into their corresponding values.
|
||||
* <br>The pattern of a valid placeholder is {@literal %<identifier>_<params>%}.
|
||||
*
|
||||
* @param player Player to parse the placeholders against
|
||||
* @param components List of Components to set the placeholder values in
|
||||
* @return List of components containing all translated placeholders
|
||||
*/
|
||||
@NotNull
|
||||
public static List<Component> setPlaceholders(final Player player, @NotNull final List<Component> components) {
|
||||
return setPlaceholders((OfflinePlayer) player, components);
|
||||
return setPlaceholders(player, components, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates all placeholders into their corresponding values.
|
||||
* <br>The pattern of a valid placeholder is {@literal %<identifier>_<params>%}.
|
||||
*
|
||||
* @param player Player to parse the placeholders against
|
||||
* @param components List of Components to set the placeholder values in
|
||||
* @param deserializer Optional function to serialize parsed placeholder values into ComponentLike
|
||||
* @return List of components containing all translated placeholders
|
||||
*/
|
||||
@NotNull
|
||||
public static List<Component> setPlaceholders(final Player player, @NotNull final List<Component> components, @Nullable Function<String, ComponentLike> deserializer) {
|
||||
return setPlaceholders((OfflinePlayer) player, components, deserializer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates all placeholders into their corresponding values.
|
||||
* <br>The pattern of a valid placeholder is {@literal {<identifier>_<params>}}.
|
||||
*
|
||||
* @param player Player to parse the placeholders against
|
||||
* @param player Player to parse the placeholders against
|
||||
* @param component Component to set the placeholder values in
|
||||
* @return Component containing all translated placeholders
|
||||
*/
|
||||
@NotNull
|
||||
public static Component setBracketPlaceholders(final OfflinePlayer player, @NotNull final Component component) {
|
||||
return setBracketPlaceholders(player, component, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates all placeholders into their corresponding values.
|
||||
* <br>The pattern of a valid placeholder is {@literal {<identifier>_<params>}}.
|
||||
*
|
||||
* @param player Player to parse the placeholders against
|
||||
* @param component Component to set the placeholder values in
|
||||
* @param deserializer Optional function to serialize parsed placeholder values into ComponentLike
|
||||
* @return Component containing all translated placeholders
|
||||
*/
|
||||
@NotNull
|
||||
public static Component setBracketPlaceholders(final OfflinePlayer player, @NotNull final Component component, @Nullable Function<String, ComponentLike> deserializer) {
|
||||
if (PlaceholderAPIPlugin.getInstance().getPlaceholderAPIConfig().useAdventureReplacer()) {
|
||||
return component.replaceText(config -> config.match(PlaceholderAPI.BRACKET_PLACEHOLDER_PATTERN).replacement((result, builder) ->
|
||||
builder.content(BRACKET_EXACT_REPLACER.apply(result.group(), player, PlaceholderAPIPlugin.getInstance().getLocalExpansionManager()::getExpansion))));
|
||||
}
|
||||
|
||||
return ComponentReplacer.replace(component, str -> PlaceholderAPI.setBracketPlaceholders(player, str));
|
||||
return ComponentReplacer.replace(component, str -> PlaceholderAPI.setBracketPlaceholders(player, str), deserializer == null ? null : s -> deserializer.apply(s).asComponent());
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates all placeholders into their corresponding values.
|
||||
* <br>The pattern of a valid placeholder is {@literal {<identifier>_<params>}}.
|
||||
*
|
||||
* @param player Player to parse the placeholders against
|
||||
* @param player Player to parse the placeholders against
|
||||
* @param components List of Components to set the placeholder values in
|
||||
* @return List of Components containing all translated placeholders
|
||||
*/
|
||||
@NotNull
|
||||
public static List<Component> setBracketPlaceholders(final OfflinePlayer player, @NotNull final List<Component> components) {
|
||||
return components.stream().map(component -> setBracketPlaceholders(player, component)).collect(Collectors.toList());
|
||||
return setBracketPlaceholders(player, components, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates all placeholders into their corresponding values.
|
||||
* <br>The pattern of a valid placeholder is {@literal {<identifier>_<params>}}.
|
||||
*
|
||||
* @param player Player to parse the placeholders against
|
||||
* @param player Player to parse the placeholders against
|
||||
* @param components List of Components to set the placeholder values in
|
||||
* @param deserializer Optional function to serialize parsed placeholder values into ComponentLike
|
||||
* @return List of Components containing all translated placeholders
|
||||
*/
|
||||
@NotNull
|
||||
public static List<Component> setBracketPlaceholders(final OfflinePlayer player, @NotNull final List<Component> components, @Nullable Function<String, ComponentLike> deserializer) {
|
||||
return components.stream().map(component -> setBracketPlaceholders(player, component, deserializer)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates all placeholders into their corresponding values.
|
||||
* <br>The pattern of a valid placeholder is {@literal {<identifier>_<params>}}.
|
||||
*
|
||||
* @param player Player to parse the placeholders against
|
||||
* @param component Component to set the placeholder values in
|
||||
* @return Component containing all translated placeholders
|
||||
*/
|
||||
@NotNull
|
||||
public static Component setBracketPlaceholders(final Player player, @NotNull final Component component) {
|
||||
return setBracketPlaceholders((OfflinePlayer) player, component);
|
||||
return setBracketPlaceholders(player, component, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates all placeholders into their corresponding values.
|
||||
* <br>The pattern of a valid placeholder is {@literal {<identifier>_<params>}}.
|
||||
*
|
||||
* @param player Player to parse the placeholders against
|
||||
* @param player Player to parse the placeholders against
|
||||
* @param component Component to set the placeholder values in
|
||||
* @param deserializer Optional function to serialize parsed placeholder values into ComponentLike
|
||||
* @return Component containing all translated placeholders
|
||||
*/
|
||||
@NotNull
|
||||
public static Component setBracketPlaceholders(final Player player, @NotNull final Component component, @Nullable Function<String, ComponentLike> deserializer) {
|
||||
return setBracketPlaceholders((OfflinePlayer) player, component, deserializer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates all placeholders into their corresponding values.
|
||||
* <br>The pattern of a valid placeholder is {@literal {<identifier>_<params>}}.
|
||||
*
|
||||
* @param player Player to parse the placeholders against
|
||||
* @param components List of Components to set the placeholder values in
|
||||
* @return List of Components containing all translated placeholders
|
||||
*/
|
||||
@NotNull
|
||||
public static List<Component> setBracketPlaceholders(final Player player, @NotNull final List<Component> components) {
|
||||
return setBracketPlaceholders((OfflinePlayer) player, components);
|
||||
return setBracketPlaceholders(player, components, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates all placeholders into their corresponding values.
|
||||
* <br>The pattern of a valid placeholder is {@literal {<identifier>_<params>}}.
|
||||
*
|
||||
* @param player Player to parse the placeholders against
|
||||
* @param components List of Components to set the placeholder values in
|
||||
* @param deserializer Optional function to serialize parsed placeholder values into ComponentLike
|
||||
* @return List of Components containing all translated placeholders
|
||||
*/
|
||||
@NotNull
|
||||
public static List<Component> setBracketPlaceholders(final Player player, @NotNull final List<Component> components, @Nullable Function<String, ComponentLike> deserializer) {
|
||||
return setBracketPlaceholders((OfflinePlayer) player, components, deserializer);
|
||||
}
|
||||
|
||||
/**
|
||||
* set relational placeholders in the text specified placeholders are matched with the pattern
|
||||
* {@literal %<rel_(identifier)_(params)>%} when set with this method
|
||||
*
|
||||
* @param one First player to compare
|
||||
* @param two Second player to compare
|
||||
* @param one First player to compare
|
||||
* @param two Second player to compare
|
||||
* @param component Component to parse the placeholders in
|
||||
* @return The Component containing the parsed relational placeholders
|
||||
*/
|
||||
@@ -172,8 +289,8 @@ public final class PAPIComponents {
|
||||
* Translate placeholders in the provided List based on the relation of the two provided players.
|
||||
* <br>The pattern of a valid placeholder is {@literal %rel_<identifier>_<param>%}.
|
||||
*
|
||||
* @param one Player to compare
|
||||
* @param two Player to compare
|
||||
* @param one Player to compare
|
||||
* @param two Player to compare
|
||||
* @param components List of Components to parse the placeholder values to
|
||||
* @return The List of Components containing the parsed relational placeholders
|
||||
*/
|
||||
@@ -181,4 +298,4 @@ public final class PAPIComponents {
|
||||
return components.stream().map(line -> setRelationalPlaceholders(one, two, line))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import net.kyori.adventure.text.event.DataComponentValue;
|
||||
import net.kyori.adventure.text.event.HoverEvent;
|
||||
import net.kyori.adventure.text.format.Style;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
@@ -17,25 +18,25 @@ import java.util.function.Function;
|
||||
|
||||
public class ComponentReplacer {
|
||||
@NotNull
|
||||
public static Component replace(@NotNull final Component component, @NotNull final Function<String, String> replacer) {
|
||||
return rebuild(component, replacer);
|
||||
public static Component replace(@NotNull final Component component, @NotNull final Function<String, String> replacer, @Nullable final Function<String, Component> deserializer) {
|
||||
return rebuild(component, replacer, deserializer);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Component rebuild(@NotNull final Component component, @NotNull final Function<String, String> replacer) {
|
||||
private static Component rebuild(@NotNull final Component component, @NotNull final Function<String, String> replacer, @Nullable final Function<String, Component> deserializer) {
|
||||
Component rebuilt;
|
||||
|
||||
if (component instanceof TextComponent) {
|
||||
final TextComponent text = (TextComponent) component;
|
||||
final String replaced = replacer.apply(text.content());
|
||||
|
||||
rebuilt = Component.text(replaced);
|
||||
rebuilt = deserializer == null ? Component.text(replaced) : deserializer.apply(replaced);
|
||||
} else if (component instanceof TranslatableComponent) {
|
||||
final TranslatableComponent translatable = (TranslatableComponent) component;
|
||||
final List<Component> arguments = new ArrayList<>();
|
||||
|
||||
for (final ComponentLike arg : translatable.arguments()) {
|
||||
arguments.add(rebuild(arg.asComponent(), replacer));
|
||||
arguments.add(rebuild(arg.asComponent(), replacer, deserializer));
|
||||
}
|
||||
|
||||
rebuilt = Component.translatable(translatable.key(), arguments);
|
||||
@@ -52,12 +53,12 @@ public class ComponentReplacer {
|
||||
rebuilt = Component.empty();
|
||||
}
|
||||
|
||||
rebuilt = rebuilt.style(rebuildStyle(component.style(), replacer));
|
||||
rebuilt = rebuilt.style(rebuildStyle(component.style(), replacer, deserializer));
|
||||
|
||||
if (!component.children().isEmpty()) {
|
||||
final List<Component> children = new ArrayList<>();
|
||||
for (Component child : component.children()) {
|
||||
children.add(rebuild(child, replacer));
|
||||
children.add(rebuild(child, replacer, deserializer));
|
||||
}
|
||||
rebuilt = rebuilt.children(children);
|
||||
}
|
||||
@@ -66,7 +67,7 @@ public class ComponentReplacer {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Style rebuildStyle(@NotNull final Style style, @NotNull final Function<String, String> replacer) {
|
||||
private static Style rebuildStyle(@NotNull final Style style, @NotNull final Function<String, String> replacer, @Nullable final Function<String, Component> deserializer) {
|
||||
final Style.Builder builder = style.toBuilder();
|
||||
final ClickEvent click = style.clickEvent();
|
||||
|
||||
@@ -77,7 +78,7 @@ public class ComponentReplacer {
|
||||
final HoverEvent<?> hover = style.hoverEvent();
|
||||
|
||||
if (hover != null) {
|
||||
builder.hoverEvent(rebuildHoverEvent(hover, replacer));
|
||||
builder.hoverEvent(rebuildHoverEvent(hover, replacer, deserializer));
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
@@ -113,11 +114,11 @@ public class ComponentReplacer {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static HoverEvent<?> rebuildHoverEvent(@NotNull final HoverEvent<?> hover, @NotNull final Function<String, String> replacer) {
|
||||
private static HoverEvent<?> rebuildHoverEvent(@NotNull final HoverEvent<?> hover, @NotNull final Function<String, String> replacer, @Nullable final Function<String, Component> deserializer) {
|
||||
final Object value = hover.value();
|
||||
|
||||
if (value instanceof Component) {
|
||||
final Component rebuilt = rebuild((Component) value, replacer);
|
||||
final Component rebuilt = rebuild((Component) value, replacer, deserializer);
|
||||
return HoverEvent.showText(rebuilt);
|
||||
}
|
||||
|
||||
@@ -130,7 +131,7 @@ public class ComponentReplacer {
|
||||
|
||||
Component rebuiltName = null;
|
||||
if (entity.name() != null) {
|
||||
rebuiltName = rebuild(entity.name(), replacer);
|
||||
rebuiltName = rebuild(entity.name(), replacer, deserializer);
|
||||
}
|
||||
|
||||
return HoverEvent.showEntity(entity.type(), entity.id(), rebuiltName);
|
||||
|
||||
@@ -30,6 +30,8 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public interface Values {
|
||||
|
||||
String NO_ARGUMENTS_PLACEHOLDER = "%player%";
|
||||
String EMPTY_ARGUMENT_PLACEHOLDER = "%player_%";
|
||||
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%";
|
||||
|
||||
@@ -43,6 +45,7 @@ public interface Values {
|
||||
|
||||
final class MockPlayerPlaceholderExpansion extends PlaceholderExpansion {
|
||||
|
||||
public static final String EMPTY_ARGUMENT = "Empty Argument";
|
||||
public static final String PLAYER_X = "10";
|
||||
public static final String PLAYER_Y = "20";
|
||||
public static final String PLAYER_Z = "30";
|
||||
@@ -83,6 +86,8 @@ public interface Values {
|
||||
return PLAYER_Y;
|
||||
case "z":
|
||||
return PLAYER_Z;
|
||||
case "":
|
||||
return EMPTY_ARGUMENT;
|
||||
}
|
||||
|
||||
return 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import static me.clip.placeholderapi.Values.MockPlayerPlaceholderExpansion.PLAYE
|
||||
import static me.clip.placeholderapi.Values.MockPlayerPlaceholderExpansion.PLAYER_X;
|
||||
import static me.clip.placeholderapi.Values.MockPlayerPlaceholderExpansion.PLAYER_Y;
|
||||
import static me.clip.placeholderapi.Values.MockPlayerPlaceholderExpansion.PLAYER_Z;
|
||||
import static me.clip.placeholderapi.Values.MockPlayerPlaceholderExpansion.EMPTY_ARGUMENT;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import me.clip.placeholderapi.Values;
|
||||
@@ -37,6 +38,18 @@ public final class ReplacerUnitTester {
|
||||
Values.CHARS_REPLACER.apply("%player_name%", null, Values.PLACEHOLDERS::get));
|
||||
}
|
||||
|
||||
@Test
|
||||
void charsReplacersDoesNotParsePlaceholdersWithNoArguments() {
|
||||
assertEquals(Values.NO_ARGUMENTS_PLACEHOLDER,
|
||||
Values.CHARS_REPLACER.apply(Values.NO_ARGUMENTS_PLACEHOLDER, null, Values.PLACEHOLDERS::get));
|
||||
}
|
||||
|
||||
@Test
|
||||
void charsReplacersParsesPlaceholdersWithOneArgumentThatIsEmpty() {
|
||||
assertEquals(EMPTY_ARGUMENT,
|
||||
Values.CHARS_REPLACER.apply(Values.EMPTY_ARGUMENT_PLACEHOLDER, null, Values.PLACEHOLDERS::get));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCharsReplacerProducesExpectedSentence() {
|
||||
assertEquals(String.format(
|
||||
|
||||
Reference in New Issue
Block a user