/* * This file is part of NucleusFramework for Bukkit, licensed under the MIT License (MIT). * * Copyright (c) JCThePants (www.jcwhatever.com) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.jcwhatever.nucleus.utils.text; import com.jcwhatever.nucleus.internal.NucLang; import com.jcwhatever.nucleus.managed.language.Localizable; import com.jcwhatever.nucleus.managed.language.Localized; import com.jcwhatever.nucleus.utils.CollectionUtils; import com.jcwhatever.nucleus.utils.PreCon; import com.jcwhatever.nucleus.utils.coords.SyncLocation; import com.jcwhatever.nucleus.utils.text.components.IChatMessage; import com.jcwhatever.nucleus.utils.text.format.IFormatterAppendable; import com.jcwhatever.nucleus.utils.text.format.ITagFormatter; import com.jcwhatever.nucleus.utils.text.format.TextFormatter; import com.jcwhatever.nucleus.utils.text.format.TextFormatterSettings; import com.jcwhatever.nucleus.utils.validate.IValidator; import org.bukkit.ChatColor; import org.bukkit.Location; import org.bukkit.plugin.Plugin; import javax.annotation.Nullable; import java.io.IOException; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.WeakHashMap; import java.util.regex.Pattern; /** * Static methods to aid in string related tasks */ public final class TextUtils { private TextUtils() {} @Localizable static final String _FORMAT_TEMPLATE_RAW = "{0}"; @Localizable static final String _FORMAT_TEMPLATE_HEADER = "{AQUA}{0}"; @Localizable static final String _FORMAT_TEMPLATE_SUB_HEADER = "{LIGHT_PURPLE}{ITALIC}{0}:"; @Localizable static final String _FORMAT_TEMPLATE_ITEM = "{YELLOW}{0}"; @Localizable static final String _FORMAT_TEMPLATE_DEFINITION = "{GOLD}{0}{AQUA} - {GRAY}{1}"; @Localizable static final String _FORMAT_TEMPLATE_ITEM_DESCRIPTION = "{YELLOW}{0}{AQUA} - {GRAY}{1}"; @Localizable static final String _LOCATION_FORMAT_COLOR = "{LIGHT_PURPLE}X:{GRAY}{0: x}{WHITE}, " + "{LIGHT_PURPLE}Y:{GRAY}{1: y}{WHITE}, " + "{LIGHT_PURPLE}Z:{GRAY}{2: x}{WHITE}, " + "{LIGHT_PURPLE}W:{GRAY}{3: world}"; @Localizable static final String _LOCATION_FORMAT = "X:{0: x}, Y:{1: y}, Z:{2: x}, W:{3: world}"; @Localizable static final String _TITLE_CASE_EXCLUSIONS = "above, about, across, against, along, among, around, at, before, behind, below, beneath, " + "beside, between, beyond, by, down, during, except, for, from, in, inside, into, like, " + "near, of, off, on, since, to, toward, through, under, until, up, upon, with, within," + "the, a, an, and, but, or, for, either, be, is"; //"a,an,the,and,but,or,on,in,with,upon,of,to,at,under,near,upon,by"; public static final Pattern PATTERN_DOT = Pattern.compile("\\."); public static final Pattern PATTERN_COMMA = Pattern.compile(","); public static final Pattern PATTERN_COLON = Pattern.compile(":"); public static final Pattern PATTERN_SEMI_COLON = Pattern.compile(";"); public static final Pattern PATTERN_DASH = Pattern.compile("-"); public static final Pattern PATTERN_SPACE = Pattern.compile(" "); public static final Pattern PATTERN_EQUALS = Pattern.compile("="); public static final Pattern PATTERN_PERCENT = Pattern.compile("%"); public static final Pattern PATTERN_PIPE = Pattern.compile("\\|"); public static final Pattern PATTERN_NUMBERS = Pattern.compile("-?\\d+"); public static final Pattern PATTERN_DECIMAL_NUMBERS = Pattern.compile("-?\\d+(\\.\\d+)?"); public static final Pattern PATTERN_NAMES = Pattern.compile("^[a-zA-Z][a-zA-Z0-9_]*$"); public static final Pattern PATTERN_NODE_NAMES = Pattern.compile("^[a-zA-Z0-9_-]*$"); public static final Pattern PATTERN_NODE_PATHS = Pattern.compile("^[a-zA-Z0-9_.-]*$"); public static final Pattern PATTERN_FILEPATH_SLASH = Pattern.compile("[\\/\\\\]"); public static final Pattern PATTERN_UNDERSCORE = Pattern.compile("_"); public static final Pattern PATTERN_DOUBLE_QUOTE = Pattern.compile("\""); public static final Pattern PATTERN_SINGLE_QUOTE = Pattern.compile("'"); public static final Pattern PATTERN_NEW_LINE = Pattern.compile("\r\n|\r|\n"); public static final TextFormatterSettings TEXT_FORMATTER_SETTINGS = new TextFormatterSettings(); public static final TextFormatter TEXT_FORMATTER = new TextFormatter(TEXT_FORMATTER_SETTINGS); private static final Map<Plugin, TextFormatterSettings> _pluginFormatters = new WeakHashMap<>(10); private static Set<String> _titleCaseExclusions; public enum FormatTemplate { /** * No formatting. */ RAW (_FORMAT_TEMPLATE_RAW), /** * A header in a list of items. */ HEADER (_FORMAT_TEMPLATE_HEADER), /** * A sub header in a list of items. */ SUB_HEADER (_FORMAT_TEMPLATE_SUB_HEADER), /** * A single item in a list. */ LIST_ITEM (_FORMAT_TEMPLATE_ITEM), /** * A single item in a list with a description. */ LIST_ITEM_DESCRIPTION (_FORMAT_TEMPLATE_ITEM_DESCRIPTION), /** * A definition for an item that * generally does not change (i.e commands) */ CONSTANT_DEFINITION (_FORMAT_TEMPLATE_DEFINITION); private final String _template; FormatTemplate(String template) { _template = template; } @Override @Localized public String toString() { return NucLang.get(_template).toString(); } } /** * Specify case sensitivity. */ public enum CaseSensitivity { /** * Characters must be exact. */ EXACT, /** * Ignore case, case insensitive. */ IGNORE_CASE } /** * Specify optional title case parsing options. */ public enum TitleCaseOption { /** * Forces lower case in all letters of a word except the first letter. */ FORCE_CASING, /** * Forces title casing on words that are excluded from title casing. */ IGNORE_EXCLUDED_WORDS, /** * Remove underscores and replace them with spaces. */ REMOVE_UNDERSCORE, /** * Use all options. */ ALL } /** * Determine if a string is valid for use as a name. * * <p>The string must begin with a letter and use only alphanumeric characters * and underscores.</p> * * <p>The string must also be at least 1 character in length and be no more * than 16 characters long.</p> * * @param name The name to check. */ public static boolean isValidName(String name) { return isValidName(name, 16); } /** * Determine if a string is valid for use as a name. * * <p>The string must begin with a letter and use only alphanumeric * characters and underscores.</p> * * <p>The string must also be at least 1 character in length.</p> * * @param name The name to check. * @param maxLen The max length of the name. */ public static boolean isValidName(@Nullable String name, int maxLen) { PreCon.greaterThanZero(maxLen); return name != null && (maxLen == -1 || name.length() <= maxLen) && name.length() > 0 && PATTERN_NAMES.matcher(name).matches(); } /** * Search a collection of strings for candidates that start with the specified * prefix. * * @param prefix The prefix to search for. * @param searchCandidates The search candidates. */ public static List<String> startsWith(String prefix, Collection<String> searchCandidates) { return startsWith(prefix, searchCandidates, CaseSensitivity.EXACT); } /** * Search a collection of strings for candidates that start with the specified * prefix. * * @param prefix The prefix to search for. * @param searchCandidates The search candidates. * @param casing The case sensitivity of the search. */ public static List<String> startsWith(String prefix, Collection<String> searchCandidates, CaseSensitivity casing) { PreCon.notNull(prefix); PreCon.notNull(searchCandidates); PreCon.notNull(casing); if (prefix.isEmpty()) { return new ArrayList<>(searchCandidates); } if (casing == CaseSensitivity.IGNORE_CASE) { prefix = prefix.toLowerCase(); } List<String> result = new ArrayList<>(searchCandidates.size()); for (String candidate : searchCandidates) { if ((casing == CaseSensitivity.IGNORE_CASE && candidate.toLowerCase().startsWith(prefix)) || candidate.startsWith(prefix)) { result.add(candidate); } } return result; } /** * Search a collection of strings for candidates that end with the specified * suffix. * * @param suffix The suffix to search for. * @param searchCandidates The search candidates. */ public static List<String> endsWith(String suffix, Collection<String> searchCandidates) { return endsWith(suffix, searchCandidates, CaseSensitivity.EXACT); } /** * Search a collection of strings for candidates that end with the specified * suffix. * * @param suffix The suffix to search for. * @param searchCandidates The search candidates. * @param casing The case sensitivity of the search. */ public static List<String> endsWith(String suffix, Collection<String> searchCandidates, CaseSensitivity casing) { PreCon.notNull(suffix); PreCon.notNull(searchCandidates); PreCon.notNull(casing); if (suffix.isEmpty()) { return new ArrayList<>(searchCandidates); } if (casing == CaseSensitivity.IGNORE_CASE) { suffix = suffix.toLowerCase(); } List<String> result = new ArrayList<>(searchCandidates.size()); for (String candidate : searchCandidates) { if ((casing == CaseSensitivity.IGNORE_CASE && candidate.toLowerCase().endsWith(suffix)) || candidate.endsWith(suffix)) { result.add(candidate); } } return result; } /** * Search a collection of strings for candidates that contain the specified * search text. * * @param searchText The text to search for. * @param searchCandidates The search candidates. */ public static List<String> contains(String searchText, Collection<String> searchCandidates) { return contains(searchText, searchCandidates, CaseSensitivity.EXACT); } /** * Search a collection of strings for candidates that contain the specified * search text. * * @param searchText The text to search for. * @param searchCandidates The search candidates. * @param casing The case sensitivity of the search. */ public static List<String> contains(String searchText, Collection<String> searchCandidates, CaseSensitivity casing) { PreCon.notNull(searchText); PreCon.notNull(searchCandidates); PreCon.notNull(casing); if (searchText.isEmpty()) { return new ArrayList<>(searchCandidates); } if (casing == CaseSensitivity.IGNORE_CASE) { searchText = searchText.toLowerCase(); } List<String> result = new ArrayList<>(searchCandidates.size()); for (String candidate : searchCandidates) { if ((casing == CaseSensitivity.IGNORE_CASE && candidate.toLowerCase().contains(searchText)) || candidate.contains(searchText)) { result.add(candidate); } } return result; } /** * Search a collection of strings for valid candidates using an {@link IValidator} * to validate. * * @param searchCandidates The search candidates. * @param entryValidator The entry validator. */ public static List<String> search(Collection<String> searchCandidates, IValidator<String> entryValidator) { return CollectionUtils.search(searchCandidates, entryValidator); } /** * Pad the right side of a string with the specified characters. * * @param s The String to pad. * @param length The number of characters to pad. * @param pad The character to path with. */ public static String padRight(String s, int length, char pad) { PreCon.notNull(s); PreCon.positiveNumber(length); StringBuilder buffy = new StringBuilder(s.length() + length); buffy.append(s); for (int i = 0; i < length; ++i) { buffy.append(pad); } return buffy.toString(); } /** * Pad the right side of a string with spaces. * * @param s The String to pad. * @param length The number of spaces to pad with. */ public static String padRight(String s, int length) { return TextUtils.padRight(s, length, ' '); } /** * Pad the left side of a string with the specified characters. * * @param s The String to pad. * @param length The number of characters to pad. * @param pad The character to path with. */ public static String padLeft(String s, int length, char pad) { PreCon.notNull(s); PreCon.positiveNumber(length); StringBuilder buffy = new StringBuilder(s.length() + length); for (int i = 0; i < length; i++) { buffy.append(pad); } buffy.append(s); return buffy.toString(); } /** * Pad left side of specified string with spaces. * * @param s The string to pad. * @param length The number of spaces to pad with. */ public static String padLeft(String s, int length) { return TextUtils.padLeft(s, length, ' '); } /** * Reduce the number of characters in a string by removing characters from the end. * * <p>Returns input string if input string length is less than or equal to 16 * characters.</p> * * @param s The string to truncate. * @param length The new length of the string. */ public static String truncate(String s, int length) { PreCon.notNull(s); PreCon.positiveNumber(length); PreCon.lessThan(length, s.length()); if (s.length() > length) return s.substring(0, length); return s; } /** * Reduce the number of characters in a string to 16. * * <p>Returns input string if input string length is less than or equal to * 16 characters.</p> * * @param s The string to truncate. */ public static String truncate(String s) { return TextUtils.truncate(s, 16); } /** * Converts supplied string to camel casing. * * @param s The string to convert. */ public static String camelCase(String s) { PreCon.notNull(s); if (s.length() < 2) { return s.toLowerCase(); } String[] words = PATTERN_SPACE.split(s); StringBuilder resultSB = new StringBuilder(s.length() + 20); for (int i=0; i < words.length; i++) { String firstLetter = String.valueOf(words[i].charAt(0)); resultSB.append(i == 0 ? firstLetter.toLowerCase() : firstLetter.toUpperCase()); resultSB.append(words[i].substring(1).toLowerCase()); } return resultSB.toString(); } /** * Converts the casing of the supplied string for use as a title. * * <p>The normal operation is to change the first letter of every word over * 3 characters in length to upper case.</p> * * <p>Additional operations can be added.</p> * * @param text The string to modify. * @param options Parsing options. */ public static String titleCase(String text, TitleCaseOption... options) { PreCon.notNull(text); if (text.length() < 2) { return text; } boolean forceCasing = false; boolean removeUnderscores = false; boolean ignoreExcluded = false; for (TitleCaseOption option : options) { switch (option) { case ALL: removeUnderscores = true; ignoreExcluded = true; forceCasing = true; break; case REMOVE_UNDERSCORE: removeUnderscores = true; break; case IGNORE_EXCLUDED_WORDS: ignoreExcluded = true; break; case FORCE_CASING: forceCasing = true; break; } } if (removeUnderscores) { text = text.replace('_', ' '); } String[] words = PATTERN_SPACE.split(text); StringBuilder resultSB = new StringBuilder(text.length() + 10); for (int i=0; i < words.length; i++) { if (i != 0) resultSB.append(' '); String word = words[i]; String firstLetter = word.substring(0, 1); String wordEnd = word.substring(1, word.length()); if (i == 0 || ignoreExcluded || !isTitleCaseExcluded(word)) { firstLetter = firstLetter.toUpperCase(); } if (forceCasing) { wordEnd = wordEnd.toLowerCase(); } resultSB.append(firstLetter); resultSB.append(wordEnd); } return resultSB.toString(); } /** * Concatenates a collection into a single string using the specified separator * string. * * @param collection The collection to concatenate. * @param separator The string to insert between elements. */ public static String concat(Collection<?> collection, String separator) { //noinspection ConstantConditions return concat(collection, separator, ""); } /** * Concatenates a collection into a single string using the specified separator * string. * * @param collection The collection to concatenate. * @param separator The string to insert between elements. * @param emptyValue The string to return if the collection is null or empty. */ @Nullable public static String concat(Collection<?> collection, String separator, String emptyValue) { if (collection == null || collection.isEmpty()) { return emptyValue; } if (separator == null) separator = ""; StringBuilder buffy = new StringBuilder(collection.size() * 25); for (Object o : collection) { buffy.append(separator); buffy.append(o.toString()); } return buffy.substring(separator.length()); } /** * Concatenates an array in a single string using the specified separator string. * * @param strArray The array to concatenate. * @param separator The string to insert between elements. */ public static <T> String concat(T[] strArray, @Nullable String separator) { //noinspection ConstantConditions return concat(0, strArray.length, strArray, separator, ""); } /** * Concatenates an array into a single string using the specified separator string. * * @param strArray The array to concatenate. * @param separator The string to insert between elements. * @param emptyValue The string to return if the array is null or empty. */ @Nullable public static <T> String concat(T[] strArray, @Nullable String separator, @Nullable String emptyValue) { return concat(0, strArray.length, strArray, separator, emptyValue); } /** * Concatenates an array into a single string using the specified separator string. * * @param startIndex The index to start concatenating at. * @param strArray The array to concatenate. * @param separator The separator to insert between elements. */ public static <T> String concat(int startIndex, T[] strArray, @Nullable String separator) { //noinspection ConstantConditions return concat(startIndex, strArray.length, strArray, separator, ""); } /** * Concatenates an array into a single string using the specified separator string. * * @param startIndex The index to start concatenating at. * @param strArray The array to concatenate. * @param separator The separator to insert between elements. * @param emptyValue The value to return if the array is empty or null. */ @Nullable public static <T> String concat(int startIndex, T[] strArray, @Nullable String separator, @Nullable String emptyValue) { return concat(startIndex, strArray.length, strArray, separator, emptyValue); } /** * Concatenates an array into a single string using the specified separator string. * * @param startIndex The index to start concatenating at. * @param endIndexP1 The index to stop concatenating at (+1, add 1 to the end index). * @param strArray The array to concatenate. */ public static <T> String concat(int startIndex, int endIndexP1, T[] strArray) { //noinspection ConstantConditions return concat(startIndex, endIndexP1, strArray, null, ""); } /** * Concatenates an array into a single string using the specified separator string. * * @param startIndex The index to start concatenating at. * @param endIndexP1 The index to stop concatenating at (+1, add 1 to the end index). * @param strArray The array to concatenate. * @param separator The separator to insert between elements. */ public static <T> String concat(int startIndex, int endIndexP1, T[] strArray, @Nullable String separator) { //noinspection ConstantConditions return concat(startIndex, endIndexP1, strArray, separator, ""); } /** * Concatenates an array into a single string using the specified separator string. * * @param startIndex The index to start concatenating at. * @param endIndexP1 The index to stop concatenating at (+1, add 1 to the end index). * @param strArray The array to concatenate. */ @Nullable public static <T> String concat(int startIndex, int endIndexP1, T[] strArray, @Nullable String separator, @Nullable String emptyValue) { PreCon.notNull(strArray); if (strArray.length == 0 || startIndex == endIndexP1) return emptyValue; if (separator == null) separator = ""; StringBuilder buffy = new StringBuilder((endIndexP1 - startIndex) * 25); boolean isEnum = strArray[0] instanceof Enum<?>; for (int i = startIndex; i < endIndexP1; i++) { T str = strArray[i]; if (str == null) continue; buffy.append(separator); if (isEnum) { Enum<?> en = (Enum<?>)str; buffy.append(en.name()); } else { buffy.append(str); } } return buffy.substring(separator.length()); } /** * Splits a string into multiple string based on max character length specified * for a line. * * @param str The string to split/paginate. * @param maxLineLen The max length of a line. * @param excludeColorsFromLen True to exclude color characters from length * calculations. */ public static List<String> paginateString(String str, int maxLineLen, boolean excludeColorsFromLen) { return paginateString(str, null, maxLineLen, excludeColorsFromLen); } /** * Splits a string into multiple string based on max character length specified * for a line. * * @param str The string to split/paginate. * @param linePrefix The prefix to append to each line. * @param maxLineLen The max length of a line. Must be greater than 1. * @param excludeColorsFromLen True to exclude color characters from length calculations. */ public static List<String> paginateString(String str, @Nullable String linePrefix, int maxLineLen, boolean excludeColorsFromLen) { PreCon.notNull(str); PreCon.isValid(maxLineLen > 1); if (linePrefix != null) str = str.replace(linePrefix, ""); else linePrefix = ""; String[] words = PATTERN_SPACE.split(str); List<String> results = new ArrayList<String>(str.length() / maxLineLen); StringBuilder line = new StringBuilder(maxLineLen); line.append(linePrefix); int prefixSize = linePrefix.length(); boolean wordAddedToLine = false; for (int i=0; i < words.length; i++) { int lineLength = excludeColorsFromLen ? TextColor.remove(line).length() : line.length(); int wordLength = excludeColorsFromLen ? TextColor.remove(words[i]).length() : words[i].length(); if (lineLength + wordLength + 1 <= maxLineLen && prefixSize + wordLength < maxLineLen) { // append to current line if (wordAddedToLine) line.append(' '); line.append(words[i]); wordAddedToLine = true; } else { // create new line String format = null; if (line.length() != 0 && i != 0) { String finishedLine = line.toString(); format = ChatColor.getLastColors(finishedLine); results.add(finishedLine); } line.setLength(0); line.append(linePrefix); if (format != null) line.append(format); if (prefixSize + wordLength >= maxLineLen) { line.append(words[i]); wordAddedToLine = true; } else { if (i != 0) { i = i - 1; } wordAddedToLine = false; } } } // make sure last line is added. // get left behind if number of words runs out // and there is still room for more. if (wordAddedToLine) { results.add(line.toString()); } return results; } /** * Format a location into a human readable string. * * @param loc The location to format. * @param addColor True to add color. */ public static String formatLocation(Location loc, boolean addColor) { if (loc == null) return "null"; String worldName; if (loc.getWorld() == null) { worldName = loc instanceof SyncLocation ? ((SyncLocation) loc).getWorldName() : "null"; } else { worldName = loc.getWorld().getName(); } DecimalFormat format = new DecimalFormat("###.#"); return TextUtils.format(addColor ? _LOCATION_FORMAT_COLOR : _LOCATION_FORMAT, format.format(loc.getX()), format.format(loc.getY()), format.format(loc.getZ()), worldName).toString(); } /** * Format text string by replacing placeholders with the information about the * specified plugin. * * <p>Placeholders: {plugin-version}, {plugin-name}, {plugin-full-name}, * {plugin-author}, {plugin-command}</p> * * @param plugin The plugin. * @param msg The message to format plugin info into. * @param args Optional message format arguments. */ public static IChatMessage formatPluginInfo(Plugin plugin, CharSequence msg, Object... args) { return formatPluginInfo(plugin, null, msg, args); } /** * Format text string by replacing placeholders with the information about the * specified plugin. * * <p>Placeholders: {plugin-version}, {plugin-name}, {plugin-full-name}, * {plugin-author}, {plugin-command}</p> * * @param plugin The plugin. * @param settings The settings to use. * @param msg The message to format plugin info into. * @param args Optional message format arguments. */ public static IChatMessage formatPluginInfo(Plugin plugin, @Nullable TextFormatterSettings settings, CharSequence msg, Object... args) { PreCon.notNull(plugin); PreCon.notNull(msg); PreCon.notNull(args); TextFormatterSettings formatters; if (settings == null) { formatters = _pluginFormatters.get(plugin); if (formatters == null) { formatters = getPluginFormatters(plugin, null); _pluginFormatters.put(plugin, formatters); } } else { formatters = getPluginFormatters(plugin, settings); } return TEXT_FORMATTER.format(formatters, msg, args); } /** * Formats text string by replacing placeholders (i.e {0}) with the string * representation of the objects provided. * * <p>The index order of the object params as provided is mapped to the number * inside the placeholder. </p> * * <p>Color can be specified using {@link TextColor} enum names in curly brackets. * (i.e. {GREEN})</p> * * @param template An object whose {@link #toString} method yields the message template. * @param args Optional format arguments. */ public static IChatMessage format(Object template, Object... args) { return template instanceof CharSequence ? format((CharSequence) template, args) : format(template.toString(), args); } /** * Formats text string by replacing placeholders (i.e {0}) with the string * representation of the objects provided. * * <p>The index order of the object params as provided is mapped to the number * inside the placeholder. </p> * * <p>Color can be specified using {@link TextColor} enum names in curly brackets. * (i.e. {GREEN})</p> * * @param settings A custom set of settings to use. * @param template An object whose {@link #toString} method yields the message template. * @param args Optional format arguments. */ public static IChatMessage format(TextFormatterSettings settings, Object template, Object... args) { PreCon.notNull(settings); PreCon.notNull(template); PreCon.notNull(args); return TEXT_FORMATTER.format(settings, template.toString(), args); } /** * Formats text string by replacing placeholders (i.e {0}) with the string * representation of the object params provided. * * <p>The index order of the objects as provided is mapped to the number * inside the placeholder. </p> * * <p>Color can be specified using {@link TextColor} names in curly brackets. * (i.e. {GREEN})</p> * * @param msg The message to format. * @param args Optional format arguments. */ public static IChatMessage format(CharSequence msg, Object... args) { PreCon.notNull(msg); PreCon.notNull(args); return TEXT_FORMATTER.format(msg, args); } /** * Creates a new {@link TextFormatterSettings} that contains the settings from the * specified {@link TextFormatterSettings} and additionally formatters for the specified * plugin. */ public static TextFormatterSettings getPluginFormatters(final Plugin plugin, @Nullable TextFormatterSettings settings) { PreCon.notNull(plugin); Map<String, ITagFormatter> formatters = new HashMap<>(10); // Plugin Version formatters.put("plugin-version", new ITagFormatter() { @Override public String getTag() { return "plugin-version"; } @Override public void append(IFormatterAppendable output, String rawTag) { output.append(plugin.getDescription().getVersion()); } }); // Plugin Name formatters.put("plugin-name", new ITagFormatter() { @Override public String getTag() { return "plugin-name"; } @Override public void append(IFormatterAppendable output, String rawTag) { output.append(plugin.getDescription().getName()); } }); // Plugin Full Name formatters.put("plugin-full-name", new ITagFormatter() { @Override public String getTag() { return "plugin-full-name"; } @Override public void append(IFormatterAppendable output, String rawTag) { output.append(plugin.getDescription().getFullName()); } }); // Plugin Author formatters.put("plugin-author", new ITagFormatter() { @Override public String getTag() { return "plugin-author"; } @Override public void append(IFormatterAppendable output, String rawTag) { output.append(TextUtils.concat(plugin.getDescription().getAuthors(), ", ")); } }); // Plugin Command formatters.put("plugin-command", new ITagFormatter() { @Override public String getTag() { return "plugin-command"; } @Override public void append(IFormatterAppendable output, String rawTag) { Map<String, Map<String, Object>> commands = plugin.getDescription().getCommands(); if (commands != null) { Set<String> commandKeys = commands.keySet(); if (!commandKeys.isEmpty()) { String cmd = commandKeys.iterator().next(); output.append(cmd); } } } }); return settings != null ? new TextFormatterSettings(settings, formatters) : new TextFormatterSettings(formatters); } /** * Parse a boolean from a string and include "yes" and "1" as values that * return true. * * @param string The string to parse. */ public static boolean parseBoolean(@Nullable String string) { if (string == null) return false; switch (string.toLowerCase()) { case "true": case "yes": case "1": return true; default: return false; } } /** * Parse a byte value from a string and return a default value if parsing fails. * * @param string The string to parse. * @param defaultVal The default value to return if parsing fails. */ public static byte parseByte(@Nullable String string, byte defaultVal) { if (string == null) return defaultVal; try { return Byte.parseByte(string); } catch (NumberFormatException e) { return defaultVal; } } /** * Parse a short value from a string and return a default value if parsing fails. * * @param string The string to parse. * @param defaultVal The default value to return if parsing fails. */ public static short parseShort(@Nullable String string, short defaultVal) { if (string == null) return defaultVal; try { return Short.parseShort(string); } catch (NumberFormatException e) { return defaultVal; } } /** * Parse an integer value from a string and return a default value if parsing * fails. * * @param string The string to parse. * @param defaultVal The default value to return if parsing fails. */ public static int parseInt(@Nullable String string, int defaultVal) { if (string == null) return defaultVal; try { return Integer.parseInt(string); } catch (NumberFormatException e) { return defaultVal; } } /** * Parse a long value from a string and return a default value if parsing fails. * * @param string The string to parse. * @param defaultVal The default value to return if parsing fails. */ public static long parseLong(@Nullable String string, long defaultVal) { if (string == null) return defaultVal; try { return Long.parseLong(string); } catch (NumberFormatException e) { return defaultVal; } } /** * Parse a float value from a string and return a default value if parsing fails. * * @param string The string to parse. * @param defaultVal The default value to return if parsing fails. */ public static float parseFloat(@Nullable String string, float defaultVal) { if (string == null) return defaultVal; try { return Float.parseFloat(string); } catch (NumberFormatException e) { return defaultVal; } } /** * Parse a double value from a string and return a default value if parsing fails. * * @param string The string to parse. * @param defaultVal The default value to return if parsing fails. */ public static double parseDouble(@Nullable String string, double defaultVal) { if (string == null) return defaultVal; try { return Double.parseDouble(string); } catch (NumberFormatException e) { return defaultVal; } } /** * Parse the {@link java.util.UUID} from the supplied string. * If parsing fails, null is returned. * * @param string The string to parse. */ @Nullable public static UUID parseUUID(String string) { if (string == null) return null; try { return UUID.fromString(string); } catch (IllegalArgumentException iae) { return null; } } /** * Parse the {@link java.util.UUID}'s from the supplied collection of strings. * * <p>If a string cannot be parsed, it is not included in the results.</p> * * <p>Failure to parse one or more results can be detected by compare the size * of the result with the size of the input collection.</p> * * @param strings The string collection to parse. */ public static List<UUID> parseUUID(Collection<String> strings) { List<UUID> results = new ArrayList<UUID>(strings.size()); for (String raw : strings) { UUID id = parseUUID(raw); if (id == null) continue; results.add(id); } return results; } /** * Parse the {@link java.util.UUID}'s from the supplied string array. * * <p>If a string cannot be parsed, it is not included in the results.</p> * * <p>Failure to parse one or more results can be detected by compare the * size of the result with the size of the input collection.</p> * * @param strings The string array to parse. */ public static List<UUID> parseUUID(String[] strings) { List<UUID> results = new ArrayList<UUID>(strings.length); for (String raw : strings) { UUID id = parseUUID(raw); if (id == null) continue; results.add(id); } return results; } /** * Convert a UUID to a string without dashes. * * @param uuid The UUID to convert. */ public static String uuidToString(UUID uuid) { return TextUtils.PATTERN_DASH.matcher( uuid.toString()).replaceAll(""); } /** * Print an array of {@link java.lang.StackTraceElement} to an {@link java.lang.Appendable}. * * @param stackTrace The stack trace to append. * @param appendable The output to print to. * * @throws java.io.IOException if the {@link Appendable} throws the exception. */ public static void printStackTrace(StackTraceElement[] stackTrace, Appendable appendable) throws IOException { PreCon.notNull(stackTrace); PreCon.notNull(appendable); for (StackTraceElement element : stackTrace) { if (element.getClassName().equals("java.lang.Thread") && element.getMethodName().equals("getStackTrace")) { continue; // skip call to getStackTrace } appendable.append(" at "); appendable.append(element.getClassName()); appendable.append('.'); appendable.append(element.getMethodName()); appendable.append(" ("); appendable.append(element.getFileName()); appendable.append(':'); appendable.append(String.valueOf(element.getLineNumber())); appendable.append(")\n"); } } private static boolean isTitleCaseExcluded(String word) { if (_titleCaseExclusions == null) { IChatMessage exclusionString = NucLang.get(_TITLE_CASE_EXCLUSIONS); String[] excl = PATTERN_COMMA.split(exclusionString); _titleCaseExclusions = new HashSet<>(excl.length + 10); for (String excluded : excl) { _titleCaseExclusions.add(excluded.trim().toLowerCase()); } } return _titleCaseExclusions.contains(word.toLowerCase()); } }