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

org.abego.commons.lang.StringUtil Maven / Gradle / Ivy

There is a newer version: 1.1.0
Show newest version
/*
 * MIT License
 *
 * Copyright (c) 2020 Udo Borkowski, ([email protected])
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package org.abego.commons.lang;

import org.abego.commons.lang.exception.MustNotInstantiateException;
import org.abego.commons.range.IntRange;
import org.abego.commons.seq.AbstractSeq;
import org.abego.commons.seq.Seq;
import org.abego.commons.util.LocaleUtil;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;

import java.util.Collection;
import java.util.Iterator;
import java.util.Locale;
import java.util.NoSuchElementException;
import java.util.function.Supplier;
import java.util.regex.Pattern;

import static org.abego.commons.lang.CharacterUtil.BACKSLASH_CHAR;
import static org.abego.commons.lang.CharacterUtil.BEL_CHAR;
import static org.abego.commons.lang.CharacterUtil.CARRIAGE_RETURN_CHAR;
import static org.abego.commons.lang.CharacterUtil.DOUBLE_QUOTE_CHAR;
import static org.abego.commons.lang.CharacterUtil.FIRST_PRINTABLE_ASCII_CHAR_VALUE;
import static org.abego.commons.lang.CharacterUtil.FORM_FEED_CHAR;
import static org.abego.commons.lang.CharacterUtil.LAST_PRINTABLE_ASCII_CHAR_VALUE;
import static org.abego.commons.lang.CharacterUtil.NEWLINE_CHAR;
import static org.abego.commons.lang.CharacterUtil.SINGLE_QUOTE_CHAR;
import static org.abego.commons.lang.CharacterUtil.TAB_CHAR;
import static org.abego.commons.lang.IntUtil.limit;
import static org.abego.commons.lang.IterableUtil.textOf;
import static org.abego.commons.lang.IterableUtil.toIterable;
import static org.abego.commons.seq.SeqUtil.newSeq;

@SuppressWarnings("WeakerAccess")
public final class StringUtil {

    /**
     * The string to represent represent a null value, i.e.
     * "null".
     */
    public static final String NULL_STRING = "null"; //NON-NLS

    private static final Pattern ESCAPED_CHAR = Pattern.compile("\\\\(.)");
    private static final String[] EMPTY_STRING_ARRAY = new String[0];
    private static final Pattern END_OF_LINE_PATTERN = Pattern
            .compile("\\r?\\n");

    StringUtil() {
        throw new MustNotInstantiateException();
    }

    public static @Nullable String[] arrayOfNullables(@Nullable String... items) {
        int length = items.length;
        @Nullable String[] result = new @Nullable String[length];
        System.arraycopy(items, 0, result, 0, length);
        return result;
    }

    public static @NonNull String[] array(@NonNull String... items) {
        int length = items.length;
        @NonNull String[] result = new @NonNull String[length];
        System.arraycopy(items, 0, result, 0, length);
        return result;
    }

    public static @Nullable String[] arrayNullable(@Nullable String... items) {
        int length = items.length;
        @Nullable String[] result = new @Nullable String[length];
        System.arraycopy(items, 0, result, 0, length);
        return result;
    }

    public static boolean isNullOrEmpty(@Nullable String s) {
        return s == null || s.isEmpty();
    }

    /**
     * Return true if {@code s} has text, i.e. is not {@code null}
     * and is not the empty string.
     */
    public static boolean hasText(@Nullable String s) {
        return !isNullOrEmpty(s);
    }

    /**
     * Returns {@code string} when it is a non-empty String; returns
     * {@code defaultValue} when {@code string} is {@code null} or empty.
     */
    public static String stringOrDefault(
            @Nullable String string,
            String defaultValue) {

        return string == null || string.isEmpty() ? defaultValue : string;
    }

    /**
     * Return {@code string} when it is a non-empty {@link String}. Return
     * the value provided by the {@code defaultValueSupplier} otherwise,
     * i.e. when {@code string} is {@code null} or empty.
     */
    public static String stringOrDefault(
            @Nullable String string,
            Supplier defaultValueSupplier) {

        return string == null || string.isEmpty()
                ? defaultValueSupplier.get() : string;
    }

    /**
     * Return {@code object} as a String (as defined by the object's
     * {@link #toString()} method) when {@code object} is not {@code null}.
     * Return the empty String when {@code object} is {@code null}.
     *
     * 

Note this is different from Objects#toString that returns a string * "null" for null values.

*/ public static String string(@Nullable Object object) { return object == null ? "" : object.toString(); } /** * Return {@code object} as a String (as defined by the object's * {@link #toString()} method) when {@code object} is not {@code null}, * {@code null} otherwise. */ @Nullable public static String stringOrNull(@Nullable Object object) { return object == null ? null : object.toString(); } public static String repeat(String text, int n) { StringBuilder result = new StringBuilder(); for (int i = 0; i < n; i++) { result.append(text); } return result.toString(); } /** * Return the first character of the string. * * @param string [!s.isEmpty()] * @return the first character of the string */ public static char firstChar(String string) { return string.charAt(0); } /** * Return the last character of the string. * * @param string [!s.isEmpty()] * @return the last character of the string */ public static char lastChar(String string) { return string.charAt(string.length() - 1); } /** * Return a 'single-quoted' version of the toString representation of the * object or "null" when the object is null. * *

The text between the quotes is not escaped in any way.

* * @param object [nullable] * @return 'single-quoted' object.toString() or "null" if object is null. */ public static String singleQuotedStringWithoutEscapes(@Nullable Object object) { return object != null ? String.format("'%s'", object) // NON-NLS : NULL_STRING; } /** * Return the {@code text} with backslash-escaped characters (e.g. {@code * \"}) unescaped (e.g. {@code "}). * *

I.e. replace every occurrence of a character {@code c} that is * preceded by a {@code \} (backslash) by {@code c}.

* *

No characters are treated specially, e.g. "{@code \n}" will * not be converted to a newline character.

*/ public static String unescapeCharacters(String text) { return ESCAPED_CHAR.matcher(text).replaceAll("$1"); } private static String quotedHelper( @Nullable String text, String nullResult, char quoteChar, String singleQuoteText, String doubleQuoteText, StringBuilderAppender nonAsciiCharHandler) { if (text == null) { return nullResult; } StringBuilder result = new StringBuilder(); result.append(quoteChar); appendEscapedString(result, text, singleQuoteText, doubleQuoteText, nonAsciiCharHandler); result.append(quoteChar); return result.toString(); } /** * @param s the String to escape * @return the escaped string, as it would be written inside a Java String literal */ public static String escaped(@Nullable String s) { return appendEscapedString(new StringBuilder(), s, "'", "\\\"", StringUtil::appendUnicodeEscaped).toString(); } /** * @param s the String to escape * @return the escaped string, as it would be written inside a Java String literal */ public static @Nullable String escapedOrNull(@Nullable String s) { return s == null ? null : escaped(s); } /** * @param c the character to escape * @return the escaped character as a String, as it would be written inside * a Java String literal */ public static String escaped(char c) { return escaped(String.valueOf(c)); } public static String substringSafe(String s, int beginIndex, int endIndex) { int len = s.length(); int b = limit(beginIndex, 0, len); int e = limit(endIndex, 0, len); return b <= e ? s.substring(b, e) : ""; } public static String substringSafe(String s, int beginIndex) { return substringSafe(s, beginIndex, s.length()); } /** * Append the text to the stringBuilder, but in an escaped form, * as if the string would be written inside a String literal. * * @param stringBuilder the StringBuilder to append to * @param text the String to be escaped and appended * @param singleQuoteText the text to use to "escape" a single quote char * @param nonASCIICharAppender used to append the text for non-ASCII chars * @return the escaped string, as it would be written inside a String literal */ private static StringBuilder appendEscapedString( StringBuilder stringBuilder, @Nullable String text, String singleQuoteText, String doubleQuoteText, StringBuilderAppender nonASCIICharAppender) { if (text != null) { int length = text.length(); for (int i = 0; i < length; i++) { char c = text.charAt(i); appendEscapedChar(stringBuilder, c, singleQuoteText, doubleQuoteText, nonASCIICharAppender); } } return stringBuilder; } private static void appendEscapedChar( StringBuilder stringBuilder, char character, String singleQuoteText, String doubleQuoteText, StringBuilderAppender nonASCIICharAppender) { switch (character) { case BEL_CHAR: stringBuilder.append("\\b"); //NON-NLS break; case FORM_FEED_CHAR: stringBuilder.append("\\f"); //NON-NLS break; case NEWLINE_CHAR: stringBuilder.append("\\n"); //NON-NLS break; case CARRIAGE_RETURN_CHAR: stringBuilder.append("\\r"); //NON-NLS break; case TAB_CHAR: stringBuilder.append("\\t"); //NON-NLS break; case BACKSLASH_CHAR: stringBuilder.append("\\\\"); break; case DOUBLE_QUOTE_CHAR: stringBuilder.append(doubleQuoteText); break; case SINGLE_QUOTE_CHAR: stringBuilder.append(singleQuoteText); break; default: if (character < FIRST_PRINTABLE_ASCII_CHAR_VALUE) { appendUnicodeEscaped(stringBuilder, character); } else if (character > LAST_PRINTABLE_ASCII_CHAR_VALUE) { nonASCIICharAppender.append(stringBuilder, character); } else { stringBuilder.append(character); } break; } } private static void appendUnicodeEscaped(StringBuilder stringBuilder, char character) { String n = Integer.toHexString(character); stringBuilder.append("\\u"); //NON-NLS stringBuilder.append("0000".substring(n.length())); stringBuilder.append(n); } private static void append(StringBuilder stringBuilder, char character) { stringBuilder.append(character); //NON-NLS } /** * Returns the prefix of the {@code string} of the given {@code size} when * {@code size >= 0}; or the {@code string} without its last {@code -size} * characters when {@code size < 0}. * *

Returns {@code string} when {@code size >= string.length}. * Returns the empty string when {@code size <= -string.length}

*/ public static String prefix(String string, int size) { return string.substring(0, limit( size >= 0 ? size : string.length() + size, 0, string.length())); } @Nullable public static String toHtml(@Nullable String s) { if (s == null) { return null; } StringBuilder result = new StringBuilder(s.length()); int len = s.length(); for (int i = 0; i < len; i++) { char c = s.charAt(i); switch (c) { //noinspection MagicCharacter case '&': result.append("&"); //NON-NLS break; //noinspection MagicCharacter case '<': result.append("<"); //NON-NLS break; //noinspection MagicCharacter case '>': result.append(">"); //NON-NLS break; //noinspection MagicCharacter case '"': result.append("""); //NON-NLS break; //noinspection MagicCharacter case '\'': // The single quote has no name (like ""e;") so use the // generic "&#nnn;" construct result.append("'"); break; default: result.append(c); break; } } return result.toString(); } /** * Return a (double) quoted version of text. * *

Non-ASCII characters are "backslash-u..." escaped, resulting in a * string only contains ASCII characters.

* * @param text the string to quote * @param nullResult the String to be returned for null values. */ public static String quoted(@Nullable String text, String nullResult) { return quotedHelper(text, nullResult, DOUBLE_QUOTE_CHAR, "'", "\\\"", StringUtil::appendUnicodeEscaped); } /** * Return a (double) quoted version of text, with non-ASCII * characters not escaped. * * @param text the string to quote * @param nullResult the String to be returned for null values. */ public static String quoted2(@Nullable String text, String nullResult) { return quotedHelper(text, nullResult, DOUBLE_QUOTE_CHAR, "'", "\\\"", StringUtil::append); } public static String quoted2(@Nullable String text) { return quoted2(text, NULL_STRING); } /** * see {@link #quoted(String, String)} */ public static String quoted(@Nullable String text) { return quoted(text, NULL_STRING); } /** * Return a single quoted version of text. * * @param text the string to quote * @param nullResult the String to be returned for null values. */ public static String singleQuoted(@Nullable String text, String nullResult) { return quotedHelper(text, nullResult, SINGLE_QUOTE_CHAR, "\\'", "\"", StringUtil::appendUnicodeEscaped); } /** * see {@link #singleQuoted(String, String)} */ public static String singleQuoted(@Nullable String s) { return singleQuoted(s, NULL_STRING); } /** * Return a String composed of the {@link #toString()} representations of * the {@code elements}, separated with the given {@code delimiter}. * *
For example, *
{@code
     *     String message = StringUtil.join("-","Java", 3.14, null, true, '!');
     *     // message returned is: "Java-3.14-null-true-!"
     * }
*

* Note that if an element is {code null}, then {@code ""} is added. *

* (The method is a generalization of * {@link String#join(CharSequence, CharSequence...)}). *

*/ public static String join(CharSequence delimiter, Object... elements) { return textOf(toIterable(elements), delimiter); } public static String camelCased(String string) { return LocaleUtil.camelCased(string, LocaleUtil.DEFAULT_LOCALE); } public static String dashCased(String string) { return LocaleUtil.dashCased(string, LocaleUtil.DEFAULT_LOCALE); } public static String snakeCased(String string) { return LocaleUtil.snakeCased(string, LocaleUtil.DEFAULT_LOCALE); } public static String snakeUpperCased(String string) { return LocaleUtil.snakeUpperCased(string, LocaleUtil.DEFAULT_LOCALE); } /** * Return a String composed of the {@link #toString()} representations of * the {@code elements}, separated with the given {@code delimiter}. * *
For example, *
{@code
     *     String message = StringUtil.joinWithEmptyStringForNull("-","Java", 3.14, null, true, '!');
     *     // message returned is: "Java-3.14--true-!"
     * }
*

* Note that if an element is {code null}, then {@code ""} is added. *

* (The method is a generalization of * {@link String#join(CharSequence, CharSequence...)}), but adds {@code ""} * instead of {code "null"} for {code null} elements. *

*/ public static String joinWithEmptyStringForNull(CharSequence delimiter, Object... elements) { return textOf(toIterable(elements), delimiter, "", Object::toString, ""); } /** * Return the {@code string} if it has {@code maxLen} or less characters, * otherwise a string of length {@code maxLen} starting with the left of * {@code string} and ending with {@code truncatedStringSuffix}. */ public static String limitString(String string, int maxLen, String truncatedStringSuffix) { if (truncatedStringSuffix.length() > maxLen) { throw new IllegalArgumentException(String.format( "truncatedStringSuffix.length must be <= maxLen, got %d > %d", //NON-NLS truncatedStringSuffix.length(), maxLen)); } //noinspection StringConcatenation return string.length() <= maxLen ? string : prefix(string, maxLen - truncatedStringSuffix.length()) + truncatedStringSuffix; } /** * Return the {@code string} if it has {@code maxLen} or less characters, * otherwise a string of length {@code maxLen} starting with the left of * {@code string} and ending with "...". */ public static String limitString(String string, int maxLen) { return limitString(string, maxLen, "..."); } public static @NonNull String[] splitWhitespaceSeparatedString(String s) { return s.split("\\s+", -1); //NON-NLS } public static String[] toArray(Collection<@NonNull String> collection) { return collection.isEmpty() ? EMPTY_STRING_ARRAY : collection.toArray(new String[0]); } /** * Return the {@code text} with all newlines ('\n') replaced by the system's * line separator. */ public static String withLineSeparatorsForNewlines(final String text) { return text.replaceAll("\n", SystemUtil.getLineSeparator()); } /** * Return the {@code string} in upper case, or {@code "null"} when {@code string} is {@code null}. * *

Use {@code Locale#ENGLISH} as locale.

*/ public static String toUpperCaseSafe(@Nullable String string) { return string != null ? string.toUpperCase(Locale.ENGLISH) : "null"; } /** * Return the {@code string} in lower case, or {@code "null"} when {@code string} is {@code null}. * *

Use {@code Locale#ENGLISH} as locale.

*/ public static String toLowerCaseSafe(@Nullable String string) { return string != null ? string.toLowerCase(Locale.ENGLISH) : "null"; } public static Seq characters(String text) { return new AbstractSeq() { @Override public Iterator iterator() { return new Iterator() { private int i = 0; @Override public boolean hasNext() { return i < text.length(); } @Override public Character next() { if (!hasNext()) { throw new NoSuchElementException(); } return text.charAt(i++); } }; } @Override public int size() { return text.length(); } @Override public Character item(int i) { return text.charAt(i); } }; } public static Seq lines(String text) { return newSeq(END_OF_LINE_PATTERN.split(text, -1)); } public static String firstLine(String text) { return lines(text).first(); } public static String replaceRange( String text, int startIndex, int endIndex, String newRangeText) { return join("", text.substring(0, startIndex), newRangeText, text.substring(endIndex)); } public static String replaceRange( String text, IntRange range, String newRangeText) { return replaceRange( text, range.getStart(), range.getEnd(), newRangeText); } private interface StringBuilderAppender { void append(StringBuilder builder, char c); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy