All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.freeutils.util.Strings Maven / Gradle / Ivy

The newest version!
/*
 *  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); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy