io.ultreia.java4all.lang.Strings Maven / Gradle / Ivy
Show all versions of java-lang Show documentation
package io.ultreia.java4all.lang;
/*-
* #%L
* Java Lang extends by Ultreia.io
* %%
* Copyright (C) 2017 - 2024 Ultreia.io
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* .
* #L%
*/
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
/**
* Created by tchemit on 29/12/2017.
*
* @author Tony Chemit - [email protected]
*/
public class Strings {
public static final String[] EMPTY_STRING_ARRAY = new String[0];
/**
* A String for a space character.
*
* @since 3.2
*/
public static final String SPACE = " ";
public static final String EMPTY = "";
private static final double[] timeFactors = {1000000, 1000, 60, 60, 24};
private static final String[] timeUnites = {"ns", "ms", "s", "m", "h", "d"};
private static final double[] memoryFactors = {1024, 1024, 1024, 1024};
private static final String[] memoryUnites = {"o", "Ko", "Mo", "Go", "To"};
private static final char[] HEX_CHARS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',};
/**
* The maximum size to which the padding constant(s) can expand.
*/
private static final int PAD_LIMIT = 8192;
/**
* Convert a String to SHA1.
*
* @param toEncode string to encode
* @return sha1 corresponding
* @throws IllegalStateException if could not found algorithm SHA1
*/
public static String encodeSHA1(String toEncode) {
try {
MessageDigest sha1Md = MessageDigest.getInstance("SHA-1");
byte[] digest = sha1Md.digest(toEncode.getBytes());
return asHex(digest);
} catch (NoSuchAlgorithmException ex) {
throw new IllegalStateException("Can't find SHA-1 message digest algorithm", ex);
}
}
/**
* Turns array of bytes into string representing each byte as
* unsigned hex number.
*
* @param hash Array of bytes to convert to hex-string
* @return Generated hex string
*/
private static String asHex(byte hash[]) {
char buf[] = new char[hash.length * 2];
for (int i = 0, x = 0; i < hash.length; i++) {
buf[x++] = HEX_CHARS[hash[i] >>> 4 & 0xf];
buf[x++] = HEX_CHARS[hash[i] & 0xf];
}
return new String(buf);
}
/**
* Converts an time delay into a human readable format.
*
* @param value the delay to convert
* @return the memory representation of the given value
* @see #convert(long, double[], String[])
*/
public static String convertTime(long value) {
return convert(value, timeFactors, timeUnites);
}
/**
* Converts an time period into a human readable format.
*
* @param value the begin time
* @param value2 the end time
* @return the time representation of the given value
* @see #convert(long, double[], String[])
*/
public static String convertTime(long value, long value2) {
return convertTime(value2 - value);
}
/**
* Converts an memory measure into a human readable format.
*
* @param value the memory measure to convert
* @return the memory representation of the given value
* @see #convert(long, double[], String[])
*/
public static String convertMemory(long value) {
return convert(value, memoryFactors, memoryUnites);
}
/**
* Note: this method use the current locale (the {@link Locale#getDefault()}) in the method
* {@link MessageFormat#MessageFormat(String)}.
*
* @param value value to convert
* @param factors factors used form conversion
* @param unites libelle of unites to use
* @return the converted representation of the given value
*/
public static String convert(long value, double[] factors, String[] unites) {
long sign = value == 0 ? 1 : value / Math.abs(value);
int i = 0;
double tmp = Math.abs(value);
while (i < factors.length && i < unites.length && tmp > factors[i]) {
tmp = tmp / factors[i++];
}
tmp *= sign;
String result;
result = MessageFormat.format("{0,number,0.###}{1}", tmp, unites[i]);
return result;
}
/**
* Convertir un nom en une constante Java
*
* Les seuls caractères autorisés sont les alpha numériques, ains
* que l'underscore. tous les autres caractères seront ignorés.
*
* @param name le nom à convertir
* @return la constante générée
*/
public static String convertToConstantName(String name) {
StringBuilder sb = new StringBuilder();
char lastChar = 0;
for (int i = 0, j = name.length(); i < j; i++) {
char c = name.charAt(i);
if (Character.isDigit(c)) {
sb.append(c);
lastChar = c;
continue;
}
if (!Character.isLetter(c)) {
if (lastChar != '_') {
sb.append('_');
}
lastChar = '_';
continue;
}
if (Character.isUpperCase(c)) {
if (!Character.isUpperCase(lastChar) && lastChar != '_') {
sb.append('_');
}
sb.append(c);
} else {
sb.append(Character.toUpperCase(c));
}
lastChar = c;
}
String result = sb.toString();
// clean tail
while (!result.isEmpty() && result.endsWith("_")) {
result = result.substring(0, result.length() - 1);
}
// clean head
while (!result.isEmpty() && result.startsWith("_")) {
result = result.substring(1);
}
return result;
}
/**
* Get the relative camel case name for the given {@code fullyQualifiedName}.
*
* first try to remove {@code packageName}, then any {@code excludes} on starts, at last reduce with came case.
*
* @param packageName starts package
* @param fullyQualifiedName fqn to transform
* @param excludes optional starts packages to remove
* @return the camel case result
*/
public static String getRelativeCamelCaseName(String packageName, String fullyQualifiedName, String... excludes) {
String relativeType = removeStart(Objects.requireNonNull(fullyQualifiedName), Objects.requireNonNull(packageName));
for (String exclude : excludes) {
relativeType = removeStart(relativeType, "." + exclude);
}
relativeType = removeStart(relativeType, ".");
String[] parts = relativeType.split("\\.");
StringBuilder result = new StringBuilder();
for (String part : parts) {
result.append(("" + part.charAt(0)).toUpperCase());
result.append(part.substring(1));
}
return result.toString();
}
/**
*
Left pad a String with a specified String.
*
* Pad to a size of {@code size}.
*
*
* Strings.leftPad(null, *, *) = null
* Strings.leftPad("", 3, "z") = "zzz"
* Strings.leftPad("bat", 3, "yz") = "bat"
* Strings.leftPad("bat", 5, "yz") = "yzbat"
* Strings.leftPad("bat", 8, "yz") = "yzyzybat"
* Strings.leftPad("bat", 1, "yz") = "bat"
* Strings.leftPad("bat", -1, "yz") = "bat"
* Strings.leftPad("bat", 5, null) = " bat"
* Strings.leftPad("bat", 5, "") = " bat"
*
*
* @param str the String to pad out, may be null
* @param size the size to pad to
* @param padStr the String to pad with, null or empty treated as single space
* @return left padded String or original String if no padding is necessary, {@code null} if null String input
*/
public static String leftPad(final String str, final int size, String padStr) {
if (str == null) {
return null;
}
if (isEmpty(padStr)) {
padStr = SPACE;
}
final int padLen = padStr.length();
final int strLen = str.length();
final int pads = size - strLen;
if (pads <= 0) {
return str; // returns original String when possible
}
if (padLen == 1 && pads <= PAD_LIMIT) {
return leftPad(str, size, padStr.charAt(0));
}
if (pads == padLen) {
return padStr.concat(str);
} else if (pads < padLen) {
return padStr.substring(0, pads).concat(str);
} else {
final char[] padding = new char[pads];
final char[] padChars = padStr.toCharArray();
for (int i = 0; i < pads; i++) {
padding[i] = padChars[i % padLen];
}
return new String(padding).concat(str);
}
}
/**
* Left pad a String with spaces (' ').
*
* The String is padded to the size of {@code size}.
*
*
* Strings.leftPad(null, *) = null
* Strings.leftPad("", 3) = " "
* Strings.leftPad("bat", 3) = "bat"
* Strings.leftPad("bat", 5) = " bat"
* Strings.leftPad("bat", 1) = "bat"
* Strings.leftPad("bat", -1) = "bat"
*
*
* @param str the String to pad out, may be null
* @param size the size to pad to
* @return left padded String or original String if no padding is necessary, {@code null} if null String input
*/
public static String leftPad(final String str, final int size) {
return leftPad(str, size, ' ');
}
/**
* Left pad a String with a specified character.
*
* Pad to a size of {@code size}.
*
*
* Strings.leftPad(null, *, *) = null
* Strings.leftPad("", 3, 'z') = "zzz"
* Strings.leftPad("bat", 3, 'z') = "bat"
* Strings.leftPad("bat", 5, 'z') = "zzbat"
* Strings.leftPad("bat", 1, 'z') = "bat"
* Strings.leftPad("bat", -1, 'z') = "bat"
*
*
* @param str the String to pad out, may be null
* @param size the size to pad to
* @param padChar the character to pad with
* @return left padded String or original String if no padding is necessary, {@code null} if null String input
*/
public static String leftPad(final String str, final int size, final char padChar) {
if (str == null) {
return null;
}
final int pads = size - str.length();
if (pads <= 0) {
return str; // returns original String when possible
}
if (pads > PAD_LIMIT) {
return leftPad(str, size, String.valueOf(padChar));
}
return repeat(padChar, pads).concat(str);
}
public static boolean isEmpty(String o) {
return o == null || o.isEmpty();
}
public static boolean isNotEmpty(String o) {
return !isEmpty(o);
}
/**
* Checks if the CharSequence contains only Unicode digits.
* A decimal point is not a Unicode digit and returns false.
*
* {@code null} will return {@code false}.
* An empty CharSequence (length()=0) will return {@code false}.
*
* Note that the method does not allow for a leading sign, either positive or negative.
* Also, if a String passes the numeric test, it may still generate a NumberFormatException
* when parsed by Integer.parseInt or Long.parseLong, e.g. if the value is outside the range
* for int or long respectively.
*
*
* Strings.isNumeric(null) = false
* Strings.isNumeric("") = false
* Strings.isNumeric(" ") = false
* Strings.isNumeric("123") = true
* Strings.isNumeric("\u0967\u0968\u0969") = true
* Strings.isNumeric("12 3") = false
* Strings.isNumeric("ab2c") = false
* Strings.isNumeric("12-3") = false
* Strings.isNumeric("12.3") = false
* Strings.isNumeric("-123") = false
* Strings.isNumeric("+123") = false
*
*
* @param cs the CharSequence to check, may be null
* @return {@code true} if only contains digits, and is non-null
*/
public static boolean isNumeric(String cs) {
if (isEmpty(cs)) {
return false;
}
final int sz = cs.length();
for (int i = 0; i < sz; i++) {
if (!Character.isDigit(cs.charAt(i))) {
return false;
}
}
return true;
}
/**
* Capitalizes a String changing the first character to title case as
* per {@link Character#toTitleCase(int)}. No other characters are changed.
*
*
* Strings.capitalize(null) = null
* Strings.capitalize("") = ""
* Strings.capitalize("cat") = "Cat"
* Strings.capitalize("cAt") = "CAt"
* Strings.capitalize("'cat'") = "'cat'"
*
*
* @param str the String to capitalize, may be null
* @return the capitalized String, {@code null} if null String input
*/
public static String capitalize(String str) {
final int strLen = str == null ? 0 : str.length();
if (strLen == 0) {
return str;
}
final int firstCodepoint = str.codePointAt(0);
final int newCodePoint = Character.toTitleCase(firstCodepoint);
if (firstCodepoint == newCodePoint) {
// already capitalized
return str;
}
final int[] newCodePoints = new int[strLen]; // cannot be longer than the char array
int outOffset = 0;
newCodePoints[outOffset++] = newCodePoint; // copy the first codepoint
for (int inOffset = Character.charCount(firstCodepoint); inOffset < strLen; ) {
final int codepoint = str.codePointAt(inOffset);
newCodePoints[outOffset++] = codepoint; // copy the remaining ones
inOffset += Character.charCount(codepoint);
}
return new String(newCodePoints, 0, outOffset);
}
public static String removeStart(String str, String remove) {
if (isEmpty(str) || isEmpty(remove)) {
return str;
}
if (str.startsWith(remove)) {
return str.substring(remove.length());
}
return str;
}
/**
* Removes a substring only if it is at the end of a source string,
* otherwise returns the source string.
*
* A {@code null} source string will return {@code null}.
* An empty ("") source string will return the empty string.
* A {@code null} search string will return the source string.
*
*
* Strings.removeEnd(null, *) = null
* Strings.removeEnd("", *) = ""
* Strings.removeEnd(*, null) = *
* Strings.removeEnd("www.domain.com", ".com.") = "www.domain.com"
* Strings.removeEnd("www.domain.com", ".com") = "www.domain"
* Strings.removeEnd("www.domain.com", "domain") = "www.domain.com"
* Strings.removeEnd("abc", "") = "abc"
*
*
* @param str the source String to search, may be null
* @param remove the String to search for and remove, may be null
* @return the substring with the string removed if found,
* {@code null} if null String input
* @since 2.1
*/
public static String removeEnd(final String str, final String remove) {
if (isEmpty(str) || isEmpty(remove)) {
return str;
}
if (str.endsWith(remove)) {
return str.substring(0, str.length() - remove.length());
}
return str;
}
/**
* Right pad a String with a specified String.
*
* The String is padded to the size of {@code size}.
*
*
* Strings.rightPad(null, *, *) = null
* Strings.rightPad("", 3, "z") = "zzz"
* Strings.rightPad("bat", 3, "yz") = "bat"
* Strings.rightPad("bat", 5, "yz") = "batyz"
* Strings.rightPad("bat", 8, "yz") = "batyzyzy"
* Strings.rightPad("bat", 1, "yz") = "bat"
* Strings.rightPad("bat", -1, "yz") = "bat"
* Strings.rightPad("bat", 5, null) = "bat "
* Strings.rightPad("bat", 5, "") = "bat "
*
*
* @param str the String to pad out, may be null
* @param size the size to pad to
* @param padStr the String to pad with, null or empty treated as single space
* @return right padded String or original String if no padding is necessary, {@code null} if null String input
*/
public static String rightPad(String str, int size, String padStr) {
if (str == null) {
return null;
}
if (isEmpty(padStr)) {
padStr = SPACE;
}
final int padLen = padStr.length();
final int strLen = str.length();
final int pads = size - strLen;
if (pads <= 0) {
return str; // returns original String when possible
}
if (padLen == 1 && pads <= PAD_LIMIT) {
return rightPad(str, size, padStr.charAt(0));
}
if (pads == padLen) {
return str.concat(padStr);
} else if (pads < padLen) {
return str.concat(padStr.substring(0, pads));
} else {
final char[] padding = new char[pads];
final char[] padChars = padStr.toCharArray();
for (int i = 0; i < pads; i++) {
padding[i] = padChars[i % padLen];
}
return str.concat(new String(padding));
}
}
/**
* Right pad a String with a specified character.
*
* The String is padded to the size of {@code size}.
*
*
* Strings.rightPad(null, *, *) = null
* Strings.rightPad("", 3, 'z') = "zzz"
* Strings.rightPad("bat", 3, 'z') = "bat"
* Strings.rightPad("bat", 5, 'z') = "batzz"
* Strings.rightPad("bat", 1, 'z') = "bat"
* Strings.rightPad("bat", -1, 'z') = "bat"
*
*
* @param str the String to pad out, may be null
* @param size the size to pad to
* @param padChar the character to pad with
* @return right padded String or original String if no padding is necessary, {@code null} if null String input
*/
public static String rightPad(String str, int size, char padChar) {
if (str == null) {
return null;
}
final int pads = size - str.length();
if (pads <= 0) {
return str; // returns original String when possible
}
if (pads > PAD_LIMIT) {
return rightPad(str, size, String.valueOf(padChar));
}
return str.concat(repeat(padChar, pads));
}
/**
* Gets the substring after the first occurrence of a separator.
* The separator is not returned.
*
* A {@code null} string input will return {@code null}.
* An empty ("") string input will return the empty string.
* A {@code null} separator will return the empty string if the
* input string is not {@code null}.
*
* If nothing is found, the empty string is returned.
*
*
* Strings.substringAfter(null, *) = null
* Strings.substringAfter("", *) = ""
* Strings.substringAfter(*, null) = ""
* Strings.substringAfter("abc", "a") = "bc"
* Strings.substringAfter("abcba", "b") = "cba"
* Strings.substringAfter("abc", "c") = ""
* Strings.substringAfter("abc", "d") = ""
* Strings.substringAfter("abc", "") = "abc"
*
*
* @param str the String to get a substring from, may be null
* @param separator the String to search for, may be null
* @return the substring after the first occurrence of the separator,
* {@code null} if null String input
*/
public static String substringAfter(final String str, final String separator) {
if (isEmpty(str)) {
return str;
}
if (separator == null) {
return EMPTY;
}
final int pos = str.indexOf(separator);
if (pos == -1) {
return EMPTY;
}
return str.substring(pos + separator.length());
}
/**
* Gets the substring before the first occurrence of a separator.
* The separator is not returned.
*
* A {@code null} string input will return {@code null}.
* An empty ("") string input will return the empty string.
* A {@code null} separator will return the input string.
*
* If nothing is found, the string input is returned.
*
*
* Strings.substringBefore(null, *) = null
* Strings.substringBefore("", *) = ""
* Strings.substringBefore("abc", "a") = ""
* Strings.substringBefore("abcba", "b") = "a"
* Strings.substringBefore("abc", "c") = "ab"
* Strings.substringBefore("abc", "d") = "abc"
* Strings.substringBefore("abc", "") = ""
* Strings.substringBefore("abc", null) = "abc"
*
*
* @param str the String to get a substring from, may be null
* @param separator the String to search for, may be null
* @return the substring before the first occurrence of the separator, {@code null} if null String input
*/
public static String substringBefore(String str, String separator) {
if (isEmpty(str) || separator == null) {
return str;
}
if (separator.isEmpty()) {
return EMPTY;
}
final int pos = str.indexOf(separator);
if (pos == -1) {
return str;
}
return str.substring(0, pos);
}
/**
* Splits the provided text into an array, separators specified.
* This is an alternative to using StringTokenizer.
*
* 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}.
* A {@code null} separatorChars splits on whitespace.
*
*
* Strings.split(null, *) = null
* Strings.split("", *) = []
* Strings.split("abc def", null) = ["abc", "def"]
* Strings.split("abc def", " ") = ["abc", "def"]
* Strings.split("abc def", " ") = ["abc", "def"]
* Strings.split("ab:cd:ef", ":") = ["ab", "cd", "ef"]
*
*
* @param str the String to parse, may be null
* @param separatorChars the characters used as the delimiters,
* {@code null} splits on whitespace
* @return an array of parsed Strings, {@code null} if null String input
*/
public static String[] split(final String str, final String separatorChars) {
return splitWorker(str, separatorChars, -1, false);
}
/**
* Performs the logic for the {@code split} and
* {@code splitPreserveAllTokens} methods that return a maximum array
* length.
*
* @param str the String to parse, may be {@code null}
* @param separatorChars the separate character
* @param max the maximum number of elements to include in the
* array. A zero or negative value implies no limit.
* @param preserveAllTokens if {@code true}, adjacent separators are
* treated as empty token separators; if {@code false}, adjacent
* separators are treated as one separator.
* @return an array of parsed Strings, {@code null} if null String input
*/
private static String[] splitWorker(final String str, final String separatorChars, final int max, final boolean preserveAllTokens) {
// Performance tuned for 2.0 (JDK1.4)
// Direct code is quicker than StringTokenizer.
// Also, StringTokenizer uses isSpace() not isWhitespace()
if (str == null) {
return null;
}
final int len = str.length();
if (len == 0) {
return EMPTY_STRING_ARRAY;
}
final List list = new ArrayList<>();
int sizePlus1 = 1;
int i = 0;
int start = 0;
boolean match = false;
boolean lastMatch = false;
if (separatorChars == null) {
// Null separator means use whitespace
while (i < len) {
if (Character.isWhitespace(str.charAt(i))) {
if (match || preserveAllTokens) {
lastMatch = true;
if (sizePlus1++ == max) {
i = len;
lastMatch = false;
}
list.add(str.substring(start, i));
match = false;
}
start = ++i;
continue;
}
lastMatch = false;
match = true;
i++;
}
} else if (separatorChars.length() == 1) {
// Optimise 1 character case
final char sep = separatorChars.charAt(0);
while (i < len) {
if (str.charAt(i) == sep) {
if (match || preserveAllTokens) {
lastMatch = true;
if (sizePlus1++ == max) {
i = len;
lastMatch = false;
}
list.add(str.substring(start, i));
match = false;
}
start = ++i;
continue;
}
lastMatch = false;
match = true;
i++;
}
} else {
// standard case
while (i < len) {
if (separatorChars.indexOf(str.charAt(i)) >= 0) {
if (match || preserveAllTokens) {
lastMatch = true;
if (sizePlus1++ == max) {
i = len;
lastMatch = false;
}
list.add(str.substring(start, i));
match = false;
}
start = ++i;
continue;
}
lastMatch = false;
match = true;
i++;
}
}
if (match || preserveAllTokens && lastMatch) {
list.add(str.substring(start, i));
}
return list.toArray(EMPTY_STRING_ARRAY);
}
/**
* Returns padding using the specified delimiter repeated
* to a given length.
*
*
* Strings.repeat('e', 0) = ""
* Strings.repeat('e', 3) = "eee"
* Strings.repeat('e', -2) = ""
*
*
* @param ch character to repeat
* @param repeat number of times to repeat char, negative treated as zero
* @return String with repeated character
*/
public static String repeat(char ch, int repeat) {
if (repeat <= 0) {
return EMPTY;
}
final char[] buf = new char[repeat];
Arrays.fill(buf, ch);
return new String(buf);
}
}