Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
package me.deecaad.core.utils
import org.intellij.lang.annotations.RegExp
import java.util.regex.Pattern
import kotlin.math.abs
/**
* Utility class for working with strings.
*/
object StringUtil {
const val LOWER_ALPHABET = "abcdefghijklmnopqrstuvwxyz"
const val VALID_HEX = "0123456789AaBbCcDdEeFf"
const val MINECRAFT_COLOR_CODES = VALID_HEX + "KkLlMmNnOoRrXx"
private val SUFFIXES = arrayOf("th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th")
/**
* Counts the occurrences of a character in a string.
*
* @param str The string to search in.
* @param char The character to search for.
* @return The number of occurrences of the character in the string.
*/
@JvmStatic
fun countOccurrences(
str: String,
char: Char,
): Int {
return str.count { it == char }
}
/**
* Repeat a string a number of times.
*
* If the string is empty, or the number of times is less than or equal to
* 0, an empty string is returned.
*
* @param str The string to repeat.
* @param times The number of times to repeat the string.
* @return The repeated string.
*/
@JvmStatic
fun repeat(
str: String,
times: Int,
): String {
if (str.isEmpty() || times <= 0) {
return ""
}
return str.repeat(times)
}
/**
* Returns `true` if the character at the given index is escaped.
*
* A character is considered escaped if it is preceded by a **non-escaped**
* backslash (`\`).
*
* When `includeBackslash` is `true`, the backslash character itself is
* considered an escape character.
*
* @param str The string to check.
* @param index The index of the character to check.
* @return `true` if the character is escaped, `false` otherwise.
* @throws IndexOutOfBoundsException if the index is out of range.
*/
@JvmStatic
@JvmOverloads
fun isEscaped(
str: String,
index: Int,
includeBackslash: Boolean = false,
): Boolean {
if (index == 0) {
return false
}
// An odd number of backslashes means the character is escaped
var backslashes = 0
for (i in index - 1 downTo 0) {
if (str[i] != '\\') {
break
}
backslashes++
}
// The `|| str[index] == '\\'` part is needed to handle the characters
// that ESCAPE a character. It counts the backslashes preceding the
// escape character as an escaped character.
return backslashes % 2 == 1 || (!includeBackslash && str[index] == '\\')
}
/**
* Matches the first occurrence of a regex in a string, or returns `null`.
*
* @param regex The regex to match.
* @param str The string to search in.
* @return The first occurrence of the regex in the string, or `null`.
*/
@JvmStatic
fun match(
@RegExp regex: String,
str: String,
): String? {
return match(regex.toRegex(), str)
}
/**
* Matches the first occurrence of a regex in a string, or returns `null`.
*
* @param regex The regex to match.
* @param str The string to search in.
* @return The first occurrence of the regex in the string, or `null`.
*/
@JvmStatic
fun match(
regex: Regex,
str: String,
): String? {
val match = regex.find(str)
return match?.value
}
/**
* Colors a given string using Minecraft color codes by replacing the
* ampersand (`&`) with the section symbol (`§`).
*
* Supports hex strings formatted by `RRGGBB`, by using the `&x` code.
* For example, `ff0000` gets translates to `§x§f§f§0§0§0§0`.
*
* @param str The string to colorize.
* @return The colorized string.
*/
@JvmStatic
fun colorBukkit(str: String): String {
val result = StringBuilder(str.length)
var i = 0
while (i < str.length) {
val c: Char = str[i]
if (c != '&') {
result.append(c)
} else if (i != 0 && str[i - 1] == '\\') {
result.setCharAt(result.length - 1, '&')
} else if (i + 1 != str.length) {
if (str[i + 1] in MINECRAFT_COLOR_CODES) {
result.append('§')
} else if (str[i + 1] == '#') {
val bound = i + 7
if (bound <= str.length) {
result.append('§').append('x')
// We have to skip forward 2 for the color code and hex symbol
i += 2
// We could validate the hex, but we are just going
// to let people debug that themselves. The msg
// in chat will look obviously wrong, so people
// should have no problem checking their hex.
while (i <= bound) {
result.append('§').append(str[i])
i++
}
// We have to go back one because the loop will
// increment the index one more time
i--
}
} else {
result.append('&')
}
} else {
result.append('&')
}
i++
}
return result.toString()
}
/**
* Colors a given string using Adventure color codes by replacing the
* ampersand (`&`) with the corresponding adventure tag.
*
* This is useful for converting strings to the MiniMessage format provided
* by the Adventure library. Since users mostly prefer to use the Minecraft
* color codes, this method will convert the codes to the Adventure format.
*
* @param value The string to colorize.
* @return The colorized string.
*/
@JvmStatic
fun colorAdventure(value: String): String {
// Basically, if the user is trying to use MC color codes, we try to
// translate them to Bukkit codes. Doesn't properly handle &x format.
var value = value.replace('§', '&')
// Adventure text is formatted using tags instead
// of with symbols &7. While not a perfect fix, we can replace the
// symbols with their equivalent open color tags.
// Hardcoded literal colors (Hex is handled separately)
val replacements: MutableMap = HashMap()
replacements["&0"] = ""
replacements["&1"] = ""
replacements["&2"] = ""
replacements["&3"] = ""
replacements["&4"] = ""
replacements["&5"] = ""
replacements["&6"] = ""
replacements["&7"] = ""
replacements["&8"] = ""
replacements["&9"] = ""
replacements["&(a|A)"] = ""
replacements["&(b|B)"] = ""
replacements["&(c|C)"] = ""
replacements["&(d|D)"] = ""
replacements["&(e|E)"] = ""
replacements["&(f|F)"] = ""
// Hardcoded literal decorations
replacements["&(k|K)"] = ""
replacements["&(l|L)"] = ""
replacements["&(m|M)"] = ""
replacements["&(n|N)"] = ""
replacements["&(o|O)"] = ""
replacements["&(r|R)"] = ""
// Regex matcher to find hex color strings
val regex = Pattern.compile("([a-f]|[A-F]|\\d){6}")
val matcher = regex.matcher(value)
val builder = java.lang.StringBuilder()
while (matcher.find()) {
val match = matcher.group(0)
val replacement = "<" + match.substring(1) + ">"
matcher.appendReplacement(builder, replacement)
}
matcher.appendTail(builder)
value = builder.toString()
// Now convert simple colors and decorations
for ((key, value1) in replacements) {
value = value.replace(key.toRegex(), value1)
}
return value
}
/**
* Returns the ordinal representation of a number.
*
* For example, `1` becomes `1st`, `2` becomes `2nd`, `3` becomes `3rd`,
* `4` becomes `4th`, and so on.
*
* @param number The number to convert.
* @return The ordinal representation of the number.
*/
@JvmStatic
fun ordinal(number: Int): String {
return when {
number % 100 in 11..13 -> number.toString() + "th"
else -> number.toString() + SUFFIXES[number % 10]
}
}
/**
* Splits a string before uppercase letters.
*
* ```
* List split = StringUtils.splitCapitalLetters("HelloWorld");
* // split = ["Hello", "World"]
* ```
*
* @param str The string to split.
* @return The split string.
*/
@JvmStatic
fun splitCapitalLetters(str: String): List {
return str.split("(? split = StringUtils.splitAfterWord("Hello World");
* // split = ["Hello", "World"]
* ```
*
* @param str The string to split.
* @return The split string.
*/
@JvmStatic
fun splitAfterWord(str: String): List {
return str.split("(?!\\S+) |(?!\\S+)".toRegex()).filter {
it.isNotBlank()
}
}
/**
* Splits a string at common delimiters (whitespace, tilde `~`, and
* dashes `-`).
*
* This method correctly handles negative numbers separated by dashes.
*
* ```
* StringUtil.split("SOUND-1-5"); // ["SOUND", "1", "1"]
* StringUtil.split("Value--2-6"); // ["Value", "-2", "6"]
* StringUtil.split("Something 22 -634"); // ["Something", "22", "-634"]
* StringUtil.split("Yayyy~6423~-2"); // ["Yayyy", "6424", "-2"]
* ```
*
* @param str The string to split.
* @return The split string.
*/
@JvmStatic
fun split(str: String): List {
var str = str
var addDash = false
if (str.startsWith("-")) {
addDash = true
str = str.substring(1)
}
val split = str.split("[~ ]+|(?,
): String {
var closest: String? = null
var closestDistance = Int.MAX_VALUE
val table = toCharTable(input)
for (possibility in possibilities) {
val localTable = toCharTable(possibility)
var localDifference = abs(input.length - possibility.length)
for (i in table.indices) {
localDifference += abs(table[i] - localTable[i])
}
if (localDifference < closestDistance) {
closest = possibility
closestDistance = localDifference
}
}
return closest ?: throw IllegalArgumentException("You passed 0 possibilities to the didYouMean function.")
}
/**
* Returns a character table for a string.
*
* The character table is an array of integers where each index represents
* a character in the alphabet. The value at each index is the number of
* occurrences of the character in the string.
*
* @param str The string to convert to a character table.
* @return The character table.
*/
@JvmStatic
fun toCharTable(str: String): IntArray {
val str = str.lowercase()
val table: IntArray = IntArray(LOWER_ALPHABET.length)
for (c in str) {
val index = c.lowercaseChar() - 'a'
if (index < 0 || index >= table.size) {
continue
}
table[index]++
}
return table
}
}