net.freeutils.util.Strings Maven / Gradle / Ivy
Show all versions of jelementary Show documentation
/*
* Copyright © 2003-2024 Amichai Rothman
*
* This file is part of JElementary - the Java Elementary Utilities package.
*
* JElementary is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* JElementary 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 Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with JElementary. If not, see .
*
* For additional info see https://www.freeutils.net/source/jelementary/
*/
package net.freeutils.util;
import java.io.*;
import java.net.URLDecoder;
import java.nio.ByteBuffer;
import java.nio.charset.*;
import java.util.*;
/**
* The Strings
class contains utility methods related to Strings.
*/
public class Strings {
/**
* A convenience array containing the carriage-return and line feed ASCII characters.
*/
public static final byte[] CRLF = { 0x0d, 0x0a };
public static final String
DIGITS = "0123456789",
HEX_DIGITS_LOWERCASE = DIGITS + "abcdef",
HEX_DIGITS_UPPERCASE = DIGITS + "ABCDEF",
HEX_DIGITS = DIGITS + "abcdefABCDEF",
ALPHA_LOWERCASE = "abcdefghijklmnopqrstuvwxyz",
ALPHA_UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
ALPHA = ALPHA_LOWERCASE + ALPHA_UPPERCASE,
ALPHANUMERIC_LOWERCASE = ALPHA_LOWERCASE + DIGITS,
ALPHANUMERIC_UPPERCASE = ALPHA_UPPERCASE + DIGITS,
ALPHANUMERIC = ALPHA + DIGITS;
private static final char[] HEX_CHARS_UPPERCASE = HEX_DIGITS_UPPERCASE.toCharArray();
/**
* Private constructor to avoid external instantiation.
*/
private Strings() {}
/**
* Returns the given object's toString(), or an empty String if either
* the object or its toString() is null.
*
* @param o the object whose string representation is requested
* @return the object's string representation, or an empty string
*/
public static String contentOf(Object o) {
o = o == null ? "" : o.toString();
return o == null ? "" : (String)o;
}
/**
* Returns the output of {@link Arrays#deepToString(Object[]) deepToString}
* if object is an array, or the output of the given object's
* {@link Object#toString() toString()} method, or an empty String
* if either the object or its toString() is null.
*
* @param o the object whose string representation is requested
* @return the object's string representation, or an empty string
*/
public static String contentOfArray(Object o) {
o = o == null ? "" : o.getClass().isArray() ? Arrays.deepToString((Object[])o) : o.toString();
return o == null ? "" : (String)o;
}
/**
* Returns the given object's toString(), or the default String if either
* the object or its toString() is null.
*
* @param o the object whose string representation is requested
* @param def the default string to return if the object or its string is null
* @return the object's string representation, or the default
*/
public static String contentOf(Object o, String def) {
o = o == null ? def : o.toString();
return o == null ? def : (String)o;
}
/**
* Returns the index of given char within given character array, or -1
* if the char does not appear in the array.
*
* @param chars the characters to search within
* @param c the character to search for
* @return the index of c within chars, or -1 if it does not appear
* in chars
*/
public static int indexOf(char[] chars, char c) {
for (int i = 0; i < chars.length; i++)
if (chars[i] == c)
return i;
return -1;
}
/**
* Gets a formatted String specifying the given size,
* e.g. "316", "1.8K", "324M", etc.
*
* @param size the size to format
* @return a formatted size String
*/
public static String toSizeString(long size) {
if (size < 0)
return "-";
final char[] units = { 'B', 'K', 'M', 'G', 'T', 'P', 'E' };
int u;
double s;
for (u = 0, s = size; s >= 1024; u++, s /= 1024);
return String.format(u > 1 ? "%.2f%c" : "%." + u + "f%c", s, units[u]);
}
/**
* Parses a formatted String specifying a given size, return the size in bytes,
* e.g. "316", "1.8K", "324M", etc.
*
* @param str the formatted size string
* @return the size in bytes
*/
public static long parseSizeString(String str) {
int len = str.length();
if (len == 0)
throw new IllegalArgumentException("invalid size string: " + str);
final char[] units = { 'B', 'K', 'M', 'G', 'T', 'P', 'E' };
int ind;
int c = Character.toUpperCase(str.charAt(len - 1));
if (c == 'B' && len > 1) { // only 'B' (the same as no suffix) or non-digit followed by B (e.g. "MB")
len--;
c = Character.toUpperCase(str.charAt(len - 1));
}
if (c >= '0' && c <= '9') // no suffix - bytes
return Long.parseLong(str);
for (ind = 0; ind < units.length && c != units[ind]; ind++);
if (ind == units.length) // unrecognized suffix
throw new IllegalArgumentException("invalid size string: " + str);
str = str.substring(0, len - 1).trim();
return (long)(Double.parseDouble(str) * (1 << (10 * ind))); // val * 1024^unit
}
/**
* Returns a human-friendly string approximating the given data size,
* e.g. "316", "1.8K", "324M", etc.
*
* @param size the size to display
* @return a human-friendly string approximating the given data size
*/
public static String toSizeApproxString(long size) {
final char[] units = { ' ', 'K', 'M', 'G', 'T', 'P', 'E' };
int u;
double s;
for (u = 0, s = size; s >= 1000; u++, s /= 1024);
return String.format(s < 10 ? "%.1f%c" : "%.0f%c", s, units[u]);
}
/**
* Formats a duration time (relative time) as a string
* in standard HH:MM:SS.mmm format.
*
* @param millis a time value in milliseconds
* @return a standard string representation of the given time
*/
public static String toDurationString(long millis) {
return toDurationString(millis, true);
}
/**
* Formats a duration time (relative time) as a string
* in standard HH:MM:SS.mmm format.
*
* @param millis a time value in milliseconds
* @param withMillis specifies whether milliseconds are included
* @return a standard string representation of the given time
*/
public static String toDurationString(long millis, boolean withMillis) {
int ms = (int)(millis % 1000);
millis /= 1000;
int s = (int)(millis % 60);
millis /= 60;
int m = (int)(millis % 60);
millis /= 60;
int h = (int)millis;
StringBuilder sb = new StringBuilder(13)
.append(padZeros(h, 2)).append(':')
.append(padZeros(m, 2)).append(':')
.append(padZeros(s, 2));
if (withMillis)
sb.append('.').append(padZeros(ms, 3));
return sb.toString();
}
/**
* Returns a verbose English representation of the given duration (relative time).
*
* @param millis a time value in milliseconds
* @return a verbose English representation of the given time
*/
public static String toVerboseDurationString(long millis) {
long ms = millis % 1000;
millis /= 1000;
long s = millis % 60;
millis /= 60;
long m = millis % 60;
millis /= 60;
long h = millis % 60;
millis /= 60;
long d = millis % 24;
StringBuilder sb = new StringBuilder();
if (d > 0)
sb.append(d).append(" days ");
if (h > 0)
sb.append(h).append(" hours ");
if (m > 0)
sb.append(m).append(" minutes ");
if (s > 0)
sb.append(s).append(" seconds ");
if (ms > 0)
sb.append(ms).append(" millis ");
return sb.toString().trim();
}
/**
* Replaces each substring of the given string that matches the literal
* target sequence with the specified literal replacement sequence.
* The replacement proceeds from the beginning of the string to the end,
* for example, replacing "aa" with "b" in the string "aaa" will result
* in "ba" rather than "ab".
*
* Note: This method is an exact replacement for the String.replace()
* method which was introduced in JDK 1.5, for use in older JDKs.
*
* @param s the string to process
* @param target the sequence of char values to be replaced
* @param replacement the replacement sequence of char values
* @return the resulting string
* @throws NullPointerException if s, target or replacement are null
*/
public static String replace(String s, String target, String replacement) {
if (target == null || replacement == null)
throw new NullPointerException();
int ind = s.indexOf(target);
if (ind > -1) { // optimization: if no occurrence, skip everything
int len = s.length();
int tlen = target.length();
int rlen = replacement.length();
int add = 0;
StringBuilder sb = new StringBuilder(s);
do {
sb.replace(ind + add, ind + add + tlen, replacement);
add += rlen - tlen;
// ensure progress even if it's an empty search string
ind += tlen > 1 ? tlen : 1;
// we allow ind == len to include match at end of string
ind = ind <= len ? s.indexOf(target, ind) : -1;
} while (ind > -1);
s = sb.toString();
}
return s;
}
/**
* Returns a string consisting of repetitions of a given string.
*
* @param s the string to repeat
* @param count the number of times to repeat it
* @return a string of consisting of count repetitions of the given string
*/
public static String repeat(String s, int count) {
if (s == null || count < 1)
return "";
char[] schars = s.toCharArray();
int len = schars.length;
char[] chars = new char[count * len];
for (int i = 0; i < chars.length; i += len)
System.arraycopy(schars, 0, chars, i, len);
return new String(chars);
}
/**
* Returns an HTML-escaped version of the given string for safe display
* within a web page. The characters '&', '>' and '<' must always be
* escaped, and single and double quotes must be escaped within
* attribute values; this method escapes them always. This method can
* be used for generating both HTML and XHTML valid content.
*
* @param s the string to escape
* @return the escaped string
* @see
* The W3C FAQ
*/
public static String escapeHTML(String s) {
int len = s.length();
StringBuilder es = new StringBuilder(len + 30);
int start = 0;
for (int i = 0; i < len; i++) {
String ref = null;
switch (s.charAt(i)) {
case '&': ref = "&"; break;
case '>': ref = ">"; break;
case '<': ref = "<"; break;
case '"': ref = """; break;
case '\'': ref = "'"; break;
}
if (ref != null) {
es.append(s, start, i).append(ref);
start = i + 1;
}
}
return start == 0 ? s : es.append(s.substring(start)).toString();
}
/**
* Returns an unescaped version of this string with HTML escapes
* removed. This method does NOT process all valid character entity
* references, but rather only the basic ones generated by the
* {@link #escapeHTML(String) escapeHTML} method. Unknown or invalid
* entities are left unchanged.
*
* @param s the string to unescape
* @return the unescaped string
* @see
* The W3C FAQ
*/
public static String unescapeHTML(String s) {
int len = s.length();
StringBuilder es = new StringBuilder(len);
int start = 0;
int end = 0;
for (int i = 0; i < len; i++) {
int ch = -1;
if (s.charAt(i) == '&') {
end = s.indexOf(';', ++i);
if (end > i) {
String ref = s.substring(i, end);
switch (s.charAt(i)) {
case '#':
try {
if (s.charAt(i + 1) == 'x')
ch = Integer.parseInt(ref.substring(2), 16);
else
ch = Integer.parseInt(ref.substring(1), 10);
} catch (NumberFormatException ignore) {}
break;
case 'a': if (ref.equals("amp")) ch = '&'; break;
case 'g': if (ref.equals("gt")) ch = '>'; break;
case 'l': if (ref.equals("lt")) ch = '<'; break;
case 'q': if (ref.equals("quot")) ch = '"'; break;
}
}
}
if (ch > -1) {
es.append(s, start, i - 1).append((char)ch);
start = end + 1;
}
}
return start == 0 ? s : es.append(s.substring(start)).toString();
}
/**
* Parses name-value pair parameters from the given "x-www-form-urlencoded"
* MIME-type string. This is the encoding used both for parameters passed
* as the query of an HTTP GET method, and as the content of HTML forms
* submitted using the HTTP POST method (as long as they use the default
* "application/x-www-form-urlencoded" encoding in their ENCTYPE attribute).
* UTF-8 encoding is assumed.
*
* @param s an "application/x-www-form-urlencoded" string
* @return the parameter name-value pairs parsed from the given string,
* or an empty map if it does not contain any
*/
public static Map parseParams(String s) {
if (s == null || s.isEmpty())
return Collections.emptyMap();
Map params = new LinkedHashMap<>(8);
Scanner sc = new Scanner(s).useDelimiter("&");
while (sc.hasNext()) {
String pair = sc.next();
int pos = pair.indexOf('=');
String name = pos == -1 ? pair : pair.substring(0, pos);
String val = pos == -1 ? "" : pair.substring(pos + 1);
try {
name = URLDecoder.decode(name.trim(), "UTF-8");
val = URLDecoder.decode(val.trim(), "UTF-8");
if (name.length() > 0)
params.put(name, val);
} catch (UnsupportedEncodingException ignore) {} // never thrown
}
return params;
}
/**
* Escapes the given special characters within a string by preceding them with a backslash.
* Control characters which appear among the given special characters are replaced with
* their well known escape sequence (e.g. "\n").
*
* @param s the string to escape
* @param chars a string containing all characters that should be escaped
* @return the escaped string, or null if the string is null
*/
public static String escape(String s, String chars) {
if (s == null || s.isEmpty())
return s;
StringBuilder sb = null;
int len = s.length();
for (int i = 0, j = 0; i < len; i++) {
int pos = chars.indexOf(s.charAt(i));
if (pos > -1) {
if (sb == null)
sb = new StringBuilder(s);
sb.insert(i + j++, '\\');
int c = chars.charAt(pos);
if (c < 32) {
switch(c) {
case '\b': c = 'b'; break;
case '\t': c = 't'; break;
case '\n': c = 'n'; break;
case '\f': c = 'f'; break;
case '\r': c = 'r'; break;
default: c = 0;
}
if (c > 0)
sb.setCharAt(i + j, (char)c);
}
}
}
return sb == null ? s : sb.toString();
}
/**
* Returns whether the given character sequence is null or empty.
*
* @param s the character sequence
* @return true if the character sequence is null or empty, false otherwise
*/
public static boolean isEmpty(CharSequence s) {
return s == null || s.length() == 0;
}
/**
* Returns whether any of the given character sequences is null or empty.
*
* @param ss the character sequences
* @return true if any of the character sequences is null or empty, false otherwise
*/
public static boolean isEmpty(CharSequence... ss) {
for (CharSequence s : ss)
if (s == null || s.length() == 0)
return true;
return false;
}
/**
* Returns the given string or an empty string if it is null.
*
* @param s a string
* @return the given string or an empty string if it is null
*/
public static String safe(String s) {
return s == null ? "" : s;
}
/**
* Returns the given string with all occurrences of the given character removed from
* both its left and right sides.
*
* @param s the string to trim
* @param c the character to remove
* @return the trimmed string
*/
public static String trim(String s, char c) {
int start = 0;
int end = s.length() - 1;
while (start <= end && s.charAt(start) == c)
start++;
while (end >= start && s.charAt(end) == c)
end--;
end++;
return end - start == s.length() ? s : s.substring(start, end);
}
/**
* Returns the given string with the first and last characters removed
* if they correspond to the given quote characters.
*
* @param s the string to unquote
* @param beginQuote the character to remove from the beginning
* @param endQuote the character to remove from the end
* @param enforce if true and the given string is not quoted
* by the given quote characters, an exception is thrown;
* if false, missing quote characters are ignored
* @return the unquoted string
* @throws IllegalArgumentException if enforce is true and the
* given string is not quoted by the given quote characters
*/
public static String unquote(String s, char beginQuote, char endQuote, boolean enforce) {
int len = s.length();
boolean hasBegin = len > 0 && s.charAt(0) == beginQuote;
if (enforce && !hasBegin)
throw new IllegalArgumentException("missing beginning '" + beginQuote + "' character");
boolean hasEnd = (len > 1 || (len == 1 && !hasBegin)) && s.charAt(len - 1) == endQuote;
if (enforce && !hasEnd)
throw new IllegalArgumentException("missing end '" + endQuote + "' character");
return s.substring(hasBegin ? 1 : 0, hasEnd ? len - 1 : len);
}
/**
* Returns the given string with the first and last characters removed
* if they correspond to the given quote character.
*
* @param s the string to unquote
* @param quote the quote character to remove
* @return the unquoted string
*/
public static String unquote(String s, char quote) {
return unquote(s, quote, quote, false);
}
/**
* Returns the given string with all occurrences of the given character
* removed from its left side.
*
* @param s the string to trim
* @param c the character to remove
* @return the trimmed string
*/
public static String trimLeft(String s, char c) {
int len = s.length();
int start;
for (start = 0; start < len && s.charAt(start) == c; start++);
return start == 0 ? s : s.substring(start);
}
/**
* Returns the given string with all occurrences of the given character
* removed from its right side.
*
* @param s the string to trim
* @param c the character to remove
* @return the trimmed string
*/
public static String trimRight(String s, char c) {
int len = s.length() - 1;
int end;
for (end = len; end >= 0 && s.charAt(end) == c; end--);
return end == len ? s : s.substring(0, end + 1);
}
/**
* Trims duplicate consecutive occurrences of the given character within the
* given string, replacing them with a single instance of the character.
*
* @param s the string to trim
* @param c the character to trim
* @return the given string with duplicate consecutive occurrences of c
* replaced by a single instance of c
*/
public static String trimDuplicates(String s, char c) {
int i = -1;
while ((i = s.indexOf(c, i + 1)) > -1) {
int end;
for (end = i + 1; end < s.length() && s.charAt(end) == c; end++);
if (end > i + 1)
s = s.substring(0, i + 1) + s.substring(end);
}
return s;
}
/**
* Pads a string with a given character so that it reaches a given minimum length.
*
* @param s a string
* @param len the minimum length of the padded string
* @param c the character to pad with
* @param leftPad specifies is padding is added on the left or right
* @return the padded string
*/
public static String pad(CharSequence s, int len, char c, boolean leftPad) {
int slen = s.length();
if (slen >= len)
return s.toString();
char[] chars = new char[len];
if (leftPad) {
s.toString().getChars(0, slen, chars, len - slen);
Arrays.fill(chars, 0, len - slen, c);
} else {
s.toString().getChars(0, slen, chars, 0);
Arrays.fill(chars, slen, len, c);
}
return new String(chars);
}
/**
* Pads a string from the left with a given character so that it reaches a given minimum length.
*
* @param s a string
* @param len the minimum length of the padded string
* @param c the character to pad with
* @return the padded string
*/
public static String padLeft(CharSequence s, int len, char c) {
return pad(s, len, c, true);
}
/**
* Pads a string from the left with spaces so that it reaches a given minimum length.
*
* @param s a string
* @param len the minimum length of the padded string
* @return the padded string
*/
public static String padLeft(CharSequence s, int len) {
return padLeft(s, len, ' ');
}
/**
* Pads a string from the right with a given character so that it reaches a given minimum length.
*
* @param s a string
* @param len the minimum length of the padded string
* @param c the character to pad with
* @return the padded string
*/
public static String padRight(CharSequence s, int len, char c) {
return pad(s, len, c, false);
}
/**
* Pads a string from the right with spaces so that it reaches a given minimum length.
*
* @param s a string
* @param len the minimum length of the padded string
* @return the padded string
*/
public static String padRight(CharSequence s, int len) {
return padRight(s, len, ' ');
}
/**
* Pads a string from the left with zeros so that it reaches a given minimum length.
*
* @param s a string
* @param len the minimum length of the padded string
* @return the padded string
*/
public static String padZeros(CharSequence s, int len) {
return padLeft(s, len, '0');
}
/**
* Pads a string representation of an integer from the left
* with zeros so that it reaches a given minimum length.
*
* @param n an integer
* @param len the minimum length of the padded string
* @return the padded string
*/
public static String padZeros(int n, int len) {
return padLeft(Integer.toString(n), len, '0');
}
/**
* Splits the given string around matches of the given delimiter.
* This can be used as a drop-in replacement to {@link String#split(String)}
* when no regex is required, preventing unnecessary (potentially bug-creating)
* regex escaping, and improving performance.
*
* Note: this method behaves exactly like {@link String#split(String)} with
* respect to null arguments and empty tokens.
*
* @param s the string to split
* @param delimiter the delimiter
* @return the array of substrings
*/
public static String[] splitWithEmptyTokens(String s, String delimiter) {
final List tokens = new ArrayList<>();
final int delimLength = delimiter.length();
// note: the implementation is somewhat elaborate in order to
// be consistent with String.split() in handling empty tokens
int start = 0;
int emptyTokens = 0;
boolean last = false;
while (!last) {
int next = s.indexOf(delimiter, start);
last = next == -1;
String token = s.substring(start, last ? s.length() : next);
if (token.isEmpty() && (!last || start > 0)) {
emptyTokens++;
} else {
for (; emptyTokens > 0; emptyTokens--)
tokens.add("");
tokens.add(token);
}
start = next + delimLength;
}
return Containers.toArray(tokens, String.class);
}
/**
* Splits a string into lines.
*
* @param s the string to split
* @param lenient if true, either an LF or a CRLF can end the line;
* if false, only a CRLF can end the line
* @return the lines
*/
static String[] splitLines(String s, boolean lenient) {
return lenient ? s.split("\r?\n") : split(s, "\r\n", false);
}
/**
* Splits the given string into its constituent non-empty elements,
* which are delimited by the given delimiter. This is a more direct
* and efficient implementation than using {@link String#split(String)}.
*
* Note: unlike {@link String#split(String)}, this method returns an empty
* array if the given string is null.
*
* @param s the string to split
* @param delim the delimiter between elements
* @param trim if true, elements are {@link String#trim() trimmed}
* @param empty if true, empty elements are returned, otherwise they are skipped
* @return the non-empty elements in the string, or an empty array
*/
public static String[] split(String s, String delim, boolean trim, boolean empty) {
if (s == null)
return new String[0];
final Collection tokens = new ArrayList<>();
final int delimLength = delim.length();
final int len = s.length();
int start = 0;
while (start < len) {
int end = s.indexOf(delim, start);
if (end == -1)
end = len; // last token is until end of string
String token = s.substring(start, end);
if (trim)
token = token.trim();
if (token.length() > 0 || empty)
tokens.add(token);
start = end + delimLength;
}
return Containers.toArray(tokens, String.class);
}
/**
* Splits the given string into its constituent non-empty trimmed elements,
* which are delimited by the given character. This is a more direct
* and efficient implementation than using a regex (e.g. String.split()).
*
* @param str the string to split
* @param delim the character used as the delimiter between elements
* @return the non-empty elements in the string, or an empty array
*/
public static String[] split(String str, char delim) {
if (str == null)
return new String[0];
Collection elements = new ArrayList<>();
int len = str.length();
int start = 0;
while (start < len) {
int end = str.indexOf(delim, start);
if (end == -1)
end = len; // last token is until end of string
String element = str.substring(start, end).trim();
if (element.length() > 0)
elements.add(element);
start = end + 1;
}
return elements.toArray(new String[0]);
}
/**
* Splits the given string into two around the given delimiter interval.
*
* If the interval offset is negative,
* the returned array contains the original string and a null.
*
* @param str the string to split
* @param off the offset of the interval within the string
* @param len the length of the delimiter
* @return an array of size 2, containing the pair of substrings
* obtained from splitting the string around the given delimiter
* interval (excluding the delimiter itself)
*/
public static String[] splitPair(String str, int off, int len) {
return off < 0
? new String[] { str, null }
: new String[] { str.substring(0, off), str.substring(off + len) };
}
/**
* Splits the given string into two around the n-th occurrence of the
* given delimiter. If n is negative, occurrences are counted backward
* from the end of the string.
*
* If there are less than n occurrences of the delimiter in the string,
* the returned array contains the original string and a null.
*
* @param str the string to split
* @param delim the delimiter to split around
* @param n the occurrence number of the delimiter around which the
* string is split; if negative, occurrences are counted
* backwards from the end of the string
* @return an array of size 2, containing the pair of substrings
* obtained from splitting the string around the n-th occurrence
* of the delimiter (excluding the delimiter itself)
*/
public static String[] splitPair(String str, String delim, int n) {
return splitPair(str, indexOf(str, delim, n), delim.length());
}
/**
* Splits the given string into two around the first occurrence of the
* given delimiter.
*
* If there are no occurrences of the delimiter in the string,
* the returned array contains the original string and a null.
*
* @param str the string to split
* @param delim the delimiter to split around
* @return an array of size 2, containing the pair of substrings
* obtained from splitting the string around the first occurrence
* of the delimiter (excluding the delimiter itself)
*/
public static String[] splitPair(String str, String delim) {
return splitPair(str, str.indexOf(delim), delim.length());
}
/**
* Splits the given string into two around the first occurrence of the
* given delimiter.
*
* If there are no occurrences of the delimiter in the string,
* the returned array contains the original string and a null.
*
* @param str the string to split
* @param delim the delimiter to split around
* @return an array of size 2, containing the pair of substrings
* obtained from splitting the string around the first occurrence
* of the delimiter (excluding the delimiter itself)
*/
public static String[] splitPair(String str, char delim) {
return splitPair(str, str.indexOf(delim), 1);
}
/**
* Splits the given string into its constituent non-empty elements,
* which are delimited by the given delimiter. This is a more direct
* and efficient implementation than using {@link String#split(String)}.
*
* Note: unlike {@link String#split(String)}, this method returns an empty
* array if the given string is null, and never returns empty elements.
*
* @param s the string to split
* @param delim the delimiter between elements
* @param trim if true, elements are {@link String#trim() trimmed}
* @return the non-empty elements in the string, or an empty array
*/
public static String[] split(String s, String delim, boolean trim) {
return split(s, delim, trim, false);
}
/**
* Splits the given string into its constituent non-empty elements,
* which are delimited by the given delimiter. This is a more direct
* and efficient implementation than using {@link String#split(String)}.
*
* Note: unlike {@link String#split(String)}, this method returns an empty
* array if the given string is null, and never returns empty elements.
*
* @param s the string to split
* @param delim the delimiter between elements
* @return the non-empty elements in the string, or an empty array
*/
public static String[] split(String s, String delim) {
return split(s, delim, false, false);
}
/**
* Splits the given string into a two-dimensional table.
*
* @param table the string to split
* @param rowDelimiter the string which delimits the table rows
* @param colDelimiter the string which delimits the table columns
* @return the table (two-dimensional array)
*/
public static String[][] splitTable(String table, String rowDelimiter, String colDelimiter) {
String[] rows = table.split(rowDelimiter);
String[][] t = new String[rows.length][];
for (int i = 0; i < rows.length; i++)
t[i] = rows[i].split(colDelimiter);
return t;
}
/**
* Joins the given strings using the given delimiter.
*
* @param delimiter the delimiter used in between the strings
* @param strings the strings to join
* @return the joined string
*/
public static String join(String delimiter, String... strings) {
int count = strings.length;
if (count < 2) // optimize for single element
return count == 1 ? strings[0] : "";
int delimLength = delimiter.length();
StringBuilder sb = new StringBuilder(length(strings) + (delimLength * count));
for (String s : strings)
sb.append(s).append(delimiter);
return sb.substring(0, sb.length() - delimLength);
}
/**
* Returns the combined length of all given strings.
*
* @param strings the strings (a null string is considered of length zero)
* @return the combined length of all given strings
*/
public static int length(String... strings) {
int len = 0;
for (String s : strings)
len += s == null ? 0 : s.length();
return len;
}
/**
* Changes the case of the first character in the string and all characters which
* immediately follow a {@link Character#isWhitespace(char) whitespace} character
* (except for such an uppercase character followed by another uppercase character,
* in which case it will remain uppercase - to keep acronyms intact).
*
* @param s the string to capitalize
* @param upper if true the case is changed to uppercase, otherwise it is changed to lowercase
* @return the capitalized string
*/
public static String capitalize(String s, boolean upper) {
if (s == null || s.isEmpty())
return s;
final StringBuilder sb = new StringBuilder(s);
final int end = s.length();
boolean prev = true;
for (int i = 0; i < end; i++) {
char c = sb.charAt(i);
boolean isWhitespace = Character.isWhitespace(c);
if (prev && !isWhitespace)
sb.setCharAt(i, upper || i < end - 1 && Character.isUpperCase(sb.charAt(i + 1))
? Character.toUpperCase(c) : Character.toLowerCase(c));
prev = isWhitespace;
}
return sb.toString();
}
/**
* Capitalizes the given string by converting to uppercase the first character in the string
* and all characters which immediately follow a {@link Character#isWhitespace(char) whitespace} character.
*
* @param s the string to capitalize
* @return the capitalized string
*/
public static String capitalize(String s) {
return capitalize(s, true);
}
/**
* De-capitalizes the given string by converting to lowercase the first character in the string
* and all characters which immediately follow a {@link Character#isWhitespace(char) whitespace} character.
*
* @param s the string to de-capitalize
* @return the de-capitalized string
*/
public static String decapitalize(String s) {
return capitalize(s, false);
}
/**
* Changes the case of the first character in the string.
*
* @param s the string to capitalize
* @param upper if true the case is changed to uppercase, otherwise it is changed to lowercase
* @return the capitalized string
*/
public static String capitalizeFirst(String s, boolean upper) {
if (s == null || s.isEmpty())
return s;
int first = s.charAt(0);
int c = upper ? Character.toUpperCase(first) : Character.toLowerCase(first);
return first == c ? s : c + s.substring(1);
}
/**
* Changes the case of the first character in the string to uppercase.
*
* @param s the string to capitalize
* @return the capitalized string
*/
public static String capitalizeFirst(String s) {
return capitalize(s, true);
}
/**
* Changes the case of the first character in the string to lowercase.
*
* @param s the string to de-capitalize
* @return the de-capitalized string
*/
public static String decapitalizeFirst(String s) {
return capitalize(s, false);
}
/**
* Returns a string representation of the given Throwable's stack trace.
*
* @param t a Throwable
* @return a string representation of the given Throwable's stack trace
*/
public static String getStackTrace(Throwable t) {
StringWriter sw = new StringWriter(128);
PrintWriter pw = new PrintWriter(sw);
t.printStackTrace(pw);
pw.close();
return sw.toString();
}
/**
* Returns a string representation of the given stack trace.
*
* This can be useful when retrieving a stack trace using the
* {@link Thread#getStackTrace()} and similar methods, or from
* {@link Throwable#getStackTrace()}, although for the latter,
* the overloaded {@link #getStackTrace(Throwable)} method is
* preferred if chained exceptions are required.
*
* @param st a stack trace
* @return a string representation of the given stack trace
*/
public static String getStackTrace(StackTraceElement[] st) {
StringBuilder sb = new StringBuilder(128);
for (StackTraceElement el : st) {
sb.append("\tat ").append(el).append('\n');
}
return sb.toString();
}
/**
* Returns a string representation of the current thread's stack trace,
* excluding this method's frame.
*
* @return a string representation of the current thread's stack trace
*/
public static String getStackTrace() {
StackTraceElement[] st = Thread.currentThread().getStackTrace();
return getStackTrace(Arrays.copyOfRange(st, 2, st.length));
}
/**
* Returns the index of the n-th occurrence of a substring within a string.
* If n is negative, occurrences are counted backward from the end of the string
* to its beginning, e.g. -1 will return the index of the last occurrence of
* the substring within the string.
*
* @param s the string in which to search
* @param sub the substring to search for
* @param n the occurrence number to search for (if negative, occurrences are
* counted backwards)
* @return the index of the n-th occurrence of the substring within the string,
* or -1 if there are less than n such occurrences or if n is zero
*/
public static int indexOf(String s, String sub, int n) {
if (n == 0) {
return -1;
} else if (n > 0) {
int i = -1;
for (;;) {
i = s.indexOf(sub, i + 1);
if (i < 0 || --n == 0)
return i;
}
} else {
int i = s.length();
for (;;) {
i = s.lastIndexOf(sub, i - 1);
if (i < 0 || ++n == 0)
return i;
}
}
}
/**
* Returns the substring of the given string which begins at the n-th occurrence of
* the given substring and ends at the following occurrence, or the end of the string
* if there are no additional occurrences.
* If n is zero, the substring starts at the beginning of the string.
* If n is negative, occurrences are counted backward from the end of the string
* to its beginning, e.g. -1 will return the substring starting at the last
* occurrence of the delimiter and ending at the end of the string.
* If there are less than n occurrences of the delimiter, the entire string is returned.
*
* @param s the string
* @param delim the substring marking the start position of the returned substring
* @param n the occurrence number of the delimiter marking the
* start position of the returned string
* @return the substring
*/
public static String substring(String s, String delim, int n) {
int start, end;
if (n == 0) {
start = 0;
end = s.indexOf(delim);
} else if (n > 0) {
start = indexOf(s, delim, n);
if (start == -1)
return s;
start += delim.length();
end = s.indexOf(delim, start);
} else {
end = indexOf(s, delim, -n);
if (end == -1)
return s;
start = s.lastIndexOf(delim, end - 1);
start = start == -1 ? 0 : start + delim.length();
}
return end == -1 ? s.substring(start) : s.substring(start, end);
}
/**
* Returns the substring of the given string which begins at the first occurrence of
* the given substring and ends at the end of the string. If the given substring is
* not found, the entire string is returned.
*
* @param s the string
* @param delim the substring marking the start position of the returned substring
* @param includeDelim if true, the delimiter string is included in the result,
* otherwise it is excluded
* @return the substring
*/
public static String rightSubstring(String s, String delim, boolean includeDelim) {
int ind = s.indexOf(delim);
return ind == -1 ? s : s.substring(includeDelim ? ind : ind + delim.length());
}
/**
* Returns the substring of the given string which begins at the beginning
* of the string and ends at the first occurrence of the given substring.
* If the given substring is not found, the entire string is returned.
*
* @param s the string
* @param delim the substring marking the end position of the returned substring
* @param includeDelim if true, the delimiter string is included in the result,
* otherwise it is excluded
* @return the substring
*/
public static String leftSubstring(String s, String delim, boolean includeDelim) {
int ind = s.indexOf(delim);
return ind == -1 ? s : s.substring(0, includeDelim ? ind + delim.length() : ind);
}
/**
* Returns the hexadecimal representation of the given byte.
* The byte's MSB and LSB hex digits are returned as chars in the
* high 16 bits and low 16 bits of the returned int, respectively.
*
* @param b a byte
* @return an int containing the two hexadecimal digits as chars
*/
public static int toHex(byte b) {
return HEX_CHARS_UPPERCASE[(b >> 4) & 0xF] << 16 | HEX_CHARS_UPPERCASE[b & 0xF];
}
/**
* Parses a single hexadecimal digit (as a char) into a byte value.
*
* @param c a hexadecimal digit as a char
* @throws NumberFormatException if the character is not a valid hex digit
*/
public static byte fromHex(char c) {
if (c >= '0' && c <= '9')
return (byte)(c - '0');
if (c >= 'a' && c <= 'f')
return (byte)(c - 'a' + 10);
if (c >= 'A' && c <= 'F')
return (byte)(c - 'A' + 10);
throw new NumberFormatException("invalid hex digit: " + c);
}
/**
* Parses two hexadecimal digits (as chars) into a byte value.
*
* Note that the result is a byte; if it is cast (implicitly or explicitly) to
* an int, it must be ANDed with 0xFF in order to remain in the range 0x00-0xFF.
*
* @param c1 the first hexadecimal digit as a char
* @param c2 the second hexadecimal digit as a char
* @throws NumberFormatException if the characters are not valid hex digits
*/
public static byte fromHex(char c1, char c2) {
return (byte)(fromHex(c1) << 4 | fromHex(c2));
}
/**
* Creates a string containing the hexadecimal representation of the given
* bytes. The string is decorated with square brackets, and a human-readable
* addition if the maximum has been reached before the data ended.
*
* @param bytes a byte array who's content is to be displayed
* @return a String containing the hexadecimal representation of the given
* bytes
*/
public static String toHexString(byte[] bytes) {
int len = bytes != null ? bytes.length : 0;
return toHexString(bytes, 0, len, len, 4);
}
/**
* Creates a string containing the hexadecimal representation of the given
* bytes. The string is decorated with square brackets, and a human-readable
* addition if the maximum has been reached before the data ended.
*
* @param bytes a byte array whose content is to be displayed
* @param offset the offset within the byte array to start at
* @param len the maximum number of bytes to be displayed,
* or a negative value for no limit
* @param total the total bytes considered part of the data bytes
* @param group the size of a byte group after which a single space is added
* @return a String containing the hexadecimal representation of the given
* bytes
*/
public static String toHexString(byte[] bytes, int offset, int len,
int total, int group) {
if (bytes == null)
return "[null]";
int count = total - offset;
len = len < 0 || count < len ? count : len;
StringBuilder s = new StringBuilder();
s.append('[');
s.append(toHexRawString(bytes, offset, len, total, group));
if (len < count)
s.append("... (").append(count).append(" bytes)");
s.append(']');
return s.toString();
}
/**
* Creates a string containing the raw hexadecimal representation
* of the given bytes. No decorations are added to the string.
*
* @param bytes a byte array who's content is to be displayed
* @return a String containing the hexadecimal representation of the given
* bytes
*/
public static String toHexRawString(byte[] bytes) {
int len = bytes != null ? bytes.length : 0;
return toHexRawString(bytes, 0, len, len, Integer.MAX_VALUE);
}
/**
* Creates a string containing the raw hexadecimal representation
* of the given bytes. No decorations are added to the string.
*
* @param bytes a byte array whose content is to be displayed
* @param offset the offset within the byte array to start at
* @param len the maximum number of bytes to be displayed,
* or a negative value for no limit
* @param total the total bytes considered part of the data bytes
* @param group the size of a byte group after which a single space is added
* @return a String containing the hexadecimal representation of the given
* bytes
*/
public static String toHexRawString(byte[] bytes, int offset, int len,
int total, int group) {
if (bytes == null)
return "";
int count = total - offset;
len = len < 0 || count < len ? count : len;
StringBuilder s = new StringBuilder();
for (int i = 0; i < len; i++) {
if (i % group == 0 && i > 0)
s.append(' ');
byte b = bytes[offset + i];
s.append(HEX_CHARS_UPPERCASE[(b >> 4) & 0xF]);
s.append(HEX_CHARS_UPPERCASE[b & 0xF]);
}
return s.toString();
}
/**
* Parses the given hex string into a byte array.
*
* The string is assumed to be a string containing the hex representation
* of a sequence of bytes, which may or may not be prefixed by the hex
* designator "0x", and is case-insensitive. If its length is odd,
* it is padded with a leading zero.
*
* Valid examples are "0x1234ABCD" and "ffff".
*
* @param s the string to parse
* @return the parsed byte array
* @throws NumberFormatException if the string is not a valid hex string
*/
public static byte[] parseHexString(String s) {
int i = 0; // source char index
int j = 0; // destination byte index
int len = s.length();
// remove 0x/0X prefix
if (len > 1 && s.charAt(0) == '0' && (s.charAt(1) == 'x' || s.charAt(1) == 'X')) {
i = 2;
len -= 2;
}
len = (len + 1) >> 1; // round up if odd
byte[] bytes = new byte[len];
if ((s.length() & 1) == 1) // if odd length, parse first digit separately
bytes[j++] = fromHex(s.charAt(i++));
while (j < len)
bytes[j++] = fromHex(s.charAt(i++), s.charAt(i++));
return bytes;
}
/**
* Generates a random sequence of characters from the given alphabet
* with the given length.
*
* @param alphabet the alphabet from which characters are used (several common
* alphabet constants are defined in this class for convenience)
* @param length the length of the sequence
* @return a sequence of characters from the given alphabet with the given length
*/
public static String createRandomString(String alphabet, int length) {
char[] seq = new char[length];
for (int i = 0; i < length; i++)
seq[i] = alphabet.charAt((int)(Math.random() * alphabet.length()));
return new String(seq);
}
/**
* Returns the strings corresponding the given items, by
* calling {@link String#valueOf(Object)} on each item.
*
* @param items the items whose string representations are returned
* @return the strings corresponding to the given items
*/
public static String[] toStrings(Object... items) {
int len = items.length;
String[] strings = new String[len];
int i = 0;
for (Object item : items)
strings[i++] = String.valueOf(item);
return strings;
}
/**
* Returns the strings corresponding the given items, by
* calling {@link String#valueOf(Object)} on each item.
*
* @param items the items whose string representations are returned
* @return the strings corresponding to the given items
*/
public static Collection toStrings(Collection> items) {
int len = items.size();
String[] strings = new String[len];
int i = 0;
for (Object item : items)
strings[i++] = String.valueOf(item);
return Arrays.asList(strings);
}
/**
* Returns the number of occurrences of the given char in the given character sequence.
*
* @param s the character sequence to search within
* @param c the character whose occurrences are counted
* @return the number of occurrences of the given char in the given character sequence
*/
public static int count(CharSequence s, char c) {
int len = s.length();
int count = 0;
for (int i = 0; i < len; i++)
if (s.charAt(i) == c)
count ++;
return count;
}
/**
* Decodes an xtext-encoded string as defined in RFC 3461 section 4.
*
* @param s the string to decode
* @return the decoded string
* @throws IllegalArgumentException if the given string is not a valid xtext encoded string
*/
public static String xtextDecode(String s) {
StringBuilder sb = new StringBuilder(s.length());
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c < 33 || c > 126 || c == '=')
throw new IllegalArgumentException("illegal xtext character at position " + i);
if (c == '+') {
if (i + 3 > s.length())
throw new IllegalArgumentException("missing hex value at position " + i);
String hex = s.substring(i + 1, i + 3);
if (!hex.equals(hex.toUpperCase(Locale.ENGLISH)))
throw new IllegalArgumentException("hex digits must be uppercase at position " + i);
try {
c = (char)Integer.parseInt(hex, 16);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("invalid hex digits at position " + i);
}
i += 2;
}
sb.append(c);
}
return sb.toString();
}
/**
* Calculates a check digit for the given string of digits using Luhn's Algorithm.
*
* This method can be used both for calculating the check digit to be added to
* a sequence of digits, or for validating a sequence of digits that already
* contains a check digit. How it is used is determined by the
* {@code includesCheckDigit} parameter. Technically, this parameter
* determines whether the odd digits or the even digits (starting at the right)
* are doubled, according to the algorithm.
*
* This method assumes that the input string contains only digits, but does
* not validate this.
*
* @param s a string of digits
* @param includesCheckDigit specifies whether the given string's last digit
* is already a check digit
* @return the integer value of the check digit
* @see Luhn's Algorithm
*/
public static int calculateLuhnCheckDigit(CharSequence s, boolean includesCheckDigit) {
int sum = 0;
boolean even = includesCheckDigit;
for (int i = s.length() - 1; i >= 0; i--) {
int digit = Character.digit(s.charAt(i), 10);
even = !even;
if (even)
digit *= 2;
sum += digit / 10 + digit % 10;
}
sum = 10 - sum % 10;
return sum == 10 ? 0 : sum;
}
/**
* Calculates a check digit for the given string of digits using Luhn's Algorithm.
*
* @param s a string of digits to which the check digit will be added
* @return the integer value of the check digit
* @see Luhn's Algorithm
*/
public static int calculateLuhnCheckDigit(CharSequence s) {
return calculateLuhnCheckDigit(s, false);
}
/**
* Validates the check digit in the given string of digits using Luhn's Algorithm.
*
* @param s a string of digits whose last digit is the check digit
* @return the integer value of the check digit
* @see Luhn's Algorithm
*/
public static boolean validateLuhnCheckDigit(CharSequence s) {
return calculateLuhnCheckDigit(s, true) == 0;
}
/**
* Returns the given string as US-ASCII bytes. This is a simple, efficient and convenient
* alternative to using {@link String#getBytes(String) String.getBytes("US-ASCII")}, which
* has Charset encoding overhead and requires explicit exception handling (even though
* US-ASCII is a required charset on all JVMs).
*
* @param s the string to encode
* @return the given string as US-ASCII bytes
* @throws IllegalArgumentException if the string contains characters outside
* the US-ASCII character range (0-127)
*/
public static byte[] getASCIIBytes(String s) {
char[] chars = s.toCharArray();
byte[] bytes = new byte[chars.length];
int i = 0;
for (char c : chars) {
if (c > 127)
throw new IllegalArgumentException("invalid ASCII byte at position " + i);
bytes[i++] = (byte)c;
}
return bytes;
}
/**
* Constructs a new {@code String} by decoding the specified subarray of
* bytes using the specified charset.
*
* This method differs from the {@link String#String(byte[], int, int, String)} constructor
* in that it throws an exception if the bytes are invalid in the given charset, whereas the
* constructor result is undefined in such a case (often implemented as replacing invalid
* bytes with question marks).
*
* @param bytes the bytes to be decoded into characters
* @param offset the index of the first byte to decode
* @param length the number of bytes to decode
* @param charsetName the name of a supported {@linkplain java.nio.charset.Charset charset}
* @return the string containing the decoded characters
* @throws UnsupportedEncodingException if the named charset is not supported
* @throws IndexOutOfBoundsException if the {@code offset} and {@code length} arguments
* index characters outside the bounds of the {@code bytes} array
* @throws CharacterCodingException if the given subarray contains a byte sequence
* which is not valid or not mappable in the given charset
*/
public static String toString(byte[] bytes, int offset, int length, String charsetName)
throws UnsupportedEncodingException, CharacterCodingException {
try {
return Charset.forName(charsetName).newDecoder()
.onMalformedInput(CodingErrorAction.REPORT)
.onUnmappableCharacter(CodingErrorAction.REPORT)
.decode(ByteBuffer.wrap(bytes, offset, length))
.toString();
} catch (UnsupportedCharsetException uce) {
throw new UnsupportedEncodingException(charsetName);
}
}
/**
* Converts the given string to camel-case, by converting it to lowercase,
* finding all occurrences of space or underscore characters and removing them,
* and converting to uppercase the first character following them.
*
* @param s a string
* @return the string converted to camel-case
*/
public static String toCamelCase(String s) {
char[] chars = s.toLowerCase().toCharArray();
int len = s.length();
boolean cap = false;
int j = 0;
for (int i = 0; i < len; i++) {
char c = chars[i];
if (c == '_' || c == ' ') {
cap = true;
} else if (cap) {
chars[j++] = Character.toUpperCase(c);
cap = false;
} else {
chars[j++] = c;
}
}
return new String(chars, 0, j);
}
}