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

com.mysql.cj.util.StringUtils Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2002, 2024, Oracle and/or its affiliates.
 *
 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, as published by
 * the Free Software Foundation.
 *
 * This program is designed to work with certain software that is licensed under separate terms, as designated in a particular file or component or in
 * included license documentation. The authors of MySQL hereby grant you an additional permission to link the program and your derivative works with the
 * separately licensed software that they have either included with the program or referenced in the documentation.
 *
 * Without limiting anything contained in the foregoing, this file, which is part of MySQL Connector/J, is also subject to the Universal FOSS Exception,
 * version 1.0, a copy of which can be found at http://oss.oracle.com/licenses/universal-foss-exception.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License, version 2.0, for more details.
 *
 * You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 */

package com.mysql.cj.util;

import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.charset.UnsupportedCharsetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;

import com.mysql.cj.Messages;
import com.mysql.cj.ServerVersion;
import com.mysql.cj.exceptions.ExceptionFactory;
import com.mysql.cj.exceptions.WrongArgumentException;

/**
 * Various utility methods for converting to/from byte arrays in the platform encoding and several other String operations.
 */
public class StringUtils {

    private static final int WILD_COMPARE_MATCH = 0;
    private static final int WILD_COMPARE_CONTINUE_WITH_WILD = 1;
    private static final int WILD_COMPARE_NO_MATCH = -1;

    static final char WILDCARD_MANY = '%';
    static final char WILDCARD_ONE = '_';
    static final char WILDCARD_ESCAPE = '\\';

    private static final String VALID_ID_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIGKLMNOPQRSTUVWXYZ0123456789$_#@";

    /**
     * Returns the given bytes as a hex and ASCII dump (up to length bytes).
     *
     * @param byteBuffer
     *            the data to dump as hex
     * @param length
     *            the number of bytes to print
     *
     * @return a hex and ASCII dump
     */
    public static String dumpAsHex(byte[] byteBuffer, int length) {
        length = Math.min(length, byteBuffer.length);
        StringBuilder fullOutBuilder = new StringBuilder(length * 4);
        StringBuilder asciiOutBuilder = new StringBuilder(16);

        for (int p = 0, l = 0; p < length; l = 0) { // p: position in buffer (1..length); l: position in line (1..8)
            for (; l < 8 && p < length; p++, l++) {
                int asInt = byteBuffer[p] & 0xff;
                if (asInt < 0x10) {
                    fullOutBuilder.append("0");
                }
                fullOutBuilder.append(Integer.toHexString(asInt)).append(" ");
                asciiOutBuilder.append(" ").append(asInt >= 0x20 && asInt < 0x7f ? (char) asInt : ".");
            }
            for (; l < 8; l++) { // if needed, fill remaining of last line with spaces
                fullOutBuilder.append("   ");
            }
            fullOutBuilder.append("   ").append(asciiOutBuilder).append(System.lineSeparator());
            asciiOutBuilder.setLength(0);
        }
        return fullOutBuilder.toString();
    }

    /**
     * Converts the given byte array into Hex String, stopping at given length.
     *
     * @param byteBuffer
     *            the byte array to convert
     * @param length
     *            the number of bytes from the given array to convert
     * @return
     *         a String containing the Hex representation of the given bytes
     */
    public static String toHexString(byte[] byteBuffer, int length) {
        length = Math.min(length, byteBuffer.length);
        StringBuilder outputBuilder = new StringBuilder(length * 2);
        for (int i = 0; i < length; i++) {
            int asInt = byteBuffer[i] & 0xff;
            if (asInt < 0x10) {
                outputBuilder.append("0");
            }
            outputBuilder.append(Integer.toHexString(asInt));
        }
        return outputBuilder.toString();
    }

    private static boolean endsWith(byte[] dataFrom, String suffix) {
        for (int i = 1; i <= suffix.length(); i++) {
            int dfOffset = dataFrom.length - i;
            int suffixOffset = suffix.length() - i;
            if (dataFrom[dfOffset] != suffix.charAt(suffixOffset)) {
                return false;
            }
        }
        return true;
    }

    /**
     * Returns the first non-whitespace char, converted to upper case
     *
     * @param searchIn
     *            the string to search in
     *
     * @return the first non-whitespace character, upper cased.
     */
    public static char firstNonWsCharUc(String searchIn) {
        return firstNonWsCharUc(searchIn, 0);
    }

    public static char firstNonWsCharUc(String searchIn, int startAt) {
        if (searchIn == null) {
            return 0;
        }

        int length = searchIn.length();

        for (int i = startAt; i < length; i++) {
            char c = searchIn.charAt(i);

            if (!Character.isWhitespace(c)) {
                return Character.toUpperCase(c);
            }
        }

        return 0;
    }

    public static char firstAlphaCharUc(String searchIn, int startAt) {
        if (searchIn == null) {
            return 0;
        }

        int length = searchIn.length();

        for (int i = startAt; i < length; i++) {
            char c = searchIn.charAt(i);

            if (Character.isLetter(c)) {
                return Character.toUpperCase(c);
            }
        }

        return 0;
    }

    /**
     * Adds '+' to decimal numbers that are positive (MySQL doesn't understand
     * them otherwise
     *
     * @param dString
     *            The value as a string
     *
     * @return String the string with a '+' added (if needed)
     */
    public static String fixDecimalExponent(String dString) {
        int ePos = dString.indexOf('E');

        if (ePos == -1) {
            ePos = dString.indexOf('e');
        }

        if (ePos != -1) {
            if (dString.length() > ePos + 1) {
                char maybeMinusChar = dString.charAt(ePos + 1);

                if (maybeMinusChar != '-' && maybeMinusChar != '+') {
                    StringBuilder strBuilder = new StringBuilder(dString.length() + 1);
                    strBuilder.append(dString.substring(0, ePos + 1));
                    strBuilder.append('+');
                    strBuilder.append(dString.substring(ePos + 1, dString.length()));
                    dString = strBuilder.toString();
                }
            }
        }

        return dString;
    }

    /**
     * Returns the byte[] representation of the given string using the given encoding.
     *
     * @param s
     *            source string
     * @param encoding
     *            java encoding
     * @return bytes
     */
    public static byte[] getBytes(String s, String encoding) {
        if (s == null) {
            return new byte[0];
        }
        if (encoding == null) {
            return getBytes(s);
        }
        try {
            return s.getBytes(encoding);
        } catch (UnsupportedEncodingException uee) {
            throw ExceptionFactory.createException(WrongArgumentException.class, Messages.getString("StringUtils.0", new Object[] { encoding }), uee);
        }
    }

    /**
     * Returns the byte[] representation of the given string properly wrapped between the given char delimiters using the given encoding.
     *
     * @param s
     *            source string
     * @param beginWrap
     *            opening char delimiter
     * @param endWrap
     *            closing char delimiter
     * @param encoding
     *            java encoding
     * @return bytes
     */
    public static byte[] getBytesWrapped(String s, char beginWrap, char endWrap, String encoding) {
        byte[] b;

        if (encoding == null) {
            StringBuilder strBuilder = new StringBuilder(s.length() + 2);
            strBuilder.append(beginWrap);
            strBuilder.append(s);
            strBuilder.append(endWrap);

            b = getBytes(strBuilder.toString());
        } else {
            StringBuilder strBuilder = new StringBuilder(s.length() + 2);
            strBuilder.append(beginWrap);
            strBuilder.append(s);
            strBuilder.append(endWrap);

            s = strBuilder.toString();
            b = getBytes(s, encoding);
        }

        return b;
    }

    /**
     * Finds the position of a substring within a string ignoring case.
     *
     * @param searchIn
     *            the string to search in
     * @param searchFor
     *            the array of strings to search for
     * @return the position where searchFor is found within searchIn starting from startingPosition.
     */
    public static int indexOfIgnoreCase(String searchIn, String searchFor) {
        return indexOfIgnoreCase(0, searchIn, searchFor);
    }

    /**
     * Finds the position of a substring within a string ignoring case.
     *
     * @param startingPosition
     *            the position to start the search from
     * @param searchIn
     *            the string to search in
     * @param searchFor
     *            the array of strings to search for
     * @return the position where searchFor is found within searchIn starting from startingPosition.
     */
    public static int indexOfIgnoreCase(int startingPosition, String searchIn, String searchFor) {
        if (searchIn == null || searchFor == null) {
            return -1;
        }

        int searchInLength = searchIn.length();
        int searchForLength = searchFor.length();
        int stopSearchingAt = searchInLength - searchForLength;

        if (startingPosition > stopSearchingAt || searchForLength == 0) {
            return -1;
        }

        // Some locales don't follow upper-case rule, so need to check both
        char firstCharOfSearchForUc = Character.toUpperCase(searchFor.charAt(0));
        char firstCharOfSearchForLc = Character.toLowerCase(searchFor.charAt(0));

        for (int i = startingPosition; i <= stopSearchingAt; i++) {
            if (isCharAtPosNotEqualIgnoreCase(searchIn, i, firstCharOfSearchForUc, firstCharOfSearchForLc)) {
                // find the first occurrence of the first character of searchFor in searchIn
                while (++i <= stopSearchingAt && isCharAtPosNotEqualIgnoreCase(searchIn, i, firstCharOfSearchForUc, firstCharOfSearchForLc)) {
                }
            }

            if (i <= stopSearchingAt && regionMatchesIgnoreCase(searchIn, i, searchFor)) {
                return i;
            }
        }

        return -1;
    }

    /**
     * Finds the position of the first of a consecutive sequence of strings within a string, ignoring case, with the option to skip text delimited by given
     * markers or within comments.
     * 

* Independently of the searchMode provided, when searching for the second and following strings SearchMode.SKIP_WHITE_SPACE will * be added and SearchMode.SKIP_BETWEEN_MARKERS removed. *

* * @param startingPosition * the position to start the search from * @param searchIn * the string to search in * @param searchForSequence * searchForSequence * @param openingMarkers * characters that delimit the beginning of a text block to skip * @param closingMarkers * characters that delimit the end of a text block to skip * @param searchMode * a Set, ideally an EnumSet, containing the flags from the enum StringUtils.SearchMode that determine the * behavior of the search * @return the position where searchFor is found within searchIn starting from startingPosition. */ public static int indexOfIgnoreCase(int startingPosition, String searchIn, String[] searchForSequence, String openingMarkers, String closingMarkers, Set searchMode) { StringInspector strInspector = new StringInspector(searchIn, startingPosition, openingMarkers, closingMarkers, "", searchMode); return strInspector.indexOfIgnoreCase(searchForSequence); } /** * Finds the position of a substring within a string, ignoring case, with the option to skip text delimited by given markers or within comments. * * @param startingPosition * the position to start the search from * @param searchIn * the string to search in * @param searchFor * the string to search for * @param openingMarkers * characters that delimit the beginning of a text block to skip * @param closingMarkers * characters that delimit the end of a text block to skip * @param searchMode * a Set, ideally an EnumSet, containing the flags from the enum StringUtils.SearchMode that determine the * behavior of the search * @return the position where searchFor is found within searchIn starting from startingPosition. */ public static int indexOfIgnoreCase(int startingPosition, String searchIn, String searchFor, String openingMarkers, String closingMarkers, Set searchMode) { return indexOfIgnoreCase(startingPosition, searchIn, searchFor, openingMarkers, closingMarkers, "", searchMode); } /** * Finds the position of a substring within a string, ignoring case, with the option to skip text delimited by given markers or within comments. * * @param startingPosition * the position to start the search from * @param searchIn * the string to search in * @param searchFor * the string to search for * @param openingMarkers * characters that delimit the beginning of a text block to skip * @param closingMarkers * characters that delimit the end of a text block to skip * @param overridingMarkers * the subset of openingMarkers that override the remaining markers, e.g., if openingMarkers = "'(" and * overridingMarkers = "'" then the block between the outer parenthesis in "start ('max('); end" is strictly consumed, * otherwise the suffix " end" would end up being consumed too in the process of handling the nested parenthesis. * @param searchMode * a Set, ideally an EnumSet, containing the flags from the enum StringUtils.SearchMode that determine the * behavior of the search * @return the position where searchFor is found within searchIn starting from startingPosition. */ public static int indexOfIgnoreCase(int startingPosition, String searchIn, String searchFor, String openingMarkers, String closingMarkers, String overridingMarkers, Set searchMode) { StringInspector strInspector = new StringInspector(searchIn, startingPosition, openingMarkers, closingMarkers, overridingMarkers, searchMode); return strInspector.indexOfIgnoreCase(searchFor); } /** * Finds the position of the next alphanumeric character within a string, with the option to skip text delimited by given markers or within comments. * * @param startingPosition * the position to start the search from * @param searchIn * the string to search in * @param openingMarkers * characters that delimit the beginning of a text block to skip * @param closingMarkers * characters that delimit the end of a text block to skip * @param overridingMarkers * the subset of openingMarkers that override the remaining markers, e.g., if openingMarkers = "'(" and * overridingMarkers = "'" then the block between the outer parenthesis in "start ('max('); end" is strictly consumed, * otherwise the suffix " end" would end up being consumed too in the process of handling the nested parenthesis. * @param searchMode * a Set, ideally an EnumSet, containing the flags from the enum StringUtils.SearchMode that determine the * behavior of the search * @return the position where the next non-whitespace character is found within searchIn starting from startingPosition. */ public static int indexOfNextAlphanumericChar(int startingPosition, String searchIn, String openingMarkers, String closingMarkers, String overridingMarkers, Set searchMode) { StringInspector strInspector = new StringInspector(searchIn, startingPosition, openingMarkers, closingMarkers, overridingMarkers, searchMode); return strInspector.indexOfNextAlphanumericChar(); } /** * Finds the position of the next non-whitespace character within a string, with the option to skip text delimited by given markers or within comments. * * @param startingPosition * the position to start the search from * @param searchIn * the string to search in * @param openingMarkers * characters that delimit the beginning of a text block to skip * @param closingMarkers * characters that delimit the end of a text block to skip * @param overridingMarkers * the subset of openingMarkers that override the remaining markers, e.g., if openingMarkers = "'(" and * overridingMarkers = "'" then the block between the outer parenthesis in "start ('max('); end" is strictly consumed, * otherwise the suffix " end" would end up being consumed too in the process of handling the nested parenthesis. * @param searchMode * a Set, ideally an EnumSet, containing the flags from the enum StringUtils.SearchMode that determine the * behavior of the search * @return the position where the next non-whitespace character is found within searchIn starting from startingPosition. */ public static int indexOfNextNonWsChar(int startingPosition, String searchIn, String openingMarkers, String closingMarkers, String overridingMarkers, Set searchMode) { StringInspector strInspector = new StringInspector(searchIn, startingPosition, openingMarkers, closingMarkers, overridingMarkers, searchMode); return strInspector.indexOfNextNonWsChar(); } /** * Finds the position of the next whitespace character within a string, with the option to skip text delimited by given markers or within comments. * * @param startingPosition * the position to start the search from * @param searchIn * the string to search in * @param openingMarkers * characters that delimit the beginning of a text block to skip * @param closingMarkers * characters that delimit the end of a text block to skip * @param overridingMarkers * the subset of openingMarkers that override the remaining markers, e.g., if openingMarkers = "'(" and * overridingMarkers = "'" then the block between the outer parenthesis in "start ('max('); end" is strictly consumed, * otherwise the suffix " end" would end up being consumed too in the process of handling the nested parenthesis. * @param searchMode * a Set, ideally an EnumSet, containing the flags from the enum StringUtils.SearchMode that determine the * behavior of the search * @return the position where the next whitespace character is found within searchIn starting from startingPosition. */ public static int indexOfNextWsChar(int startingPosition, String searchIn, String openingMarkers, String closingMarkers, String overridingMarkers, Set searchMode) { StringInspector strInspector = new StringInspector(searchIn, startingPosition, openingMarkers, closingMarkers, overridingMarkers, searchMode); return strInspector.indexOfNextWsChar(); } private static boolean isCharAtPosNotEqualIgnoreCase(String searchIn, int pos, char firstCharOfSearchForUc, char firstCharOfSearchForLc) { return Character.toLowerCase(searchIn.charAt(pos)) != firstCharOfSearchForLc && Character.toUpperCase(searchIn.charAt(pos)) != firstCharOfSearchForUc; } protected static boolean isCharEqualIgnoreCase(char charToCompare, char compareToCharUC, char compareToCharLC) { return Character.toLowerCase(charToCompare) == compareToCharLC || Character.toUpperCase(charToCompare) == compareToCharUC; } /** * Splits stringToSplit into a list, using the given delimiter * * @param stringToSplit * the string to split * @param delimiter * the string to split on * @param trim * should the split strings be whitespace trimmed? * * @return the list of strings, split by delimiter * * @throws IllegalArgumentException * if an error occurs */ public static List split(String stringToSplit, String delimiter, boolean trim) { if (stringToSplit == null) { return new ArrayList<>(); } if (delimiter == null) { throw new IllegalArgumentException(); } String[] tokens = stringToSplit.split(delimiter, -1); List tokensList = Arrays.asList(tokens); if (trim) { tokensList = tokensList.stream().map(String::trim).collect(Collectors.toList()); } return tokensList; } /** * Splits stringToSplit into a list, using the given delimiter and skipping all between the given markers. * * @param stringToSplit * the string to split * @param delimiter * the string to split on * @param openingMarkers * characters that delimit the beginning of a text block to skip * @param closingMarkers * characters that delimit the end of a text block to skip * @param trim * should the split strings be whitespace trimmed? * * @return the list of strings, split by delimiter * * @throws IllegalArgumentException * if an error occurs */ public static List split(String stringToSplit, String delimiter, String openingMarkers, String closingMarkers, boolean trim) { return split(stringToSplit, delimiter, openingMarkers, closingMarkers, "", trim); } /** * Splits stringToSplit into a list, using the given delimiter and skipping all between the given markers. * * @param stringToSplit * the string to split * @param delimiter * the string to split on * @param openingMarkers * characters that delimit the beginning of a text block to skip * @param closingMarkers * characters that delimit the end of a text block to skip * @param trim * should the split strings be whitespace trimmed? * @param searchMode * a Set, ideally an EnumSet, containing the flags from the enum StringUtils.SearchMode that determine the * behaviour of the search * * @return the list of strings, split by delimiter * * @throws IllegalArgumentException * if an error occurs */ public static List split(String stringToSplit, String delimiter, String openingMarkers, String closingMarkers, boolean trim, Set searchMode) { return split(stringToSplit, delimiter, openingMarkers, closingMarkers, "", trim, searchMode); } /** * Splits stringToSplit into a list, using the given delimiter and skipping all between the given markers. * * @param stringToSplit * the string to split * @param delimiter * the string to split on * @param openingMarkers * characters that delimit the beginning of a text block to skip * @param closingMarkers * characters that delimit the end of a text block to skip * @param overridingMarkers * the subset of openingMarkers that override the remaining markers, e.g., if openingMarkers = "'(" and * overridingMarkers = "'" then the block between the outer parenthesis in "start ('max('); end" is strictly consumed, * otherwise the suffix " end" would end up being consumed too in the process of handling the nested parenthesis. * @param trim * should the split strings be whitespace trimmed? * * @return the list of strings, split by delimiter * * @throws IllegalArgumentException * if an error occurs */ public static List split(String stringToSplit, String delimiter, String openingMarkers, String closingMarkers, String overridingMarkers, boolean trim) { return split(stringToSplit, delimiter, openingMarkers, closingMarkers, overridingMarkers, trim, SearchMode.__MRK_COM_MYM_HNT_WS); } /** * Splits stringToSplit into a list, using the given delimiter and skipping all between the given markers. * * @param stringToSplit * the string to split * @param delimiter * the string to split on * @param openingMarkers * characters that delimit the beginning of a text block to skip * @param closingMarkers * characters that delimit the end of a text block to skip * @param overridingMarkers * the subset of openingMarkers that override the remaining markers, e.g., if openingMarkers = "'(" and * overridingMarkers = "'" then the block between the outer parenthesis in "start ('max('); end" is strictly consumed, * otherwise the suffix " end" would end up being consumed too in the process of handling the nested parenthesis. * @param trim * should the split strings be whitespace trimmed? * @param searchMode * a Set, ideally an EnumSet, containing the flags from the enum StringUtils.SearchMode that determine the * behaviour of the search * * @return the list of strings, split by delimiter * * @throws IllegalArgumentException * if an error occurs */ public static List split(String stringToSplit, String delimiter, String openingMarkers, String closingMarkers, String overridingMarkers, boolean trim, Set searchMode) { StringInspector strInspector = new StringInspector(stringToSplit, openingMarkers, closingMarkers, overridingMarkers, searchMode); return strInspector.split(delimiter, trim); } private static boolean startsWith(byte[] dataFrom, String chars) { int charsLength = chars.length(); if (dataFrom.length < charsLength) { return false; } for (int i = 0; i < charsLength; i++) { if (dataFrom[i] != chars.charAt(i)) { return false; } } return true; } /** * Determines whether or not the string 'searchIn' contains the string 'searchFor', disregarding case and starting at 'startAt'. Shorthand for a * String.regionMatch(...) * * @param searchIn * the string to search in * @param startAt * the position to start at * @param searchFor * the string to search for * * @return whether searchIn starts with searchFor, ignoring case */ public static boolean regionMatchesIgnoreCase(String searchIn, int startAt, String searchFor) { return searchIn.regionMatches(true, startAt, searchFor, 0, searchFor.length()); } /** * Determines whether or not the string 'searchIn' starts with the string 'searchFor', dis-regarding case. Shorthand for a String.regionMatch(...) * * @param searchIn * the string to search in * @param searchFor * the string to search for * * @return whether searchIn starts with searchFor, ignoring case */ public static boolean startsWithIgnoreCase(String searchIn, String searchFor) { return regionMatchesIgnoreCase(searchIn, 0, searchFor); } /** * Determines whether or not the string 'searchIn' starts with the string 'searchFor', disregarding case,leading whitespace and non-alphanumeric characters. * * @param searchIn * the string to search in * @param searchFor * the string to search for * * @return true if the string starts with 'searchFor' ignoring whitespace */ public static boolean startsWithIgnoreCaseAndNonAlphaNumeric(String searchIn, String searchFor) { if (searchIn == null) { return searchFor == null; } int beginPos = 0; int inLength = searchIn.length(); for (; beginPos < inLength; beginPos++) { char c = searchIn.charAt(beginPos); if (Character.isLetterOrDigit(c)) { break; } } return regionMatchesIgnoreCase(searchIn, beginPos, searchFor); } /** * Determines whether or not the string 'searchIn' starts with the string 'searchFor', disregarding case and leading whitespace * * @param searchIn * the string to search in * @param searchFor * the string to search for * * @return true if the string starts with 'searchFor' ignoring whitespace */ public static boolean startsWithIgnoreCaseAndWs(String searchIn, String searchFor) { return startsWithIgnoreCaseAndWs(searchIn, searchFor, 0); } /** * Determines whether or not the string 'searchIn' contains the string 'searchFor', disregarding case and leading whitespace * * @param searchIn * the string to search in * @param searchFor * the string to search for * @param beginPos * where to start searching * * @return true if the string starts with 'searchFor' ignoring whitespace */ public static boolean startsWithIgnoreCaseAndWs(String searchIn, String searchFor, int beginPos) { if (searchIn == null) { return searchFor == null; } for (; beginPos < searchIn.length(); beginPos++) { if (!Character.isWhitespace(searchIn.charAt(beginPos))) { break; } } return regionMatchesIgnoreCase(searchIn, beginPos, searchFor); } /** * Determines whether or not the string 'searchIn' starts with one of the strings in 'searchFor', disregarding case and leading whitespace * * @param searchIn * the string to search in * @param searchFor * the string array to search for * * @return the 'searchFor' array index that matched or -1 if none matches */ public static int startsWithIgnoreCaseAndWs(String searchIn, String[] searchFor) { for (int i = 0; i < searchFor.length; i++) { if (startsWithIgnoreCaseAndWs(searchIn, searchFor[i], 0)) { return i; } } return -1; } /** * Determines whether or not the string 'searchIn' ends with the string 'searchFor', dis-regarding case starting at 'startAt' Shorthand for a * String.regionMatch(...) * * @param searchIn * the string to search in * @param searchFor * the string to search for * * @return whether searchIn ends with searchFor, ignoring case */ public static boolean endsWithIgnoreCase(String searchIn, String searchFor) { int len = searchFor.length(); return searchIn.regionMatches(true, searchIn.length() - len, searchFor, 0, len); } /** * @param source * bytes to strip * @param prefix * prefix * @param suffix * suffix * @return result bytes */ public static byte[] stripEnclosure(byte[] source, String prefix, String suffix) { if (source.length >= prefix.length() + suffix.length() && startsWith(source, prefix) && endsWith(source, suffix)) { int totalToStrip = prefix.length() + suffix.length(); int enclosedLength = source.length - totalToStrip; byte[] enclosed = new byte[enclosedLength]; int startPos = prefix.length(); int numToCopy = enclosed.length; System.arraycopy(source, startPos, enclosed, 0, numToCopy); return enclosed; } return source; } /** * Returns the bytes as an ASCII String. * * @param buffer * the bytes representing the string * * @return The ASCII String. */ public static String toAsciiString(byte[] buffer) { return toAsciiString(buffer, 0, buffer.length); } /** * Returns the bytes as an ASCII String. * * @param buffer * the bytes to convert * @param startPos * the position to start converting * @param length * the length of the string to convert * * @return the ASCII string */ public static String toAsciiString(byte[] buffer, int startPos, int length) { return new String(toAsciiCharArray(buffer, startPos, length)); } /** * Returns the bytes as an ASCII String. * * @param buffer * the bytes to convert * @param startPos * the position to start converting * @param length * the length of the string to convert * * @return the ASCII char array */ public static char[] toAsciiCharArray(byte[] buffer, int startPos, int length) { char[] charArray = new char[length]; int readpoint = startPos; for (int i = 0; i < length; i++) { charArray[i] = (char) buffer[readpoint]; readpoint++; } return charArray; } /** * Compares searchIn against searchForWildcard with wildcards, in a case insensitive manner. * * @param searchIn * the string to search in * @param searchFor * the string to search for, using the 'standard' SQL wildcard chars of '%' and '_' * @return true if matches */ public static boolean wildCompareIgnoreCase(String searchIn, String searchFor) { return wildCompareInternal(searchIn, searchFor) == WILD_COMPARE_MATCH; } /** * Compares searchIn against searchForWildcard with wildcards (heavily borrowed from strings/ctype-simple.c in the server sources) * * This method does a single passage matching for normal characters and WILDCARD_ONE (_), and recursive matching for WILDCARD_MANY (%) which may be repeated * for as many anchor chars are found. * * @param searchIn * the string to search in * @param searchFor * the string to search for, using the 'standard' SQL wildcard chars of '%' and '_' * * @return WILD_COMPARE_MATCH if matched, WILD_COMPARE_NO_MATCH if not matched, WILD_COMPARE_CONTINUE_WITH_WILD if not matched yet, but it may in one of * following recursion rounds */ private static int wildCompareInternal(String searchIn, String searchFor) { if (searchIn == null || searchFor == null) { return WILD_COMPARE_NO_MATCH; } if (searchFor.equals("%")) { return WILD_COMPARE_MATCH; } int searchForPos = 0; int searchForEnd = searchFor.length(); int searchInPos = 0; int searchInEnd = searchIn.length(); int result = WILD_COMPARE_NO_MATCH; /* Not found, using wildcards */ while (searchForPos != searchForEnd) { while (searchFor.charAt(searchForPos) != WILDCARD_MANY && searchFor.charAt(searchForPos) != WILDCARD_ONE) { if (searchFor.charAt(searchForPos) == WILDCARD_ESCAPE && searchForPos + 1 != searchForEnd) { searchForPos++; } if (searchInPos == searchInEnd || Character.toUpperCase(searchFor.charAt(searchForPos++)) != Character.toUpperCase(searchIn.charAt(searchInPos++))) { return WILD_COMPARE_CONTINUE_WITH_WILD; /* No match */ } if (searchForPos == searchForEnd) { return searchInPos != searchInEnd ? WILD_COMPARE_CONTINUE_WITH_WILD : WILD_COMPARE_MATCH; /* Match if both are at end */ } result = WILD_COMPARE_CONTINUE_WITH_WILD; /* Found an anchor char */ } if (searchFor.charAt(searchForPos) == WILDCARD_ONE) { do { if (searchInPos == searchInEnd) { /* Skip one char if possible */ return result; } searchInPos++; } while (++searchForPos < searchForEnd && searchFor.charAt(searchForPos) == WILDCARD_ONE); if (searchForPos == searchForEnd) { break; } } if (searchFor.charAt(searchForPos) == WILDCARD_MANY) { /* Found w_many */ searchForPos++; /* Remove any '%' and '_' from the wild search string */ for (; searchForPos != searchForEnd; searchForPos++) { if (searchFor.charAt(searchForPos) == WILDCARD_MANY) { continue; } if (searchFor.charAt(searchForPos) == WILDCARD_ONE) { if (searchInPos == searchInEnd) { /* Skip one char if possible */ return WILD_COMPARE_NO_MATCH; } searchInPos++; continue; } break; /* Not a wild character */ } if (searchForPos == searchForEnd) { return WILD_COMPARE_MATCH; /* Ok if w_many is last */ } if (searchInPos == searchInEnd) { return WILD_COMPARE_NO_MATCH; } char cmp; if ((cmp = searchFor.charAt(searchForPos)) == WILDCARD_ESCAPE && searchForPos + 1 != searchForEnd) { cmp = searchFor.charAt(++searchForPos); } searchForPos++; do { while (searchInPos != searchInEnd && Character.toUpperCase(searchIn.charAt(searchInPos)) != Character.toUpperCase(cmp)) { searchInPos++; } /* Searches for an anchor char */ if (searchInPos++ == searchInEnd) { return WILD_COMPARE_NO_MATCH; } int tmp = wildCompareInternal(searchIn.substring(searchInPos), searchFor.substring(searchForPos)); if (tmp <= 0) { return tmp; } } while (searchInPos != searchInEnd); return WILD_COMPARE_NO_MATCH; } } return searchInPos != searchInEnd ? WILD_COMPARE_CONTINUE_WITH_WILD : WILD_COMPARE_MATCH; } public static int lastIndexOf(byte[] s, char c) { if (s == null) { return -1; } for (int i = s.length - 1; i >= 0; i--) { if (s[i] == c) { return i; } } return -1; } public static int indexOf(byte[] s, char c) { if (s == null) { return -1; } int length = s.length; for (int i = 0; i < length; i++) { if (s[i] == c) { return i; } } return -1; } public static boolean isNullOrEmpty(String str) { return str == null || str.isEmpty(); } /** * Two given strings are considered equal if both are null or if they have the same string value. * * @param str1 * first string to compare * @param str2 * fecond string to compare * @return * true if both strings are null or have the same value */ public static boolean nullSafeEqual(String str1, String str2) { return str1 == null && str2 == null || str1 != null && str1.equals(str2); } /** * Removes comments and hints from the given string. * * @param source * the query string to clean up. * @param openingMarkers * characters that delimit the beginning of a text block to skip * @param closingMarkers * characters that delimit the end of a text block to skip * @param allowBackslashEscapes * whether or not backslash escapes are allowed * @return the query string with all comment-delimited data removed */ public static String stripCommentsAndHints(final String source, final String openingMarkers, final String closingMarkers, final boolean allowBackslashEscapes) { StringInspector strInspector = new StringInspector(source, openingMarkers, closingMarkers, "", allowBackslashEscapes ? SearchMode.__BSE_MRK_COM_MYM_HNT_WS : SearchMode.__MRK_COM_MYM_HNT_WS); return strInspector.stripCommentsAndHints(); } /** * Next two functions are to help DBMD check if the given string is in form of database.name and return it as "database";"name" with comments removed. * If string is NULL or wildcard (%), returns null and exits. * * First, we sanitize... * * @param src * the source string * @return the input string with all comment-delimited data removed */ public static String sanitizeProcOrFuncName(String src) { if (src == null || src.equals("%")) { return null; } return src; } /** * Splits an entity identifier into its parts (database and entity name) and returns a list containing the two elements. If the identifier doesn't contain * the database part then the argument db is used in its place and source corresponds to the full entity name. * If argument source is NULL or wildcard (%), returns an empty list. * * @param source * the source string * @param db * database, if available * @param quoteId * quote character as defined on server * @param isNoBslashEscSet * is our connection in no BackSlashEscape mode * @return the input string with all comment-delimited data removed */ public static List splitDBdotName(String source, String db, String quoteId, boolean isNoBslashEscSet) { if (source == null || source.equals("%")) { return Collections.emptyList(); } int dotIndex = -1; if (" ".equals(quoteId)) { dotIndex = source.indexOf("."); } else { dotIndex = indexOfIgnoreCase(0, source, ".", quoteId, quoteId, isNoBslashEscSet ? SearchMode.__MRK_WS : SearchMode.__BSE_MRK_WS); } String database = db; String entityName; if (dotIndex != -1) { database = unQuoteIdentifier(source.substring(0, dotIndex), quoteId); entityName = unQuoteIdentifier(source.substring(dotIndex + 1), quoteId); } else { entityName = unQuoteIdentifier(source, quoteId); } return Arrays.asList(database, entityName); } /** * Builds and returns a fully qualified name, quoted if necessary, for the given database entity. * * @param db * database name * @param entity * identifier * @param quoteId * quote character as defined on server * @param isPedantic * are we in pedantic mode * @return fully qualified name */ public static String getFullyQualifiedName(String db, String entity, String quoteId, boolean isPedantic) { StringBuilder fullyQualifiedName = new StringBuilder(StringUtils.quoteIdentifier(db == null ? "" : db, quoteId, isPedantic)); fullyQualifiedName.append('.'); fullyQualifiedName.append(StringUtils.quoteIdentifier(entity, quoteId, isPedantic)); return fullyQualifiedName.toString(); } public static boolean isEmptyOrWhitespaceOnly(String str) { if (str == null || str.length() == 0) { return true; } int length = str.length(); for (int i = 0; i < length; i++) { if (!Character.isWhitespace(str.charAt(i))) { return false; } } return true; } public static String escapeQuote(String str, String quotChar) { if (str == null) { return null; } str = StringUtils.toString(stripEnclosure(str.getBytes(), quotChar, quotChar)); int lastNdx = str.indexOf(quotChar); String tmpSrc; String tmpRest; tmpSrc = str.substring(0, lastNdx); tmpSrc = tmpSrc + quotChar + quotChar; tmpRest = str.substring(lastNdx + 1, str.length()); lastNdx = tmpRest.indexOf(quotChar); while (lastNdx > -1) { tmpSrc = tmpSrc + tmpRest.substring(0, lastNdx); tmpSrc = tmpSrc + quotChar + quotChar; tmpRest = tmpRest.substring(lastNdx + 1, tmpRest.length()); lastNdx = tmpRest.indexOf(quotChar); } tmpSrc = tmpSrc + tmpRest; str = tmpSrc; return str; } /** * Surrounds identifier with quoteChar and duplicates these symbols inside the identifier. * * @param quoteChar * ` or " * @param identifier * in pedantic mode (connection property pedantic=true) identifier is treated as unquoted * (as it is stored in the database) even if it starts and ends with quoteChar; * in non-pedantic mode if identifier starts and ends with quoteChar method treats it as already quoted and doesn't modify. * @param isPedantic * are we in pedantic mode * * @return * With quoteChar="`":
*
    *
  • null {@code ->} null
  • *
  • abc {@code ->} `abc`
  • *
  • ab`c {@code ->} `ab``c`
  • *
  • ab"c {@code ->} `ab"c`
  • *
  • `ab``c` {@code ->} `ab``c` in non-pedantic mode or ```ab````c``` in pedantic mode
  • *
* With quoteChar="\"":
*
    *
  • null {@code ->} null
  • *
  • abc {@code ->} "abc"
  • *
  • ab`c {@code ->} "ab`c"
  • *
  • ab"c {@code ->} "ab""c"
  • *
  • "ab""c" {@code ->} "ab""c" in non-pedantic mode or """ab""""c""" in pedantic mode
  • *
*/ public static String quoteIdentifier(String identifier, String quoteChar, boolean isPedantic) { if (identifier == null) { return null; } identifier = identifier.trim(); int quoteCharLength = quoteChar.length(); if (quoteCharLength == 0) { return identifier; } // Check if the identifier is correctly quoted and if quotes within are correctly escaped. If not, quote and escape it. if (!isPedantic && identifier.startsWith(quoteChar) && identifier.endsWith(quoteChar)) { // Trim outermost quotes from the identifier. String identifierQuoteTrimmed = identifier.substring(quoteCharLength, identifier.length() - quoteCharLength); // Check for pairs of quotes. int quoteCharPos = identifierQuoteTrimmed.indexOf(quoteChar); while (quoteCharPos >= 0) { int quoteCharNextExpectedPos = quoteCharPos + quoteCharLength; int quoteCharNextPosition = identifierQuoteTrimmed.indexOf(quoteChar, quoteCharNextExpectedPos); if (quoteCharNextPosition == quoteCharNextExpectedPos) { quoteCharPos = identifierQuoteTrimmed.indexOf(quoteChar, quoteCharNextPosition + quoteCharLength); } else { // Not a pair of quotes! break; } } if (quoteCharPos < 0) { return identifier; } } return quoteChar + identifier.replaceAll(quoteChar, quoteChar + quoteChar) + quoteChar; } /** * Surrounds identifier with "`" and duplicates these symbols inside the identifier. * * @param identifier * in pedantic mode (connection property pedantic=true) identifier is treated as unquoted (as it is stored in the database) even if it starts and * ends with "`"; * in non-pedantic mode if identifier starts and ends with "`" method treats it as already quoted and doesn't modify. * @param isPedantic * are we in pedantic mode * * @return *
    *
  • null {@code ->} null
  • *
  • abc {@code ->} `abc`
  • *
  • ab`c {@code ->} `ab``c`
  • *
  • ab"c {@code ->} `ab"c`
  • *
  • `ab``c` {@code ->} `ab``c` in non-pedantic mode or ```ab````c``` in pedantic mode
  • *
*/ public static String quoteIdentifier(String identifier, boolean isPedantic) { return quoteIdentifier(identifier, "`", isPedantic); } /** * Trims the identifier, removes quote chars from first and last positions and replaces double occurrences of quote char from entire identifier, i.e. * converts quoted identifier into the form as it is stored in database. * * @param identifier * identifier * @param quoteChar * ` or " * @return *
    *
  • null {@code ->} null
  • *
  • abc {@code ->} abc
  • *
  • `abc` {@code ->} abc
  • *
  • `ab``c` {@code ->} ab`c
  • *
  • `"ab`c"` {@code ->} "ab`c"
  • *
  • `ab"c` {@code ->} ab"c
  • *
  • "abc" {@code ->} abc
  • *
  • "`ab""c`" {@code ->} `ab"c`
  • *
  • "ab`c" {@code ->} ab`c
  • *
*/ public static String unQuoteIdentifier(String identifier, String quoteChar) { if (identifier == null) { return null; } identifier = identifier.trim(); int quoteCharLength = quoteChar.length(); if (quoteCharLength == 0) { return identifier; } // Check if the identifier is really quoted or if it simply contains quote chars in it (assuming that the value is a valid identifier). if (identifier.startsWith(quoteChar) && identifier.endsWith(quoteChar)) { // Trim outermost quotes from the identifier. String identifierQuoteTrimmed = identifier.substring(quoteCharLength, identifier.length() - quoteCharLength); // Check for pairs of quotes. int quoteCharPos = identifierQuoteTrimmed.indexOf(quoteChar); while (quoteCharPos >= 0) { int quoteCharNextExpectedPos = quoteCharPos + quoteCharLength; int quoteCharNextPosition = identifierQuoteTrimmed.indexOf(quoteChar, quoteCharNextExpectedPos); if (quoteCharNextPosition == quoteCharNextExpectedPos) { quoteCharPos = identifierQuoteTrimmed.indexOf(quoteChar, quoteCharNextPosition + quoteCharLength); } else { // Not a pair of quotes! Return as it is... return identifier; } } return identifier.substring(quoteCharLength, identifier.length() - quoteCharLength).replaceAll(quoteChar + quoteChar, quoteChar); } return identifier; } public static int indexOfQuoteDoubleAware(String searchIn, String quoteChar, int startFrom) { if (searchIn == null || quoteChar == null || quoteChar.length() == 0 || startFrom > searchIn.length()) { return -1; } int lastIndex = searchIn.length() - 1; int beginPos = startFrom; int pos = -1; boolean next = true; while (next) { pos = searchIn.indexOf(quoteChar, beginPos); if (pos == -1 || pos == lastIndex || !searchIn.startsWith(quoteChar, pos + 1)) { next = false; } else { beginPos = pos + 2; } } return pos; } public static String toString(byte[] value, int offset, int length, String encoding) { if (encoding == null || "null".equalsIgnoreCase(encoding)) { return new String(value, offset, length); } try { return new String(value, offset, length, encoding); } catch (UnsupportedEncodingException uee) { throw ExceptionFactory.createException(WrongArgumentException.class, Messages.getString("StringUtils.0", new Object[] { encoding }), uee); } } public static String toString(byte[] value, String encoding) { if (encoding == null) { return new String(value); } try { return new String(value, encoding); } catch (UnsupportedEncodingException uee) { throw ExceptionFactory.createException(WrongArgumentException.class, Messages.getString("StringUtils.0", new Object[] { encoding }), uee); } } public static String toString(byte[] value, Charset charset) { return new String(value, charset); } public static String toString(byte[] value, int offset, int length) { return new String(value, offset, length); } public static String toString(byte[] value) { return new String(value); } /** * Returns the byte[] representation of subset of the given char[] using the default/platform encoding. * * @param value * chars * @return bytes */ public static byte[] getBytes(char[] value) { return getBytes(value, 0, value.length); } /** * Returns the byte[] representation of subset of the given char[] using the given encoding. * * @param c * chars * @param encoding * java encoding * @return bytes */ public static byte[] getBytes(char[] c, String encoding) { return getBytes(c, 0, c.length, encoding); } public static byte[] getBytes(char[] value, int offset, int length) { return getBytes(value, offset, length, null); } /** * Returns the byte[] representation of subset of the given char[] using the given encoding. * * @param value * chars * @param offset * offset * @param length * length * @param encoding * java encoding * @return bytes */ public static byte[] getBytes(char[] value, int offset, int length, String encoding) { Charset cs; try { if (encoding == null) { cs = Charset.defaultCharset(); } else { cs = Charset.forName(encoding); } } catch (UnsupportedCharsetException ex) { throw ExceptionFactory.createException(WrongArgumentException.class, Messages.getString("StringUtils.0", new Object[] { encoding }), ex); } ByteBuffer buf = cs.encode(CharBuffer.wrap(value, offset, length)); // can't simply .array() this to get the bytes especially with variable-length charsets the buffer is sometimes larger than the actual encoded data int encodedLen = buf.limit(); byte[] asBytes = new byte[encodedLen]; buf.get(asBytes, 0, encodedLen); return asBytes; } public static byte[] getBytes(String value) { return value.getBytes(); } public static byte[] getBytes(String value, int offset, int length) { return value.substring(offset, offset + length).getBytes(); } public static byte[] getBytes(String value, int offset, int length, String encoding) { if (encoding == null) { return getBytes(value, offset, length); } try { return value.substring(offset, offset + length).getBytes(encoding); } catch (UnsupportedEncodingException uee) { throw ExceptionFactory.createException(WrongArgumentException.class, Messages.getString("StringUtils.0", new Object[] { encoding }), uee); } } public static final boolean isValidIdChar(char c) { return VALID_ID_CHARS.indexOf(c) != -1; } private static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; /** * Used to escape binary data with hex * * @param buf * source bytes * @param size * number of bytes to read * @param bc * consumer for low and high bits of each byte */ public static final void hexEscapeBlock(byte[] buf, int size, BiConsumer bc) { for (int i = 0; i < size; i++) { bc.accept((byte) HEX_DIGITS[buf[i] >>> 4 & 0xF], (byte) HEX_DIGITS[buf[i] & 0xF]); } } public static void appendAsHex(StringBuilder builder, byte[] bytes) { builder.append("0x"); for (byte b : bytes) { builder.append(HEX_DIGITS[b >>> 4 & 0xF]).append(HEX_DIGITS[b & 0xF]); } } public static void appendAsHex(StringBuilder builder, int value) { if (value == 0) { builder.append("0x0"); return; } int shift = 32; byte nibble; boolean nonZeroFound = false; builder.append("0x"); do { shift -= 4; nibble = (byte) (value >>> shift & 0xF); if (nonZeroFound) { builder.append(HEX_DIGITS[nibble]); } else if (nibble != 0) { builder.append(HEX_DIGITS[nibble]); nonZeroFound = true; } } while (shift != 0); } public static byte[] getBytesNullTerminated(String value, String encoding) { Charset cs = Charset.forName(encoding); ByteBuffer buf = cs.encode(value); int encodedLen = buf.limit(); byte[] asBytes = new byte[encodedLen + 1]; buf.get(asBytes, 0, encodedLen); asBytes[encodedLen] = 0; return asBytes; } public static boolean canHandleAsServerPreparedStatementNoCache(String sql, ServerVersion serverVersion, boolean allowMultiQueries, boolean noBackslashEscapes, boolean useAnsiQuotes) { // Can't use server-side prepare for CALL if (startsWithIgnoreCaseAndNonAlphaNumeric(sql, "CALL")) { return false; } boolean canHandleAsStatement = true; boolean allowBackslashEscapes = !noBackslashEscapes; String quoteChar = useAnsiQuotes ? "\"" : "'"; if (allowMultiQueries) { if (StringUtils.indexOfIgnoreCase(0, sql, ";", quoteChar, quoteChar, allowBackslashEscapes ? SearchMode.__BSE_MRK_COM_MYM_HNT_WS : SearchMode.__MRK_COM_MYM_HNT_WS) != -1) { canHandleAsStatement = false; } } else if (startsWithIgnoreCaseAndWs(sql, "XA ")) { canHandleAsStatement = false; } else if (startsWithIgnoreCaseAndWs(sql, "CREATE TABLE")) { canHandleAsStatement = false; } else if (startsWithIgnoreCaseAndWs(sql, "DO")) { canHandleAsStatement = false; } else if (startsWithIgnoreCaseAndWs(sql, "SET")) { canHandleAsStatement = false; } else if (StringUtils.startsWithIgnoreCaseAndWs(sql, "SHOW WARNINGS") && serverVersion.meetsMinimum(ServerVersion.parseVersion("5.7.2"))) { canHandleAsStatement = false; } else if (sql.startsWith("/* ping */")) { canHandleAsStatement = false; } return canHandleAsStatement; } final static char[] EMPTY_SPACE = new char[255]; static { for (int i = 0; i < EMPTY_SPACE.length; i++) { EMPTY_SPACE[i] = ' '; } } public static String padString(String stringVal, int requiredLength) { int currentLength = stringVal.length(); int difference = requiredLength - currentLength; if (difference > 0) { StringBuilder paddedBuf = new StringBuilder(requiredLength); paddedBuf.append(stringVal); paddedBuf.append(EMPTY_SPACE, 0, difference); return paddedBuf.toString(); } return stringVal; } public static String zeroFill(String value, int length) { char[] zeros = new char[length]; Arrays.fill(zeros, '0'); return new String(zeros) + value; } public static int safeIntParse(String intAsString) { try { return Integer.parseInt(intAsString); } catch (NumberFormatException nfe) { return 0; } } /** * Checks is the CharSequence contains digits only. No leading sign and thousands or decimal separators are allowed. * * @param cs * The CharSequence to check. * @return * {@code true} if the CharSequence not empty and contains only digits, {@code false} otherwise. */ public static boolean isStrictlyNumeric(CharSequence cs) { if (cs == null || cs.length() == 0) { return false; } for (int i = 0; i < cs.length(); i++) { if (!Character.isDigit(cs.charAt(i))) { return false; } } return true; } public static String safeTrim(String toTrim) { return isNullOrEmpty(toTrim) ? toTrim : toTrim.trim(); } /** * Constructs a String containing all the elements in the String array bounded and joined by the provided concatenation elements. The last element uses a * different delimiter. * * @param elems * the String array from where to take the elements. * @param prefix * the prefix of the resulting String. * @param midDelimiter * the delimiter to be used between the N-1 elements * @param lastDelimiter * the delimiter to be used before the last element. * @param suffix * the suffix of the resulting String. * @return * a String built from the provided String array and concatenation elements. */ public static String stringArrayToString(String[] elems, String prefix, String midDelimiter, String lastDelimiter, String suffix) { StringBuilder valuesString = new StringBuilder(); if (elems.length > 1) { valuesString.append(Arrays.stream(elems).limit(elems.length - 1).collect(Collectors.joining(midDelimiter, prefix, lastDelimiter))); } else { valuesString.append(prefix); } valuesString.append(elems[elems.length - 1]).append(suffix); return valuesString.toString(); } /** * Does the string contain wildcard symbols ('%' or '_'). Used in DatabaseMetaData. * * @param src * string * @return true if src contains wildcard symbols */ public static boolean hasWildcards(String src) { return indexOfIgnoreCase(0, src, "%") > -1 || indexOfIgnoreCase(0, src, "_") > -1; } public static String getUniqueSavepointId() { String uuid = UUID.randomUUID().toString(); return uuid.replaceAll("-", "_"); // for safety } /** * Joins all elements of the given list using serial comma (Oxford comma) rules. * E.g.: * - "A" * - "A and B" * - "A, B, and C" * * @param elements * the elements to join * @return * the String with all elements, joined by commas and "and". */ public static String joinWithSerialComma(List elements) { if (elements == null || elements.size() == 0) { return ""; } if (elements.size() == 1) { return elements.get(0).toString(); } if (elements.size() == 2) { return elements.get(0) + " and " + elements.get(1); } return elements.subList(0, elements.size() - 1).stream().map(Object::toString).collect(Collectors.joining(", ", "", ", and ")) + elements.get(elements.size() - 1).toString(); } public static byte[] unquoteBytes(byte[] bytes) { if (bytes[0] == '\'' && bytes[bytes.length - 1] == '\'') { byte[] valNoQuotes = new byte[bytes.length - 2]; int j = 0; int quoteCnt = 0; for (int i = 1; i < bytes.length - 1; i++) { if (bytes[i] == '\'') { quoteCnt++; } else { quoteCnt = 0; } if (quoteCnt == 2) { quoteCnt = 0; } else { valNoQuotes[j++] = bytes[i]; } } byte[] res = new byte[j]; System.arraycopy(valNoQuotes, 0, res, 0, j); return res; } return bytes; } public static byte[] quoteBytes(byte[] bytes) { byte[] withQuotes = new byte[bytes.length * 2 + 2]; int j = 0; withQuotes[j++] = '\''; for (int i = 0; i < bytes.length; i++) { if (bytes[i] == '\'') { withQuotes[j++] = '\''; } withQuotes[j++] = bytes[i]; } withQuotes[j++] = '\''; byte[] res = new byte[j]; System.arraycopy(withQuotes, 0, res, 0, j); return res; } public static StringBuilder escapeString(StringBuilder buf, String x, boolean useAnsiQuotedIdentifiers, CharsetEncoder charsetEncoder) { int stringLength = x.length(); buf.append('\''); // // Note: buf.append(char) is _faster_ than appending in blocks, because the block append requires a System.arraycopy().... go figure... // for (int i = 0; i < stringLength; ++i) { char c = x.charAt(i); switch (c) { case 0: /* Must be escaped for 'mysql' */ buf.append('\\'); buf.append('0'); break; case '\n': /* Must be escaped for logs */ buf.append('\\'); buf.append('n'); break; case '\r': buf.append('\\'); buf.append('r'); break; case '\\': buf.append('\\'); buf.append('\\'); break; case '\'': buf.append('\''); buf.append('\''); break; case '"': /* Better safe than sorry */ if (useAnsiQuotedIdentifiers) { buf.append('\\'); } buf.append('"'); break; case '\032': /* This gives problems on Win32 */ buf.append('\\'); buf.append('Z'); break; case '\u00a5': case '\u20a9': // escape characters interpreted as backslash by mysql if (charsetEncoder != null) { CharBuffer cbuf = CharBuffer.allocate(1); ByteBuffer bbuf = ByteBuffer.allocate(1); cbuf.put(c); cbuf.position(0); charsetEncoder.encode(cbuf, bbuf, true); if (bbuf.get(0) == '\\') { buf.append('\\'); } } buf.append(c); break; default: buf.append(c); } } buf.append('\''); return buf; } public static void escapeBytes(ByteArrayOutputStream bOut, byte[] x) { int numBytes = x.length; for (int i = 0; i < numBytes; ++i) { byte b = x[i]; switch (b) { case 0: /* Must be escaped for 'mysql' */ bOut.write('\\'); bOut.write('0'); break; case '\n': /* Must be escaped for logs */ bOut.write('\\'); bOut.write('n'); break; case '\r': bOut.write('\\'); bOut.write('r'); break; case '\\': bOut.write('\\'); bOut.write('\\'); break; case '\'': bOut.write('\\'); bOut.write('\''); break; case '"': /* Better safe than sorry */ bOut.write('\\'); bOut.write('"'); break; case '\032': /* This gives problems on Win32 */ bOut.write('\\'); bOut.write('Z'); break; default: bOut.write(b); } } } /** * URL-encode the given string. * * @param stringToEncode * the string to encode * @return * the encoded string */ public static String urlEncode(String stringToEncode) { try { return URLEncoder.encode(stringToEncode, StandardCharsets.UTF_8.name()); } catch (UnsupportedEncodingException e) { // Won't happen. return null; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy