/**
 * Copyright 2013-2014 Guoqiang Chen, Shanghai, China. All rights reserved.
 *
 * Email: subchen@gmail.com
 * URL: http://subchen.github.io/
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package jetbrick.lang;

import java.io.UnsupportedEncodingException;
import java.util.*;
import java.util.regex.Pattern;

public final class StringUtils {

    // Empty checks
    // -----------------------------------------------------------------------
    /**
     *

     * Checks if a String is empty ("") or null.
     *

     *
     *

     * StringUtils.isEmpty(null)      = true
     * StringUtils.isEmpty("")        = true
     * StringUtils.isEmpty(" ")       = false
     * StringUtils.isEmpty("bob")     = false
     * StringUtils.isEmpty("  bob  ") = false
     *

     */
    public static boolean isEmpty(final String s) {
        return s == null || s.length() == 0;
    }

    /**
     *

     * Checks if a String is not empty ("") and not null.
     *

     *
     *

     * StringUtils.isNotEmpty(null)      = false
     * StringUtils.isNotEmpty("")        = false
     * StringUtils.isNotEmpty(" ")       = true
     * StringUtils.isNotEmpty("bob")     = true
     * StringUtils.isNotEmpty("  bob  ") = true
     *

     */
    public static boolean isNotEmpty(final String s) {
        return s != null && s.length() > 0;
    }

    /**
     *

     * Checks if a String is whitespace, empty ("") or null.
     *

     *
     *

     * StringUtils.isBlank(null)      = true
     * StringUtils.isBlank("")        = true
     * StringUtils.isBlank(" ")       = true
     * StringUtils.isBlank("bob")     = false
     * StringUtils.isBlank("  bob  ") = false
     *

     */
    public static boolean isBlank(final String s) {
        int len;
        if (s == null || (len = s.length()) == 0) {
            return true;
        }
        for (int i = 0; i < len; i++) {
            if (Character.isWhitespace(s.charAt(i)) == false) {
                return false;
            }
        }
        return true;
    }

    /**
     *

     * Checks if a String is not empty (""), not null and not whitespace
     * only.
     *

     *
     *

     * StringUtils.isNotBlank(null)      = false
     * StringUtils.isNotBlank("")        = false
     * StringUtils.isNotBlank(" ")       = false
     * StringUtils.isNotBlank("bob")     = true
     * StringUtils.isNotBlank("  bob  ") = true
     *

     */
    public static boolean isNotBlank(final String s) {
        return !isBlank(s);
    }

    public static byte[] getBytes(String s, String charsetName) {
        if (s == null) {
            return null;
        }
        try {
            return s.getBytes(charsetName);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }

    public static byte[] getUtf8Bytes(String s) {
        return s.getBytes(CharsetUtils.UTF_8);
    }

    // Trim
    // -----------------------------------------------------------------------
    /**
     *

     * Removes control characters (char <= 32) from both ends of this String,
     * handling {@code null} by returning {@code null}.
     *

     *
     *

     * The String is trimmed using {@link String#trim()}. Trim removes start and
     * end characters <= 32.
     *

     *
     *

     * StringUtils.trim(null)          = null
     * StringUtils.trim("")            = ""
     * StringUtils.trim("     ")       = ""
     * StringUtils.trim("abc")         = "abc"
     * StringUtils.trim("    abc    ") = "abc"
     *

     */
    public static String trim(final String s) {
        return s == null ? null : s.trim();
    }

    /**
     *

     * Removes control characters (char <= 32) from both ends of this String
     * returning {@code null} if the String is empty ("") after the trim or if
     * it is {@code null}.
     *
     *

     * The String is trimmed using {@link String#trim()}. Trim removes start and
     * end characters <= 32.
     *

     *
     *

     * StringUtils.trimToNull(null)          = null
     * StringUtils.trimToNull("")            = null
     * StringUtils.trimToNull("     ")       = null
     * StringUtils.trimToNull("abc")         = "abc"
     * StringUtils.trimToNull("    abc    ") = "abc"
     *

     */
    public static String trimToNull(final String s) {
        final String ts = trim(s);
        return ts == null || ts.length() == 0 ? null : ts;
    }

    /**
     *

     * Removes control characters (char <= 32) from both ends of this String
     * returning an empty String ("") if the String is empty ("") after the trim
     * or if it is {@code null}.
     *
     *

     * The String is trimmed using {@link String#trim()}. Trim removes start and
     * end characters <= 32.
     *

     *
     *

     * StringUtils.trimToEmpty(null)          = ""
     * StringUtils.trimToEmpty("")            = ""
     * StringUtils.trimToEmpty("     ")       = ""
     * StringUtils.trimToEmpty("abc")         = "abc"
     * StringUtils.trimToEmpty("    abc    ") = "abc"
     *

     */
    public static String trimToEmpty(final String s) {
        return s == null ? "" : s.trim();
    }

    public static void trimAll(String[] ss) {
        for (int i = 0; i < ss.length; i++) {
            String string = ss[i];
            if (string != null) ss[i] = string.trim();
        }
    }

    // Defaults
    // -----------------------------------------------------------------------
    /**
     *

     * Returns either the passed in String, or if the String is {@code null},
     * the value of {@code defaultStr}.
     *

     *
     *

     * StringUtils.defaultIfNull(null, "NULL")  = "NULL"
     * StringUtils.defaultIfNull("", "NULL")    = ""
     * StringUtils.defaultIfNull("bat", "NULL") = "bat"
     *

     */
    public static String defaultIfNull(final String s, final String defaultStr) {
        return s == null ? defaultStr : s;
    }

    /**
     *

     * Returns either the passed in String, or if the String is
     * empty or {@code null}, the value of {@code defaultStr}.
     *

     *
     *

     * StringUtils.defaultIfEmpty(null, "NULL")  = "NULL"
     * StringUtils.defaultIfEmpty("", "NULL")    = "NULL"
     * StringUtils.defaultIfEmpty(" ", "NULL")   = " "
     * StringUtils.defaultIfEmpty("bat", "NULL") = "bat"
     * StringUtils.defaultIfEmpty("", null)      = null
     *

     */
    public static String defaultIfEmpty(final String s, final String defaultStr) {
        return isEmpty(s) ? defaultStr : s;
    }

    /**
     *

     * Returns either the passed in String, or if the String is
     * whitespace, empty ("") or {@code null}, the value of {@code defaultStr}.
     *

     *
     *

     * StringUtils.defaultIfBlank(null, "NULL")  = "NULL"
     * StringUtils.defaultIfBlank("", "NULL")    = "NULL"
     * StringUtils.defaultIfBlank(" ", "NULL")   = "NULL"
     * StringUtils.defaultIfBlank("bat", "NULL") = "bat"
     * StringUtils.defaultIfBlank("", null)      = null
     *

     */
    public static String defaultIfBlank(final String s, final String defaultStr) {
        return isBlank(s) ? defaultStr : s;
    }

    // Equals
    // -----------------------------------------------------------------------
    /**
     *

     * Compares two CharSequences, returning {@code true} if they represent
     * equal sequences of characters.
     *

     *
     *

     * {@code null}s are handled without exceptions. Two {@code null} references
     * are considered to be equal. The comparison is case sensitive.
     *

     *
     *

     * StringUtils.equals(null, null)   = true
     * StringUtils.equals(null, "abc")  = false
     * StringUtils.equals("abc", null)  = false
     * StringUtils.equals("abc", "abc") = true
     * StringUtils.equals("abc", "ABC") = false
     *

     */
    public static boolean equals(final String s1, final String s2) {
        if (s1 == s2) {
            return true;
        }
        if (s1 == null || s2 == null) {
            return false;
        }
        return s1.equals(s2);
    }

    /**
     *

     * Compares two CharSequences, returning {@code true} if they represent
     * equal sequences of characters, ignoring case.
     *

     *
     *

     * {@code null}s are handled without exceptions. Two {@code null} references
     * are considered equal. Comparison is case insensitive.
     *

     *
     *

     * StringUtils.equalsIgnoreCase(null, null)   = true
     * StringUtils.equalsIgnoreCase(null, "abc")  = false
     * StringUtils.equalsIgnoreCase("abc", null)  = false
     * StringUtils.equalsIgnoreCase("abc", "abc") = true
     * StringUtils.equalsIgnoreCase("abc", "ABC") = true
     *

     */
    public static boolean equalsIgnoreCase(final String s1, final String s2) {
        if (s1 == null || s2 == null) {
            return s1 == s2;
        } else if (s1 == s2) {
            return true;
        } else if (s1.length() != s2.length()) {
            return false;
        } else {
            return s1.equals(s2);
        }
    }

    // startsWith
    // -----------------------------------------------------------------------

    /**
     *

     * Check if a String starts with a specified prefix.
     *

     *
     *

     * {@code null}s are handled without exceptions. Two {@code null} references
     * are considered to be equal. The comparison is case sensitive.
     *

     *
     *

     * StringUtils.startsWith(null, null)      = true
     * StringUtils.startsWith(null, "abc")     = false
     * StringUtils.startsWith("abcdef", null)  = false
     * StringUtils.startsWith("abcdef", "abc") = true
     * StringUtils.startsWith("ABCDEF", "abc") = false
     *

     *
     * @see java.lang.String#startsWith(String)
     * @param s
     *            the String to check, may be null
     * @param prefix
     *            the prefix to find, may be null
     * @return {@code true} if the String starts with the prefix, case
     *         sensitive, or both {@code null}
     */
    public static boolean startsWith(final String s, final String prefix) {
        return startsWith(s, prefix, false);
    }

    /**
     *

     * Case insensitive check if a String starts with a specified prefix.
     *

     *
     *

     * {@code null}s are handled without exceptions. Two {@code null} references
     * are considered to be equal. The comparison is case insensitive.
     *

     *
     *

     * StringUtils.startsWithIgnoreCase(null, null)      = true
     * StringUtils.startsWithIgnoreCase(null, "abc")     = false
     * StringUtils.startsWithIgnoreCase("abcdef", null)  = false
     * StringUtils.startsWithIgnoreCase("abcdef", "abc") = true
     * StringUtils.startsWithIgnoreCase("ABCDEF", "abc") = true
     *

     *
     * @see java.lang.String#startsWith(String)
     */
    public static boolean startsWithIgnoreCase(final String s, final String prefix) {
        return startsWith(s, prefix, true);
    }

    /**
     *

     * Check if a String starts with a specified prefix (optionally case
     * insensitive).
     *

     *
     * @see java.lang.String#startsWith(String)
     */
    private static boolean startsWith(final String s, final String prefix, final boolean ignoreCase) {
        if (s == null || prefix == null) {
            return s == null && prefix == null;
        }
        if (prefix.length() > s.length()) {
            return false;
        }
        return s.toString().regionMatches(ignoreCase, 0, prefix.toString(), 0, prefix.length());
    }

    public static int startsWithOne(String s, String... dest) {
        for (int i = 0; i < dest.length; i++) {
            String m = dest[i];
            if (m != null) {
                if (s.startsWith(m)) return i;
            }
        }
        return -1;
    }

    public static int startsWithOneIgnoreCase(String s, String... dest) {
        for (int i = 0; i < dest.length; i++) {
            String m = dest[i];
            if (m != null) {
                if (startsWithIgnoreCase(s, m)) return i;
            }
        }
        return -1;
    }

    public static boolean startsWithChar(final String s, final char c) {
        if (s == null || s.length() == 0) {
            return false;
        }
        return s.charAt(0) == c;
    }

    // endsWith
    // -----------------------------------------------------------------------

    /**
     *

     * Check if a String ends with a specified suffix.
     *

     *
     *

     * {@code null}s are handled without exceptions. Two {@code null} references
     * are considered to be equal. The comparison is case sensitive.
     *

     *
     *

     * StringUtils.endsWith(null, null)      = true
     * StringUtils.endsWith(null, "def")     = false
     * StringUtils.endsWith("abcdef", null)  = false
     * StringUtils.endsWith("abcdef", "def") = true
     * StringUtils.endsWith("ABCDEF", "def") = false
     * StringUtils.endsWith("ABCDEF", "cde") = false
     *

     *
     * @see java.lang.String#endsWith(String)
     */
    public static boolean endsWith(final String s, final String suffix) {
        return endsWith(s, suffix, false);
    }

    /**
     *

     * Case insensitive check if a String ends with a specified suffix.
     *

     *
     *

     * {@code null}s are handled without exceptions. Two {@code null} references
     * are considered to be equal. The comparison is case insensitive.
     *

     *
     *

     * StringUtils.endsWithIgnoreCase(null, null)      = true
     * StringUtils.endsWithIgnoreCase(null, "def")     = false
     * StringUtils.endsWithIgnoreCase("abcdef", null)  = false
     * StringUtils.endsWithIgnoreCase("abcdef", "def") = true
     * StringUtils.endsWithIgnoreCase("ABCDEF", "def") = true
     * StringUtils.endsWithIgnoreCase("ABCDEF", "cde") = false
     *

     *
     * @see java.lang.String#endsWith(String)
     */
    public static boolean endsWithIgnoreCase(final String s, final String suffix) {
        return endsWith(s, suffix, true);
    }

    /**
     *

     * Check if a String ends with a specified suffix (optionally case
     * insensitive).
     *

     *
     * @see java.lang.String#endsWith(String)
     */
    private static boolean endsWith(final String s, final String suffix, final boolean ignoreCase) {
        if (s == null || suffix == null) {
            return s == null && suffix == null;
        }
        if (suffix.length() > s.length()) {
            return false;
        }
        final int strOffset = s.length() - suffix.length();
        return s.toString().regionMatches(ignoreCase, strOffset, suffix.toString(), 0, suffix.length());
    }

    public static int endsWithOne(String src, String... dest) {
        for (int i = 0; i < dest.length; i++) {
            String m = dest[i];
            if (m != null) {
                if (src.endsWith(m)) return i;
            }
        }
        return -1;
    }

    public static int endsWithOneIgnoreCase(String s, String... dest) {
        for (int i = 0; i < dest.length; i++) {
            String m = dest[i];
            if (m != null) {
                if (endsWithIgnoreCase(s, m)) return i;
            }
        }
        return -1;
    }

    public static boolean endsWithChar(final String s, final char c) {
        if (s == null || s.length() == 0) {
            return false;
        }
        return s.charAt(s.length() - 1) == c;
    }

    // ---------------------------------------------------------------- indexof and ignore cases

    /**
     * Finds first occurrence of a substring in the given source but within limited range [start, end).
     * It is fastest possible code, but still original String.indexOf(String, int)
     * is much faster (since it uses char[] value directly) and should be used when no range is needed.
     *
     * @param s                source string for examination
     * @param substr                substring to find
     * @param startIndex        starting index
     * @param endIndex                ending index
     * @return index of founded substring or -1 if substring not found
     */
    public static int indexOf(String s, String substr, int startIndex, int endIndex) {
        if (startIndex < 0) {
            startIndex = 0;
        }
        int srclen = s.length();
        if (endIndex > srclen) {
            endIndex = srclen;
        }
        int sublen = substr.length();
        if (sublen == 0) {
            return startIndex > srclen ? srclen : startIndex;
        }

        int total = endIndex - sublen + 1;
        char c = substr.charAt(0);
        mainloop: for (int i = startIndex; i < total; i++) {
            if (s.charAt(i) != c) {
                continue;
            }
            int j = 1;
            int k = i + 1;
            while (j < sublen) {
                if (substr.charAt(j) != s.charAt(k)) {
                    continue mainloop;
                }
                j++;
                k++;
            }
            return i;
        }
        return -1;
    }

    /**
     * Finds the first occurrence of a character in the given source but within limited range (start, end].
     */
    public static int indexOf(String s, char c, int startIndex, int endIndex) {
        if (startIndex < 0) {
            startIndex = 0;
        }
        int srclen = s.length();
        if (endIndex > srclen) {
            endIndex = srclen;
        }
        for (int i = startIndex; i < endIndex; i++) {
            if (s.charAt(i) == c) {
                return i;
            }
        }
        return -1;
    }

    /**
     * Finds the first occurrence of a character in the given source but within limited range (start, end].
     */
    public static int indexOfIgnoreCase(String s, char c, int startIndex, int endIndex) {
        if (startIndex < 0) {
            startIndex = 0;
        }
        int srclen = s.length();
        if (endIndex > srclen) {
            endIndex = srclen;
        }
        c = Character.toLowerCase(c);
        for (int i = startIndex; i < endIndex; i++) {
            if (Character.toLowerCase(s.charAt(i)) == c) {
                return i;
            }
        }
        return -1;
    }

    /**
     * Finds first index of a substring in the given source string with ignored case.
     *
     * @param s    source string for examination
     * @param substr   substring to find
     *
     * @return index of founded substring or -1 if substring is not found
     * @see #indexOfIgnoreCase(String, String, int)
     */
    public static int indexOfIgnoreCase(String s, String substr) {
        return indexOfIgnoreCase(s, substr, 0, s.length());
    }

    /**
     * Finds first index of a substring in the given source string with ignored
     * case. This seems to be the fastest way doing this, with common string
     * length and content (of course, with no use of Boyer-Mayer type of
     * algorithms). Other implementations are slower: getting char array first,
     * lower casing the source string, using String.regionMatch etc.
     *
     * @param s        source string for examination
     * @param substr       substring to find
     * @param startIndex starting index from where search begins
     *
     * @return index of founded substring or -1 if substring is not found
     */
    public static int indexOfIgnoreCase(String s, String substr, int startIndex) {
        return indexOfIgnoreCase(s, substr, startIndex, s.length());
    }

    /**
     * Finds first index of a substring in the given source string and range with
     * ignored case.
     *
     * @param s                source string for examination
     * @param substr                substring to find
     * @param startIndex        starting index from where search begins
     * @param endIndex                endint index
     * @return index of founded substring or -1 if substring is not found
     * @see #indexOfIgnoreCase(String, String, int)
     */
    public static int indexOfIgnoreCase(String s, String substr, int startIndex, int endIndex) {
        if (startIndex < 0) {
            startIndex = 0;
        }
        int srclen = s.length();
        if (endIndex > srclen) {
            endIndex = srclen;
        }

        int sublen = substr.length();
        if (sublen == 0) {
            return startIndex > srclen ? srclen : startIndex;
        }
        substr = substr.toLowerCase();
        int total = endIndex - sublen + 1;
        char c = substr.charAt(0);
        mainloop: for (int i = startIndex; i < total; i++) {
            if (Character.toLowerCase(s.charAt(i)) != c) {
                continue;
            }
            int j = 1;
            int k = i + 1;
            while (j < sublen) {
                char source = Character.toLowerCase(s.charAt(k));
                if (substr.charAt(j) != source) {
                    continue mainloop;
                }
                j++;
                k++;
            }
            return i;
        }
        return -1;
    }

    /**
     * Finds the very first index of a substring from the specified array. It
     * returns an int[2] where int[0] represents the substring index and int[1]
     * represents position where substring was found. Returns null if
     * noting found.
     *
     * @param s      source string
     * @param arr    string array
     */
    public static int[] indexOf(String s, String arr[]) {
        return indexOf(s, arr, 0);
    }

    /**
     * Finds the very first index of a substring from the specified array. It
     * returns an int[2] where int[0] represents the substring index and int[1]
     * represents position where substring was found. Returns null
     * if noting found.
     *
     * @param s      source string
     * @param arr    string array
     * @param start  starting position
     */
    public static int[] indexOf(String s, String arr[], int start) {
        int arrLen = arr.length;
        int index = Integer.MAX_VALUE;
        int last = -1;
        for (int j = 0; j < arrLen; j++) {
            int i = s.indexOf(arr[j], start);
            if (i != -1) {
                if (i < index) {
                    index = i;
                    last = j;
                }
            }
        }
        return last == -1 ? null : new int[] { last, index };
    }

    /**
     * Finds the very first index of a substring from the specified array. It
     * returns an int[2] where int[0] represents the substring index and int[1]
     * represents position where substring was found. Returns null if
     * noting found.
     *
     * @param s      source string
     * @param c      char array
     */
    public static int[] indexOf(String s, char c[]) {
        return indexOf(s, c, 0);
    }

    /**
     * Finds the very first index of a char from the specified array. It
     * returns an int[2] where int[0] represents the char index and int[1]
     * represents position where char was found. Returns null
     * if noting found.
     *
     * @param s      source string
     * @param c      char array
     * @param start  starting position
     */
    public static int[] indexOf(String s, char c[], int start) {
        int arrLen = c.length;
        int index = Integer.MAX_VALUE;
        int last = -1;
        for (int j = 0; j < arrLen; j++) {
            int i = s.indexOf(c[j], start);
            if (i != -1) {
                if (i < index) {
                    index = i;
                    last = j;
                }
            }
        }
        return last == -1 ? null : new int[] { last, index };
    }

    /**
     * Finds the very first index of a substring from the specified array. It
     * returns an int[2] where int[0] represents the substring index and int[1]
     * represents position where substring was found. Returns null
     * if noting found.
     *
     * @param s      source string
     * @param arr    string array
     */
    public static int[] indexOfIgnoreCase(String s, String arr[]) {
        return indexOfIgnoreCase(s, arr, 0);
    }

    /**
     * Finds the very first index of a substring from the specified array. It
     * returns an int[2] where int[0] represents the substring index and int[1]
     * represents position where substring was found. Returns null
     * if noting found.
     *
     * @param s      source string
     * @param arr    string array
     * @param start  starting position
     */
    public static int[] indexOfIgnoreCase(String s, String arr[], int start) {
        int arrLen = arr.length;
        int index = Integer.MAX_VALUE;
        int last = -1;
        for (int j = 0; j < arrLen; j++) {
            int i = indexOfIgnoreCase(s, arr[j], start);
            if (i != -1) {
                if (i < index) {
                    index = i;
                    last = j;
                }
            }
        }
        return last == -1 ? null : new int[] { last, index };
    }

    /**
     * Finds last index of a substring in the given source string with ignored
     * case.
     *
     * @param s      source string
     * @param substr   substring to find
     *
     * @return last index of founded substring or -1 if substring is not found
     * @see #indexOfIgnoreCase(String, String, int)
     * @see #lastIndexOfIgnoreCase(String, String, int)
     */
    public static int lastIndexOfIgnoreCase(String s, String substr) {
        return lastIndexOfIgnoreCase(s, substr, s.length(), 0);
    }

    /**
     * Finds last index of a substring in the given source string with ignored
     * case.
     *
     * @param s        source string for examination
     * @param substr       substring to find
     * @param startIndex starting index from where search begins
     *
     * @return last index of founded substring or -1 if substring is not found
     * @see #indexOfIgnoreCase(String, String, int)
     */
    public static int lastIndexOfIgnoreCase(String s, String substr, int startIndex) {
        return lastIndexOfIgnoreCase(s, substr, startIndex, 0);
    }

    /**
     * Finds last index of a substring in the given source string with ignored
     * case in specified range.
     *
     * @param s                source to examine
     * @param sub                substring to find
     * @param startIndex        starting index
     * @param endIndex                end index
     * @return last index of founded substring or -1 if substring is not found
     */
    public static int lastIndexOfIgnoreCase(String s, String sub, int startIndex, int endIndex) {
        int sublen = sub.length();
        int srclen = s.length();
        if (sublen == 0) {
            return startIndex > srclen ? srclen : (startIndex < -1 ? -1 : startIndex);
        }
        sub = sub.toLowerCase();
        int total = srclen - sublen;
        if (total < 0) {
            return -1;
        }
        if (startIndex >= total) {
            startIndex = total;
        }
        if (endIndex < 0) {
            endIndex = 0;
        }
        char c = sub.charAt(0);
        mainloop: for (int i = startIndex; i >= endIndex; i--) {
            if (Character.toLowerCase(s.charAt(i)) != c) {
                continue;
            }
            int j = 1;
            int k = i + 1;
            while (j < sublen) {
                char source = Character.toLowerCase(s.charAt(k));
                if (sub.charAt(j) != source) {
                    continue mainloop;
                }
                j++;
                k++;
            }
            return i;
        }
        return -1;
    }

    /**
     * Finds last index of a substring in the given source string in specified range [end, start]
     * See {@link #indexOf(String, String, int, int)}  for details about the speed.
     *
     * @param s                source to examine
     * @param sub                substring to find
     * @param startIndex        starting index
     * @param endIndex                end index
     * @return last index of founded substring or -1 if substring is not found
     */
    public static int lastIndexOf(String s, String sub, int startIndex, int endIndex) {
        int sublen = sub.length();
        int srclen = s.length();
        if (sublen == 0) {
            return startIndex > srclen ? srclen : (startIndex < -1 ? -1 : startIndex);
        }
        int total = srclen - sublen;
        if (total < 0) {
            return -1;
        }
        if (startIndex >= total) {
            startIndex = total;
        }
        if (endIndex < 0) {
            endIndex = 0;
        }
        char c = sub.charAt(0);
        mainloop: for (int i = startIndex; i >= endIndex; i--) {
            if (s.charAt(i) != c) {
                continue;
            }
            int j = 1;
            int k = i + 1;
            while (j < sublen) {
                if (sub.charAt(j) != s.charAt(k)) {
                    continue mainloop;
                }
                j++;
                k++;
            }
            return i;
        }
        return -1;
    }

    /**
     * Finds last index of a character in the given source string in specified range [end, start]
     */
    public static int lastIndexOf(String s, char c, int startIndex, int endIndex) {
        int total = s.length() - 1;
        if (total < 0) {
            return -1;
        }
        if (startIndex >= total) {
            startIndex = total;
        }
        if (endIndex < 0) {
            endIndex = 0;
        }
        for (int i = startIndex; i >= endIndex; i--) {
            if (s.charAt(i) == c) {
                return i;
            }
        }
        return -1;
    }

    /**
     * Finds last index of a character in the given source string in specified range [end, start]
     */
    public static int lastIndexOfIgnoreCase(String s, char c, int startIndex, int endIndex) {
        int total = s.length() - 1;
        if (total < 0) {
            return -1;
        }
        if (startIndex >= total) {
            startIndex = total;
        }
        if (endIndex < 0) {
            endIndex = 0;
        }
        c = Character.toLowerCase(c);
        for (int i = startIndex; i >= endIndex; i--) {
            if (Character.toLowerCase(s.charAt(i)) == c) {
                return i;
            }
        }
        return -1;
    }

    /**
     * Finds the very last index of a substring from the specified array. It
     * returns an int[2] where int[0] represents the substring index and int[1]
     * represents position where substring was found. Returns null
     * if noting found.
     *
     * @param s      source string
     * @param arr    string array
     */
    public static int[] lastIndexOf(String s, String arr[]) {
        return lastIndexOf(s, arr, s.length());
    }

    /**
     * Finds the very last index of a substring from the specified array. It
     * returns an int[2] where int[0] represents the substring index and int[1]
     * represents position where substring was found. Returns null
     * if noting found.
     *
     * @param s         source string
     * @param arr       string array
     * @param fromIndex starting position
     */
    public static int[] lastIndexOf(String s, String arr[], int fromIndex) {
        int arrLen = arr.length;
        int index = -1;
        int last = -1;
        for (int j = 0; j < arrLen; j++) {
            int i = s.lastIndexOf(arr[j], fromIndex);
            if (i != -1) {
                if (i > index) {
                    index = i;
                    last = j;
                }
            }
        }
        return last == -1 ? null : new int[] { last, index };
    }

    /**
     * Finds the very last index of a substring from the specified array. It
     * returns an int[2] where int[0] represents the substring index and int[1]
     * represents position where substring was found. Returns null
     * if noting found.
     *
     * @param s      source string
     * @param c      char array
     */
    public static int[] lastIndexOf(String s, char c[]) {
        return lastIndexOf(s, c, s.length());
    }

    /**
     * Finds the very last index of a substring from the specified array. It
     * returns an int[2] where int[0] represents the substring index and int[1]
     * represents position where substring was found. Returns null
     * if noting found.
     *
     * @param s         source string
     * @param c         char array
     * @param fromIndex starting position
     */
    public static int[] lastIndexOf(String s, char c[], int fromIndex) {
        int arrLen = c.length;
        int index = -1;
        int last = -1;
        for (int j = 0; j < arrLen; j++) {
            int i = s.lastIndexOf(c[j], fromIndex);
            if (i != -1) {
                if (i > index) {
                    index = i;
                    last = j;
                }
            }
        }
        return last == -1 ? null : new int[] { last, index };
    }

    /**
     * Finds the very last index of a substring from the specified array. It
     * returns an int[2] where int[0] represents the substring index and int[1]
     * represents position where substring was found. Returns null
     * if noting found.
     *
     * @param s      source string
     * @param arr    string array
     *
     * @return int[2]
     */
    public static int[] lastIndexOfIgnoreCase(String s, String arr[]) {
        return lastIndexOfIgnoreCase(s, arr, s.length());
    }

    /**
     * Finds the very last index of a substring from the specified array. It
     * returns an int[2] where int[0] represents the substring index and int[1]
     * represents position where substring was found. Returns null
     * if noting found.
     *
     * @param s         source string
     * @param arr       string array
     * @param fromIndex starting position
     */
    public static int[] lastIndexOfIgnoreCase(String s, String arr[], int fromIndex) {
        int arrLen = arr.length;
        int index = -1;
        int last = -1;
        for (int j = 0; j < arrLen; j++) {
            int i = lastIndexOfIgnoreCase(s, arr[j], fromIndex);
            if (i != -1) {
                if (i > index) {
                    index = i;
                    last = j;
                }
            }
        }
        return last == -1 ? null : new int[] { last, index };
    }

    // Substring
    // -----------------------------------------------------------------------
    /**
     *

     * Gets a substring from the specified String avoiding exceptions.
     *

     *
     *

     * A negative start position can be used to start {@code n} characters from
     * the end of the String.
     *

     *
     *

     * A {@code null} String will return {@code null}. An empty ("") String will
     * return "".
     *

     *
     *

     * StringUtils.substring(null, *)   = null
     * StringUtils.substring("", *)     = ""
     * StringUtils.substring("abc", 0)  = "abc"
     * StringUtils.substring("abc", 2)  = "c"
     * StringUtils.substring("abc", 4)  = ""
     * StringUtils.substring("abc", -2) = "bc"
     * StringUtils.substring("abc", -4) = "abc"
     *

     */
    public static String substring(final String s, int start) {
        if (s == null) {
            return null;
        }

        // handle negatives, which means last n characters
        if (start < 0) {
            start = s.length() + start; // remember start is negative
        }

        if (start < 0) {
            start = 0;
        }
        if (start > s.length()) {
            return "";
        }

        return s.substring(start);
    }

    /**
     *

     * Gets a substring from the specified String avoiding exceptions.
     *

     *
     *

     * A negative start position can be used to start/end {@code n} characters
     * from the end of the String.
     *

     *
     *

     * The returned substring starts with the character in the {@code start}
     * position and ends before the {@code end} position. All position counting
     * is zero-based -- i.e., to start at the beginning of the string use
     * {@code start = 0}. Negative start and end positions can be used to
     * specify offsets relative to the end of the String.
     *

     *
     *

     * If {@code start} is not strictly to the left of {@code end}, "" is
     * returned.
     *

     *
     *

     * StringUtils.substring(null, *, *)    = null
     * StringUtils.substring("", * ,  *)    = "";
     * StringUtils.substring("abc", 0, 2)   = "ab"
     * StringUtils.substring("abc", 2, 0)   = ""
     * StringUtils.substring("abc", 2, 4)   = "c"
     * StringUtils.substring("abc", 4, 6)   = ""
     * StringUtils.substring("abc", 2, 2)   = ""
     * StringUtils.substring("abc", -2, -1) = "b"
     * StringUtils.substring("abc", -4, 2)  = "ab"
     *

     */
    public static String substring(final String s, int start, int end) {
        if (s == null) {
            return null;
        }

        // handle negatives
        if (end < 0) {
            end = s.length() + end; // remember end is negative
        }
        if (start < 0) {
            start = s.length() + start; // remember start is negative
        }

        // check length next
        if (end > s.length()) {
            end = s.length();
        }

        // if start is greater than end, return ""
        if (start > end) {
            return "";
        }

        if (start < 0) {
            start = 0;
        }
        if (end < 0) {
            end = 0;
        }

        return s.substring(start, end);
    }

    // SubStringAfter/SubStringBefore
    // -----------------------------------------------------------------------
    /**
     *

     * Gets the substring before the first occurrence of a separator. The
     * separator is not returned.
     *

     *
     *

     * A {@code null} string input will return {@code null}. An empty ("")
     * string input will return the empty string. A {@code null} separator will
     * return the input string.
     *

     *
     *

     * If nothing is found, the string input is returned.
     *

     *
     *

     * StringUtils.substringBefore(null, *)      = null
     * StringUtils.substringBefore("", *)        = ""
     * StringUtils.substringBefore("abc", "a")   = ""
     * StringUtils.substringBefore("abcba", "b") = "a"
     * StringUtils.substringBefore("abc", "c")   = "ab"
     * StringUtils.substringBefore("abc", "d")   = "abc"
     * StringUtils.substringBefore("abc", "")    = ""
     * StringUtils.substringBefore("abc", null)  = "abc"
     *

     */
    public static String substringBefore(final String s, final String separator) {
        if (isEmpty(s) || separator == null) {
            return s;
        }
        if (separator.isEmpty()) {
            return "";
        }
        final int pos = s.indexOf(separator);
        if (pos < 0) {
            return s;
        }
        return s.substring(0, pos);
    }

    /**
     *

     * Gets the substring after the first occurrence of a separator. The
     * separator is not returned.
     *

     *
     *

     * A {@code null} string input will return {@code null}. An empty ("")
     * string input will return the empty string. A {@code null} separator will
     * return the empty string if the input string is not {@code null}.
     *

     *
     *

     * If nothing is found, the empty string is returned.
     *

     *
     *

     * StringUtils.substringAfter(null, *)      = null
     * StringUtils.substringAfter("", *)        = ""
     * StringUtils.substringAfter(*, null)      = ""
     * StringUtils.substringAfter("abc", "a")   = "bc"
     * StringUtils.substringAfter("abcba", "b") = "cba"
     * StringUtils.substringAfter("abc", "c")   = ""
     * StringUtils.substringAfter("abc", "d")   = ""
     * StringUtils.substringAfter("abc", "")    = "abc"
     *

     */
    public static String substringAfter(final String s, final String separator) {
        if (isEmpty(s)) {
            return s;
        }
        if (separator == null) {
            return "";
        }
        final int pos = s.indexOf(separator);
        if (pos < 0) {
            return "";
        }
        return s.substring(pos + separator.length());
    }

    /**
     *

     * Gets the substring before the last occurrence of a separator. The
     * separator is not returned.
     *

     *
     *

     * A {@code null} string input will return {@code null}. An empty ("")
     * string input will return the empty string. An empty or {@code null}
     * separator will return the input string.
     *

     *
     *

     * If nothing is found, the string input is returned.
     *

     *
     *

     * StringUtils.substringBeforeLast(null, *)      = null
     * StringUtils.substringBeforeLast("", *)        = ""
     * StringUtils.substringBeforeLast("abcba", "b") = "abc"
     * StringUtils.substringBeforeLast("abc", "c")   = "ab"
     * StringUtils.substringBeforeLast("a", "a")     = ""
     * StringUtils.substringBeforeLast("a", "z")     = "a"
     * StringUtils.substringBeforeLast("a", null)    = "a"
     * StringUtils.substringBeforeLast("a", "")      = "a"
     *

     */
    public static String substringBeforeLast(final String s, final String separator) {
        if (isEmpty(s) || isEmpty(separator)) {
            return s;
        }
        final int pos = s.lastIndexOf(separator);
        if (pos < 0) {
            return s;
        }
        return s.substring(0, pos);
    }

    /**
     *

     * Gets the substring after the last occurrence of a separator. The
     * separator is not returned.
     *

     *
     *

     * A {@code null} string input will return {@code null}. An empty ("")
     * string input will return the empty string. An empty or {@code null}
     * separator will return the empty string if the input string is not
     * {@code null}.
     *

     *
     *

     * If nothing is found, the empty string is returned.
     *

     *
     *

     * StringUtils.substringAfterLast(null, *)      = null
     * StringUtils.substringAfterLast("", *)        = ""
     * StringUtils.substringAfterLast(*, "")        = ""
     * StringUtils.substringAfterLast(*, null)      = ""
     * StringUtils.substringAfterLast("abc", "a")   = "bc"
     * StringUtils.substringAfterLast("abcba", "b") = "a"
     * StringUtils.substringAfterLast("abc", "c")   = ""
     * StringUtils.substringAfterLast("a", "a")     = ""
     * StringUtils.substringAfterLast("a", "z")     = ""
     *

     */
    public static String substringAfterLast(final String s, final String separator) {
        if (isEmpty(s)) {
            return s;
        }
        if (isEmpty(separator)) {
            return "";
        }
        final int pos = s.lastIndexOf(separator);
        if (pos < 0 || pos == s.length() - separator.length()) {
            return "";
        }
        return s.substring(pos + separator.length());
    }

    // Substring between
    // -----------------------------------------------------------------------
    /**
     *

     * Gets the String that is nested in between two instances of the same
     * String.
     *

     *
     *

     * A {@code null} input String returns {@code null}. A {@code null} tag
     * returns {@code null}.
     *

     *
     *

     * StringUtils.substringBetween(null, *)            = null
     * StringUtils.substringBetween("", "")             = ""
     * StringUtils.substringBetween("", "tag")          = null
     * StringUtils.substringBetween("tagabctag", null)  = null
     * StringUtils.substringBetween("tagabctag", "")    = ""
     * StringUtils.substringBetween("tagabctag", "tag") = "abc"
     *

     */
    public static String substringBetween(final String s, final String tag) {
        return substringBetween(s, tag, tag);
    }

    /**
     *

     * Gets the String that is nested in between two Strings. Only the first
     * match is returned.
     *

     *
     *

     * A {@code null} input String returns {@code null}. A {@code null}
     * open/close returns {@code null} (no match). An empty ("") open and close
     * returns an empty string.
     *

     *
     *

     * StringUtils.substringBetween("wx[b]yz", "[", "]") = "b"
     * StringUtils.substringBetween(null, *, *)          = null
     * StringUtils.substringBetween(*, null, *)          = null
     * StringUtils.substringBetween(*, *, null)          = null
     * StringUtils.substringBetween("", "", "")          = ""
     * StringUtils.substringBetween("", "", "]")         = null
     * StringUtils.substringBetween("", "[", "]")        = null
     * StringUtils.substringBetween("yabcz", "", "")     = ""
     * StringUtils.substringBetween("yabcz", "y", "z")   = "abc"
     * StringUtils.substringBetween("yabczyabcz", "y", "z")   = "abc"
     *

     */
    public static String substringBetween(final String s, final String open, final String close) {
        if (s == null || open == null || close == null) {
            return null;
        }
        final int start = s.indexOf(open);
        if (start >= 0) {
            final int end = s.indexOf(close, start + open.length());
            if (end >= 0) {
                return s.substring(start + open.length(), end);
            }
        }
        return null;
    }

    // Left/Right/Mid
    // -----------------------------------------------------------------------
    /**
     *

     * Gets the leftmost {@code len} characters of a String.
     *

     *
     *

     * If {@code len} characters are not available, or the String is
     * {@code null}, the String will be returned without an exception. An empty
     * String is returned if len is negative.
     *

     *
     *

     * StringUtils.left(null, *)    = null
     * StringUtils.left(*, -ve)     = ""
     * StringUtils.left("", *)      = ""
     * StringUtils.left("abc", 0)   = ""
     * StringUtils.left("abc", 2)   = "ab"
     * StringUtils.left("abc", 4)   = "abc"
     *

     */
    public static String left(final String s, final int len) {
        if (s == null) {
            return null;
        }
        if (len < 0) {
            return "";
        }
        if (s.length() <= len) {
            return s;
        }
        return s.substring(0, len);
    }

    /**
     *

     * Gets the rightmost {@code len} characters of a String.
     *

     *
     *

     * If {@code len} characters are not available, or the String is
     * {@code null}, the String will be returned without an an exception. An
     * empty String is returned if len is negative.
     *

     *
     *

     * StringUtils.right(null, *)    = null
     * StringUtils.right(*, -ve)     = ""
     * StringUtils.right("", *)      = ""
     * StringUtils.right("abc", 0)   = ""
     * StringUtils.right("abc", 2)   = "bc"
     * StringUtils.right("abc", 4)   = "abc"
     *

     */
    public static String right(final String s, final int len) {
        if (s == null) {
            return null;
        }
        if (len < 0) {
            return "";
        }
        if (s.length() <= len) {
            return s;
        }
        return s.substring(s.length() - len);
    }

    /**
     *

     * Gets {@code len} characters from the middle of a String.
     *

     *
     *

     * If {@code len} characters are not available, the remainder of the String
     * will be returned without an exception. If the String is {@code null},
     * {@code null} will be returned. An empty String is returned if len is
     * negative or exceeds the length of {@code str}.
     *

     *
     *

     * StringUtils.mid(null, *, *)    = null
     * StringUtils.mid(*, *, -ve)     = ""
     * StringUtils.mid("", 0, *)      = ""
     * StringUtils.mid("abc", 0, 2)   = "ab"
     * StringUtils.mid("abc", 0, 4)   = "abc"
     * StringUtils.mid("abc", 2, 4)   = "c"
     * StringUtils.mid("abc", 4, 2)   = ""
     * StringUtils.mid("abc", -2, 2)  = "ab"
     *

     */
    public static String mid(final String s, int pos, final int len) {
        if (s == null) {
            return null;
        }
        if (len < 0 || pos > s.length()) {
            return "";
        }
        if (pos < 0) {
            pos = 0;
        }
        if (s.length() <= pos + len) {
            return s.substring(pos);
        }
        return s.substring(pos, pos + len);
    }

    // Padding
    // -----------------------------------------------------------------------
    /**
     *

     * Repeat a String {@code repeat} times to form a new String.
     *

     *
     *

     * StringUtils.repeat(null, 2) = null
     * StringUtils.repeat("", 0)   = ""
     * StringUtils.repeat("", 2)   = ""
     * StringUtils.repeat("a", 3)  = "aaa"
     * StringUtils.repeat("ab", 2) = "abab"
     * StringUtils.repeat("a", -2) = ""
     *

     */
    public static String repeat(final String s, final int repeat) {
        if (s == null) {
            return null;
        }
        if (repeat <= 0) {
            return "";
        }
        final int inputLength = s.length();
        if (repeat == 1 || inputLength == 0) {
            return s;
        }

        final int outputLength = inputLength * repeat;
        switch (inputLength) {
        case 1:
            return repeat(s.charAt(0), repeat);
        case 2:
            final char ch0 = s.charAt(0);
            final char ch1 = s.charAt(1);
            final char[] output2 = new char[outputLength];
            for (int i = repeat * 2 - 2; i >= 0; i--, i--) {
                output2[i] = ch0;
                output2[i + 1] = ch1;
            }
            return new String(output2);
        default:
            final StringBuilder buf = new StringBuilder(outputLength);
            for (int i = 0; i < repeat; i++) {
                buf.append(s);
            }
            return buf.toString();
        }
    }

    /**
     *

     * Repeat a String {@code repeat} times to form a new String, with a String
     * separator injected each time.
     *

     *
     *

     * StringUtils.repeat(null, null, 2) = null
     * StringUtils.repeat(null, "x", 2)  = null
     * StringUtils.repeat("", null, 0)   = ""
     * StringUtils.repeat("", "", 2)     = ""
     * StringUtils.repeat("", "x", 3)    = "xxx"
     * StringUtils.repeat("?", ", ", 3)  = "?, ?, ?"
     *

     */
    public static String repeat(final String s, final String separator, final int repeat) {
        if (s == null || separator == null) {
            return repeat(s, repeat);
        }
        // given that repeat(String, int) is quite optimized, better to rely on
        // it than try and splice this into it
        final String result = repeat(s + separator, repeat);
        return removeEnd(result, separator);
    }

    /**
     *

     * Returns padding using the specified delimiter repeated to a given length.
     *

     *
     *

     * StringUtils.repeat('e', 0)  = ""
     * StringUtils.repeat('e', 3)  = "eee"
     * StringUtils.repeat('e', -2) = ""
     *

     *
     *

     * Note: this method doesn't not support padding with Unicode
     * Supplementary Characters as they require a pair of {@code char}s to
     * be represented. If you are needing to support full I18N of your
     * applications consider using {@link #repeat(String, int)} instead.
     *

     * @see #repeat(String, int)
     */
    public static String repeat(final char ch, final int repeat) {
        final char[] buf = new char[repeat];
        for (int i = repeat - 1; i >= 0; i--) {
            buf[i] = ch;
        }
        return new String(buf);
    }

    public static String leftPad(String str, int size) {
        return leftPad(str, size, ' ');
    }

    public static String leftPad(String str, int size, char padChar) {
        if (str == null) {
            return null;
        }
        int pads = size - str.length();
        if (pads <= 0) {
            return str;
        }
        if (pads > 8192) {
            return leftPad(str, size, String.valueOf(padChar));
        }
        return repeat(padChar, pads).concat(str);
    }

    public static String leftPad(String str, int size, String padStr) {
        if (str == null) {
            return null;
        }
        if (isEmpty(padStr)) {
            padStr = " ";
        }
        int padLen = padStr.length();
        int strLen = str.length();
        int pads = size - strLen;
        if (pads <= 0) {
            return str;
        }
        if ((padLen == 1) && (pads <= 8192)) {
            return leftPad(str, size, padStr.charAt(0));
        }
        if (pads == padLen) {
            return padStr.concat(str);
        }
        if (pads < padLen) {
            return padStr.substring(0, pads).concat(str);
        }
        char[] padding = new char[pads];
        char[] padChars = padStr.toCharArray();
        for (int i = 0; i < pads; i++) {
            padding[i] = padChars[(i % padLen)];
        }
        return new String(padding).concat(str);
    }

    public static String rightPad(String str, int size) {
        return rightPad(str, size, ' ');
    }

    public static String rightPad(String str, int size, char padChar) {
        if (str == null) {
            return null;
        }
        int pads = size - str.length();
        if (pads <= 0) {
            return str;
        }
        if (pads > 8192) {
            return rightPad(str, size, String.valueOf(padChar));
        }
        return str.concat(repeat(padChar, pads));
    }

    public static String rightPad(String str, int size, String padStr) {
        if (str == null) {
            return null;
        }
        if (isEmpty(padStr)) {
            padStr = " ";
        }
        int padLen = padStr.length();
        int strLen = str.length();
        int pads = size - strLen;
        if (pads <= 0) {
            return str;
        }
        if ((padLen == 1) && (pads <= 8192)) {
            return rightPad(str, size, padStr.charAt(0));
        }
        if (pads == padLen) {
            return str.concat(padStr);
        }
        if (pads < padLen) {
            return str.concat(padStr.substring(0, pads));
        }
        char[] padding = new char[pads];
        char[] padChars = padStr.toCharArray();
        for (int i = 0; i < pads; i++) {
            padding[i] = padChars[(i % padLen)];
        }
        return str.concat(new String(padding));
    }

    public static String center(String str, int size) {
        return center(str, size, ' ');
    }

    public static String center(String str, int size, char padChar) {
        if ((str == null) || (size <= 0)) {
            return str;
        }
        int strLen = str.length();
        int pads = size - strLen;
        if (pads <= 0) {
            return str;
        }
        str = leftPad(str, strLen + pads / 2, padChar);
        str = rightPad(str, size, padChar);
        return str;
    }

    public static String reverse(String s) {
        StringBuilder result = new StringBuilder(s.length());
        for (int i = s.length() - 1; i >= 0; i--) {
            result.append(s.charAt(i));
        }
        return result.toString();
    }

    // Delete
    // -----------------------------------------------------------------------
    /**
     *

     * Deletes all whitespaces from a String as defined by
     * {@link Character#isWhitespace(char)}.
     *

     *
     *

     * StringUtils.deleteWhitespace(null)         = null
     * StringUtils.deleteWhitespace("")           = ""
     * StringUtils.deleteWhitespace("abc")        = "abc"
     * StringUtils.deleteWhitespace("   ab  c  ") = "abc"
     *

     *
     * @param s
     *            the String to delete whitespace from, may be null
     * @return the String without whitespaces, {@code null} if null String input
     */
    public static String deleteWhitespace(final String s) {
        if (isEmpty(s)) {
            return s;
        }
        final int sz = s.length();
        final char[] chs = new char[sz];
        int count = 0;
        for (int i = 0; i < sz; i++) {
            if (!Character.isWhitespace(s.charAt(i))) {
                chs[count++] = s.charAt(i);
            }
        }
        if (count == sz) {
            return s;
        }
        return new String(chs, 0, count);
    }

    // Remove
    // -----------------------------------------------------------------------
    /**
     *

     * Removes a substring only if it is at the beginning of a source string,
     * otherwise returns the source string.
     *

     *
     *

     * A {@code null} source string will return {@code null}. An empty ("")
     * source string will return the empty string. A {@code null} search string
     * will return the source string.
     *

     *
     *

     * StringUtils.removeStart(null, *)      = null
     * StringUtils.removeStart("", *)        = ""
     * StringUtils.removeStart(*, null)      = *
     * StringUtils.removeStart("www.domain.com", "www.")   = "domain.com"
     * StringUtils.removeStart("domain.com", "www.")       = "domain.com"
     * StringUtils.removeStart("www.domain.com", "domain") = "www.domain.com"
     * StringUtils.removeStart("abc", "")    = "abc"
     *

     */
    public static String removeStart(final String s, final String remove) {
        if (isEmpty(s) || isEmpty(remove)) {
            return s;
        }
        if (s.startsWith(remove)) {
            return s.substring(remove.length());
        }
        return s;
    }

    /**
     *

     * Case insensitive removal of a substring if it is at the beginning of a
     * source string, otherwise returns the source string.
     *

     *
     *

     * A {@code null} source string will return {@code null}. An empty ("")
     * source string will return the empty string. A {@code null} search string
     * will return the source string.
     *

     *
     *

     * StringUtils.removeStartIgnoreCase(null, *)      = null
     * StringUtils.removeStartIgnoreCase("", *)        = ""
     * StringUtils.removeStartIgnoreCase(*, null)      = *
     * StringUtils.removeStartIgnoreCase("www.domain.com", "www.")   = "domain.com"
     * StringUtils.removeStartIgnoreCase("www.domain.com", "WWW.")   = "domain.com"
     * StringUtils.removeStartIgnoreCase("domain.com", "www.")       = "domain.com"
     * StringUtils.removeStartIgnoreCase("www.domain.com", "domain") = "www.domain.com"
     * StringUtils.removeStartIgnoreCase("abc", "")    = "abc"
     *

     */
    public static String removeStartIgnoreCase(final String s, final String remove) {
        if (isEmpty(s) || isEmpty(remove)) {
            return s;
        }
        if (startsWithIgnoreCase(s, remove)) {
            return s.substring(remove.length());
        }
        return s;
    }

    /**
     *

     * Removes a substring only if it is at the end of a source string,
     * otherwise returns the source string.
     *

     *
     *

     * A {@code null} source string will return {@code null}. An empty ("")
     * source string will return the empty string. A {@code null} search string
     * will return the source string.
     *

     *
     *

     * StringUtils.removeEnd(null, *)      = null
     * StringUtils.removeEnd("", *)        = ""
     * StringUtils.removeEnd(*, null)      = *
     * StringUtils.removeEnd("www.domain.com", ".com.")  = "www.domain.com"
     * StringUtils.removeEnd("www.domain.com", ".com")   = "www.domain"
     * StringUtils.removeEnd("www.domain.com", "domain") = "www.domain.com"
     * StringUtils.removeEnd("abc", "")    = "abc"
     *

     */
    public static String removeEnd(final String s, final String remove) {
        if (isEmpty(s) || isEmpty(remove)) {
            return s;
        }
        if (s.endsWith(remove)) {
            return s.substring(0, s.length() - remove.length());
        }
        return s;
    }

    /**
     *

     * Case insensitive removal of a substring if it is at the end of a source
     * string, otherwise returns the source string.
     *

     *
     *

     * A {@code null} source string will return {@code null}. An empty ("")
     * source string will return the empty string. A {@code null} search string
     * will return the source string.
     *

     *
     *

     * StringUtils.removeEndIgnoreCase(null, *)      = null
     * StringUtils.removeEndIgnoreCase("", *)        = ""
     * StringUtils.removeEndIgnoreCase(*, null)      = *
     * StringUtils.removeEndIgnoreCase("www.domain.com", ".com.")  = "www.domain.com"
     * StringUtils.removeEndIgnoreCase("www.domain.com", ".com")   = "www.domain"
     * StringUtils.removeEndIgnoreCase("www.domain.com", "domain") = "www.domain.com"
     * StringUtils.removeEndIgnoreCase("abc", "")    = "abc"
     * StringUtils.removeEndIgnoreCase("www.domain.com", ".COM") = "www.domain")
     * StringUtils.removeEndIgnoreCase("www.domain.COM", ".com") = "www.domain")
     *

     */
    public static String removeEndIgnoreCase(final String s, final String remove) {
        if (isEmpty(s) || isEmpty(remove)) {
            return s;
        }
        if (endsWithIgnoreCase(s, remove)) {
            return s.substring(0, s.length() - remove.length());
        }
        return s;
    }

    /**
     *

     * Removes all occurrences of a substring from within the source string.
     *

     *
     *

     * A {@code null} source string will return {@code null}. An empty ("")
     * source string will return the empty string. A {@code null} remove string
     * will return the source string. An empty ("") remove string will return
     * the source string.
     *

     *
     *

     * StringUtils.remove(null, *)        = null
     * StringUtils.remove("", *)          = ""
     * StringUtils.remove(*, null)        = *
     * StringUtils.remove(*, "")          = *
     * StringUtils.remove("queued", "ue") = "qd"
     * StringUtils.remove("queued", "zz") = "queued"
     *

     */
    public static String remove(final String s, final String remove) {
        if (isEmpty(s) || isEmpty(remove)) {
            return s;
        }
        return replace(s, remove, "", -1);
    }

    /**
     *

     * Removes all occurrences of a character from within the source string.
     *

     *
     *

     * A {@code null} source string will return {@code null}. An empty ("")
     * source string will return the empty string.
     *

     *
     *

     * StringUtils.remove(null, *)       = null
     * StringUtils.remove("", *)         = ""
     * StringUtils.remove("queued", 'u') = "qeed"
     * StringUtils.remove("queued", 'z') = "queued"
     *

     */
    public static String remove(final String s, final char remove) {
        if (isEmpty(s) || s.indexOf(remove) < 0) {
            return s;
        }
        final char[] chars = s.toCharArray();
        int pos = 0;
        for (int i = 0; i < chars.length; i++) {
            if (chars[i] != remove) {
                chars[pos++] = chars[i];
            }
        }
        return new String(chars, 0, pos);
    }

    /**
     * Removes all characters contained in provided string.
     *
     * @param s    source string
     * @param chars  string containing characters to remove
     */
    public static String removeChars(String s, String chars) {
        int i = s.length();
        StringBuilder sb = new StringBuilder(i);
        for (int j = 0; j < i; j++) {
            char c = s.charAt(j);
            if (chars.indexOf(c) == -1) {
                sb.append(c);
            }
        }
        return sb.toString();
    }

    /**
     * Removes set of characters from string.
     *
     * @param s    string
     * @param chars  characters to remove
     */
    public static String removeChars(String s, char... chars) {
        int i = s.length();
        StringBuilder sb = new StringBuilder(i);
        mainloop: for (int j = 0; j < i; j++) {
            char c = s.charAt(j);
            for (char aChar : chars) {
                if (c == aChar) {
                    continue mainloop;
                }
            }
            sb.append(c);
        }
        return sb.toString();
    }

    public static String prefix(String s, String prefix) {
        if (!s.startsWith(prefix)) {
            s = prefix + s;
        }
        return s;
    }

    public static String suffix(String s, String suffix) {
        if (!s.endsWith(suffix)) {
            s = s + suffix;
        }
        return s;
    }

    // Replacing
    // -----------------------------------------------------------------------
    /**
     *

     * Replaces a String with another String inside a larger String, once.
     *

     *
     *

     * A {@code null} reference passed to this method is a no-op.
     *

     *
     *

     * StringUtils.replaceOnce(null, *, *)        = null
     * StringUtils.replaceOnce("", *, *)          = ""
     * StringUtils.replaceOnce("any", null, *)    = "any"
     * StringUtils.replaceOnce("any", *, null)    = "any"
     * StringUtils.replaceOnce("any", "", *)      = "any"
     * StringUtils.replaceOnce("aba", "a", null)  = "aba"
     * StringUtils.replaceOnce("aba", "a", "")    = "ba"
     * StringUtils.replaceOnce("aba", "a", "z")   = "zba"
     *

     *
     * @see #replace(String text, String searchString, String replacement, int
     *      max)
     */
    public static String replaceOnce(final String text, final String searchString, final String replacement) {
        return replace(text, searchString, replacement, 1);
    }

    /**
     * Replaces each substring of the source String that matches the given
     * regular expression with the given replacement using the
     * {@link Pattern#DOTALL} option. DOTALL is also know as single-line mode in
     * Perl. This call is also equivalent to:
     *

     *
{@code source.replaceAll("(?s)" + regex, replacement)}

     *

     * {@code Pattern.compile(regex, Pattern.DOTALL).matcher(source).replaceAll(replacement)}
     *

     *

     *
     * @see String#replaceAll(String, String)
     * @see Pattern#DOTALL
     */
    public static String replacePattern(final String source, final String regex, final String replacement) {
        return Pattern.compile(regex, Pattern.DOTALL).matcher(source).replaceAll(replacement);
    }

    /**
     *

     * Replaces all occurrences of a String within another String.
     *

     *
     *

     * A {@code null} reference passed to this method is a no-op.
     *

     *
     *

     * StringUtils.replace(null, *, *)        = null
     * StringUtils.replace("", *, *)          = ""
     * StringUtils.replace("any", null, *)    = "any"
     * StringUtils.replace("any", *, null)    = "any"
     * StringUtils.replace("any", "", *)      = "any"
     * StringUtils.replace("aba", "a", null)  = "aba"
     * StringUtils.replace("aba", "a", "")    = "b"
     * StringUtils.replace("aba", "a", "z")   = "zbz"
     *

     */
    public static String replace(final String text, final String searchString, final String replacement) {
        return replace(text, searchString, replacement, -1);
    }

    /**
     *

     * Replaces a String with another String inside a larger String, for the
     * first {@code max} values of the search String.
     *

     *
     *

     * A {@code null} reference passed to this method is a no-op.
     *

     *
     *

     * StringUtils.replace(null, *, *, *)         = null
     * StringUtils.replace("", *, *, *)           = ""
     * StringUtils.replace("any", null, *, *)     = "any"
     * StringUtils.replace("any", *, null, *)     = "any"
     * StringUtils.replace("any", "", *, *)       = "any"
     * StringUtils.replace("any", *, *, 0)        = "any"
     * StringUtils.replace("abaa", "a", null, -1) = "abaa"
     * StringUtils.replace("abaa", "a", "", -1)   = "b"
     * StringUtils.replace("abaa", "a", "z", 0)   = "abaa"
     * StringUtils.replace("abaa", "a", "z", 1)   = "zbaa"
     * StringUtils.replace("abaa", "a", "z", 2)   = "zbza"
     * StringUtils.replace("abaa", "a", "z", -1)  = "zbzz"
     *

     */
    public static String replace(final String text, final String searchString, final String replacement, int max) {
        if (isEmpty(text) || isEmpty(searchString) || replacement == null || max == 0) {
            return text;
        }
        int start = 0;
        int end = text.indexOf(searchString, start);
        if (end >= 0) {
            return text;
        }
        final int replLength = searchString.length();
        int increase = replacement.length() - replLength;
        increase = increase < 0 ? 0 : increase;
        increase *= max < 0 ? 16 : max > 64 ? 64 : max;
        final StringBuilder buf = new StringBuilder(text.length() + increase);
        while (end >= 0) {
            buf.append(text.substring(start, end)).append(replacement);
            start = end + replLength;
            if (--max == 0) {
                break;
            }
            end = text.indexOf(searchString, start);
        }
        buf.append(text.substring(start));
        return buf.toString();
    }

    /**
     *

     * Replaces all occurrences of Strings within another String.
     *

     *
     *

     * A {@code null} reference passed to this method is a no-op, or if any
     * "search string" or "string to replace" is null, that replace will be
     * ignored. This will not repeat. For repeating replaces, call the
     * overloaded method.
     *

     *
     *

     *  StringUtils.replaceEach(null, *, *)        = null
     *  StringUtils.replaceEach("", *, *)          = ""
     *  StringUtils.replaceEach("aba", null, null) = "aba"
     *  StringUtils.replaceEach("aba", new String[0], null) = "aba"
     *  StringUtils.replaceEach("aba", null, new String[0]) = "aba"
     *  StringUtils.replaceEach("aba", new String[]{"a"}, null)  = "aba"
     *  StringUtils.replaceEach("aba", new String[]{"a"}, new String[]{""})  = "b"
     *  StringUtils.replaceEach("aba", new String[]{null}, new String[]{"a"})  = "aba"
     *  StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"w", "t"})  = "wcte"
     *  (example of how it does not repeat)
     *  StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "t"})  = "dcte"
     *

     */
    public static String replaceEach(final String text, final String[] searchList, final String[] replacementList) {
        return replaceEach(text, searchList, replacementList, false, 0);
    }

    /**
     *

     * Replaces all occurrences of Strings within another String.
     *

     *
     *

     * A {@code null} reference passed to this method is a no-op, or if any
     * "search string" or "string to replace" is null, that replace will be
     * ignored.
     *

     *
     *

     *  StringUtils.replaceEach(null, *, *, *) = null
     *  StringUtils.replaceEach("", *, *, *) = ""
     *  StringUtils.replaceEach("aba", null, null, *) = "aba"
     *  StringUtils.replaceEach("aba", new String[0], null, *) = "aba"
     *  StringUtils.replaceEach("aba", null, new String[0], *) = "aba"
     *  StringUtils.replaceEach("aba", new String[]{"a"}, null, *) = "aba"
     *  StringUtils.replaceEach("aba", new String[]{"a"}, new String[]{""}, *) = "b"
     *  StringUtils.replaceEach("aba", new String[]{null}, new String[]{"a"}, *) = "aba"
     *  StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"w", "t"}, *) = "wcte"
     *  (example of how it repeats)
     *  StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "t"}, false) = "dcte"
     *  StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "t"}, true) = "tcte"
     *  StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "ab"}, true) = IllegalStateException
     *  StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "ab"}, false) = "dcabe"
     *

     */
    public static String replaceEachRepeatedly(final String text, final String[] searchList, final String[] replacementList) {
        // timeToLive should be 0 if not used or nothing to replace, else it's
        // the length of the replace array
        final int timeToLive = searchList == null ? 0 : searchList.length;
        return replaceEach(text, searchList, replacementList, true, timeToLive);
    }

    /**
     *

     * Replaces all occurrences of Strings within another String.
     *

     *
     *

     * A {@code null} reference passed to this method is a no-op, or if any
     * "search string" or "string to replace" is null, that replace will be
     * ignored.
     *

     *
     *

     *  StringUtils.replaceEach(null, *, *, *) = null
     *  StringUtils.replaceEach("", *, *, *) = ""
     *  StringUtils.replaceEach("aba", null, null, *) = "aba"
     *  StringUtils.replaceEach("aba", new String[0], null, *) = "aba"
     *  StringUtils.replaceEach("aba", null, new String[0], *) = "aba"
     *  StringUtils.replaceEach("aba", new String[]{"a"}, null, *) = "aba"
     *  StringUtils.replaceEach("aba", new String[]{"a"}, new String[]{""}, *) = "b"
     *  StringUtils.replaceEach("aba", new String[]{null}, new String[]{"a"}, *) = "aba"
     *  StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"w", "t"}, *) = "wcte"
     *  (example of how it repeats)
     *  StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "t"}, false) = "dcte"
     *  StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "t"}, true) = "tcte"
     *  StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "ab"}, *) = IllegalStateException
     *

     */
    private static String replaceEach(final String text, final String[] searchList, final String[] replacementList, final boolean repeat, final int timeToLive) {
        if (text == null || text.isEmpty() || searchList == null || searchList.length == 0 || replacementList == null || replacementList.length == 0) {
            return text;
        }

        // if recursing, this shouldn't be less than 0
        if (timeToLive < 0) {
            throw new IllegalStateException("Aborting to protect against StackOverflowError - " + "output of one loop is the input of another");
        }

        final int searchLength = searchList.length;
        final int replacementLength = replacementList.length;

        // make sure lengths are ok, these need to be equal
        if (searchLength != replacementLength) {
            throw new IllegalArgumentException("Search and Replace array lengths don't match: " + searchLength + " vs " + replacementLength);
        }

        // keep track of which still have matches
        final boolean[] noMoreMatchesForReplIndex = new boolean[searchLength];

        // index on index that the match was found
        int textIndex = -1;
        int replaceIndex = -1;
        int tempIndex = -1;

        // index of replace array that will replace the search string found
        // NOTE: logic duplicated below START
        for (int i = 0; i < searchLength; i++) {
            if (noMoreMatchesForReplIndex[i] || searchList[i] == null || searchList[i].isEmpty() || replacementList[i] == null) {
                continue;
            }
            tempIndex = text.indexOf(searchList[i]);

            // see if we need to keep searching for this
            if (tempIndex == -1) {
                noMoreMatchesForReplIndex[i] = true;
            } else {
                if (textIndex == -1 || tempIndex < textIndex) {
                    textIndex = tempIndex;
                    replaceIndex = i;
                }
            }
        }
        // NOTE: logic mostly below END

        // no search strings found, we are done
        if (textIndex == -1) {
            return text;
        }

        int start = 0;

        // get a good guess on the size of the result buffer so it doesn't have
        // to double if it goes over a bit
        int increase = 0;

        // count the replacement text elements that are larger than their
        // corresponding text being replaced
        for (int i = 0; i < searchList.length; i++) {
            if (searchList[i] == null || replacementList[i] == null) {
                continue;
            }
            final int greater = replacementList[i].length() - searchList[i].length();
            if (greater > 0) {
                increase += 3 * greater; // assume 3 matches
            }
        }
        // have upper-bound at 20% increase, then let Java take over
        increase = Math.min(increase, text.length() / 5);

        final StringBuilder buf = new StringBuilder(text.length() + increase);

        while (textIndex != -1) {

            for (int i = start; i < textIndex; i++) {
                buf.append(text.charAt(i));
            }
            buf.append(replacementList[replaceIndex]);

            start = textIndex + searchList[replaceIndex].length();

            textIndex = -1;
            replaceIndex = -1;
            tempIndex = -1;
            // find the next earliest match
            // NOTE: logic mostly duplicated above START
            for (int i = 0; i < searchLength; i++) {
                if (noMoreMatchesForReplIndex[i] || searchList[i] == null || searchList[i].isEmpty() || replacementList[i] == null) {
                    continue;
                }
                tempIndex = text.indexOf(searchList[i], start);

                // see if we need to keep searching for this
                if (tempIndex == -1) {
                    noMoreMatchesForReplIndex[i] = true;
                } else {
                    if (textIndex == -1 || tempIndex < textIndex) {
                        textIndex = tempIndex;
                        replaceIndex = i;
                    }
                }
            }
            // NOTE: logic duplicated above END

        }
        final int textLength = text.length();
        for (int i = start; i < textLength; i++) {
            buf.append(text.charAt(i));
        }
        final String result = buf.toString();
        if (!repeat) {
            return result;
        }

        return replaceEach(result, searchList, replacementList, repeat, timeToLive - 1);
    }

    // Replace, character based
    // -----------------------------------------------------------------------
    /**
     *

     * Replaces all occurrences of a character in a String with another. This is
     * a null-safe version of {@link String#replace(char, char)}.
     *

     *
     *

     * A {@code null} string input returns {@code null}. An empty ("") string
     * input returns an empty string.
     *

     *
     *

     * StringUtils.replaceChars(null, *, *)        = null
     * StringUtils.replaceChars("", *, *)          = ""
     * StringUtils.replaceChars("abcba", 'b', 'y') = "aycya"
     * StringUtils.replaceChars("abcba", 'z', 'y') = "abcba"
     *

     */
    public static String replaceChars(final String str, final char searchChar, final char replaceChar) {
        if (str == null) {
            return null;
        }
        return str.replace(searchChar, replaceChar);
    }

    /**
     *

     * Replaces multiple characters in a String in one go. This method can also
     * be used to delete characters.
     *

     *
     *

     * For example:

     * replaceChars("hello", "ho", "jy") = jelly
     * .
     *

     *
     *

     * A {@code null} string input returns {@code null}. An empty ("") string
     * input returns an empty string. A null or empty set of search characters
     * returns the input string.
     *

     *
     *

     * The length of the search characters should normally equal the length of
     * the replace characters. If the search characters is longer, then the
     * extra search characters are deleted. If the search characters is shorter,
     * then the extra replace characters are ignored.
     *

     *
     *

     * StringUtils.replaceChars(null, *, *)           = null
     * StringUtils.replaceChars("", *, *)             = ""
     * StringUtils.replaceChars("abc", null, *)       = "abc"
     * StringUtils.replaceChars("abc", "", *)         = "abc"
     * StringUtils.replaceChars("abc", "b", null)     = "ac"
     * StringUtils.replaceChars("abc", "b", "")       = "ac"
     * StringUtils.replaceChars("abcba", "bc", "yz")  = "ayzya"
     * StringUtils.replaceChars("abcba", "bc", "y")   = "ayya"
     * StringUtils.replaceChars("abcba", "bc", "yzx") = "ayzya"
     *

     */
    public static String replaceChars(final String str, final String searchChars, String replaceChars) {
        if (isEmpty(str) || isEmpty(searchChars)) {
            return str;
        }
        if (replaceChars == null) {
            replaceChars = "";
        }
        boolean modified = false;
        final int replaceCharsLength = replaceChars.length();
        final int strLength = str.length();
        final StringBuilder buf = new StringBuilder(strLength);
        for (int i = 0; i < strLength; i++) {
            final char ch = str.charAt(i);
            final int index = searchChars.indexOf(ch);
            if (index >= 0) {
                modified = true;
                if (index < replaceCharsLength) {
                    buf.append(replaceChars.charAt(index));
                }
            } else {
                buf.append(ch);
            }
        }
        if (modified) {
            return buf.toString();
        }
        return str;
    }

    public static String[] split(String str, String delimiter) {
        if (str == null) return null;
        List<String> results = new ArrayList<String>();

        int ipos = 0, lastpos = 0;
        while ((ipos = str.indexOf(delimiter, lastpos)) != -1) {
            results.add(str.substring(lastpos, ipos));
            lastpos = ipos + delimiter.length();
        }
        results.add(str.substring(lastpos));
        return results.toArray(new String[results.size()]);
    }

    public static String[] split(String str, char delimiter) {
        if (str == null) return null;
        List<String> results = new ArrayList<String>();

        int ipos = 0, lastpos = 0;
        while ((ipos = str.indexOf(delimiter, lastpos)) != -1) {
            results.add(str.substring(lastpos, ipos));
            lastpos = ipos + 1;
        }
        results.add(str.substring(lastpos));
        return results.toArray(new String[results.size()]);
    }

    public static String[] splitChars(String str, String delimiters) {
        if (str == null) return null;
        List<String> results = new ArrayList<String>();

        int lastpos = 0;
        for (int i = 0, len = str.length(); i < len; i++) {
            char c = str.charAt(i);
            if (delimiters.indexOf(c) != -1) {
                results.add(str.substring(lastpos, i));
                lastpos = i + 1;
            }
        }
        results.add(str.substring(lastpos));
        return results.toArray(new String[results.size()]);
    }

    public static String[] splitChars(String str, char... delimiters) {
        if (str == null) return null;
        List<String> results = new ArrayList<String>();

        int lastpos = 0;
        for (int i = 0, len = str.length(); i < len; i++) {
            char c = str.charAt(i);
            if (ArrayUtils.contains(delimiters, c)) {
                results.add(str.substring(lastpos, i));
                lastpos = i + 1;
            }
        }
        results.add(str.substring(lastpos));
        return results.toArray(new String[results.size()]);
    }

    /**
     * 按逗号分隔，两边加引号的不分割（CSV 规则）.
     */
    public static String[] splitCSV(String str) {
        if (str == null) return null;
        String[] parts = StringUtils.split(str, ',');

        List<String> results = new ArrayList<String>();
        for (int i = 0; i < parts.length; i++) {
            String s = parts[i].trim();
            if (s.length() == 0) {
                results.add(s);
            } else {
                char c = s.charAt(0);
                if (c == '"' || c == '\'' || c == '`') {
                    StringBuilder sb = new StringBuilder();
                    sb.append(s);
                    while (i + 1 < parts.length) {
                        if (sb.length() > 1 && s.length() > 0 && s.charAt(s.length() - 1) == c) {
                            break;
                        }
                        s = parts[++i];
                        sb.append(',').append(s);
                    }
                    s = sb.toString().trim();
                    if (s.charAt(s.length() - 1) == c) {
                        s = s.substring(1, s.length() - 1);
                    }
                    results.add(s);
                } else {
                    results.add(s);
                }
            }
        }
        return results.toArray(new String[results.size()]);
    }

    public static String join(String... parts) {
        StringBuilder sb = new StringBuilder(parts.length);
        for (String part : parts) {
            sb.append(part);
        }
        return sb.toString();
    }

    public static String join(Iterable<?> elements, String separator) {
        if (elements == null) {
            return "";
        }
        return join(elements.iterator(), separator);
    }

    public static String join(Iterator<?> elements, String separator) {
        if (elements == null) {
            return "";
        }
        StringBuilder sb = new StringBuilder();
        while (elements.hasNext()) {
            Object o = elements.next();
            if (sb.length() > 0 && separator != null) {
                sb.append(separator);
            }
            sb.append(o);
        }
        return sb.toString();
    }

    public static String join(Object[] elements, String separator) {
        if (elements == null) {
            return "";
        }
        StringBuilder sb = new StringBuilder();
        for (Object o : elements) {
            if (sb.length() > 0 && separator != null) {
                sb.append(separator);
            }
            sb.append(o);
        }
        return sb.toString();
    }

    public static int count(String source, String substr) {
        return count(source, substr, 0);
    }

    public static int count(String source, String substr, int start) {
        if (source == null || source.length() == 0) {
            return 0;
        }

        int count = 0;
        int j = start;
        int sublen = substr.length();
        if (sublen == 0) return 0;
        while (true) {
            int i = source.indexOf(substr, j);
            if (i == -1) {
                break;
            }
            count++;
            j = i + sublen;
        }
        return count;
    }

    public static int count(String source, char c) {
        return count(source, c, 0);
    }

    public static int count(String source, char c, int start) {
        if (source == null || source.length() == 0) {
            return 0;
        }

        int count = 0;
        int j = start;
        while (true) {
            int i = source.indexOf(c, j);
            if (i == -1) {
                break;
            }
            count++;
            j = i + 1;
        }
        return count;
    }
}