org.openl.util.StringUtils Maven / Gradle / Ivy
package org.openl.util;
import java.util.Arrays;
import java.util.function.IntPredicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* Utils to manipulate with strings.
*
* @author Yury Molchan
*/
public class StringUtils {
public static final String[] EMPTY_STRING_ARRAY = new String[0];
public static final String EMPTY = "";
public static final String SPACE = " ";
/**
*
* Splits the provided string into an array of trimmed strings, separator specified. The separator is not included
* in the returned String array. Adjacent separators are treated as one separator.
*
*
* A {@code null} input String returns {@code null}.
*
*
*
* StringUtils.split(null, *) = null
* StringUtils.split("", *) = []
* StringUtils.split("a.b.c", '.') = ["a", "b", "c"]
* StringUtils.split("a..b.c", '.') = ["a", "b", "c"]
* StringUtils.split("a . .b.c", '.') = ["a", "b", "c"]
* StringUtils.split(" a b:c ", '.') = ["a b:c"]
* StringUtils.split("a b c", ' ') = ["a", "b", "c"]
* StringUtils.split(" a, b, c", ',') = ["a", "b", "c"]
*
*
* @param str the String to parse, may be null
* @param separator the character used as the delimiter
* @return an array of parsed Strings, {@code null} if null String input
*/
public static String[] split(final String str, final char separator) {
return splitWorker(str, ch -> ch == separator);
}
/**
*
* Splits the provided text into an array, using whitespace as the separator. Whitespace is defined by
* {@link Character#isWhitespace(char)}.
*
*
* The separator is not included in the returned String array. Adjacent separators are treated as one separator. For
* more control over the split use the StrTokenizer class.
*
*
* A {@code null} input String returns {@code null}.
*
*
*
* StringUtils.split(null) = null
* StringUtils.split("") = []
* StringUtils.split("abc def") = ["abc", "def"]
* StringUtils.split("abc def") = ["abc", "def"]
* StringUtils.split(" abc ") = ["abc"]
* StringUtils.split(" abc def ") = ["abc", "def"]
*
*
* @param str the String to parse, may be null
* @return an array of parsed Strings, {@code null} if null String input
*/
public static String[] split(final String str) {
return splitWorker(str, Character::isWhitespace);
}
private static String[] splitWorker(final String str, final IntPredicate tester) {
if (str == null) {
return null;
}
final int len = str.length();
// count of the result size
var count = 0;
var match = false;
for (int i = 0; i < len; i++) {
var ch = str.charAt(i);
if (tester.test(ch)) {
match = false;
} else if (!match && !isSpaceOrControl(ch)) {
count++;
match = true;
}
}
if (count == 0) {
return EMPTY_STRING_ARRAY;
}
var result = new String[count];
// splitting by separator and stripping a token
int i = 0, start = 0, end = 0;
match = false;
count = 0;
while (i < len) {
char ch = str.charAt(i++);
if (tester.test(ch)) {
if (match) {
result[count] = str.substring(start, end);
match = false;
count++;
}
start = i;
} else if (isSpaceOrControl(ch)) {
if (!match) {
start = i;
}
} else {
match = true;
end = i;
}
}
if (match) {
result[count] = str.substring(start, end);
}
return result;
}
/**
*
* Splits the provided string into an array of trimmed strings, separated by new line character codes: {@code '\n'}
* and {@code '\r'}. Blank lines are omitted from the result.
*
*
* A blank or {@code null} input String returns {@code null}.
*
*
*
* StringUtils.toLines(null) = null
* StringUtils.toLines("") = null
* StringUtils.toLines(" ") = null
* StringUtils.toLines(" abc ") = ["abc"]
* StringUtils.toLines("a b c") = ["a b c"]
* StringUtils.toLines("a\rb\nc") = ["a", "b", "c"]
* StringUtils.toLines("\r a\n\t\r \n c \n") = ["a", "c"]
*
*
* @param text the String to parse, may be null
* @return an array of parsed Strings, {@code null} if blank String input
*/
public static String[] toLines(final String text) {
if (isBlank(text)) {
return null;
}
// Trim and split by one of the
return text.trim().split("\\s*[\r\n]\\s*");
}
/**
*
* Joins the elements of the provided array into a single String containing the provided list of elements.
*
*
* No delimiter is added before or after the list. A {@code null} separator is the same as an empty String ("").
* Null objects or empty strings within the array are represented by empty strings.
*
*
*
* StringUtils.join(null, *) = null
* StringUtils.join([], *) = ""
* StringUtils.join([null], *) = "null"
* StringUtils.join(["a", "b", "c"], "--") = "a--b--c"
* StringUtils.join(["a", "b", "c"], "") = "abc"
* StringUtils.join([null, "", "a"], ',') = "null,,a"
*
*
* @param values the array of values to join together, may be null
* @param separator the separator character to use, null treated as ""
* @return the joined String, {@code null} if null array input
*/
public static String join(Object[] values, String separator) {
if (values == null) {
return null;
}
return Arrays.stream(values).map(String::valueOf).collect(Collectors.joining(separator));
}
/**
*
* Checks if a CharSequence is empty ("") or null.
*
*
*
* StringUtils.isEmpty(null) = true
* StringUtils.isEmpty("") = true
* StringUtils.isEmpty(" ") = false
* StringUtils.isEmpty("boo") = false
* StringUtils.isEmpty(" boo ") = false
*
*
* @param cs the CharSequence to check, may be null
* @return {@code true} if the CharSequence is empty or null
*/
public static boolean isEmpty(final CharSequence cs) {
return cs == null || cs.length() == 0;
}
/**
*
* Checks if a CharSequence is not empty ("") and not null.
*
*
*
* StringUtils.isNotEmpty(null) = false
* StringUtils.isNotEmpty("") = false
* StringUtils.isNotEmpty(" ") = true
* StringUtils.isNotEmpty("boo") = true
* StringUtils.isNotEmpty(" boo ") = true
*
*
* @param cs the CharSequence to check, may be null
* @return {@code true} if the CharSequence is not empty and not null
*/
public static boolean isNotEmpty(final CharSequence cs) {
return !isEmpty(cs);
}
/**
*
* Checks if a CharSequence is whitespace, empty ("") or null.
*
*
*
* StringUtils.isBlank(null) = true
* StringUtils.isBlank("") = true
* StringUtils.isBlank(" ") = true
* StringUtils.isBlank("boo") = false
* StringUtils.isBlank(" boo ") = false
*
*
* @param cs the CharSequence to check, may be null
* @return {@code true} if the CharSequence is null, empty or whitespace
*/
public static boolean isBlank(final CharSequence cs) {
if (isEmpty(cs)) {
return true;
}
return firstNonSpace(cs, 0, cs.length()) < 0;
}
/**
*
* Checks if a CharSequence is not empty (""), not null and not whitespace only.
*
*
*
* StringUtils.isNotBlank(null) = false
* StringUtils.isNotBlank("") = false
* StringUtils.isNotBlank(" ") = false
* StringUtils.isNotBlank("boo") = true
* StringUtils.isNotBlank(" boo ") = true
*
*
* @param cs the CharSequence to check, may be null
* @return {@code true} if the CharSequence is not empty and not null and not whitespace
*/
public static boolean isNotBlank(final CharSequence cs) {
return !isBlank(cs);
}
/**
*
* Checks if String contains a search String irrespective of case, handling {@code null}. Case-insensitivity is
* defined as by {@link String#equalsIgnoreCase(String)}.
*
*
* A {@code null} String will return {@code false}.
*
*
*
* StringUtils.contains(null, *) = false
* StringUtils.contains(*, null) = false
* StringUtils.contains("", "") = true
* StringUtils.contains("abc", "") = true
* StringUtils.contains("abc", "a") = true
* StringUtils.contains("abc", "Bc") = true
* StringUtils.contains("abc", "z") = false
* StringUtils.contains("abc", "Z") = false
*
*
* @param str the String to check, may be null
* @param searchStr the String to find, may be null
* @return true if the String contains the search String irrespective of case or false if not or {@code null} string
* input
*/
public static boolean containsIgnoreCase(final String str, final String searchStr) {
if (str == null || searchStr == null) {
return false;
}
final int len = searchStr.length();
final int end = str.length() - len;
for (int i = 0; i <= end; i++) {
if (str.regionMatches(true, i, searchStr, 0, len)) {
return true;
}
}
return false;
}
/**
* Do the same as {@link java.lang.String#matches(String)}
*
* @param regex a Pattern to which this string is to be matched
* @param input an input string to match regexp Pattern
* @return {@code true} if, and only if, this string matches the given regular expression
*/
public static boolean matches(Pattern regex, CharSequence input) {
Matcher m = regex.matcher(input);
return m.matches();
}
/**
*
* Removes control characters (char <= 32 or (>= 127 and <= 160) from both ends of this String, handling
* {@code null} by returning {@code null}.
*
*
*
* StringUtils.trim(null) = null
* StringUtils.trim("") = ""
* StringUtils.trim(" ") = ""
* StringUtils.trim("boo") = "boo"
* StringUtils.trim(" boo ") = "boo"
*
*
* @param str the String to be trimmed, may be null
* @return the trimmed string, {@code null} if null String input
*/
public static String trim(final String str) {
if (str == null || str.isEmpty()) {
return str;
}
int start = 0;
while (start < str.length() && isSpaceOrControl(str.charAt(start))) {
start++;
}
int end = str.length();
while (start < end && isSpaceOrControl(str.charAt(end - 1))) {
end--;
}
return (start > 0 || end < str.length()) ? str.substring(start, end) : str;
}
/**
* Determines if the specified character is whitespace or control character (char <= 32 or (>= 127 and <=
* 160)
*
* @param ch the character to be tested
* @return {@code true} if character is whitespace or control, otherwise {@code false}
*/
public static boolean isSpaceOrControl(int ch) {
return ch <= ' ' || Character.isWhitespace(ch) || Character.isISOControl(ch) || Character.isSpaceChar(ch);
}
/**
*
* Removes control characters (char <= 32 or (>= 127 and <= 160) from both ends of this String returning
* {@code null} if the String is empty ("") after the trim or if it is {@code null}.
*
*
*
* StringUtils.trimToNull(null) = null
* StringUtils.trimToNull("") = null
* StringUtils.trimToNull(" ") = null
* StringUtils.trimToNull("boo") = "boo"
* StringUtils.trimToNull(" boo ") = "boo"
*
*
* @param str the String to be trimmed, may be null
* @return the trimmed String, {@code null} if only chars <= 32, empty or null String input
*/
public static String trimToNull(final String str) {
final String ts = trim(str);
return isEmpty(ts) ? null : ts;
}
/**
*
* Removes control characters (char <= 32 or (>= 127 and <= 160) from both ends of this String returning an
* empty String ("") if the String is empty ("") after the trim or if it is {@code null}.
*
*
*
* StringUtils.trimToEmpty(null) = ""
* StringUtils.trimToEmpty("") = ""
* StringUtils.trimToEmpty(" ") = ""
* StringUtils.trimToEmpty("boo") = "boo"
* StringUtils.trimToEmpty(" boo ") = "boo"
*
*
* @param str the String to be trimmed, may be null
* @return the trimmed String, or an empty String if {@code null} input
*/
public static String trimToEmpty(final String str) {
return str == null ? EMPTY : trim(str);
}
/**
*
* Capitalizes a String changing the first letter to title case as per {@link Character#toTitleCase(char)}. No other
* letters are changed.
*
*
* A {@code null} input String returns {@code null}.
*
*
*
* StringUtils.capitalize(null) = null
* StringUtils.capitalize("") = ""
* StringUtils.capitalize("foo") = "Foo"
* StringUtils.capitalize("fOo") = "FOo"
*
*
* @param str the String to capitalize, may be null
* @return the capitalized String, {@code null} if null String input
* @see #uncapitalize(String)
*/
public static String capitalize(final String str) {
if (isEmpty(str)) {
return str;
}
char firstChar = str.charAt(0);
if (Character.isTitleCase(firstChar)) {
// already capitalized
return str;
}
return Character.toTitleCase(firstChar) + str.substring(1);
}
/**
*
* Uncapitalizes a String changing the first letter to title case as per {@link Character#toLowerCase(char)}. No
* other letters are changed.
*
*
* A {@code null} input String returns {@code null}.
*
*
*
* StringUtils.uncapitalize(null) = null
* StringUtils.uncapitalize("") = ""
* StringUtils.uncapitalize("Foo") = "foo"
* StringUtils.uncapitalize("FOO") = "fOO"
*
*
* @param str the String to uncapitalize, may be null
* @return the uncapitalized String, {@code null} if null String input
* @see #capitalize(String)
*/
public static String uncapitalize(final String str) {
if (isEmpty(str)) {
return str;
}
char firstChar = str.charAt(0);
if (Character.isLowerCase(firstChar)) {
// already uncapitalized
return str;
}
return Character.toLowerCase(firstChar) + str.substring(1);
}
/**
* Converts CamelCased string to kebab-cased. It is useful for converting Java's fields or methods to case
* insensetive form, e.g. for properties keys, urls, MS Windows file names and e.t.c.
*
*
* StringUtils.camelToKebab(null) = null
* StringUtils.camelToKebab("") = ""
* StringUtils.camelToKebab("FOO") = "foo"
* StringUtils.camelToKebab("Foo") = "foo"
* StringUtils.camelToKebab("foo") = "foo"
* StringUtils.camelToKebab("FooBar") = "foo-bar"
* StringUtils.camelToKebab("fooBar") = "foo-bar"
* StringUtils.camelToKebab("FOOBar") = "foo-bar"
* StringUtils.camelToKebab("ABar") = "a-bar"
* StringUtils.camelToKebab("aBar") = "a-bar"
* StringUtils.camelToKebab("aBAR") = "a-bar"
*
*
* @param camel - CamelCased string
* @return - kebab-cased string
*/
public static String camelToKebab(String camel) {
if (isEmpty(camel)) {
return camel;
}
int length = camel.length();
StringBuilder sb = new StringBuilder(length + (length / 4) + 1); // Every 4th symbol expected to be Upper cased
for (int i = 0; i < length; i++) {
char c = camel.charAt(i);
if (Character.isUpperCase(c)) {
if (i > 0 && (i < (length - 1) && Character.isLowerCase(camel.charAt(i + 1)) || Character
.isLowerCase(camel.charAt(i - 1)))) {
sb.append("-");
}
c = Character.toLowerCase(c);
sb.append(c);
} else {
sb.append(c);
}
}
return sb.toString();
}
/**
* Find position of the first occurrence of the symbol is matched to the predicate.
*
* @param text the string where a not white space symbol should be searched
* @param start the start position of the searching inclusive
* @param end the final position of searching exclusive
* @param tester the predicate for searching the symbol
* @return position of the symbol or -1
*/
public static int first(CharSequence text, int start, int end, IntPredicate tester) {
end = Math.min(text.length(), end);
for (int i = Math.max(start, 0); i < end; i++) {
char ch = text.charAt(i);
if (tester.test(ch)) {
return i;
}
}
return -1;
}
/**
* Find position of the last occurrence of the symbol is matched to the predicate.
*
* @param text the string where a not white space symbol should be searched
* @param start the start position of the searching inclusive
* @param end the final position of searching exclusive
* @param tester the predicate for searching the symbol
* @return position of the symbol or -1
*/
public static int last(CharSequence text, int start, int end, IntPredicate tester) {
start = Math.max(start, 0);
for (int i = Math.min(text.length(), end) - 1; i >= start; i--) {
char ch = text.charAt(i);
if (tester.test(ch)) {
return i;
}
}
return -1;
}
/**
* Find position of the next not white space symbol.
*
* @param text the string where a not white space symbol should be searched
* @param start the start position of the searching inclusive
* @param end the final position of searching exclusive
* @return position of a not white space symbol or -1
*/
public static int firstNonSpace(CharSequence text, int start, int end) {
return first(text, start, end, ch -> !isSpaceOrControl(ch));
}
/**
* Find position of the last not white space symbol.
*
* @param text the string where a not white space symbol should be searched
* @param start the start position of the searching inclusive
* @param end the final position of searching exclusive
* @return position of a not white space symbol or -1
*/
public static int lastNonSpace(CharSequence text, int start, int end) {
return last(text, start, end, ch -> !isSpaceOrControl(ch));
}
}