mirror of
				https://github.com/PlaceholderAPI/PlaceholderAPI
				synced 2025-11-04 07:22:30 +01:00 
			
		
		
		
	replacer api, unit tests, and benchmarks (#354)
* added abstracted replacer api, and both char and regex based implementations * added test dependencies for jmh and junit * added unit tests and benchmarks for the replacer implementations * updated replacers to accept specific closure types, added test to verify malformed placeholder handling * updated jmh to 1.23, updated junit to 5.6.2
This commit is contained in:
		
							
								
								
									
										129
									
								
								src/main/java/me/clip/placeholderapi/replacer/CharsReplacer.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								src/main/java/me/clip/placeholderapi/replacer/CharsReplacer.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,129 @@
 | 
			
		||||
package me.clip.placeholderapi.replacer;
 | 
			
		||||
 | 
			
		||||
import me.clip.placeholderapi.PlaceholderHook;
 | 
			
		||||
import org.bukkit.OfflinePlayer;
 | 
			
		||||
import org.jetbrains.annotations.NotNull;
 | 
			
		||||
import org.jetbrains.annotations.Nullable;
 | 
			
		||||
 | 
			
		||||
import java.util.function.Function;
 | 
			
		||||
 | 
			
		||||
public final class CharsReplacer implements Replacer
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
	@NotNull
 | 
			
		||||
	private final Closure closure;
 | 
			
		||||
 | 
			
		||||
	public CharsReplacer(@NotNull final Closure closure)
 | 
			
		||||
	{
 | 
			
		||||
		this.closure = closure;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public @NotNull String apply(@NotNull final String text, @Nullable final OfflinePlayer player, @NotNull final Function<String, @Nullable PlaceholderHook> 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 == '&' && ++i < chars.length)
 | 
			
		||||
			{
 | 
			
		||||
				final char c = chars[i];
 | 
			
		||||
 | 
			
		||||
				if (c != '0' && c != '1' && c != '2' && c != '3' && c != '4' && c != '5' && c != '6' && c != '7' && c != '8' && c != '9' && c != 'a' && c != 'b' && c != 'c' && c != 'd' && c != 'e' && c != 'f' && c != 'k' && c != 'l' && c != 'm' && c != 'o' && c != 'r' && c != 'x')
 | 
			
		||||
				{
 | 
			
		||||
					builder.append(l).append(c);
 | 
			
		||||
				}
 | 
			
		||||
				else
 | 
			
		||||
				{
 | 
			
		||||
					builder.append('§').append(c);
 | 
			
		||||
				}
 | 
			
		||||
				continue;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (l != closure.head || i + 1 >= chars.length)
 | 
			
		||||
			{
 | 
			
		||||
				builder.append(l);
 | 
			
		||||
				continue;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			boolean identified = false;
 | 
			
		||||
			boolean oopsitsbad = false;
 | 
			
		||||
 | 
			
		||||
			while (++i < chars.length)
 | 
			
		||||
			{
 | 
			
		||||
				final char p = chars[i];
 | 
			
		||||
 | 
			
		||||
				if (p == closure.tail)
 | 
			
		||||
				{
 | 
			
		||||
					break;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if (p == ' ')
 | 
			
		||||
				{
 | 
			
		||||
					oopsitsbad = true;
 | 
			
		||||
					break;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if (p == '_' && !identified)
 | 
			
		||||
				{
 | 
			
		||||
					identified = true;
 | 
			
		||||
					continue;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if (identified)
 | 
			
		||||
				{
 | 
			
		||||
					parameters.append(p);
 | 
			
		||||
				}
 | 
			
		||||
				else
 | 
			
		||||
				{
 | 
			
		||||
					identifier.append(p);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			final String identifierString = identifier.toString();
 | 
			
		||||
			final String parametersString = parameters.toString();
 | 
			
		||||
 | 
			
		||||
			identifier.setLength(0);
 | 
			
		||||
			parameters.setLength(0);
 | 
			
		||||
 | 
			
		||||
			if (oopsitsbad)
 | 
			
		||||
			{
 | 
			
		||||
				builder.append(closure.head).append(identifierString);
 | 
			
		||||
 | 
			
		||||
				if (identified)
 | 
			
		||||
				{
 | 
			
		||||
					builder.append('_').append(parametersString);
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				builder.append(' ');
 | 
			
		||||
				continue;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			final PlaceholderHook placeholder = lookup.apply(identifierString);
 | 
			
		||||
			if (placeholder == null)
 | 
			
		||||
			{
 | 
			
		||||
				builder.append(closure.head).append(identifierString).append('_').append(parametersString).append(closure.tail);
 | 
			
		||||
				continue;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			final String replacement = placeholder.onRequest(player, parametersString);
 | 
			
		||||
			if (replacement == null)
 | 
			
		||||
			{
 | 
			
		||||
				builder.append(closure.head).append(identifierString).append('_').append(parametersString).append(closure.tail);
 | 
			
		||||
				continue;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			builder.append(replacement);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return builder.toString();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,55 @@
 | 
			
		||||
package me.clip.placeholderapi.replacer;
 | 
			
		||||
 | 
			
		||||
import me.clip.placeholderapi.PlaceholderHook;
 | 
			
		||||
import org.bukkit.ChatColor;
 | 
			
		||||
import org.bukkit.OfflinePlayer;
 | 
			
		||||
import org.jetbrains.annotations.NotNull;
 | 
			
		||||
import org.jetbrains.annotations.Nullable;
 | 
			
		||||
 | 
			
		||||
import java.util.function.Function;
 | 
			
		||||
import java.util.regex.Matcher;
 | 
			
		||||
import java.util.regex.Pattern;
 | 
			
		||||
 | 
			
		||||
public final class RegexReplacer implements Replacer
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
	@NotNull
 | 
			
		||||
	private final Pattern pattern;
 | 
			
		||||
 | 
			
		||||
	public RegexReplacer(@NotNull final Closure closure)
 | 
			
		||||
	{
 | 
			
		||||
		this.pattern = Pattern.compile(String.format("\\%s((?<identifier>[a-zA-Z0-9]+)_)(?<parameters>[^%s%s]+)\\%s", closure.head, closure.head, closure.tail, closure.tail));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public @NotNull String apply(@NotNull final String text, @Nullable final OfflinePlayer player, @NotNull final Function<String, @Nullable PlaceholderHook> lookup)
 | 
			
		||||
	{
 | 
			
		||||
		final Matcher matcher = pattern.matcher(text);
 | 
			
		||||
		if (!matcher.find())
 | 
			
		||||
		{
 | 
			
		||||
			return text;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		final StringBuffer builder = new StringBuffer();
 | 
			
		||||
 | 
			
		||||
		do
 | 
			
		||||
		{
 | 
			
		||||
			final String identifier = matcher.group("identifier");
 | 
			
		||||
			final String parameters = matcher.group("parameters");
 | 
			
		||||
 | 
			
		||||
			final PlaceholderHook hook = lookup.apply(identifier);
 | 
			
		||||
			if (hook == null)
 | 
			
		||||
			{
 | 
			
		||||
				continue;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			final String requested = hook.onRequest(player, parameters);
 | 
			
		||||
			matcher.appendReplacement(builder, requested != null ? requested : matcher.group(0));
 | 
			
		||||
		}
 | 
			
		||||
		while (matcher.find());
 | 
			
		||||
 | 
			
		||||
		return ChatColor.translateAlternateColorCodes('&', matcher.appendTail(builder).toString());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										33
									
								
								src/main/java/me/clip/placeholderapi/replacer/Replacer.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/main/java/me/clip/placeholderapi/replacer/Replacer.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
			
		||||
package me.clip.placeholderapi.replacer;
 | 
			
		||||
 | 
			
		||||
import me.clip.placeholderapi.PlaceholderHook;
 | 
			
		||||
import org.bukkit.OfflinePlayer;
 | 
			
		||||
import org.jetbrains.annotations.NotNull;
 | 
			
		||||
import org.jetbrains.annotations.Nullable;
 | 
			
		||||
 | 
			
		||||
import java.util.function.Function;
 | 
			
		||||
 | 
			
		||||
public interface Replacer
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
	@NotNull
 | 
			
		||||
	String apply(@NotNull final String text, @Nullable final OfflinePlayer player, @NotNull final Function<String, @Nullable PlaceholderHook> lookup);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	enum Closure
 | 
			
		||||
	{
 | 
			
		||||
		BRACES('{', '}'),
 | 
			
		||||
		BRACKETS('[', ']'),
 | 
			
		||||
		PERCENT('%', '%');
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		public final char head, tail;
 | 
			
		||||
 | 
			
		||||
		Closure(final char head, final char tail)
 | 
			
		||||
		{
 | 
			
		||||
			this.head = head;
 | 
			
		||||
			this.tail = tail;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user