diff --git a/src/main/java/me/clip/placeholderapi/PlaceholderAPI.java b/src/main/java/me/clip/placeholderapi/PlaceholderAPI.java index 8ce1674..a990636 100644 --- a/src/main/java/me/clip/placeholderapi/PlaceholderAPI.java +++ b/src/main/java/me/clip/placeholderapi/PlaceholderAPI.java @@ -27,539 +27,644 @@ import me.clip.placeholderapi.events.ExpansionUnregisterEvent; import me.clip.placeholderapi.expansion.Cacheable; import me.clip.placeholderapi.expansion.PlaceholderExpansion; import me.clip.placeholderapi.expansion.Relational; +import me.clip.placeholderapi.replacer.CharsReplacer; +import me.clip.placeholderapi.replacer.Replacer; +import me.clip.placeholderapi.replacer.Replacer.Closure; import me.clip.placeholderapi.util.Msg; -import org.apache.commons.lang.Validate; import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; -import java.util.*; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; -import static me.clip.placeholderapi.util.Msg.color; - -public class PlaceholderAPI { - - private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("[%]([^%]+)[%]"); - private static final Pattern BRACKET_PLACEHOLDER_PATTERN = Pattern.compile("[{]([^{}]+)[}]"); - private static final Pattern RELATIONAL_PLACEHOLDER_PATTERN = Pattern.compile("[%](rel_)([^%]+)[%]"); - private static final Map placeholders = new HashMap<>(); - - private PlaceholderAPI() { - } - - /** - * Check if a specific placeholder identifier is currently registered - * - * @param identifier The identifier to check - * @return true if identifier is already registered - */ - public static boolean isRegistered(String identifier) { - return getRegisteredIdentifiers().stream() - .filter(id -> id.equalsIgnoreCase(identifier)) - .findFirst().orElse(null) != null; - } - - /** - * Register a new placeholder hook - * - * @param identifier Identifier of the placeholder -> "%(identifier)_(args...)% - * @param placeholderHook Implementing class that contains the onPlaceholderRequest method which - * is called when a value is needed for the specific placeholder - * @return true if the hook was successfully registered, false if there is already a hook - * registered for the specified identifier - */ - public static boolean registerPlaceholderHook(String identifier, PlaceholderHook placeholderHook) { - Validate.notNull(identifier, "Identifier can not be null"); - Validate.notNull(placeholderHook, "Placeholderhook can not be null"); - - if (isRegistered(identifier)) { - return false; - } - - placeholders.put(identifier.toLowerCase(), placeholderHook); - - return true; - } - - /** - * Unregister a placeholder hook by identifier - * - * @param identifier The identifier for the placeholder hook to unregister - * @return true if the placeholder hook was successfully unregistered, false if there was no - * placeholder hook registered for the identifier specified - */ - public static boolean unregisterPlaceholderHook(String identifier) { - Validate.notNull(identifier, "Identifier can not be null"); - return placeholders.remove(identifier.toLowerCase()) != null; - } - - /** - * Get all registered placeholder identifiers - * - * @return All registered placeholder identifiers - */ - public static Set getRegisteredIdentifiers() { - return ImmutableSet.copyOf(placeholders.keySet()); - } - - /** - * Get map of registered placeholders - * - * @return Copy of the internal placeholder map - */ - public static Map getPlaceholders() { - return ImmutableMap.copyOf(placeholders); - } - - public static Set getExpansions() { - Set set = getPlaceholders().values().stream() - .filter(PlaceholderExpansion.class::isInstance).map(PlaceholderExpansion.class::cast) - .collect(Collectors.toCollection(HashSet::new)); - - return ImmutableSet.copyOf(set); - } - - /** - * Check if a String contains any PlaceholderAPI placeholders ({@literal %_%}). - * - * @param text String to check - * @return true if String contains any registered placeholder identifiers, false otherwise - */ - public static boolean containsPlaceholders(String text) { - return text != null && PLACEHOLDER_PATTERN.matcher(text).find(); - } - - /** - * Check if a String contains any PlaceholderAPI bracket placeholders ({@literal {_}}). - * - * @param text String to check - * @return true if String contains any registered placeholder identifiers, false otherwise - */ - public static boolean containsBracketPlaceholders(String text) { - return text != null && BRACKET_PLACEHOLDER_PATTERN.matcher(text).find(); - } - - /** - * Translates all placeholders into their corresponding values. - *
The pattern of a valid placeholder is {@literal {_}}. - * - * @param player Player to parse the placeholders against - * @param text List of Strings to set the placeholder values in - * @return String containing all translated placeholders - */ - public static List setBracketPlaceholders(OfflinePlayer player, List text) { - return setPlaceholders(player, text, BRACKET_PLACEHOLDER_PATTERN, true); - } - - /** - * Translates all placeholders into their corresponding values. - *
The pattern of a valid placeholder is {@literal {_}}. - * - * @param player Player to parse the placeholders against - * @param text List of Strings to set the placeholder values in - * @param colorize If color codes (&[0-1a-fk-o]) should be translated - * @return String containing all translated placeholders - */ - public static List setBracketPlaceholders(OfflinePlayer player, List text, boolean colorize) { - return setPlaceholders(player, text, BRACKET_PLACEHOLDER_PATTERN, colorize); - } - - /** - * Translates all placeholders into their corresponding values. - *
The pattern of a valid placeholder is {@literal %_%}. - * - * @param player Player to parse the placeholders against - * @param text List of Strings to set the placeholder values in - * @return String containing all translated placeholders - */ - public static List setPlaceholders(OfflinePlayer player, List text) { - return setPlaceholders(player, text, PLACEHOLDER_PATTERN, true); - } - - /** - * Translates all placeholders into their corresponding values. - *
The pattern of a valid placeholder is {@literal %_%}. - * - * @param player Player to parse the placeholders against - * @param text List of Strings to set the placeholder values in - * @param colorize If color codes (&[0-1a-fk-o]) should be translated - * @return String containing all translated placeholders - */ - public static List setPlaceholders(OfflinePlayer player, List text, boolean colorize) { - return setPlaceholders(player, text, PLACEHOLDER_PATTERN, colorize); - } - - /** - * Translates all placeholders into their corresponding values. - *
You set the pattern yourself through this method. - * - * @param player Player to parse the placeholders against - * @param text List of Strings to set the placeholder values in - * @param pattern The pattern to match placeholders to. Capture group 1 must contain an underscore separating the - * identifier from the params - * @return String containing all translated placeholders - */ - public static List setPlaceholders(OfflinePlayer player, List text, Pattern pattern) { - return setPlaceholders(player, text, pattern, true); - } - - /** - * Translates all placeholders into their corresponding values. - *
You set the pattern yourself through this method. - * - * @param player Player to parse the placeholders against - * @param text List of Strings to set the placeholder values in - * @param pattern The pattern to match placeholders to. Capture group 1 must contain an underscore separating the - * identifier from the params - * @param colorize If color codes (&[0-1a-fk-o]) should be translated - * @return String containing all translated placeholders - */ - public static List setPlaceholders(OfflinePlayer player, List text, Pattern pattern, boolean colorize) { - if (text == null) { - return null; - } - - return text.stream() - .map(line -> setPlaceholders(player, line, pattern, colorize)) - .collect(Collectors.toList()); - } - - /** - * Translates all placeholders into their corresponding values. - *
The pattern of a valid placeholder is {@literal {_}}. - * - * @param player Player to parse the placeholders against - * @param text Text to set the placeholder values in - * @return String containing all translated placeholders - */ - public static String setBracketPlaceholders(OfflinePlayer player, String text) { - return setPlaceholders(player, text, BRACKET_PLACEHOLDER_PATTERN, true); - } - - /** - * Translates all placeholders into their corresponding values. - *
The pattern of a valid placeholder is {@literal {_}} - * - * @param player Player to parse the placeholders against - * @param text Text to set the placeholder values in - * @param colorize If color codes (&[0-1a-fk-o]) should be translated - * @return String containing all translated placeholders - */ - public static String setBracketPlaceholders(OfflinePlayer player, String text, boolean colorize) { - return setPlaceholders(player, text, BRACKET_PLACEHOLDER_PATTERN, colorize); - } - - /** - * Translates all placeholders into their corresponding values. - *
The pattern of a valid placeholder is {@literal %_%}. - * - * @param player Player to parse the placeholders against - * @param text Text to set the placeholder values in - * @return String containing all translated placeholders - */ - public static String setPlaceholders(OfflinePlayer player, String text) { - return setPlaceholders(player, text, PLACEHOLDER_PATTERN); - } - - /** - * Translates all placeholders into their corresponding values. - *
The pattern of a valid placeholder is {@literal %_%}. - * - * @param player Player to parse the placeholder against - * @param text Text to parse the placeholders in - * @param colorize If color codes (&[0-1a-fk-o]) should be translated - * @return The text containing the parsed placeholders - */ - public static String setPlaceholders(OfflinePlayer player, String text, boolean colorize) { - return setPlaceholders(player, text, PLACEHOLDER_PATTERN, colorize); - } - - /** - * Translates all placeholders into their corresponding values. - *
You set the pattern yourself through this method. - * - * @param player Player to parse the placeholders against - * @param text Text to set the placeholder values in - * @param pattern The pattern to match placeholders to. Capture group 1 must contain an underscore separating the - * identifier from the params - * @return The text containing the parsed placeholders - */ - public static String setPlaceholders(OfflinePlayer player, String text, Pattern pattern) { - return setPlaceholders(player, text, pattern, true); - } - - /** - * Translates all placeholders into their corresponding values. - *
You set the pattern yourself through this method. - * - * @param player Player to parse the placeholders against - * @param text Text to set the placeholder values in - * @param pattern The pattern to match placeholders to. Capture group 1 must contain an underscore separating the - * identifier from the params - * @param colorize If color codes (&[0-1a-fk-o]) should be translated - * @return The text containing the parsed placeholders - */ - public static String setPlaceholders(OfflinePlayer player, String text, Pattern pattern, boolean colorize) { - if (text == null) { - return null; - } - - if (placeholders.isEmpty()) { - return colorize ? color(text) : text; - } - - final Matcher matcher = pattern.matcher(text); - final Map hooks = getPlaceholders(); - - while (matcher.find()) { - final String format = matcher.group(1); - final int index = format.indexOf("_"); - - if (index <= 0 || index >= format.length()) { - continue; - } - - final String identifier = format.substring(0, index).toLowerCase(); - final String params = format.substring(index + 1); - final PlaceholderHook hook = hooks.get(identifier); - - if (hook == null) { - continue; - } - - final String value = hook.onRequest(player, params); - - if (value != null) { - text = text.replaceAll(Pattern.quote(matcher.group()), Matcher.quoteReplacement(value)); - } - } - - return colorize ? color(text) : text; - } - - /** - * Translate placeholders in the provided List based on the relation of the two provided players. - *
The pattern of a valid placeholder is {@literal %rel__%}. - * - * @param one Player to compare - * @param two Player to compare - * @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 setRelationalPlaceholders(one, two, text, true); - } - - /** - * Translate placeholders in the provided list based on the relation of the two provided players. - *
The pattern of a valid placeholder is {@literal %rel__%}. - * - * @param one First player to compare - * @param two Second player to compare - * @param text Text to parse the placeholders in - * @param colorize If color codes (&[0-1a-fk-o]) should be translated - * @return The text containing the parsed relational placeholders - */ - public static List setRelationalPlaceholders(Player one, Player two, List text, boolean colorize) { - if (text == null) { - return null; - } - - return text.stream() - .map(line -> setRelationalPlaceholders(one, two, line, colorize)) - .collect(Collectors.toList()); - } - - /** - * set relational placeholders in the text specified placeholders are matched with the pattern - * %% when set with this method - * - * @param one First player to compare - * @param two Second player to compare - * @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) { - return setRelationalPlaceholders(one, two, text, true); - } - - /** - * set relational placeholders in the text specified placeholders are matched with the pattern - * %% when set with this method - * - * @param one Player to compare - * @param two Player to compare - * @param text Text to parse the placeholders in - * @param colorize If color codes (&[0-1a-fk-o]) should be translated - * @return The text containing the parsed relational placeholders - */ - @SuppressWarnings("DuplicatedCode") - public static String setRelationalPlaceholders(Player one, Player two, String text, boolean colorize) { - if (text == null) { - return null; - } - - if (placeholders.isEmpty()) { - return colorize ? Msg.color(text) : text; - } - - final Matcher matcher = RELATIONAL_PLACEHOLDER_PATTERN.matcher(text); - final Map hooks = getPlaceholders(); - - while (matcher.find()) { - final String format = matcher.group(2); - final int index = format.indexOf("_"); - - if (index <= 0 || index >= format.length()) { - continue; - } - - String identifier = format.substring(0, index).toLowerCase(); - String params = format.substring(index + 1); - final PlaceholderHook hook = hooks.get(identifier); - - if (!(hook instanceof Relational)) { - continue; - } - - final String value = ((Relational) hook).onPlaceholderRequest(one, two, params); - - if (value != null) { - text = text.replaceAll(Pattern.quote(matcher.group()), Matcher.quoteReplacement(value)); - } - } - - return colorize ? Msg.color(text) : text; - } - - /** - * Unregister ALL placeholder hooks that are currently registered - */ - protected static void unregisterAll() { - unregisterAllProvidedExpansions(); - placeholders.clear(); - } - - /** - * Unregister all expansions provided by PlaceholderAPI - */ - public static void unregisterAllProvidedExpansions() { - final Set set = new HashSet<>(placeholders.values()); - - for (PlaceholderHook hook : set) { - if (hook instanceof PlaceholderExpansion) { - final PlaceholderExpansion expansion = (PlaceholderExpansion) hook; - - if (!expansion.persist()) { - unregisterExpansion(expansion); - } - } - - if (hook instanceof Cacheable) { - ((Cacheable) hook).clear(); - } - } - } - - public static boolean registerExpansion(PlaceholderExpansion ex) { - ExpansionRegisterEvent ev = new ExpansionRegisterEvent(ex); - Bukkit.getPluginManager().callEvent(ev); - if (ev.isCancelled()) { - return false; - } - - return registerPlaceholderHook(ex.getIdentifier(), ex); - } - - public static boolean unregisterExpansion(PlaceholderExpansion ex) { - if (unregisterPlaceholderHook(ex.getIdentifier())) { - Bukkit.getPluginManager().callEvent(new ExpansionUnregisterEvent(ex)); - return true; - } - - return false; - } - - /** - * Gets the placeholder pattern for the default placeholders. - * - * @return The pattern for {@literal %_%} - */ - public static Pattern getPlaceholderPattern() { - return PLACEHOLDER_PATTERN; - } - - /** - * Gets the placeholder pattern for the bracket placeholders. - * - * @return The pattern for {@literal {_}} - */ - public static Pattern getBracketPlaceholderPattern() { - return BRACKET_PLACEHOLDER_PATTERN; - } - - /** - * Gets the placeholder pattern for the relational placeholders. - * - * @return The pattern for {@literal %rel__%} - */ - public static Pattern getRelationalPlaceholderPattern() { - return RELATIONAL_PLACEHOLDER_PATTERN; - } - - @Deprecated - public static Set getRegisteredPlaceholderPlugins() { - return getRegisteredIdentifiers(); - } - - @Deprecated - public static Set getExternalPlaceholderPlugins() { - return null; - } - - @Deprecated - public static boolean registerPlaceholderHook(Plugin plugin, PlaceholderHook placeholderHook) { - return plugin != null && registerPlaceholderHook(plugin.getName(), placeholderHook); - } - - @Deprecated - public static boolean unregisterPlaceholderHook(Plugin plugin) { - return plugin != null && unregisterPlaceholderHook(plugin.getName()); - } - - public static String setPlaceholders(Player player, String text) { - return setPlaceholders(player, text, PLACEHOLDER_PATTERN, true); - } - - public static String setPlaceholders(Player player, String text, boolean colorize) { - return setPlaceholders(player, text, PLACEHOLDER_PATTERN, colorize); - } - - public static List setPlaceholders(Player player, List text) { - return setPlaceholders(player, text, PLACEHOLDER_PATTERN, true); - } - - public static List setPlaceholders(Player player, List text, boolean colorize) { - return setPlaceholders(player, text, PLACEHOLDER_PATTERN, colorize); - } - - public static String setBracketPlaceholders(Player player, String text) { - return setPlaceholders(player, text, BRACKET_PLACEHOLDER_PATTERN, true); - } - - public static String setBracketPlaceholders(Player player, String text, boolean colorize) { - return setPlaceholders(player, text, BRACKET_PLACEHOLDER_PATTERN, colorize); - } - - public static List setBracketPlaceholders(Player player, List text) { - return setPlaceholders(player, text, BRACKET_PLACEHOLDER_PATTERN, true); - } - - public static List setBracketPlaceholders(Player player, List text, boolean colorize) { - return setPlaceholders(player, text, BRACKET_PLACEHOLDER_PATTERN, colorize); - } +public final class PlaceholderAPI +{ + + private static final Replacer REPLACER_PERCENT = new CharsReplacer(Closure.PERCENT); + private static final Replacer REPLACER_BRACKET = new CharsReplacer(Closure.BRACKET); + + private static final Map PLACEHOLDERS = new HashMap<>(); + + + @Deprecated + private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("[%]([^%]+)[%]"); + @Deprecated + private static final Pattern BRACKET_PLACEHOLDER_PATTERN = Pattern.compile("[{]([^{}]+)[}]"); + @Deprecated + private static final Pattern RELATIONAL_PLACEHOLDER_PATTERN = Pattern.compile("[%](rel_)([^%]+)[%]"); + + + private PlaceholderAPI() + { + } + + + // === Current API === + + /** + * Translates all placeholders into their corresponding values. + *
The pattern of a valid placeholder is {@literal %_%}. + * + * @param player Player to parse the placeholders against + * @param text Text to set the placeholder values in + * @return String containing all translated placeholders + */ + @NotNull + public static String setPlaceholders(@Nullable final OfflinePlayer player, @NotNull final String text) + { + return REPLACER_PERCENT.apply(text, player, PLACEHOLDERS::get); + } + + /** + * Translates all placeholders into their corresponding values. + *
The pattern of a valid placeholder is {@literal %_%}. + * + * @param player Player to parse the placeholders against + * @param text List of Strings to set the placeholder values in + * @return String containing all translated placeholders + */ + @NotNull + public static List setPlaceholders(@Nullable final OfflinePlayer player, @NotNull final List<@NotNull String> text) + { + return text.stream().map(line -> setPlaceholders(player, line)).collect(Collectors.toList()); + } + + /** + * Translates all placeholders into their corresponding values. + *
The pattern of a valid placeholder is {@literal {_}}. + * + * @param player Player to parse the placeholders against + * @param text Text to set the placeholder values in + * + * @return String containing all translated placeholders + */ + @NotNull + public static String setBracketPlaceholders(@Nullable final OfflinePlayer player, @NotNull final String text) + { + return REPLACER_BRACKET.apply(text, player, PLACEHOLDERS::get); + } + + /** + * Translates all placeholders into their corresponding values. + *
The pattern of a valid placeholder is {@literal {_}}. + * + * @param player Player to parse the placeholders against + * @param text List of Strings to set the placeholder values in + * + * @return String containing all translated placeholders + */ + @NotNull + public static List setBracketPlaceholders(@Nullable final OfflinePlayer player, @NotNull final List<@NotNull String> text) + { + return text.stream().map(line -> setBracketPlaceholders(player, line)).collect(Collectors.toList()); + } + + + /** + * Check if a specific placeholder identifier is currently registered + * + * @param identifier The identifier to check + * @return true if identifier is already registered + */ + public static boolean isRegistered(@NotNull final String identifier) + { + return PLACEHOLDERS.containsKey(identifier.toLowerCase()); + } + + /** + * Register a new placeholder hook + * + * @param identifier Identifier of the placeholder -> "%(identifier)_(args...)% + * @param placeholderHook Implementing class that contains the onPlaceholderRequest method which + * is called when a value is needed for the specific placeholder + * @return true if the hook was successfully registered, false if there is already a hook + * registered for the specified identifier + */ + public static boolean registerPlaceholderHook(@NotNull final String identifier, @NotNull final PlaceholderHook placeholderHook) + { + return PLACEHOLDERS.putIfAbsent(identifier.toLowerCase(), placeholderHook) == null; + } + + public static boolean registerPlaceholderHook(@NotNull final Plugin plugin, @NotNull final PlaceholderHook placeholderHook) + { + return registerPlaceholderHook(plugin.getName(), placeholderHook); + } + + /** + * Unregister a placeholder hook by identifier + * + * @param identifier The identifier for the placeholder hook to unregister + * @return true if the placeholder hook was successfully unregistered, false if there was no + * placeholder hook registered for the identifier specified + */ + public static boolean unregisterPlaceholderHook(@NotNull final String identifier) + { + return PLACEHOLDERS.remove(identifier.toLowerCase()) != null; + } + + public static boolean unregisterPlaceholderHook(@NotNull final Plugin plugin) + { + return unregisterPlaceholderHook(plugin.getName()); + } + + + /** + * Get all registered placeholder identifiers + * + * @return All registered placeholder identifiers + */ + @NotNull + public static Set getRegisteredIdentifiers() + { + return ImmutableSet.copyOf(PLACEHOLDERS.keySet()); + } + + /** + * Get map of registered placeholders + * + * @return Copy of the internal placeholder map + */ + @NotNull + public static Map getPlaceholders() + { + return ImmutableMap.copyOf(PLACEHOLDERS); + } + + + /** + * Unregister ALL placeholder hooks that are currently registered + */ + protected static void unregisterAll() + { + unregisterAllProvidedExpansions(); + PLACEHOLDERS.clear(); + } + + /** + * Unregister all expansions provided by PlaceholderAPI + */ + public static void unregisterAllProvidedExpansions() + { + final Set set = new HashSet<>(PLACEHOLDERS.values()); + + for (PlaceholderHook hook : set) + { + if (hook instanceof PlaceholderExpansion) + { + final PlaceholderExpansion expansion = (PlaceholderExpansion) hook; + + if (!expansion.persist()) + { + unregisterExpansion(expansion); + } + } + + if (hook instanceof Cacheable) + { + ((Cacheable) hook).clear(); + } + } + } + + public static boolean registerExpansion(@NotNull final PlaceholderExpansion expansion) + { + final ExpansionRegisterEvent event = new ExpansionRegisterEvent(expansion); + Bukkit.getPluginManager().callEvent(event); + + if (event.isCancelled()) + { + return false; + } + + return registerPlaceholderHook(expansion.getIdentifier(), expansion); + } + + public static boolean unregisterExpansion(@NotNull final PlaceholderExpansion expansion) + { + if (unregisterPlaceholderHook(expansion.getIdentifier())) + { + Bukkit.getPluginManager().callEvent(new ExpansionUnregisterEvent(expansion)); + return true; + } + + return false; + } + + + // === Deprecated API === + + /** + * Translates all placeholders into their corresponding values. + *
You set the pattern yourself through this method. + * + * @param player Player to parse the placeholders against + * @param text Text to set the placeholder values in + * @param pattern The pattern to match placeholders to. Capture group 1 must contain an underscore separating the + * identifier from the params + * @param colorize If color codes (&[0-1a-fk-o]) should be translated + * @return The text containing the parsed placeholders + * @deprecated Please use {@link #setPlaceholders(OfflinePlayer, String)} instead + */ + @NotNull + @Deprecated + public static String setPlaceholders(@Nullable final OfflinePlayer player, @NotNull final String text, @NotNull final Pattern pattern, final boolean colorize) + { + return setPlaceholders(player, text); + } + + /** + * Translates all placeholders into their corresponding values. + *
You set the pattern yourself through this method. + * + * @param player Player to parse the placeholders against + * @param text List of Strings to set the placeholder values in + * @param pattern The pattern to match placeholders to. Capture group 1 must contain an underscore separating the + * identifier from the params + * @param colorize If color codes (&[0-1a-fk-o]) should be translated + * @return String containing all translated placeholders + * @deprecated Please use {@link #setPlaceholders(OfflinePlayer, List)} instead + */ + @NotNull + @Deprecated + public static List setPlaceholders(@Nullable final OfflinePlayer player, @NotNull final List text, @NotNull final Pattern pattern, final boolean colorize) + { + return setPlaceholders(player, text); + } + + @Deprecated + public static Set getExpansions() + { + Set set = getPlaceholders().values().stream() + .filter(PlaceholderExpansion.class::isInstance).map(PlaceholderExpansion.class::cast) + .collect(Collectors.toCollection(HashSet::new)); + + return ImmutableSet.copyOf(set); + } + + /** + * Check if a String contains any PlaceholderAPI placeholders ({@literal %_%}). + * + * @param text String to check + * @return true if String contains any registered placeholder identifiers, false otherwise + * @deprecated Will be removed in a future release. + */ + @Deprecated + public static boolean containsPlaceholders(String text) + { + return text != null && PLACEHOLDER_PATTERN.matcher(text).find(); + } + + /** + * Check if a String contains any PlaceholderAPI bracket placeholders ({@literal {_}}). + * + * @param text String to check + * @return true if String contains any registered placeholder identifiers, false otherwise + * @deprecated Will be removed in a future release. + */ + @Deprecated + public static boolean containsBracketPlaceholders(String text) + { + return text != null && BRACKET_PLACEHOLDER_PATTERN.matcher(text).find(); + } + + /** + * Translates all placeholders into their corresponding values. + *
The pattern of a valid placeholder is {@literal {_}}. + * + * @param player Player to parse the placeholders against + * @param text List of Strings to set the placeholder values in + * @param colorize If color codes (&[0-1a-fk-o]) should be translated + * @return String containing all translated placeholders + * @deprecated Use {@link #setPlaceholders(OfflinePlayer, List)} instead. + */ + @Deprecated + public static List setBracketPlaceholders(OfflinePlayer player, List text, boolean colorize) + { + return setPlaceholders(player, text, BRACKET_PLACEHOLDER_PATTERN, colorize); + } + + /** + * Translates all placeholders into their corresponding values. + *
The pattern of a valid placeholder is {@literal %_%}. + * + * @param player Player to parse the placeholders against + * @param text List of Strings to set the placeholder values in + * @param colorize If color codes (&[0-1a-fk-o]) should be translated + * @return String containing all translated placeholders + * @deprecated Use {@link #setPlaceholders(OfflinePlayer, List)} instead. + */ + @Deprecated + public static List setPlaceholders(OfflinePlayer player, List text, boolean colorize) + { + return setPlaceholders(player, text, PLACEHOLDER_PATTERN, colorize); + } + + /** + * Translates all placeholders into their corresponding values. + *
You set the pattern yourself through this method. + * + * @param player Player to parse the placeholders against + * @param text List of Strings to set the placeholder values in + * @param pattern The pattern to match placeholders to. Capture group 1 must contain an underscore separating the + * identifier from the params + * @return String containing all translated placeholders + * @deprecated Use {@link #setPlaceholders(OfflinePlayer, List)} instead. + */ + @Deprecated + public static List setPlaceholders(OfflinePlayer player, List text, Pattern pattern) + { + return setPlaceholders(player, text, pattern, true); + } + + + /** + * Translates all placeholders into their corresponding values. + *
The pattern of a valid placeholder is {@literal {_}} + * + * @param player Player to parse the placeholders against + * @param text Text to set the placeholder values in + * @param colorize If color codes (&[0-1a-fk-o]) should be translated + * @return String containing all translated placeholders + * @deprecated Use {@link #setPlaceholders(OfflinePlayer, String)} instead. + */ + @Deprecated + public static String setBracketPlaceholders(OfflinePlayer player, String text, boolean colorize) + { + return setPlaceholders(player, text, BRACKET_PLACEHOLDER_PATTERN, colorize); + } + + /** + * Translates all placeholders into their corresponding values. + *
The pattern of a valid placeholder is {@literal %_%}. + * + * @param player Player to parse the placeholder against + * @param text Text to parse the placeholders in + * @param colorize If color codes (&[0-1a-fk-o]) should be translated + * @return The text containing the parsed placeholders + * @deprecated Use {@link #setPlaceholders(OfflinePlayer, String)} instead. + */ + @Deprecated + public static String setPlaceholders(OfflinePlayer player, String text, boolean colorize) + { + return setPlaceholders(player, text, PLACEHOLDER_PATTERN, colorize); + } + + /** + * Translates all placeholders into their corresponding values. + *
You set the pattern yourself through this method. + * + * @param player Player to parse the placeholders against + * @param text Text to set the placeholder values in + * @param pattern The pattern to match placeholders to. Capture group 1 must contain an underscore separating the + * identifier from the params + * @return The text containing the parsed placeholders + * @deprecated Use {@link #setPlaceholders(OfflinePlayer, String)} instead. + */ + @Deprecated + public static String setPlaceholders(OfflinePlayer player, String text, Pattern pattern) + { + return setPlaceholders(player, text, pattern, true); + } + + /** + * Translate placeholders in the provided List based on the relation of the two provided players. + *
The pattern of a valid placeholder is {@literal %rel__%}. + * + * @param one Player to compare + * @param two Player to compare + * @param text text to parse the placeholder values to + * @return The text containing the parsed relational placeholders + * @deprecated Use {@link #setPlaceholders(OfflinePlayer, List)} instead. + */ + @Deprecated + public static List setRelationalPlaceholders(Player one, Player two, List text) + { + return setRelationalPlaceholders(one, two, text, true); + } + + /** + * Translate placeholders in the provided list based on the relation of the two provided players. + *
The pattern of a valid placeholder is {@literal %rel__%}. + * + * @param one First player to compare + * @param two Second player to compare + * @param text Text to parse the placeholders in + * @param colorize If color codes (&[0-1a-fk-o]) should be translated + * @return The text containing the parsed relational placeholders + * @deprecated Use {@link #setPlaceholders(OfflinePlayer, List)} instead. + */ + @Deprecated + public static List setRelationalPlaceholders(Player one, Player two, List text, boolean colorize) + { + if (text == null) + { + return null; + } + + return text.stream() + .map(line -> setRelationalPlaceholders(one, two, line, colorize)) + .collect(Collectors.toList()); + } + + /** + * set relational placeholders in the text specified placeholders are matched with the pattern + * %% when set with this method + * + * @param one First player to compare + * @param two Second player to compare + * @param text Text to parse the placeholders in + * @return The text containing the parsed relational placeholders + * @deprecated Use {@link #setPlaceholders(OfflinePlayer, String)} instead. + */ + @Deprecated + public static String setRelationalPlaceholders(Player one, Player two, String text) + { + return setRelationalPlaceholders(one, two, text, true); + } + + /** + * set relational placeholders in the text specified placeholders are matched with the pattern + * %% when set with this method + * + * @param one Player to compare + * @param two Player to compare + * @param text Text to parse the placeholders in + * @param colorize If color codes (&[0-1a-fk-o]) should be translated + * @return The text containing the parsed relational placeholders + * @deprecated Use {@link #setPlaceholders(OfflinePlayer, String)} instead. + */ + @Deprecated + @SuppressWarnings("DuplicatedCode") + public static String setRelationalPlaceholders(Player one, Player two, String text, boolean colorize) + { + if (text == null) + { + return null; + } + + if (PLACEHOLDERS.isEmpty()) + { + return colorize ? Msg.color(text) : text; + } + + final Matcher matcher = RELATIONAL_PLACEHOLDER_PATTERN.matcher(text); + final Map hooks = getPlaceholders(); + + while (matcher.find()) + { + final String format = matcher.group(2); + final int index = format.indexOf("_"); + + if (index <= 0 || index >= format.length()) + { + continue; + } + + String identifier = format.substring(0, index).toLowerCase(); + String params = format.substring(index + 1); + final PlaceholderHook hook = hooks.get(identifier); + + if (!(hook instanceof Relational)) + { + continue; + } + + final String value = ((Relational) hook).onPlaceholderRequest(one, two, params); + + if (value != null) + { + text = text.replaceAll(Pattern.quote(matcher.group()), Matcher.quoteReplacement(value)); + } + } + + return colorize ? Msg.color(text) : text; + } + + /** + * Gets the placeholder pattern for the default placeholders. + * + * @return The pattern for {@literal %_%} + * @deprecated Will be removed in a future release. + */ + @Deprecated + public static Pattern getPlaceholderPattern() + { + return PLACEHOLDER_PATTERN; + } + + /** + * Gets the placeholder pattern for the bracket placeholders. + * + * @return The pattern for {@literal {_}} + * @deprecated Will be removed in a future release. + */ + @Deprecated + public static Pattern getBracketPlaceholderPattern() + { + return BRACKET_PLACEHOLDER_PATTERN; + } + + /** + * Gets the placeholder pattern for the relational placeholders. + * + * @return The pattern for {@literal %rel__%} + * @deprecated Will be removed in a future release. + */ + @Deprecated + public static Pattern getRelationalPlaceholderPattern() + { + return RELATIONAL_PLACEHOLDER_PATTERN; + } + + /** + * @deprecated Will be removed in a future release. + */ + @Deprecated + public static Set getRegisteredPlaceholderPlugins() + { + return getRegisteredIdentifiers(); + } + + /** + * @deprecated Will be removed in a future release. + */ + @Deprecated + public static Set getExternalPlaceholderPlugins() + { + return null; + } + + /** + * @deprecated Will be removed in a future release. + */ + @Deprecated + public static String setPlaceholders(Player player, String text, boolean colorize) + { + return setPlaceholders(player, text, PLACEHOLDER_PATTERN, colorize); + } + + /** + * @deprecated Will be removed in a future release. + */ + @Deprecated + public static List setPlaceholders(Player player, List text) + { + return setPlaceholders(player, text, PLACEHOLDER_PATTERN, true); + } + + /** + * @deprecated Will be removed in a future release. + */ + @Deprecated + public static List setPlaceholders(Player player, List text, boolean colorize) + { + return setPlaceholders(player, text, PLACEHOLDER_PATTERN, colorize); + } + + /** + * @deprecated Will be removed in a future release. + */ + @Deprecated + public static String setBracketPlaceholders(Player player, String text) + { + return setPlaceholders(player, text, BRACKET_PLACEHOLDER_PATTERN, true); + } + + /** + * @deprecated Will be removed in a future release. + */ + @Deprecated + public static String setBracketPlaceholders(Player player, String text, boolean colorize) + { + return setPlaceholders(player, text, BRACKET_PLACEHOLDER_PATTERN, colorize); + } + + /** + * @deprecated Will be removed in a future release. + */ + @Deprecated + public static List setBracketPlaceholders(Player player, List text) + { + return setPlaceholders(player, text, BRACKET_PLACEHOLDER_PATTERN, true); + } + + /** + * @deprecated Will be removed in a future release. + */ + @Deprecated + public static List setBracketPlaceholders(Player player, List text, boolean colorize) + { + return setPlaceholders(player, text, BRACKET_PLACEHOLDER_PATTERN, colorize); + } + } diff --git a/src/main/java/me/clip/placeholderapi/PlaceholderHook.java b/src/main/java/me/clip/placeholderapi/PlaceholderHook.java index e41e68d..629143c 100644 --- a/src/main/java/me/clip/placeholderapi/PlaceholderHook.java +++ b/src/main/java/me/clip/placeholderapi/PlaceholderHook.java @@ -22,33 +22,43 @@ package me.clip.placeholderapi; import org.bukkit.OfflinePlayer; import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; -public abstract class PlaceholderHook { +public abstract class PlaceholderHook +{ - /** - * called when a placeholder value is requested from this hook - * - * @param player {@link OfflinePlayer} to request the placeholder value for, null if not needed for a - * player - * @param params String passed to the hook to determine what value to return - * @return value for the requested player and params - */ - public String onRequest(OfflinePlayer player, String params) { - if (player != null && player.isOnline()) { - return onPlaceholderRequest((Player) player, params); - } + /** + * called when a placeholder value is requested from this hook + * + * @param player {@link OfflinePlayer} to request the placeholder value for, null if not needed for a + * player + * @param params String passed to the hook to determine what value to return + * @return value for the requested player and params + */ + @Nullable + public String onRequest(@Nullable final OfflinePlayer player, @NotNull final String params) + { + if (player != null && player.isOnline()) + { + return onPlaceholderRequest((Player) player, params); + } - return onPlaceholderRequest(null, params); - } + return onPlaceholderRequest(null, params); + } + + /** + * called when a placeholder is requested from this hook + * + * @param player {@link Player} to request the placeholder value for, null if not needed for a player + * @param params String passed to the hook to determine what value to return + * @return value for the requested player and params + */ + @Nullable + @Deprecated + public String onPlaceholderRequest(@Nullable final Player player, @NotNull final String params) + { + return null; + } - /** - * called when a placeholder is requested from this hook - * - * @param player {@link Player} to request the placeholder value for, null if not needed for a player - * @param params String passed to the hook to determine what value to return - * @return value for the requested player and params - */ - public String onPlaceholderRequest(Player player, String params) { - return null; - } } diff --git a/src/main/java/me/clip/placeholderapi/expansion/ExpansionManager.java b/src/main/java/me/clip/placeholderapi/expansion/ExpansionManager.java index faa1f68..e1eb341 100644 --- a/src/main/java/me/clip/placeholderapi/expansion/ExpansionManager.java +++ b/src/main/java/me/clip/placeholderapi/expansion/ExpansionManager.java @@ -28,186 +28,207 @@ import me.clip.placeholderapi.util.FileUtil; import org.bukkit.Bukkit; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.event.Listener; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.io.File; -import java.lang.reflect.Constructor; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.logging.Level; -public final class ExpansionManager { - private final PlaceholderAPIPlugin plugin; +public final class ExpansionManager +{ - public ExpansionManager(PlaceholderAPIPlugin instance) { - plugin = instance; + @NotNull + private final File folder; + @NotNull + private final PlaceholderAPIPlugin plugin; - File f = new File(PlaceholderAPIPlugin.getInstance().getDataFolder(), "expansions"); - if (!f.exists()) { - f.mkdirs(); - } - } + public ExpansionManager(@NotNull final PlaceholderAPIPlugin plugin) + { + this.plugin = plugin; + this.folder = new File(plugin.getDataFolder(), "expansions"); - public PlaceholderExpansion getRegisteredExpansion(String name) { - for (Entry hook : PlaceholderAPI.getPlaceholders().entrySet()) { - if (hook.getValue() instanceof PlaceholderExpansion) { - if (name.equalsIgnoreCase(hook.getKey())) { - return (PlaceholderExpansion) hook.getValue(); - } - } - } + if (!this.folder.exists() && !folder.mkdirs()) + { + plugin.getLogger().log(Level.WARNING, "failed to create expansions folder!"); + } + } - return null; - } + public PlaceholderExpansion getRegisteredExpansion(String name) + { + for (Entry hook : PlaceholderAPI.getPlaceholders().entrySet()) + { + if (hook.getValue() instanceof PlaceholderExpansion) + { + if (name.equalsIgnoreCase(hook.getKey())) + { + return (PlaceholderExpansion) hook.getValue(); + } + } + } - public boolean registerExpansion(PlaceholderExpansion expansion) { - if (expansion == null || expansion.getIdentifier() == null) { - return false; - } + return null; + } - if (expansion instanceof Configurable) { - Map defaults = ((Configurable) expansion).getDefaults(); - String pre = "expansions." + expansion.getIdentifier() + "."; - FileConfiguration cfg = plugin.getConfig(); - boolean save = false; + public boolean registerExpansion(@NotNull final PlaceholderExpansion expansion) + { + if (expansion.getIdentifier() == null) + { + return false; + } - if (defaults != null) { - for (Entry entries : defaults.entrySet()) { - if (entries.getKey() == null || entries.getKey().isEmpty()) { - continue; - } + if (expansion instanceof Configurable) + { + Map defaults = ((Configurable) expansion).getDefaults(); + String pre = "expansions." + expansion.getIdentifier() + "."; + FileConfiguration cfg = plugin.getConfig(); + boolean save = false; - if (entries.getValue() == null) { - if (cfg.contains(pre + entries.getKey())) { - save = true; - cfg.set(pre + entries.getKey(), null); - } - } else { - if (!cfg.contains(pre + entries.getKey())) { - save = true; - cfg.set(pre + entries.getKey(), entries.getValue()); - } - } - } - } + if (defaults != null) + { + for (Entry entries : defaults.entrySet()) + { + if (entries.getKey() == null || entries.getKey().isEmpty()) + { + continue; + } - if (save) { - plugin.saveConfig(); - plugin.reloadConfig(); - } - } + if (entries.getValue() == null) + { + if (cfg.contains(pre + entries.getKey())) + { + save = true; + cfg.set(pre + entries.getKey(), null); + } + } + else + { + if (!cfg.contains(pre + entries.getKey())) + { + save = true; + cfg.set(pre + entries.getKey(), entries.getValue()); + } + } + } + } - if (expansion instanceof VersionSpecific) { - VersionSpecific nms = (VersionSpecific) expansion; - if (!nms.isCompatibleWith(PlaceholderAPIPlugin.getServerVersion())) { - plugin.getLogger() - .info( - "Your server version is not compatible with expansion: " + expansion.getIdentifier() - + " version: " + expansion.getVersion()); - return false; - } - } + if (save) + { + plugin.saveConfig(); + plugin.reloadConfig(); + } + } - if (!expansion.canRegister()) { - return false; - } + if (expansion instanceof VersionSpecific) + { + VersionSpecific nms = (VersionSpecific) expansion; + if (!nms.isCompatibleWith(PlaceholderAPIPlugin.getServerVersion())) + { + plugin.getLogger() + .info( + "Your server version is not compatible with expansion: " + expansion.getIdentifier() + + " version: " + expansion.getVersion()); + return false; + } + } - if (!expansion.register()) { - return false; - } + if (!expansion.canRegister() || !expansion.register()) + { + return false; + } - if (expansion instanceof Listener) { - Listener l = (Listener) expansion; - Bukkit.getPluginManager().registerEvents(l, plugin); - } + if (expansion instanceof Listener) + { + Bukkit.getPluginManager().registerEvents(((Listener) expansion), plugin); + } - plugin.getLogger().info("Successfully registered expansion: " + expansion.getIdentifier()); + plugin.getLogger().info("Successfully registered expansion: " + expansion.getIdentifier()); - if (expansion instanceof Taskable) { - ((Taskable) expansion).start(); - } + if (expansion instanceof Taskable) + { + ((Taskable) expansion).start(); + } - if (plugin.getExpansionCloud() != null) { - CloudExpansion ce = plugin.getExpansionCloud().getCloudExpansion(expansion.getIdentifier()); + if (plugin.getExpansionCloud() != null) + { + final CloudExpansion cloudExpansion = plugin.getExpansionCloud().getCloudExpansion(expansion.getIdentifier()); - if (ce != null) { - ce.setHasExpansion(true); - if (!ce.getLatestVersion().equals(expansion.getVersion())) { - ce.setShouldUpdate(true); - } - } - } + if (cloudExpansion != null) + { + cloudExpansion.setHasExpansion(true); + if (!cloudExpansion.getLatestVersion().equals(expansion.getVersion())) + { + cloudExpansion.setShouldUpdate(true); + } + } + } - return true; - } + return true; + } - public PlaceholderExpansion registerExpansion(String fileName) { - List> subs = FileUtil.getClasses("expansions", fileName, PlaceholderExpansion.class); - if (subs == null || subs.isEmpty()) { - return null; - } + @Nullable + public PlaceholderExpansion registerExpansion(@NotNull final String fileName) + { + final List> subs = FileUtil.getClasses(folder, PlaceholderExpansion.class, fileName); + if (subs.isEmpty()) + { + return null; + } - // only register the first instance found as an expansion jar should only have 1 class - // extending PlaceholderExpansion - PlaceholderExpansion ex = createInstance(subs.get(0)); - if (registerExpansion(ex)) { - return ex; - } + // only register the first instance found as an expansion jar should only have 1 class + // extending PlaceholderExpansion + final PlaceholderExpansion expansion = createInstance(subs.get(0)); + if (expansion != null && registerExpansion(expansion)) + { + return expansion; + } - return null; - } + return null; + } - public void registerAllExpansions() { - if (plugin == null) { - return; - } + public void registerAllExpansions() + { + final List<@NotNull Class> subs = FileUtil.getClasses(folder, PlaceholderExpansion.class); + if (subs.isEmpty()) + { + return; + } - List> subs = FileUtil.getClasses("expansions", null, PlaceholderExpansion.class); - if (subs == null || subs.isEmpty()) { - return; - } + for (final Class clazz : subs) + { + final PlaceholderExpansion expansion = createInstance(clazz); + if (expansion == null) + { + continue; + } - for (Class klass : subs) { - PlaceholderExpansion ex = createInstance(klass); - if (ex != null) { - try { - registerExpansion(ex); - } catch (Exception e) { - plugin.getLogger().info("Couldn't register " + ex.getIdentifier() + " expansion"); - e.printStackTrace(); - } - } - } - } + try + { + registerExpansion(expansion); + } + catch (final Exception ex) + { + plugin.getLogger().log(Level.WARNING, "Couldn't register " + expansion.getIdentifier() + " expansion", ex); + } + } + } - private PlaceholderExpansion createInstance(Class klass) { - if (klass == null) { - return null; - } + @Nullable + private PlaceholderExpansion createInstance(@NotNull final Class clazz) + { + try + { + return clazz.getDeclaredConstructor().newInstance(); + } + catch (final Throwable ex) + { + plugin.getLogger().log(Level.SEVERE, "Failed to load placeholder expansion from class: " + clazz.getName(), ex); + } - PlaceholderExpansion ex = null; - if (!PlaceholderExpansion.class.isAssignableFrom(klass)) { - return null; - } + return null; + } - try { - Constructor[] c = klass.getConstructors(); - if (c.length == 0) { - ex = (PlaceholderExpansion) klass.newInstance(); - } else { - for (Constructor con : c) { - if (con.getParameterTypes().length == 0) { - ex = (PlaceholderExpansion) klass.newInstance(); - break; - } - } - } - } catch (Throwable t) { - plugin.getLogger() - .severe("Failed to init placeholder expansion from class: " + klass.getName()); - plugin.getLogger().severe(t.getMessage()); - } - - return ex; - } } diff --git a/src/main/java/me/clip/placeholderapi/replacer/CharsReplacer.java b/src/main/java/me/clip/placeholderapi/replacer/CharsReplacer.java new file mode 100644 index 0000000..731ca9a --- /dev/null +++ b/src/main/java/me/clip/placeholderapi/replacer/CharsReplacer.java @@ -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 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(); + } + +} diff --git a/src/main/java/me/clip/placeholderapi/replacer/RegexReplacer.java b/src/main/java/me/clip/placeholderapi/replacer/RegexReplacer.java new file mode 100644 index 0000000..8218384 --- /dev/null +++ b/src/main/java/me/clip/placeholderapi/replacer/RegexReplacer.java @@ -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((?[a-zA-Z0-9]+)_)(?[^%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 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()); + } + +} diff --git a/src/main/java/me/clip/placeholderapi/replacer/Replacer.java b/src/main/java/me/clip/placeholderapi/replacer/Replacer.java new file mode 100644 index 0000000..005c0f7 --- /dev/null +++ b/src/main/java/me/clip/placeholderapi/replacer/Replacer.java @@ -0,0 +1,32 @@ +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 lookup); + + + enum Closure + { + BRACKET('{', '}'), + PERCENT('%', '%'); + + + public final char head, tail; + + Closure(final char head, final char tail) + { + this.head = head; + this.tail = tail; + } + } + +} diff --git a/src/main/java/me/clip/placeholderapi/util/FileUtil.java b/src/main/java/me/clip/placeholderapi/util/FileUtil.java index 84eb613..0acb44e 100644 --- a/src/main/java/me/clip/placeholderapi/util/FileUtil.java +++ b/src/main/java/me/clip/placeholderapi/util/FileUtil.java @@ -20,89 +20,85 @@ */ package me.clip.placeholderapi.util; -import me.clip.placeholderapi.PlaceholderAPIPlugin; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.FilenameFilter; +import java.io.IOException; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.jar.JarEntry; import java.util.jar.JarInputStream; -public class FileUtil { +public class FileUtil +{ - public static List> getClasses(String folder, Class type) { - return getClasses(folder, null, type); - } + @NotNull + public static List<@NotNull Class> getClasses(@NotNull final File folder, @NotNull final Class clazz) + { + return getClasses(folder, clazz, null); + } - public static List> getClasses(String folder, String fileName, Class type) { - List> list = new ArrayList<>(); + @NotNull + public static List<@NotNull Class> getClasses(@NotNull final File folder, @NotNull final Class clazz, @Nullable final String target) + { + if (!folder.exists()) + { + return Collections.emptyList(); + } - try { - File f = new File(PlaceholderAPIPlugin.getInstance().getDataFolder(), folder); - if (!f.exists()) { - return list; - } + try + { + final FilenameFilter filter = + (dir, name) -> name.endsWith(".jar") && (target == null || name.replace(".jar", "").equalsIgnoreCase(target.replace(".jar", ""))); - FilenameFilter fileNameFilter = (dir, name) -> { - if (fileName != null) { - return name.endsWith(".jar") && name.replace(".jar", "") - .equalsIgnoreCase(fileName.replace(".jar", "")); - } + final File[] jars = folder.listFiles(filter); + if (jars == null) + { + return Collections.emptyList(); + } - return name.endsWith(".jar"); - }; + final List<@NotNull Class> list = new ArrayList<>(); - File[] jars = f.listFiles(fileNameFilter); - if (jars == null) { - return list; - } + for (File file : jars) + { + gather(file.toURI().toURL(), clazz, list); + } - for (File file : jars) { - list = gather(file.toURI().toURL(), list, type); - } + return list; + } + catch (Throwable t) + { + // THIS SHOULD NOT BE EATEN LIKE THIS. + } - return list; - } catch (Throwable t) { - } + return Collections.emptyList(); + } - return null; - } + private static void gather(@NotNull final URL jar, @NotNull final Class clazz, @NotNull final List<@NotNull Class> list) throws IOException, ClassNotFoundException + { + try (final URLClassLoader loader = new URLClassLoader(new URL[]{jar}, clazz.getClassLoader()); final JarInputStream stream = new JarInputStream(jar.openStream())) + { + JarEntry entry; + while ((entry = stream.getNextJarEntry()) != null) + { + final String name = entry.getName(); + if (name == null || name.isEmpty() || !name.endsWith(".class")) + { + continue; + } - private static List> gather(URL jar, List> list, Class clazz) { - if (list == null) { - list = new ArrayList<>(); - } + final Class loaded = loader.loadClass(name.substring(0, name.lastIndexOf('.')).replace('/', '.')); + if (clazz.isAssignableFrom(loaded)) + { + list.add(loaded.asSubclass(clazz)); + } + } + } + } - try (URLClassLoader cl = new URLClassLoader(new URL[]{jar}, clazz.getClassLoader()); - JarInputStream jis = new JarInputStream(jar.openStream())) { - - while (true) { - JarEntry j = jis.getNextJarEntry(); - if (j == null) { - break; - } - - String name = j.getName(); - if (name == null || name.isEmpty()) { - continue; - } - - if (name.endsWith(".class")) { - name = name.replace("/", "."); - String cname = name.substring(0, name.lastIndexOf(".class")); - - Class c = cl.loadClass(cname); - if (clazz.isAssignableFrom(c)) { - list.add(c); - } - } - } - } catch (Throwable t) { - } - - return list; - } } diff --git a/src/main/java/me/clip/placeholderapi/util/Msg.java b/src/main/java/me/clip/placeholderapi/util/Msg.java index 3d526e4..022ef5a 100644 --- a/src/main/java/me/clip/placeholderapi/util/Msg.java +++ b/src/main/java/me/clip/placeholderapi/util/Msg.java @@ -23,21 +23,37 @@ package me.clip.placeholderapi.util; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; import java.util.Arrays; -import java.util.Objects; import java.util.stream.Collectors; -public final class Msg { - public static void msg(CommandSender s, String... msg) { - s.sendMessage(Arrays.stream(msg).filter(Objects::nonNull).map(Msg::color).collect(Collectors.joining("\n"))); - } +public final class Msg +{ - public static void broadcast(String... msg) { - Arrays.stream(msg).filter(Objects::nonNull).map(Msg::color).forEach(Bukkit::broadcastMessage); - } + public static void msg(@NotNull final CommandSender sender, @NotNull final String... messages) + { + if (messages.length == 0) + { + return; + } + + sender.sendMessage(Arrays.stream(messages).map(Msg::color).collect(Collectors.joining("\n"))); + } + + public static void broadcast(@NotNull final String... messages) + { + if (messages.length == 0) + { + return; + } + + Bukkit.broadcastMessage(Arrays.stream(messages).map(Msg::color).collect(Collectors.joining("\n"))); + } + + public static String color(@NotNull final String text) + { + return ChatColor.translateAlternateColorCodes('&', text); + } - public static String color(String text) { - return ChatColor.translateAlternateColorCodes('&', text); - } } diff --git a/src/test/java/me/clip/placeholderapi/Values.java b/src/test/java/me/clip/placeholderapi/Values.java new file mode 100644 index 0000000..37b393c --- /dev/null +++ b/src/test/java/me/clip/placeholderapi/Values.java @@ -0,0 +1,187 @@ +package me.clip.placeholderapi; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import me.clip.placeholderapi.replacer.CharsReplacer; +import me.clip.placeholderapi.replacer.RegexReplacer; +import me.clip.placeholderapi.replacer.Replacer; +import org.bukkit.ChatColor; +import org.bukkit.OfflinePlayer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Set; +import java.util.function.Function; + +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 MockPlayerPlaceholderHook()) + .build(); + + + Replacer CHARS_REPLACER = new CharsReplacer(Replacer.Closure.PERCENT); + Replacer REGEX_REPLACER = new RegexReplacer(Replacer.Closure.PERCENT); + Replacer TESTS_REPLACER = new Replacer() + { + private final Set COLOR_CODES = ImmutableSet.of('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'a', 'b', 'c', 'd', 'e', 'f', 'k', 'l', 'm', 'o', 'r', 'x'); + + private final boolean colorize = true; + private final Replacer.Closure closure = Replacer.Closure.PERCENT; + + @Override + public @NotNull String apply(final @NotNull String text, final @Nullable OfflinePlayer player, final @NotNull Function lookup) + { + char[] chars = text.toCharArray(); + StringBuilder builder = new StringBuilder(chars.length); + + // This won't cause memory leaks. It's inside a method. And we want to use setLength instead of + // creating a new string builder to use the maximum capacity and avoid initializing new objects. + StringBuilder identifier = new StringBuilder(50); + PlaceholderHook handler = null; + + // Stages: + // Stage -1: Look for the color code in the next character. + // Stage 0: No closures detected, or the detected identifier is invalid. We're going forward while appending the characters normally. + // Stage 1: The closure has been detected, looking for the placeholder identifier... + // Stage 2: Detected the identifier and the parameter. Translating the placeholder... + int stage = 0; + + for (char ch : chars) + { + if (stage == -1 && COLOR_CODES.contains(ch)) + { + builder.append(ChatColor.COLOR_CHAR).append(ch); + stage = 0; + continue; + } + + // Check if the placeholder starts or ends. + if (ch == closure.head || ch == closure.tail) + { + // If the placeholder ends. + if (stage == 2) + { + String parameter = identifier.toString(); + String translated = handler.onRequest(player, parameter); + + if (translated == null) + { + String name = ""; + builder.append(closure.head).append(name).append('_').append(parameter).append(closure.tail); + } + else + { + builder.append(translated); + } + + identifier.setLength(0); + stage = 0; + continue; + } + else if (stage == 1) + { // If it just started | Double closures | If it's still hasn't detected the indentifier, reset. + builder.append(closure.head).append(identifier); + } + + identifier.setLength(0); + stage = 1; + continue; + } + + // Placeholder identifier started. + if (stage == 1) + { + // Compare the current character with the idenfitier's. + // We reached the end of our identifier. + if (ch == '_') + { + handler = lookup.apply(identifier.toString()); + if (handler == null) + { + builder.append(closure.head).append(identifier).append('_'); + stage = 0; + } + else + { + identifier.setLength(0); + stage = 2; + } + continue; + } + + // Keep building the identifier name. + identifier.append(ch); + continue; + } + + // Building the placeholder parameter. + if (stage == 2) + { + identifier.append(ch); + continue; + } + + // Nothing placeholder related was found. + if (colorize && ch == '&') + { + stage = -1; + continue; + } + builder.append(ch); + } + + if (identifier != null) + { + if (stage > 0) + { + builder.append(closure.tail); + } + builder.append(identifier); + } + return builder.toString(); + } + }; + + + final class MockPlayerPlaceholderHook extends PlaceholderHook + { + + public static final String PLAYER_X = String.valueOf(10); + public static final String PLAYER_Y = String.valueOf(20); + public static final String PLAYER_Z = String.valueOf(30); + public static final String PLAYER_NAME = "Sxtanna"; + + + @Override + public String onRequest(final OfflinePlayer player, 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/test/java/me/clip/placeholderapi/replacer/ReplacerBenchmarks.java b/src/test/java/me/clip/placeholderapi/replacer/ReplacerBenchmarks.java new file mode 100644 index 0000000..cf08ae7 --- /dev/null +++ b/src/test/java/me/clip/placeholderapi/replacer/ReplacerBenchmarks.java @@ -0,0 +1,45 @@ +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 measureRegexReplacerSmallText() + { + Values.REGEX_REPLACER.apply(Values.SMALL_TEXT, null, Values.PLACEHOLDERS::get); + } + + @Benchmark + public void measureCharsReplacerLargeText() + { + Values.CHARS_REPLACER.apply(Values.LARGE_TEXT, null, Values.PLACEHOLDERS::get); + } + + @Benchmark + public void measureRegexReplacerLargeText() + { + Values.REGEX_REPLACER.apply(Values.LARGE_TEXT, null, Values.PLACEHOLDERS::get); + } + + @Benchmark + public void measureTestsReplacerSmallText() + { + Values.TESTS_REPLACER.apply(Values.SMALL_TEXT, null, Values.PLACEHOLDERS::get); + } + + @Benchmark + public void measureTestsReplacerLargeText() + { + Values.TESTS_REPLACER.apply(Values.LARGE_TEXT, null, Values.PLACEHOLDERS::get); + } + +} \ No newline at end of file diff --git a/src/test/java/me/clip/placeholderapi/replacer/ReplacerUnitTester.java b/src/test/java/me/clip/placeholderapi/replacer/ReplacerUnitTester.java new file mode 100644 index 0000000..03d9408 --- /dev/null +++ b/src/test/java/me/clip/placeholderapi/replacer/ReplacerUnitTester.java @@ -0,0 +1,67 @@ +package me.clip.placeholderapi.replacer; + +import me.clip.placeholderapi.Values; +import org.junit.jupiter.api.Test; + +import static me.clip.placeholderapi.Values.MockPlayerPlaceholderHook.PLAYER_NAME; +import static me.clip.placeholderapi.Values.MockPlayerPlaceholderHook.PLAYER_X; +import static me.clip.placeholderapi.Values.MockPlayerPlaceholderHook.PLAYER_Y; +import static me.clip.placeholderapi.Values.MockPlayerPlaceholderHook.PLAYER_Z; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public final class ReplacerUnitTester +{ + + @Test + void testCharsReplacerProducesExpectedSingleValue() + { + assertEquals(PLAYER_NAME, Values.CHARS_REPLACER.apply("%player_name%", null, Values.PLACEHOLDERS::get)); + } + + @Test + void testRegexReplacerProducesExpectedSingleValue() + { + assertEquals(PLAYER_NAME, Values.REGEX_REPLACER.apply("%player_name%", null, Values.PLACEHOLDERS::get)); + } + + @Test + void testCharsReplacerProducesExpectedSentence() + { + assertEquals(String.format("My name is %s and my location is (%s, %s, %s), this placeholder is invalid %%server_name%%", PLAYER_NAME, PLAYER_X, PLAYER_Y, PLAYER_Z), Values.CHARS_REPLACER.apply(Values.LARGE_TEXT, null, Values.PLACEHOLDERS::get)); + } + + @Test + void testRegexReplacerProducesExpectedSentence() + { + assertEquals(String.format("My name is %s and my location is (%s, %s, %s), this placeholder is invalid %%server_name%%", PLAYER_NAME, PLAYER_X, PLAYER_Y, PLAYER_Z), Values.REGEX_REPLACER.apply(Values.LARGE_TEXT, null, Values.PLACEHOLDERS::get)); + } + + @Test + void testResultsAreTheSameAsReplacement() + { + final String resultChars = Values.CHARS_REPLACER.apply("%player_name%", null, Values.PLACEHOLDERS::get); + final String resultRegex = Values.REGEX_REPLACER.apply("%player_name%", null, Values.PLACEHOLDERS::get); + + assertEquals(resultChars, resultRegex); + + assertEquals(PLAYER_NAME, resultChars); + } + + @Test + void testResultsAreTheSameNoReplacement() + { + final String resultChars = Values.CHARS_REPLACER.apply("%player_location%", null, Values.PLACEHOLDERS::get); + final String resultRegex = Values.REGEX_REPLACER.apply("%player_location%", null, Values.PLACEHOLDERS::get); + + assertEquals(resultChars, resultRegex); + } + + @Test + void testCharsReplacerIgnoresMalformed() + { + final String text = "10% and %hello world 15%"; + + assertEquals(text, Values.CHARS_REPLACER.apply(text, null, Values.PLACEHOLDERS::get)); + } + +}