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

io.debezium.util.Strings Maven / Gradle / Ivy

/*
 * Copyright Debezium Authors.
 *
 * Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
 */
package io.debezium.util;

import java.io.ByteArrayOutputStream;
import java.io.PrintWriter;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.DecimalFormat;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.UUID;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

import io.debezium.annotation.ThreadSafe;
import io.debezium.text.ParsingException;
import io.debezium.text.TokenStream;
import io.debezium.text.TokenStream.CharacterStream;
import io.debezium.text.TokenStream.Tokenizer;
import io.debezium.text.TokenStream.Tokens;

/**
 * String-related utility methods.
 *
 * @author Randall Hauch
 * @author Horia Chiorean
 */
@ThreadSafe
public final class Strings {

    private static final Pattern TIME_PATTERN = Pattern.compile("([0-9]*):([0-9]*):([0-9]*)(\\.([0-9]*))?");

    /**
     * Generate the set of values that are included in the list.
     *
     * @param input the input string
     * @param splitter the function that splits the input into multiple items; may not be null
     * @param factory the factory for creating string items into filter matches; may not be null
     * @return the set of objects included in the list; never null
     */
    public static  Set setOf(String input, Function splitter, Function factory) {
        if (input == null) {
            return Collections.emptySet();
        }
        Set matches = new HashSet<>();
        for (String item : splitter.apply(input)) {
            T obj = factory.apply(item);
            if (obj != null) {
                matches.add(obj);
            }
        }
        return matches;
    }

    /**
     * Generate the list of values that are included in the list.
     *
     * @param input the input string
     * @param splitter the function that splits the input into multiple items; may not be null
     * @param factory the factory for creating string items into filter matches; may not be null
     * @return the list of objects included in the list; never null
     */
    public static  List listOf(String input, Function splitter, Function factory) {
        if (input == null) {
            return Collections.emptyList();
        }
        List matches = new ArrayList();
        for (String item : splitter.apply(input)) {
            T obj = factory.apply(item);
            if (obj != null) {
                matches.add(obj);
            }
        }
        return matches;
    }

    /**
     * Generate the set of values that are included in the list delimited by the given delimiter.
     *
     * @param input the input string
     * @param delimiter the character used to delimit the items in the input
     * @param factory the factory for creating string items into filter matches; may not be null
     * @return the set of objects included in the list; never null
     */
    public static  Set setOf(String input, char delimiter, Function factory) {
        return setOf(input, (str) -> str.split("[" + delimiter + "]"), factory);
    }

    /**
     * Generate the set of values that are included in the list separated by commas.
     *
     * @param input the input string
     * @param factory the factory for creating string items into filter matches; may not be null
     * @return the set of objects included in the list; never null
     */
    public static  Set setOf(String input, Function factory) {
        return setOf(input, ',', factory);
    }

    /**
     * Generate the set of regular expression {@link Pattern}s that are specified in the string containing comma-separated
     * regular expressions.
     *
     * @param input the input string with comma-separated regular expressions. Comma can be escaped with backslash.
     * @return the set of regular expression {@link Pattern}s included within the given string; never null
     * @throws PatternSyntaxException if the input includes an invalid regular expression
     */
    public static Set setOfRegex(String input, int regexFlags) {
        return setOf(input, RegExSplitter::split, (str) -> Pattern.compile(str, regexFlags));
    }

    /**
     * Generate the set of regular expression {@link Pattern}s that are specified in the string containing comma-separated
     * regular expressions.
     *
     * @param input the input string with comma-separated regular expressions. Comma can be escaped with backslash.
     * @return the set of regular expression {@link Pattern}s included within the given string; never null
     * @throws PatternSyntaxException if the input includes an invalid regular expression
     */
    public static Set setOfRegex(String input) {
        return setOf(input, RegExSplitter::split, Pattern::compile);
    }

    /**
     * Generate the set of regular expression {@link Pattern}s that are specified in the string containing comma-separated
     * regular expressions.
     *
     * @param input the input string with comma-separated regular expressions. Comma can be escaped with backslash.
     * @param regexFlags the flags for {@link Pattern#compile(String, int) compiling regular expressions}
     * @return the list of regular expression {@link Pattern}s included in the list; never null
     * @throws PatternSyntaxException if the input includes an invalid regular expression
     * @throws IllegalArgumentException if bit values other than those corresponding to the defined
     *             match flags are set in {@code regexFlags}
     */
    public static List listOfRegex(String input, int regexFlags) {
        return listOf(input, RegExSplitter::split, (str) -> Pattern.compile(str, regexFlags));
    }

    /**
     * Represents a predicate (boolean-valued function) of one character argument.
     */
    @FunctionalInterface
    public static interface CharacterPredicate {
        /**
         * Evaluates this predicate on the given character argument.
         *
         * @param c the input argument
         * @return {@code true} if the input argument matches the predicate, or {@code false} otherwise
         */
        boolean test(char c);
    }

    /**
     * Split the supplied content into lines, returning each line as an element in the returned list.
     *
     * @param content the string content that is to be split
     * @return the list of lines; never null but may be an empty (unmodifiable) list if the supplied content is null or empty
     */
    public static List splitLines(final String content) {
        if (content == null || content.length() == 0) {
            return Collections.emptyList();
        }
        String[] lines = content.split("[\\r]?\\n");
        return Arrays.asList(lines);
    }

    /**
     * Compare two {@link CharSequence} instances.
     *
     * @param str1 the first character sequence; may be null
     * @param str2 the second character sequence; may be null
     * @return a negative integer if the first sequence is less than the second, zero if the sequence are equivalent (including if
     *         both are null), or a positive integer if the first sequence is greater than the second
     */
    public static int compareTo(CharSequence str1, CharSequence str2) {
        if (str1 == str2) {
            return 0;
        }
        if (str1 == null) {
            return -1;
        }
        if (str2 == null) {
            return 1;
        }
        return str1.toString().compareTo(str2.toString());
    }

    /**
     * Check whether the two {@link String} instances are equal ignoring case.
     *
     * @param str1 the first character sequence; may be null
     * @param str2 the second character sequence; may be null
     * @return {@code true} if both are null or if the two strings are equal to each other ignoring case, or {@code false}
     *         otherwise
     */
    public static boolean equalsIgnoreCase(String str1, String str2) {
        if (str1 == str2) {
            return true;
        }
        if (str1 == null) {
            return str2 == null;
        }
        return str1.equalsIgnoreCase(str2);
    }

    /**
     * Returns a new String composed of the supplied integer values joined together
     * with a copy of the specified {@code delimiter}.
     *
     * @param delimiter the delimiter that separates each element
     * @param values the values to join together.
     * @return a new {@code String} that is composed of the {@code elements} separated by the {@code delimiter}
     *
     * @throws NullPointerException If {@code delimiter} or {@code elements} is {@code null}
     * @see java.lang.String#join
     */
    public static String join(CharSequence delimiter, int[] values) {
        Objects.requireNonNull(delimiter);
        Objects.requireNonNull(values);
        if (values.length == 0) {
            return "";
        }
        if (values.length == 1) {
            return Integer.toString(values[0]);
        }
        StringBuilder sb = new StringBuilder();
        sb.append(values[0]);
        for (int i = 1; i != values.length; ++i) {
            sb.append(delimiter);
            sb.append(values[i]);
        }
        return sb.toString();
    }

    /**
     * Returns a new String composed of the supplied values joined together with a copy of the specified {@code delimiter}.
     * All {@code null} values are simply ignored.
     *
     * @param delimiter the delimiter that separates each element
     * @param values the values to join together.
     * @return a new {@code String} that is composed of the {@code elements} separated by the {@code delimiter}
     *
     * @throws NullPointerException If {@code delimiter} or {@code elements} is {@code null}
     * @see java.lang.String#join
     */
    public static  String join(CharSequence delimiter, Iterable values) {
        return join(delimiter, values, v -> {
            return v != null ? v.toString() : null;
        });
    }

    /**
     * Returns a new String composed of the supplied values joined together with a copy of the specified {@code delimiter}.
     *
     * @param delimiter the delimiter that separates each element
     * @param values the values to join together.
     * @param conversion the function that converts the supplied values into strings, or returns {@code null} if the value
     *            is to be excluded
     * @return a new {@code String} that is composed of the {@code elements} separated by the {@code delimiter}
     *
     * @throws NullPointerException If {@code delimiter} or {@code elements} is {@code null}
     * @see java.lang.String#join
     */
    public static  String join(CharSequence delimiter, Iterable values, Function conversion) {
        Objects.requireNonNull(delimiter);
        Objects.requireNonNull(values);
        Iterator iter = values.iterator();
        if (!iter.hasNext()) {
            return "";
        }
        StringBuilder sb = new StringBuilder();
        String first = conversion.apply(iter.next());
        boolean delimit = false;
        if (first != null) {
            sb.append(first);
            delimit = true;
        }
        while (iter.hasNext()) {
            String next = conversion.apply(iter.next());
            if (next != null) {
                if (delimit) {
                    sb.append(delimiter);
                }
                sb.append(next);
                delimit = true;
            }
        }
        return sb.toString();
    }

    /**
     * Trim away any leading or trailing whitespace characters.
     * 

* This is semantically equivalent to {@link String#trim()} but instead uses {@link #trim(String, CharacterPredicate)}. * * @param str the string to be trimmed; may not be null * @return the trimmed string; never null * @see #trim(String,CharacterPredicate) */ public static String trim(String str) { return trim(str, c -> c <= ' '); // same logic as String.trim() } /** * Trim away any leading or trailing characters that satisfy the supplied predicate * * @param str the string to be trimmed; may not be null * @param predicate the predicate function; may not be null * @return the trimmed string; never null * @see #trim(String) */ public static String trim(String str, CharacterPredicate predicate) { int len = str.length(); if (len == 0) { return str; } int st = 0; while ((st < len) && predicate.test(str.charAt(st))) { st++; } while ((st < len) && predicate.test(str.charAt(len - 1))) { len--; } return ((st > 0) || (len < str.length())) ? str.substring(st, len) : str; } /** * Create a new string containing the specified character repeated a specific number of times. * * @param charToRepeat the character to repeat * @param numberOfRepeats the number of times the character is to repeat in the result; must be greater than 0 * @return the resulting string */ public static String createString(final char charToRepeat, int numberOfRepeats) { assert numberOfRepeats >= 0; StringBuilder sb = new StringBuilder(); for (int i = 0; i < numberOfRepeats; ++i) { sb.append(charToRepeat); } return sb.toString(); } /** * Pad the string with the specific character to ensure the string is at least the specified length. * * @param original the string to be padded; may not be null * @param length the minimum desired length; must be positive * @param padChar the character to use for padding, if the supplied string is not long enough * @return the padded string of the desired length * @see #justifyLeft(String, int, char) */ public static String pad(String original, int length, char padChar) { if (original.length() >= length) { return original; } StringBuilder sb = new StringBuilder(original); while (sb.length() < length) { sb.append(padChar); } return sb.toString(); } /** * Set the length of the string, padding with the supplied character if the supplied string is shorter than desired, or * truncating the string if it is longer than desired. Unlike {@link #justifyLeft(String, int, char)}, this method does not * remove leading and trailing whitespace. * * @param original the string for which the length is to be set; may not be null * @param length the desired length; must be positive * @param padChar the character to use for padding, if the supplied string is not long enough * @return the string of the desired length * @see #justifyLeft(String, int, char) */ public static String setLength(String original, int length, char padChar) { return justifyLeft(original, length, padChar, false); } public static enum Justify { LEFT, RIGHT, CENTER; } /** * Justify the contents of the string. * * @param justify the way in which the string is to be justified * @param str the string to be right justified; if null, an empty string is used * @param width the desired width of the string; must be positive * @param padWithChar the character to use for padding, if needed * @return the right justified string */ public static String justify(Justify justify, String str, final int width, char padWithChar) { switch (justify) { case LEFT: return justifyLeft(str, width, padWithChar); case RIGHT: return justifyRight(str, width, padWithChar); case CENTER: return justifyCenter(str, width, padWithChar); } assert false; return null; } /** * Right justify the contents of the string, ensuring that the string ends at the last character. If the supplied string is * longer than the desired width, the leading characters are removed so that the last character in the supplied string at the * last position. If the supplied string is shorter than the desired width, the padding character is inserted one or more * times such that the last character in the supplied string appears as the last character in the resulting string and that * the length matches that specified. * * @param str the string to be right justified; if null, an empty string is used * @param width the desired width of the string; must be positive * @param padWithChar the character to use for padding, if needed * @return the right justified string */ public static String justifyRight(String str, final int width, char padWithChar) { assert width > 0; // Trim the leading and trailing whitespace ... str = str != null ? str.trim() : ""; final int length = str.length(); int addChars = width - length; if (addChars < 0) { // truncate the first characters, keep the last return str.subSequence(length - width, length).toString(); } // Prepend the whitespace ... final StringBuilder sb = new StringBuilder(); while (addChars > 0) { sb.append(padWithChar); --addChars; } // Write the content ... sb.append(str); return sb.toString(); } /** * Left justify the contents of the string, ensuring that the supplied string begins at the first character and that the * resulting string is of the desired length. If the supplied string is longer than the desired width, it is truncated to the * specified length. If the supplied string is shorter than the desired width, the padding character is added to the end of * the string one or more times such that the length is that specified. All leading and trailing whitespace is removed. * * @param str the string to be left justified; if null, an empty string is used * @param width the desired width of the string; must be positive * @param padWithChar the character to use for padding, if needed * @return the left justified string * @see #setLength(String, int, char) */ public static String justifyLeft(String str, final int width, char padWithChar) { return justifyLeft(str, width, padWithChar, true); } protected static String justifyLeft(String str, final int width, char padWithChar, boolean trimWhitespace) { // Trim the leading and trailing whitespace ... str = str != null ? (trimWhitespace ? str.trim() : str) : ""; int addChars = width - str.length(); if (addChars < 0) { // truncate return str.subSequence(0, width).toString(); } // Write the content ... final StringBuilder sb = new StringBuilder(); sb.append(str); // Append the whitespace ... while (addChars > 0) { sb.append(padWithChar); --addChars; } return sb.toString(); } /** * Center the contents of the string. If the supplied string is longer than the desired width, it is truncated to the * specified length. If the supplied string is shorter than the desired width, padding characters are added to the beginning * and end of the string such that the length is that specified; one additional padding character is prepended if required. * All leading and trailing whitespace is removed before centering. * * @param str the string to be left justified; if null, an empty string is used * @param width the desired width of the string; must be positive * @param padWithChar the character to use for padding, if needed * @return the left justified string * @see #setLength(String, int, char) */ public static String justifyCenter(String str, final int width, char padWithChar) { // Trim the leading and trailing whitespace ... str = str != null ? str.trim() : ""; int addChars = width - str.length(); if (addChars < 0) { // truncate return str.subSequence(0, width).toString(); } // Write the content ... int prependNumber = addChars / 2; int appendNumber = prependNumber; if ((prependNumber + appendNumber) != addChars) { ++prependNumber; } final StringBuilder sb = new StringBuilder(); // Prepend the pad character(s) ... while (prependNumber > 0) { sb.append(padWithChar); --prependNumber; } // Add the actual content sb.append(str); // Append the pad character(s) ... while (appendNumber > 0) { sb.append(padWithChar); --appendNumber; } return sb.toString(); } /** * Get the stack trace of the supplied exception. * * @param throwable the exception for which the stack trace is to be returned * @return the stack trace, or null if the supplied exception is null */ public static String getStackTrace(Throwable throwable) { if (throwable == null) { return null; } final ByteArrayOutputStream bas = new ByteArrayOutputStream(); final PrintWriter pw = new PrintWriter(bas); throwable.printStackTrace(pw); pw.close(); return bas.toString(); } /** * Parse the supplied string as a number. * * @param value the string representation of a integer value * @return the number, or {@code null} if the value is not a number */ public static Number asNumber(String value) { return asNumber(value, null); } /** * Parse the supplied string as a number. * * @param value the string representation of a integer value * @param defaultValueProvider the function that returns a value to be used when the string value is null or cannot be parsed * as a number; may be null if no default value is to be used * @return the number, or {@code null} if the value is not a number and no default value is supplied */ public static Number asNumber(String value, Supplier defaultValueProvider) { if (value != null) { try { return Short.valueOf(value); } catch (NumberFormatException e1) { try { return Integer.valueOf(value); } catch (NumberFormatException e2) { try { return Long.valueOf(value); } catch (NumberFormatException e3) { try { return Float.valueOf(value); } catch (NumberFormatException e4) { try { return Double.valueOf(value); } catch (NumberFormatException e5) { try { return new BigInteger(value); } catch (NumberFormatException e6) { try { return new BigDecimal(value); } catch (NumberFormatException e7) { } } } } } } } } return defaultValueProvider != null ? defaultValueProvider.get() : null; } /** * Parse the supplied string as a integer value. * * @param value the string representation of a integer value * @param defaultValue the value to return if the string value is null or cannot be parsed as an int * @return the int value */ public static int asInt(String value, int defaultValue) { if (value != null) { try { return Integer.parseInt(value); } catch (NumberFormatException e) { } } return defaultValue; } /** * Parse the supplied string as a long value. * * @param value the string representation of a long value * @param defaultValue the value to return if the string value is null or cannot be parsed as a long * @return the long value */ public static long asLong(String value, long defaultValue) { if (value != null) { try { return Long.parseLong(value); } catch (NumberFormatException e) { } } return defaultValue; } /** * Parse the supplied string as a double value. * * @param value the string representation of a double value * @param defaultValue the value to return if the string value is null or cannot be parsed as a double * @return the double value */ public static double asDouble(String value, double defaultValue) { if (value != null) { try { return Double.parseDouble(value); } catch (NumberFormatException e) { } } return defaultValue; } /** * Parse the supplied string as a boolean value. * * @param value the string representation of a boolean value * @param defaultValue the value to return if the string value is null or cannot be parsed as a boolean * @return the boolean value */ public static boolean asBoolean(String value, boolean defaultValue) { if (value != null) { try { return Boolean.parseBoolean(value); } catch (NumberFormatException e) { } } return defaultValue; } /** * Converts the given string (in the format 00:00:00(.0*)) into a {@link Duration}. * * @return the given value as {@code Duration} or {@code null} if {@code null} was passed. */ public static Duration asDuration(String timeString) { if (timeString == null) { return null; } Matcher matcher = TIME_PATTERN.matcher(timeString); if (!matcher.matches()) { throw new RuntimeException("Unexpected format for TIME column: " + timeString); } long hours = Long.parseLong(matcher.group(1)); long minutes = Long.parseLong(matcher.group(2)); long seconds = Long.parseLong(matcher.group(3)); long nanoSeconds = 0; String microSecondsString = matcher.group(5); if (microSecondsString != null) { nanoSeconds = Long.parseLong(Strings.justifyLeft(microSecondsString, 9, '0')); } if (hours >= 0) { return Duration.ofHours(hours) .plusMinutes(minutes) .plusSeconds(seconds) .plusNanos(nanoSeconds); } else { return Duration.ofHours(hours) .minusMinutes(minutes) .minusSeconds(seconds) .minusNanos(nanoSeconds); } } /** * For the given duration in milliseconds, obtain a readable representation of the form {@code HHH:MM:SS.mmm}, where *

*
HHH
*
is the number of hours written in at least 2 digits (e.g., "03")
*
MM
*
is the number of hours written in at least 2 digits (e.g., "05")
*
SS
*
is the number of hours written in at least 2 digits (e.g., "09")
*
mmm
*
is the fractional part of seconds, written with 1-3 digits (any trailing zeros are dropped)
*
* * @param durationInMillis the duration in milliseconds * @return the readable duration. */ public static String duration(long durationInMillis) { // Calculate how many seconds, and don't lose any information ... BigDecimal bigSeconds = BigDecimal.valueOf(Math.abs(durationInMillis)).divide(new BigDecimal(1000)); // Calculate the minutes, and round to lose the seconds int minutes = bigSeconds.intValue() / 60; // Remove the minutes from the seconds, to just have the remainder of seconds double dMinutes = minutes; double seconds = bigSeconds.doubleValue() - dMinutes * 60; // Now compute the number of full hours, and change 'minutes' to hold the remaining minutes int hours = minutes / 60; minutes = minutes - (hours * 60); // Format the string, and have at least 2 digits for the hours, minutes and whole seconds, // and between 3 and 6 digits for the fractional part of the seconds... String result = new DecimalFormat("######00").format(hours) + ':' + new DecimalFormat("00").format(minutes) + ':' + new DecimalFormat("00.0##").format(seconds); return result; } /** * Obtain a function that will replace variables in the supplied value with values from the supplied lookup function. *

* Variables may appear anywhere within a string value, and multiple variables can be used within the same value. Variables * take the form: * *

     *    variable := '${' variableNames [ ':' defaultValue ] '}'
     *    variableNames := variableName [ ',' variableNames ]
     *    variableName := // any characters except ',' and ':' and '}'
     *    defaultValue := // any characters except '}'
     * 
* * Note that variableName is the name used to look up a the property. *

* Notice that the syntax supports multiple variables. The logic will process the variables from let to right, * until an existing property is found. And at that point, it will stop and will not attempt to find values for the other * variables. *

* * @param replacementsByVariableName the function used to find the replacements for variable names; may not be null * @return the function that will replace variables in supplied strings; never null */ public static Function replaceVariablesWith(Function replacementsByVariableName) { return (value) -> { return replaceVariables(value, replacementsByVariableName); }; } private static final String CURLY_PREFIX = "${"; private static final String CURLY_SUFFIX = "}"; private static final String VAR_DELIM = ","; private static final String DEFAULT_DELIM = ":"; /** * Look in the supplied value for variables and replace them with values from the supplied lookup function. *

* Variables may appear anywhere within a string value, and multiple variables can be used within the same value. Variables * take the form: * *

     *    variable := '${' variableNames [ ':' defaultValue ] '}'
     *    variableNames := variableName [ ',' variableNames ]
     *    variableName := // any characters except ',' and ':' and '}'
     *    defaultValue := // any characters except '}'
     * 
* * Note that variableName is the name used to look up a the property. *

* Notice that the syntax supports multiple variables. The logic will process the variables from let to right, * until an existing property is found. And at that point, it will stop and will not attempt to find values for the other * variables. *

* * @param value the content in which the variables are to be found and replaced; may not be null * @param replacementsByVariableName the function used to find the replacements for variable names; may not be null * @return the function that will replace variables in supplied strings; never null */ public static String replaceVariables(String value, Function replacementsByVariableName) { if (value == null || value.trim().length() == 0) { return value; } StringBuilder sb = new StringBuilder(value); // Get the index of the first constant, if any int startName = sb.indexOf(CURLY_PREFIX); if (startName == -1) { return value; } // process as many different variable groupings that are defined, where one group will resolve to one property // substitution while (startName != -1) { String defaultValue = null; int endName = sb.indexOf(CURLY_SUFFIX, startName); if (endName == -1) { // if no suffix can be found, then this variable was probably defined incorrectly // but return what there is at this point return sb.toString(); } String varString = sb.substring(startName + 2, endName); if (varString.indexOf(DEFAULT_DELIM) > -1) { List defaults = split(varString, DEFAULT_DELIM); // get the property(s) variables that are defined left of the default delimiter. varString = defaults.get(0); // if the default is defined, then capture in case none of the other properties are found if (defaults.size() == 2) { defaultValue = defaults.get(1); } } String constValue = null; // split the property(s) based VAR_DELIM, when multiple property options are defined List vars = split(varString, VAR_DELIM); for (final String var : vars) { constValue = replacementsByVariableName.apply(var); // the first found property is the value to be substituted if (constValue != null) { break; } } // if no property is found to substitute, then use the default value, if defined if (constValue == null && defaultValue != null) { constValue = defaultValue; } if (constValue != null) { sb = sb.replace(startName, endName + 1, constValue); // Checking for another constants startName = sb.indexOf(CURLY_PREFIX); } else { // continue to try to substitute for other properties so that all defined variables // are tried to be substituted for startName = sb.indexOf(CURLY_PREFIX, endName); } } return sb.toString(); } /** * Split a string into pieces based on delimiters. Similar to the Perl function of the same name. The delimiters are not * included in the returned strings. * * @param str Full string * @param splitter Characters to split on * @return List of String pieces from full string */ private static List split(String str, String splitter) { StringTokenizer tokens = new StringTokenizer(str, splitter); ArrayList l = new ArrayList<>(tokens.countTokens()); while (tokens.hasMoreTokens()) { l.add(tokens.nextToken()); } return l; } /** * Determine if the supplied string is a valid {@link UUID}. * @param str the string to evaluate * @return {@code true} if the string is a valid representation of a UUID, or {@code false} otherwise */ public static boolean isUuid(String str) { if (str == null) { return false; } try { UUID.fromString(str); return true; } catch (IllegalArgumentException e) { return false; } } /** * Check if the string is empty or null. * * @param str the string to check * @return {@code true} if the string is empty or null */ public static boolean isNullOrEmpty(String str) { return str == null || str.isEmpty(); } /** * Check if the string contains only digits. * * @param str the string to check * @return {@code true} if only contains digits */ public static boolean isNumeric(String str) { if (isNullOrEmpty(str)) { return false; } final int sz = str.length(); for (int i = 0; i < sz; i++) { if (!Character.isDigit(str.charAt(i))) { return false; } } return true; } /** * Unquotes the given identifier part (e.g. an unqualified table name), if the * first character is one of the supported quoting characters (single quote, * double quote and back-tick) and the last character equals to the first * character. Otherwise, the original string will be returned. */ public static String unquoteIdentifierPart(String identifierPart) { if (identifierPart == null || identifierPart.length() < 2) { return identifierPart; } Character quotingChar = deriveQuotingChar(identifierPart); if (quotingChar != null) { identifierPart = identifierPart.substring(1, identifierPart.length() - 1); identifierPart = identifierPart.replace(quotingChar.toString() + quotingChar.toString(), quotingChar.toString()); } return identifierPart; } /** * Restores a byte array that is encoded as a hex string. */ public static byte[] hexStringToByteArray(String hexString) { if (hexString == null) { return null; } int length = hexString.length(); byte[] bytes = new byte[length / 2]; for (int i = 0; i < length; i += 2) { bytes[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) + Character.digit(hexString.charAt(i + 1), 16)); } return bytes; } /** * Whether the given string begins with the given prefix, ignoring casing. * Copied from https://github.com/spring-projects/spring-framework/blob/master/spring-core/src/main/java/org/springframework/util/StringUtils.java. */ public static boolean startsWithIgnoreCase(String str, String prefix) { return (str != null && prefix != null && str.length() >= prefix.length() && str.regionMatches(true, 0, prefix, 0, prefix.length())); } /** * Returns the first {@code length} characters of the given string. * * @param str The string to get the begin from * @param length The length of the string to return * @return {@code null}, if the given string is {@code null}, the first first * {@code length} characters of that string otherwise. If the string is * shorter than the given number of characters, the string itself will * be returned. */ public static String getBegin(String str, int length) { if (str == null) { return null; } else if (str.length() < length) { return str; } else { return str.substring(0, length); } } private static Character deriveQuotingChar(String identifierPart) { char first = identifierPart.charAt(0); char last = identifierPart.charAt(identifierPart.length() - 1); if (first == last && (first == '"' || first == '\'' || first == '`')) { return first; } return null; } private Strings() { } /** * A tokenization class used to split a comma-separated list of regular expressions. * If a comma is part of expression then it can be prepended with '\' so * it will not act as a separator. */ private static class RegExSplitter implements Tokenizer { public static String[] split(String identifier) { TokenStream stream = new TokenStream(identifier, new RegExSplitter(), true); stream.start(); List parts = new ArrayList<>(); while (stream.hasNext()) { final String part = stream.consume(); if (part.length() == 0) { continue; } parts.add(part.trim().replace("\\,", ",")); } return parts.toArray(new String[parts.size()]); } @Override public void tokenize(CharacterStream input, Tokens tokens) throws ParsingException { int tokenStart = 0; while (input.hasNext()) { char c = input.next(); // Escape sequence if (c == '\\') { if (!input.hasNext()) { throw new ParsingException(input.position(input.index()), "Unterminated escape sequence at the end of the string"); } input.next(); } else if (c == ',') { tokens.addToken(input.position(tokenStart), tokenStart, input.index()); tokenStart = input.index() + 1; } } tokens.addToken(input.position(tokenStart), tokenStart, input.index() + 1); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy