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

liquibase.util.StringUtil Maven / Gradle / Ivy

There is a newer version: 4.30.0
Show newest version
package liquibase.util;

import liquibase.ExtensibleObject;
import liquibase.GlobalConfiguration;
import liquibase.Scope;

import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Various utility methods for working with strings.
 */
public class StringUtil {
    private static final Pattern upperCasePattern = Pattern.compile(".*[A-Z].*");
    private static final Pattern lowerCasePattern = Pattern.compile(".*[a-z].*");
    private static final Pattern spacePattern = Pattern.compile(" ");
    private static final SecureRandom rnd = new SecureRandom();

    /**
     * Returns the trimmed (left and right) version of the input string. If null is passed, an empty string is returned.
     *
     * @param string the input string to trim
     * @return the trimmed string, or an empty string if the input was null.
     */
    public static String trimToEmpty(String string) {
        if (string == null) {
            return "";
        }
        return string.trim();
    }

    /**
     * Returns the trimmed (left and right) form of the input string. If the string is empty after trimming (or null
     * was passed in the first place), null is returned, i.e. the input string is reduced to nothing.
     *
     * @param string the string to trim
     * @return the trimmed string or null
     */
    public static String trimToNull(String string) {
        if (string == null) {
            return null;
        }
        String returnString = string.trim();
        if (returnString.isEmpty()) {
            return null;
        } else {
            return returnString;
        }
    }

    /**
     * Removes any comments from multiple line SQL using {@link #stripComments(String)}
     * and then extracts each individual statement using {@link #splitSQL(String, String)}.
     *
     * @param multiLineSQL  A String containing all the SQL statements
     * @param stripComments If true then comments will be stripped, if false then they will be left in the code
     */
    public static String[] processMultiLineSQL(String multiLineSQL, boolean stripComments, boolean splitStatements, String endDelimiter) {

        StringClauses parsed = SqlParser.parse(multiLineSQL, true, !stripComments);

        List returnArray = new ArrayList<>();

        StringBuilder currentString = new StringBuilder();
        String previousPiece = null;
        boolean previousDelimiter = false;
        List parsedArray = Arrays.asList(parsed.toArray(true));
        int isInClause = 0;
        List tokens = mergeTokens(parsedArray, endDelimiter);
        for (int i = 0; i < tokens.size(); i++) {
            Object piece = tokens.get(i);
            String nextPiece = null;
            int nextIndex = i + 1;
            while (nextPiece == null && nextIndex < tokens.size()) {
                nextPiece = StringUtil.trimToNull(String.valueOf(tokens.get(nextIndex)));
                nextIndex++;
            }

            if (piece instanceof String && ((String) piece).equalsIgnoreCase("BEGIN")
                    &&  (!"transaction".equalsIgnoreCase(nextPiece)
                        && !"trans".equalsIgnoreCase(nextPiece)
                        && !"tran".equalsIgnoreCase(nextPiece))) {
                isInClause++;
            }
            if (piece instanceof String && ((String) piece).equalsIgnoreCase("END") && isInClause > 0
                && (!"transaction".equalsIgnoreCase(nextPiece)
                    && !"trans".equalsIgnoreCase(nextPiece)
                    && !"tran".equalsIgnoreCase(nextPiece))) {
                isInClause--;
            }

            if (isInClause == 0 && splitStatements && (piece instanceof String) && isDelimiter((String) piece, previousPiece, endDelimiter)) {
                String trimmedString = StringUtil.trimToNull(currentString.toString());
                if (trimmedString != null) {
                    returnArray.add(trimmedString);
                }
                currentString = new StringBuilder();
                previousDelimiter = true;
            } else {
                if (!previousDelimiter || (StringUtil.trimToNull((String) piece) != null)) { //don't include whitespace after a delimiter
                    if ((currentString.length() > 0) || (StringUtil.trimToNull((String) piece) != null)) { //don't include whitespace before the statement
                        currentString.append(piece);
                    }
                }
                previousDelimiter = false;
            }
            previousPiece = (String) piece;
        }

        String trimmedString = StringUtil.trimToNull(currentString.toString());
        if (trimmedString != null) {
            returnArray.add(trimmedString);
        }

        return returnArray.toArray(new String[0]);
    }

    /**
     * Removes any comments from multiple line SQL using {@link #stripComments(String)}
     * and then extracts each individual statement using {@link #splitSQL(String, String)}.
     *
     * @param multiLineSQL  A String containing all the SQL statements
     * @param stripComments If true then comments will be stripped, if false then they will be left in the code
     * @deprecated The new method is {@link #processMultiLineSQL(String, boolean, boolean, String)} (String)}
     */
    public static String[] processMutliLineSQL(String multiLineSQL, boolean stripComments, boolean splitStatements, String endDelimiter) {
        return processMultiLineSQL(multiLineSQL, stripComments, splitStatements, endDelimiter);
    }

    /**
     * Delimiters like "//" may span multiple tokens. Look for them and combine them
     */
    private static List mergeTokens(List parsedArray, String endDelimiter) {
        if (endDelimiter == null) {
            return parsedArray;
        }

        List returnList = new ArrayList<>();
        List possibleMerge = new ArrayList<>();
        for (Object obj : parsedArray) {
            if (possibleMerge.size() == 0) {
                if ((obj instanceof String) && endDelimiter.startsWith((String) obj)) {
                    possibleMerge.add((String) obj);
                } else {
                    returnList.add(obj);
                }
            } else {
                String possibleMergeString = StringUtil.join(possibleMerge, "") + obj.toString();
                if (endDelimiter.equals(possibleMergeString)) {
                    returnList.add(possibleMergeString);
                    possibleMerge.clear();
                } else if (endDelimiter.startsWith(possibleMergeString)) {
                    possibleMerge.add(obj.toString());
                } else {
                    returnList.addAll(possibleMerge);
                    returnList.add(obj);
                    possibleMerge.clear();
                }
            }
        }

        return returnList;

    }

    /**
     * Returns true if the input is a delimiter in one of the popular RDBMSs. Recognized delimiters are: semicolon (;),
     * a slash (as the only content) or the word GO (as the only content).
     *
     * @param piece         the input line to test
     * @param previousPiece the characters in the input stream that came before piece
     * @param endDelimiter  ??? (need to see this in a debugger to find out)
     */
    protected static boolean isDelimiter(String piece, String previousPiece, String endDelimiter) {
        if (endDelimiter == null) {
            return ";".equals(piece) || (("go".equalsIgnoreCase(piece) || "/".equals(piece)) && ((previousPiece ==
                    null) || previousPiece.endsWith("\n")));
        } else {
            if (endDelimiter.length() == 1) {
                if ("/".equals(endDelimiter)) {
                    if (previousPiece != null && !previousPiece.endsWith("\n")) {
                        //don't count /'s the are there for comments for division signs or any other use besides a / at the beginning of a line
                        return false;
                    }
                }
                return piece.toLowerCase().equalsIgnoreCase(endDelimiter.toLowerCase());
            } else {
                return piece.toLowerCase().matches(endDelimiter.toLowerCase()) || (previousPiece + piece).toLowerCase().matches("[\\s\n\r]*" + endDelimiter.toLowerCase());
            }
        }
    }

    /**
     * Add new lines to the input string to cause output to wrap.  Optional line padding
     * can be passed in for the additional lines that are created
     *
     * @param inputStr         The string to split and wrap
     * @param wrapPoint        The point at which to split the lines
     * @param extraLinePadding Any additional spaces to add
     * @return String                  Output string with new lines
     */
    public static String wrap(final String inputStr, int wrapPoint, int extraLinePadding) {
        //
        // Just return
        //
        if (inputStr == null) {
            return null;
        }

        int inputLineLength = inputStr.length();
        int ptr = 0;
        int sizeOfMatch = -1;
        StringBuilder resultLine = new StringBuilder();
        while (ptr < inputLineLength) {
            Integer spaceToWrapAt = null;
            int min = ptr + wrapPoint + 1;
            Matcher matcher = spacePattern.matcher(inputStr.substring(ptr, Math.min(min, inputLineLength)));
            if (matcher.find()) {
                int matcherStart = matcher.start();
                if (matcherStart == 0) {
                    sizeOfMatch = matcher.end();
                    if (sizeOfMatch != 0) {
                        ptr += sizeOfMatch;
                        continue;
                    }
                    ptr += 1;
                }
                spaceToWrapAt = matcherStart + ptr;
            }

            //
            // Break because we do not have enough characters left to need to wrap
            //
            if (inputLineLength - ptr <= wrapPoint) {
                break;
            }

            //
            // Advance through all the spaces
            //
            while (matcher.find()) {
                spaceToWrapAt = matcher.start() + ptr;
            }

            if (spaceToWrapAt != null && spaceToWrapAt >= ptr) {
                resultLine.append(inputStr, ptr, spaceToWrapAt);
                resultLine.append(System.lineSeparator());
                for (int i = 0; i < extraLinePadding; i++) {
                    resultLine.append(" ");
                }
                ptr = spaceToWrapAt + 1;
            } else {
                matcher = spacePattern.matcher(inputStr.substring(ptr + wrapPoint));
                if (matcher.find()) {
                    int matcherStart = matcher.start();
                    sizeOfMatch = matcher.end() - matcherStart;
                    spaceToWrapAt = matcherStart + ptr + wrapPoint;
                }

                if (sizeOfMatch == 0 && ptr != 0) {
                    ptr--;
                }
                if (spaceToWrapAt != null && spaceToWrapAt >= 0) {
                    resultLine.append(inputStr, ptr, spaceToWrapAt);
                    resultLine.append(System.lineSeparator());
                    for (int i = 0; i < extraLinePadding; i++) {
                        resultLine.append(" ");
                    }
                    ptr = spaceToWrapAt + 1;
                } else {
                    resultLine.append(inputStr, ptr, inputStr.length());
                    ptr = inputLineLength;
                    sizeOfMatch = -1;
                }
            }
        }

        if (sizeOfMatch == 0 && ptr < inputLineLength) {
            ptr--;
        }

        //
        // Add the rest
        //
        resultLine.append(inputStr, ptr, inputLineLength);

        return resultLine.toString();
    }

    /**
     * Splits a candidate multi-line SQL statement along ;'s and "go"'s.
     */
    public static String[] splitSQL(String multiLineSQL, String endDelimiter) {
        return processMultiLineSQL(multiLineSQL, false, true, endDelimiter);
    }

    /**
     * Searches through a String which contains SQL code and strips out
     * any comments that are between \/**\/ or anything that matches
     * SP--SP\n (to support the ANSI standard commenting of --
     * at the end of a line).
     *
     * @return The String without the comments in
     */
    public static String stripComments(String multiLineSQL) {
        if (StringUtil.isEmpty(multiLineSQL)) {
            return multiLineSQL;
        }
        return SqlParser.parse(multiLineSQL, true, false).toString().trim();
    }

    public static String join(Object[] array, String delimiter, StringUtilFormatter formatter) {
        if (array == null) {
            return null;
        }
        return join(Arrays.asList(array), delimiter, formatter);
    }

    public static String join(String[] array, String delimiter) {
        if (array == null) {
            return null;
        }
        return join(Arrays.asList(array), delimiter);
    }

    public static String join(Collection collection, String delimiter) {
        return join(collection, delimiter, new ToStringFormatter());

    }

    public static String join(Collection collection, String delimiter, StringUtilFormatter formatter) {
        if (collection == null) {
            return null;
        }

        if (collection.isEmpty()) {
            return "";
        }

        StringBuilder buffer = new StringBuilder();
        for (Object val : collection) {
            buffer.append(formatter.toString(val)).append(delimiter);
        }

        String returnString = buffer.toString();
        return returnString.substring(0, returnString.length() - delimiter.length());
    }

    public static String join(Collection collection, String delimiter, StringUtilFormatter formatter, boolean sorted) {
        if (sorted) {
            TreeSet sortedSet = new TreeSet<>();
            for (Object obj : collection) {
                sortedSet.add(formatter.toString(obj));
            }
            return join(sortedSet, delimiter);
        }
        return join(collection, delimiter, formatter);
    }

    public static String join(Collection collection, String delimiter, boolean sorted) {
        if (sorted) {
            return join(new TreeSet<>(collection), delimiter);
        } else {
            return join(collection, delimiter);
        }
    }

    public static String join(Map map, String delimiter) {
        return join(map, delimiter, new ToStringFormatter());
    }

    public static String join(Map map, String delimiter, StringUtilFormatter formatter) {
        List list = new ArrayList<>();
        for (Map.Entry entry : (Set) map.entrySet()) {
            list.add(entry.getKey().toString() + "=" + formatter.toString(entry.getValue()));
        }
        return join(list, delimiter);
    }

    public static String join(ExtensibleObject extensibleObject, String delimiter) {
        return join(extensibleObject, delimiter, new ToStringFormatter());
    }

    public static String join(ExtensibleObject extensibleObject, String delimiter, StringUtilFormatter formatter) {
        List list = new ArrayList<>();
        for (String attribute : new TreeSet<>(extensibleObject.getAttributes())) {
            String formattedValue = formatter.toString(extensibleObject.get(attribute, Object.class));
            if (formattedValue != null) {
                list.add(attribute + "=" + formattedValue);
            }
        }
        return join(list, delimiter);
    }

    public static List splitAndTrim(String s, String regex) {
        if (s == null) {
            return null;
        }
        List returnList = new ArrayList<>();
        for (String string : s.split(regex)) {
            returnList.add(string.trim());
        }

        return returnList;
    }

    public static String repeat(String string, int times) {
        StringBuilder result = new StringBuilder(string.length() * times);
        for (int i = 0; i < times; i++) {
            result.append(string);
        }

        return result.toString();
    }

    public static String join(Integer[] array, String delimiter) {
        if (array == null) {
            return null;
        }

        int[] ints = new int[array.length];
        for (int i = 0; i < ints.length; i++) {
            ints[i] = array[i];
        }
        return StringUtil.join(ints, delimiter);
    }

    public static String join(int[] array, String delimiter) {
        if (array == null) {
            return null;
        }

        if (array.length == 0) {
            return "";
        }

        StringBuilder buffer = new StringBuilder();
        for (int val : array) {
            buffer.append(val).append(delimiter);
        }

        String returnString = buffer.toString();
        return returnString.substring(0, returnString.length() - delimiter.length());
    }

    public static String indent(String string) {
        return indent(string, 4);
    }

    public static String indent(String string, int padding) {
        if (string == null) {
            return null;
        }
        String pad = StringUtil.repeat(" ", padding);
        return pad + (string.replaceAll("\n", "\n" + pad));
    }

    public static String lowerCaseFirst(String string) {
        if (string == null) {
            return null;
        }
        if (string.length() < 2) {
            return string.toLowerCase();
        }

        return string.substring(0, 1).toLowerCase() + string.substring(1);
    }

    public static String upperCaseFirst(String string) {
        if (string == null) {
            return null;
        }
        if (string.length() < 2) {
            return string.toUpperCase();
        }
        return string.substring(0, 1).toUpperCase() + string.substring(1);
    }

    public static boolean hasUpperCase(String string) {
        if (string == null) {
            return false;
        }
        return upperCasePattern.matcher(string).matches();
    }

    public static boolean hasLowerCase(String string) {

        if (string == null) {
            return false;
        }
        return lowerCasePattern.matcher(string).matches();
    }

    public static String standardizeLineEndings(String string) {
        if (string == null) {
            return null;
        }
        return string.replace("\r\n", "\n").replace("\r", "\n");
    }

    public static boolean isAscii(String string) {
        if (string == null) {
            return true;
        }
        for (char c : string.toCharArray()) {
            if (!isAscii(c)) {
                return false;
            }
        }
        return true;
    }

    /**
     * Returns true if ch is a "7-bit-clean" ASCII character (ordinal number < 128).
     *
     * @param ch the character to test
     * @return true if 7 bit-clean, false otherwise.
     */
    public static boolean isAscii(char ch) {
        return ch < 128;
    }

    public static String escapeHtml(String str) {
        if (str == null) {
            return null;
        }

        StringBuilder out = new StringBuilder();
        int len = str.length();
        for (int i = 0; i < len; i++) {
            char c = str.charAt(i);
            if (c > 0x7F) {
                out.append("&#");
                out.append(Integer.toString(c, 10));
                out.append(';');
            } else {
                out.append(c);
            }
        }
        return out.toString();
    }

    /**
     * Adds spaces to the right of the input value until the string has reached the given length. Nothing is done
     * if the string already has the given length or if the string is even longer.
     *
     * @param value  The string to pad (if necessary)
     * @param length the desired length
     * @return the input string, padded if necessary.
     */
    public static String pad(String value, int length) {
        value = StringUtil.trimToEmpty(value);
        if (value.length() >= length) {
            return value;
        }

        return value + StringUtil.repeat(" ", length - value.length());
    }

    /**
     * Adds spaces to the left of the input value until the string has reached the given length. Nothing is done
     * if the string already has the given length or if the string is even longer.
     *
     * @param value  The string to pad (if necessary)
     * @param length the desired length
     * @return the input string, padded if necessary.
     */
    public static String leftPad(String value, int length) {
        value = StringUtil.trimToEmpty(value);
        if (value.length() >= length) {
            return value;
        }

        return StringUtil.repeat(" ", length - value.length()) + value;
    }

    /**
     * Returns true if the input string is the empty string (null-safe).
     *
     * @param value String to be checked
     * @return true if String is null or empty
     */
    public static boolean isEmpty(String value) {
        return (value == null) || value.isEmpty();
    }

    /**
     * Returns true if the input string is NOT the empty string. If the string is null, false is returned.
     *
     * @param value String to be checked
     * @return true if string is not null and not empty (length > 0)
     */
    public static boolean isNotEmpty(String value) {
        return !isEmpty(value);
    }

    /**
     * Checks whether the given value starts with the specified startsWith string.
     *
     * @param value      the string to check
     * @param startsWith the prefix to check for
     * @return true if value starts with startsWith, false otherwise.
     * Returns false if either argument is null.
     */
    public static boolean startsWith(String value, String startsWith) {
        if ((value == null) || (startsWith == null)) {
            return false;
        }

        return value.startsWith(startsWith);
    }

    /**
     * Returns true if the given string only consists of whitespace characters (null-safe)
     *
     * @param string the string to test
     * @return true if the string is null or only consists of whitespaces.
     */
    public static boolean isWhitespace(CharSequence string) {
        if (string == null) {
            return true;
        }
        return StringUtil.trimToNull(string.toString()) == null;
    }

    /**
     * Compares a minimum version number given in string form (only the first three parts are considered) with a
     * candidate version given as the three ints major, minor and patch.
     *
     * @param minimumVersion The minimum version that is required, given as a string with up to 3 parts, e.g. "7.4" or "9.6.3"
     * @param candidateMajor the version number to be tested, major part
     * @param candidateMinor the version number to be tested, minor part
     * @param candidatePatch the version number to be tested, patch part
     * @return true if candidateMajor.candidateMinor.candidatePatch >= minimumVersion or false if not
     */
    public static boolean isMinimumVersion(String minimumVersion, int candidateMajor, int candidateMinor,
                                           int candidatePatch) {
        if (minimumVersion == null) {
            return true;
        }
        String[] parts = minimumVersion.split("\\.", 3);
        int minMajor = Integer.parseInt(parts[0]);
        int minMinor = (parts.length > 1) ? Integer.parseInt(parts[1]) : 0;
        int minPatch = (parts.length > 2) ? Integer.parseInt(parts[2]) : 0;

        if (minMajor > candidateMajor) {
            return false;
        }

        if ((minMajor == candidateMajor) && (minMinor > candidateMinor)) {
            return false;
        }

        return !((minMajor == candidateMajor) && (minMinor == candidateMinor) && (minPatch > candidatePatch));
    }

    public static String limitSize(String string, int maxLength) {
        if (string == null) {
            return null;
        }
        if (string.length() > maxLength) {
            return string.substring(0, maxLength - 3) + "...";
        }
        return string;
    }

    /**
     * Produce a random identifer of the given length, consisting only of uppercase letters.
     *
     * @param len desired length of the string
     * @return an identifier of the desired length
     */
    public static String randomIdentifer(int len) {
        final String AB = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

        StringBuilder sb = new StringBuilder(len);
        for (int i = 0; i < len; i++)
            sb.append(AB.charAt(rnd.nextInt(AB.length())));
        return sb.toString();
    }

    /**
     * Converts a camelCase string to a kabob-case one
     */
    public static String toKabobCase(String string) {
        if (string == null) {
            return null;
        }

        if (string.length() == 1) {
            return string;
        }

        StringBuilder outString = new StringBuilder();
        char[] charString = string.toCharArray();
        for (int i = 0; i < charString.length; i++) {
            char letter = charString[i];
            if (i == 0) {
                outString.append(Character.toLowerCase(letter));
                continue;
            }
            if (Character.isUpperCase(letter)) {
                outString.append('-').append(Character.toLowerCase(letter));
            } else {
                outString.append(letter);
            }
        }

        return outString.toString();
    }

    /**
     * Converts a kabob-case or underscore_case string to a camel-case one
     */
    public static String toCamelCase(String string) {
        if (string == null) {
            return null;
        }

        StringBuilder outString = new StringBuilder();
        char[] charString = string.toCharArray();
        boolean uppercaseNext = false;
        for (char letter : charString) {
            if (letter == '-' || letter == '_') {
                uppercaseNext = true;
            } else {
                if (uppercaseNext) {
                    outString.append(Character.toUpperCase(letter));
                    uppercaseNext = false;
                } else {
                    outString.append(letter);
                }
            }
        }

        return outString.toString();
    }

    public interface StringUtilFormatter {
        String toString(Type obj);
    }

    public static class ToStringFormatter implements StringUtilFormatter {
        @Override
        public String toString(Object obj) {
            if (obj == null) {
                return null;
            }
            return obj.toString();
        }
    }

    public static class DefaultFormatter implements StringUtilFormatter {
        @Override
        public String toString(Object obj) {
            if (obj == null) {
                return null;
            } else if (obj instanceof Class) {
                return ((Class) obj).getName();
            } else if (obj instanceof Object[]) {
                if (((Object[]) obj).length == 0) {
                    return null;
                } else {
                    return "[" + StringUtil.join((Object[]) obj, ", ", this) + "]";
                }
            } else if (obj instanceof Collection) {
                if (((Collection) obj).size() == 0) {
                    return null;
                } else {
                    return "[" + StringUtil.join((Collection) obj, ", ", this) + "]";
                }

            }
            return obj.toString();
        }
    }

    /**
     * Returns if two strings are equal, ignoring:
     * 
    *
  • case (uppercase/lowercase)
  • *
  • difference between null, and empty string, and a string that only has spaces
  • *
* * @param s1 the first String to compare (or null) * @param s2 the second String to compare (or null) * @return true if the Strings are equal by the above criteria, false in all other cases */ public static boolean equalsIgnoreCaseAndEmpty(String s1, String s2) { String clean1 = trimToNull(s1); String clean2 = trimToNull(s2); if (clean1 == null && clean2 == null) { return true; } else { // Both cannot be null at this point if (clean1 == null || clean2 == null) { return false; } } return clean1.equalsIgnoreCase(clean2); } /** * Trims {@link Character#isWhitespace(char) whitespace} characters from the * end of specified string * * @param string String to trim * @return new String without the whitespace at the end */ public static String trimRight(String string) { if (string == null) { return null; } int i = string.length() - 1; while (i >= 0 && Character.isWhitespace(string.charAt(i))) { i--; } return string.substring(0, i + 1); } /** * Retrieves the last block comment in a SQL string, if any. * * @param sqlString the SQL string to search for the last block comment * @return the last block comment in the SQL string, or {@code null} if none was found */ public static String getLastBlockComment(String sqlString) { if (isEmpty(sqlString) || sqlString.length() < 4) { return null; } StringBuilder reversedSqlStringBuilder = new StringBuilder(sqlString).reverse(); String reversedString = reversedSqlStringBuilder.toString(); int idxClosingLastChar = -1, idxOpeningFirstChar = -1; for (int i = 0; i < reversedString.length(); i++) { if (idxClosingLastChar < 0) { // we have not found the start of the pair (reversed) yet) char c = reversedString.charAt(i); if (c == '/') { // check the second one char s = reversedString.charAt(i + 1); if (s == '*') { idxClosingLastChar = i; } } else if (!Character.isWhitespace(c)) { // does not look like it ends with block comment, return null return null; } } else { // look for closing pair (reversed) char c = reversedString.charAt(i); if (c == '/') { // check the previous one char s = reversedString.charAt(i - 1); char e = reversedString.charAt(i + 1); // if it was not escaped if (s == '*' && e != '\\') { idxOpeningFirstChar = i; break; } } } } // reverse the index to get the start of the last comment block int idxOfLastBlockComment = sqlString.length() - (idxOpeningFirstChar + 1); return sqlString.substring(idxOfLastBlockComment); } /** * Returns the last line comment from a given SQL string, if there is one. * * @param sqlString the SQL string to search * @return the last line comment from the SQL string, or {@code null} if there is no line comment */ public static String getLastLineComment(String sqlString) { if (isEmpty(sqlString) || sqlString.length() < 2) { return null; } boolean startOfNewLine = false; int idxOfDoubleDash = -1; for (int i = 0; i < sqlString.trim().length() - 1; i++) { char c = sqlString.charAt(i); // we have not found the start of the line comment yet if (c == '-') { // check the next one char s = sqlString.charAt(i + 1); if (s == '-') { if (idxOfDoubleDash < 0) { idxOfDoubleDash = i; } startOfNewLine = false; } } else if (!Character.isWhitespace(c)) { if (startOfNewLine) { // new line started and we found some other character, reset the index, idxOfDoubleDash = -1; } } else if (c == '\r' || c == '\n') { // new line found startOfNewLine = true; idxOfDoubleDash = -1; } } if (idxOfDoubleDash < 0) { return null; } return sqlString.substring(idxOfDoubleDash); } /** * Strips the comments and white spaces from the end of given SQL string. * * @param sqlString the SQL string to strip * @return the stripped SQL string */ public static String stripSqlCommentsAndWhitespacesFromTheEnd(String sqlString) { if (isEmpty(sqlString)) { return sqlString; } StringBuilder str = new StringBuilder(sqlString); boolean strModified = true; while (strModified) { strModified = false; // first check for last block comment // since line comments could be inside block comments, we want to // remove them first. String lastBlockComment = getLastBlockComment(str.toString()); if (lastBlockComment != null && !lastBlockComment.isEmpty()) { str.setLength(str.length() - lastBlockComment.length()); // we just modified the end of the string, // do another loop to check for next block or line comments strModified = true; } // now check for the line comments String lastLineComment = getLastLineComment(str.toString()); if (lastLineComment != null && !lastLineComment.isEmpty()) { str.setLength(str.length() - lastLineComment.length()); // we just modified the end of the string, // do another loop to check for next block or line comments strModified = true; } } return trimRight(str.toString()); } /** * Concatenates the addition string to the baseString string, adjusting the case of "addition" to match the base string. * If the string is all caps, append addition in all caps. If all lower case, append in all lower case. If baseString is mixed case, make no changes to addition. */ public static String concatConsistentCase(String baseString, String addition) { boolean hasLowerCase = hasLowerCase(baseString); boolean hasUpperCase = hasUpperCase(baseString); if ((hasLowerCase && hasUpperCase) || (!hasLowerCase && !hasUpperCase)) { //mixed case || no letters return baseString + addition; } else if (hasLowerCase) { return baseString + addition.toLowerCase(); } else { return baseString + addition.toUpperCase(); } } public static String stripEnclosingQuotes(String string) { if (string.length() > 1 && (string.charAt(0) == '"' || string.charAt(0) == '\'') && string.charAt(0) == string.charAt(string.length() - 1)) { return string.substring(1, string.length() - 1); } else { return string; } } /** * Check whether the value is 'null' (case insensitive) */ public static boolean equalsWordNull(String value) { return "NULL".equalsIgnoreCase(value); } /** *

Splits a camel-case string into words based on the came casing. *

* This code originated from the StringUtils class of commons-lang * * @param str the String to split, may be {@code null} * @return an array of parsed Strings, {@code null} if null String input */ public static String[] splitCamelCase(final String str) { if (str == null) { return null; } if (str.isEmpty()) { return new String[0]; } final char[] c = str.toCharArray(); final List list = new ArrayList<>(); int tokenStart = 0; int currentType = Character.getType(c[tokenStart]); for (int pos = tokenStart + 1; pos < c.length; pos++) { final int type = Character.getType(c[pos]); if (type == currentType) { continue; } if (type == Character.LOWERCASE_LETTER && currentType == Character.UPPERCASE_LETTER) { final int newTokenStart = pos - 1; if (newTokenStart != tokenStart) { list.add(new String(c, tokenStart, newTokenStart - tokenStart)); tokenStart = newTokenStart; } } else { list.add(new String(c, tokenStart, pos - tokenStart)); tokenStart = pos; } currentType = type; } list.add(new String(c, tokenStart, c.length - tokenStart)); return list.toArray(new String[0]); } public static byte[] getBytesWithEncoding(String string) { if (string == null) { return null; } String encoding = null; try { encoding = GlobalConfiguration.OUTPUT_FILE_ENCODING.getCurrentConfiguredValue().getValue(); if (encoding != null) { return string.getBytes(encoding); } } catch (UnsupportedEncodingException uoe) { // Consume and fall through Scope.getCurrentScope().getLog(StringUtil.class).warning("Error using encoding " + encoding); } return string.getBytes(StandardCharsets.UTF_8); } /** * @param value string to process * @return string without any whitespaces formatted to lowercase. */ public static String toLowerWithoutWhitespaces(String value) { if (value == null) { return null; } return value.toLowerCase().replaceAll("\\s+", ""); } /** *

Checks whether the char sequence is numeric by checking that all chars in the sequence are * numbers, so (-1, 1.0 and 1F) will return false *

* This code originated from the StringUtils class of commons-lang * * @param cs the arg to check if it is numeric * @return true if convertible to numeric and false otherwise */ public static boolean isNumeric(CharSequence cs) { if (isEmpty(cs)) { return false; } else { int sz = cs.length(); for (int i = 0; i < sz; ++i) { if (!Character.isDigit(cs.charAt(i))) { return false; } } return true; } } public static boolean isEmpty(CharSequence cs) { return cs == null || cs.length() == 0; } }