    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      https://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017
    018package org.apache.commons.lang3;
    019
    020import static org.apache.commons.lang3.StringUtils.INDEX_NOT_FOUND;
    021
    022import org.apache.commons.lang3.builder.AbstractSupplier;
    023import org.apache.commons.lang3.function.ToBooleanBiFunction;
    024
    025/**
    026 * String operations where you choose case-sensitive {@link #CS} vs. case-insensitive {@link #CI} through a singleton instance.
    027 *
    028 * @see CharSequenceUtils
    029 * @see StringUtils
    030 * @since 3.18.0
    031 */
    032public abstract class Strings {
    033
    034    /**
    035     * Builds {@link Strings} instances.
    036     */
    037    public static class Builder extends AbstractSupplier<Strings, Builder, RuntimeException> {
    038
    039        /**
    040         * Ignores case when possible.
    041         */
    042        private boolean ignoreCase;
    043
    044        /**
    045         * Compares null as less when possible.
    046         */
    047        private boolean nullIsLess;
    048
    049        /**
    050         * Constructs a new instance.
    051         */
    052        private Builder() {
    053            // empty
    054        }
    055
    056        /**
    057         * Gets a new {@link Strings} instance.
    058         */
    059        @Override
    060        public Strings get() {
    061            return ignoreCase ? new CiStrings(nullIsLess) : new CsStrings(nullIsLess);
    062        }
    063
    064        /**
    065         * Sets the ignoreCase property for new Strings instances.
    066         *
    067         * @param ignoreCase the ignoreCase property for new Strings instances.
    068         * @return this instance.
    069         */
    070        public Builder setIgnoreCase(final boolean ignoreCase) {
    071            this.ignoreCase = ignoreCase;
    072            return asThis();
    073        }
    074
    075        /**
    076         * Sets the nullIsLess property for new Strings instances.
    077         *
    078         * @param nullIsLess the nullIsLess property for new Strings instances.
    079         * @return this instance.
    080         */
    081        public Builder setNullIsLess(final boolean nullIsLess) {
    082            this.nullIsLess = nullIsLess;
    083            return asThis();
    084        }
    085
    086    }
    087
    088    /**
    089     * Case-insensitive extension.
    090     */
    091    private static final class CiStrings extends Strings {
    092
    093        private CiStrings(final boolean nullIsLess) {
    094            super(true, nullIsLess);
    095        }
    096
    097        @Override
    098        public int compare(final String s1, final String s2) {
    099            if (s1 == s2) {
    100                // Both null or same object
    101                return 0;
    102            }
    103            if (s1 == null) {
    104                return isNullIsLess() ? -1 : 1;
    105            }
    106            if (s2 == null) {
    107                return isNullIsLess() ? 1 : -1;
    108            }
    109            return s1.compareToIgnoreCase(s2);
    110        }
    111
    112        @Override
    113        public boolean contains(final CharSequence str, final CharSequence searchStr) {
    114            if (str == null || searchStr == null) {
    115                return false;
    116            }
    117            final int len = searchStr.length();
    118            final int max = str.length() - len;
    119            for (int i = 0; i <= max; i++) {
    120                if (CharSequenceUtils.regionMatches(str, true, i, searchStr, 0, len)) {
    121                    return true;
    122                }
    123            }
    124            return false;
    125        }
    126
    127        @Override
    128        public boolean equals(final CharSequence cs1, final CharSequence cs2) {
    129            if (cs1 == cs2) {
    130                return true;
    131            }
    132            if (cs1 == null || cs2 == null) {
    133                return false;
    134            }
    135            if (cs1.length() != cs2.length()) {
    136                return false;
    137            }
    138            return CharSequenceUtils.regionMatches(cs1, true, 0, cs2, 0, cs1.length());
    139        }
    140
    141        @Override
    142        public boolean equals(final String s1, final String s2) {
    143            return s1 == null ? s2 == null : s1.equalsIgnoreCase(s2);
    144        }
    145
    146        @Override
    147        public int indexOf(final CharSequence str, final CharSequence searchStr, int startPos) {
    148            if (str == null || searchStr == null) {
    149                return INDEX_NOT_FOUND;
    150            }
    151            if (startPos < 0) {
    152                startPos = 0;
    153            }
    154            final int endLimit = str.length() - searchStr.length() + 1;
    155            if (startPos > endLimit) {
    156                return INDEX_NOT_FOUND;
    157            }
    158            if (searchStr.length() == 0) {
    159                return startPos;
    160            }
    161            for (int i = startPos; i < endLimit; i++) {
    162                if (CharSequenceUtils.regionMatches(str, true, i, searchStr, 0, searchStr.length())) {
    163                    return i;
    164                }
    165            }
    166            return INDEX_NOT_FOUND;
    167        }
    168
    169        @Override
    170        public int lastIndexOf(final CharSequence str, final CharSequence searchStr, int startPos) {
    171            if (str == null || searchStr == null) {
    172                return INDEX_NOT_FOUND;
    173            }
    174            final int searchStrLength = searchStr.length();
    175            final int strLength = str.length();
    176            if (startPos > strLength - searchStrLength) {
    177                startPos = strLength - searchStrLength;
    178            }
    179            if (startPos < 0) {
    180                return INDEX_NOT_FOUND;
    181            }
    182            if (searchStrLength == 0) {
    183                return startPos;
    184            }
    185            for (int i = startPos; i >= 0; i--) {
    186                if (CharSequenceUtils.regionMatches(str, true, i, searchStr, 0, searchStrLength)) {
    187                    return i;
    188                }
    189            }
    190            return INDEX_NOT_FOUND;
    191        }
    192
    193    }
    194
    195    /**
    196     * Case-sentive extension.
    197     */
    198    private static final class CsStrings extends Strings {
    199
    200        private CsStrings(final boolean nullIsLess) {
    201            super(false, nullIsLess);
    202        }
    203
    204        @Override
    205        public int compare(final String s1, final String s2) {
    206            if (s1 == s2) {
    207                // Both null or same object
    208                return 0;
    209            }
    210            if (s1 == null) {
    211                return isNullIsLess() ? -1 : 1;
    212            }
    213            if (s2 == null) {
    214                return isNullIsLess() ? 1 : -1;
    215            }
    216            return s1.compareTo(s2);
    217        }
    218
    219        @Override
    220        public boolean contains(final CharSequence seq, final CharSequence searchSeq) {
    221            return CharSequenceUtils.indexOf(seq, searchSeq, 0) >= 0;
    222        }
    223
    224        @Override
    225        public boolean equals(final CharSequence cs1, final CharSequence cs2) {
    226            if (cs1 == cs2) {
    227                return true;
    228            }
    229            if (cs1 == null || cs2 == null) {
    230                return false;
    231            }
    232            if (cs1.length() != cs2.length()) {
    233                return false;
    234            }
    235            if (cs1 instanceof String && cs2 instanceof String) {
    236                return cs1.equals(cs2);
    237            }
    238            // Step-wise comparison
    239            final int length = cs1.length();
    240            for (int i = 0; i < length; i++) {
    241                if (cs1.charAt(i) != cs2.charAt(i)) {
    242                    return false;
    243                }
    244            }
    245            return true;
    246        }
    247
    248        @Override
    249        public boolean equals(final String s1, final String s2) {
    250            return eq(s1, s2);
    251        }
    252
    253        @Override
    254        public int indexOf(final CharSequence seq, final CharSequence searchSeq, final int startPos) {
    255            return CharSequenceUtils.indexOf(seq, searchSeq, startPos);
    256        }
    257
    258        @Override
    259        public int lastIndexOf(final CharSequence seq, final CharSequence searchSeq, final int startPos) {
    260            return CharSequenceUtils.lastIndexOf(seq, searchSeq, startPos);
    261        }
    262
    263    }
    264
    265    /**
    266     * The <strong>C</strong>ase-<strong>I</strong>nsensitive singleton instance.
    267     */
    268    public static final Strings CI = new CiStrings(true);
    269
    270    /**
    271     * The <strong>C</strong>ase-<strong>S</strong>nsensitive singleton instance.
    272     */
    273    public static final Strings CS = new CsStrings(true);
    274
    275    /**
    276     * Constructs a new {@link Builder} instance.
    277     *
    278     * @return a new {@link Builder} instance.
    279     */
    280    public static final Builder builder() {
    281        return new Builder();
    282    }
    283
    284    /**
    285     * Tests if the CharSequence contains any of the CharSequences in the given array.
    286     *
    287     * <p>
    288     * A {@code null} {@code cs} CharSequence will return {@code false}. A {@code null} or zero length search array will return {@code false}.
    289     * </p>
    290     *
    291     * @param cs                  The CharSequence to check, may be null
    292     * @param searchCharSequences The array of CharSequences to search for, may be null. Individual CharSequences may be null as well.
    293     * @return {@code true} if any of the search CharSequences are found, {@code false} otherwise
    294     */
    295    private static boolean containsAny(final ToBooleanBiFunction<CharSequence, CharSequence> test, final CharSequence cs,
    296            final CharSequence... searchCharSequences) {
    297        if (StringUtils.isEmpty(cs) || ArrayUtils.isEmpty(searchCharSequences)) {
    298            return false;
    299        }
    300        for (final CharSequence searchCharSequence : searchCharSequences) {
    301            if (test.applyAsBoolean(cs, searchCharSequence)) {
    302                return true;
    303            }
    304        }
    305        return false;
    306    }
    307
    308    /**
    309     * Tests for equality in a null-safe manner.
    310     *
    311     * See JDK-8015417.
    312     */
    313    private static boolean eq(final Object o1, final Object o2) {
    314        return o1 == null ? o2 == null : o1.equals(o2);
    315    }
    316
    317    /**
    318     * Ignores case when possible.
    319     */
    320    private final boolean ignoreCase;
    321
    322    /**
    323     * Compares null as less when possible.
    324     */
    325    private final boolean nullIsLess;
    326
    327    /**
    328     * Constructs a new instance.
    329     *
    330     * @param ignoreCase Ignores case when possible.
    331     * @param nullIsLess Compares null as less when possible.
    332     */
    333    private Strings(final boolean ignoreCase, final boolean nullIsLess) {
    334        this.ignoreCase = ignoreCase;
    335        this.nullIsLess = nullIsLess;
    336    }
    337
    338    /**
    339     * Appends the suffix to the end of the string if the string does not already end with the suffix.
    340     *
    341     * <p>
    342     * Case-sensitive examples
    343     * </p>
    344     *
    345     * <pre>
    346     * Strings.CS.appendIfMissing(null, null)      = null
    347     * Strings.CS.appendIfMissing("abc", null)     = "abc"
    348     * Strings.CS.appendIfMissing("", "xyz"        = "xyz"
    349     * Strings.CS.appendIfMissing("abc", "xyz")    = "abcxyz"
    350     * Strings.CS.appendIfMissing("abcxyz", "xyz") = "abcxyz"
    351     * Strings.CS.appendIfMissing("abcXYZ", "xyz") = "abcXYZxyz"
    352     * </pre>
    353     * <p>
    354     * With additional suffixes:
    355     * </p>
    356     *
    357     * <pre>
    358     * Strings.CS.appendIfMissing(null, null, null)       = null
    359     * Strings.CS.appendIfMissing("abc", null, null)      = "abc"
    360     * Strings.CS.appendIfMissing("", "xyz", null)        = "xyz"
    361     * Strings.CS.appendIfMissing("abc", "xyz", new CharSequence[]{null}) = "abcxyz"
    362     * Strings.CS.appendIfMissing("abc", "xyz", "")       = "abc"
    363     * Strings.CS.appendIfMissing("abc", "xyz", "mno")    = "abcxyz"
    364     * Strings.CS.appendIfMissing("abcxyz", "xyz", "mno") = "abcxyz"
    365     * Strings.CS.appendIfMissing("abcmno", "xyz", "mno") = "abcmno"
    366     * Strings.CS.appendIfMissing("abcXYZ", "xyz", "mno") = "abcXYZxyz"
    367     * Strings.CS.appendIfMissing("abcMNO", "xyz", "mno") = "abcMNOxyz"
    368     * </pre>
    369     *
    370     * <p>
    371     * Case-insensitive examples
    372     * </p>
    373     *
    374     * <pre>
    375     * Strings.CI.appendIfMissing(null, null)      = null
    376     * Strings.CI.appendIfMissing("abc", null)     = "abc"
    377     * Strings.CI.appendIfMissing("", "xyz")       = "xyz"
    378     * Strings.CI.appendIfMissing("abc", "xyz")    = "abcxyz"
    379     * Strings.CI.appendIfMissing("abcxyz", "xyz") = "abcxyz"
    380     * Strings.CI.appendIfMissing("abcXYZ", "xyz") = "abcXYZ"
    381     * </pre>
    382     * <p>
    383     * With additional suffixes:
    384     * </p>
    385     *
    386     * <pre>
    387     * Strings.CI.appendIfMissing(null, null, null)       = null
    388     * Strings.CI.appendIfMissing("abc", null, null)      = "abc"
    389     * Strings.CI.appendIfMissing("", "xyz", null)        = "xyz"
    390     * Strings.CI.appendIfMissing("abc", "xyz", new CharSequence[]{null}) = "abcxyz"
    391     * Strings.CI.appendIfMissing("abc", "xyz", "")       = "abc"
    392     * Strings.CI.appendIfMissing("abc", "xyz", "mno")    = "abcxyz"
    393     * Strings.CI.appendIfMissing("abcxyz", "xyz", "mno") = "abcxyz"
    394     * Strings.CI.appendIfMissing("abcmno", "xyz", "mno") = "abcmno"
    395     * Strings.CI.appendIfMissing("abcXYZ", "xyz", "mno") = "abcXYZ"
    396     * Strings.CI.appendIfMissing("abcMNO", "xyz", "mno") = "abcMNO"
    397     * </pre>
    398     *
    399     * @param str      The string.
    400     * @param suffix   The suffix to append to the end of the string.
    401     * @param suffixes Additional suffixes that are valid terminators (optional).
    402     * @return A new String if suffix was appended, the same string otherwise.
    403     */
    404    public String appendIfMissing(final String str, final CharSequence suffix, final CharSequence... suffixes) {
    405        if (str == null || StringUtils.isEmpty(suffix) || endsWith(str, suffix)) {
    406            return str;
    407        }
    408        if (ArrayUtils.isNotEmpty(suffixes)) {
    409            for (final CharSequence s : suffixes) {
    410                if (endsWith(str, s)) {
    411                    return str;
    412                }
    413            }
    414        }
    415        return str + suffix;
    416    }
    417
    418    /**
    419     * Compare two Strings lexicographically, like {@link String#compareTo(String)}.
    420     * <p>
    421     * The return values are:
    422     * </p>
    423     * <ul>
    424     * <li>{@code int = 0}, if {@code str1} is equal to {@code str2} (or both {@code null})</li>
    425     * <li>{@code int < 0}, if {@code str1} is less than {@code str2}</li>
    426     * <li>{@code int > 0}, if {@code str1} is greater than {@code str2}</li>
    427     * </ul>
    428     *
    429     * <p>
    430     * This is a {@code null} safe version of :
    431     * </p>
    432     *
    433     * <pre>
    434     * str1.compareTo(str2)
    435     * </pre>
    436     *
    437     * <p>
    438     * {@code null} value is considered less than non-{@code null} value. Two {@code null} references are considered equal.
    439     * </p>
    440     *
    441     * <p>
    442     * Case-sensitive examples
    443     * </p>
    444     *
    445     * <pre>{@code
    446     * Strings.CS.compare(null, null)   = 0
    447     * Strings.CS.compare(null , "a")   < 0
    448     * Strings.CS.compare("a", null)   > 0
    449     * Strings.CS.compare("abc", "abc") = 0
    450     * Strings.CS.compare("a", "b")     < 0
    451     * Strings.CS.compare("b", "a")     > 0
    452     * Strings.CS.compare("a", "B")     > 0
    453     * Strings.CS.compare("ab", "abc")  < 0
    454     * }</pre>
    455     * <p>
    456     * Case-insensitive examples
    457     * </p>
    458     *
    459     * <pre>{@code
    460     * Strings.CI.compareIgnoreCase(null, null)   = 0
    461     * Strings.CI.compareIgnoreCase(null , "a")   < 0
    462     * Strings.CI.compareIgnoreCase("a", null)    > 0
    463     * Strings.CI.compareIgnoreCase("abc", "abc") = 0
    464     * Strings.CI.compareIgnoreCase("abc", "ABC") = 0
    465     * Strings.CI.compareIgnoreCase("a", "b")     < 0
    466     * Strings.CI.compareIgnoreCase("b", "a")     > 0
    467     * Strings.CI.compareIgnoreCase("a", "B")     < 0
    468     * Strings.CI.compareIgnoreCase("A", "b")     < 0
    469     * Strings.CI.compareIgnoreCase("ab", "ABC")  < 0
    470     * }</pre>
    471     *
    472     * @see String#compareTo(String)
    473     * @param str1 the String to compare from
    474     * @param str2 the String to compare to
    475     * @return &lt; 0, 0, &gt; 0, if {@code str1} is respectively less, equal or greater than {@code str2}
    476     */
    477    public abstract int compare(String str1, String str2);
    478
    479    /**
    480     * Tests if CharSequence contains a search CharSequence, handling {@code null}. This method uses {@link String#indexOf(String)} if possible.
    481     *
    482     * <p>
    483     * A {@code null} CharSequence will return {@code false}.
    484     * </p>
    485     *
    486     * <p>
    487     * Case-sensitive examples
    488     * </p>
    489     *
    490     * <pre>
    491     * Strings.CS.contains(null, *)     = false
    492     * Strings.CS.contains(*, null)     = false
    493     * Strings.CS.contains("", "")      = true
    494     * Strings.CS.contains("abc", "")   = true
    495     * Strings.CS.contains("abc", "a")  = true
    496     * Strings.CS.contains("abc", "z")  = false
    497     * </pre>
    498     * <p>
    499     * Case-insensitive examples
    500     * </p>
    501     *
    502     * <pre>
    503     * Strings.CI.containsIgnoreCase(null, *)    = false
    504     * Strings.CI.containsIgnoreCase(*, null)    = false
    505     * Strings.CI.containsIgnoreCase("", "")     = true
    506     * Strings.CI.containsIgnoreCase("abc", "")  = true
    507     * Strings.CI.containsIgnoreCase("abc", "a") = true
    508     * Strings.CI.containsIgnoreCase("abc", "z") = false
    509     * Strings.CI.containsIgnoreCase("abc", "A") = true
    510     * Strings.CI.containsIgnoreCase("abc", "Z") = false
    511     * </pre>
    512     *
    513     * @param seq       the CharSequence to check, may be null
    514     * @param searchSeq the CharSequence to find, may be null
    515     * @return true if the CharSequence contains the search CharSequence, false if not or {@code null} string input
    516     */
    517    public abstract boolean contains(CharSequence seq, CharSequence searchSeq);
    518
    519    /**
    520     * Tests if the CharSequence contains any of the CharSequences in the given array.
    521     *
    522     * <p>
    523     * A {@code null} {@code cs} CharSequence will return {@code false}. A {@code null} or zero length search array will return {@code false}.
    524     * </p>
    525     *
    526     * <p>
    527     * Case-sensitive examples
    528     * </p>
    529     *
    530     * <pre>
    531     * Strings.CS.containsAny(null, *)            = false
    532     * Strings.CS.containsAny("", *)              = false
    533     * Strings.CS.containsAny(*, null)            = false
    534     * Strings.CS.containsAny(*, [])              = false
    535     * Strings.CS.containsAny("abcd", "ab", null) = true
    536     * Strings.CS.containsAny("abcd", "ab", "cd") = true
    537     * Strings.CS.containsAny("abc", "d", "abc")  = true
    538     * </pre>
    539     * <p>
    540     * Case-insensitive examples
    541     * </p>
    542     *
    543     * <pre>
    544     * Strings.CI.containsAny(null, *)            = false
    545     * Strings.CI.containsAny("", *)              = false
    546     * Strings.CI.containsAny(*, null)            = false
    547     * Strings.CI.containsAny(*, [])              = false
    548     * Strings.CI.containsAny("abcd", "ab", null) = true
    549     * Strings.CI.containsAny("abcd", "ab", "cd") = true
    550     * Strings.CI.containsAny("abc", "d", "abc")  = true
    551     * Strings.CI.containsAny("abc", "D", "ABC")  = true
    552     * Strings.CI.containsAny("ABC", "d", "abc")  = true
    553     * </pre>
    554     *
    555     * @param cs                  The CharSequence to check, may be null
    556     * @param searchCharSequences The array of CharSequences to search for, may be null. Individual CharSequences may be null as well.
    557     * @return {@code true} if any of the search CharSequences are found, {@code false} otherwise
    558     */
    559    public boolean containsAny(final CharSequence cs, final CharSequence... searchCharSequences) {
    560        return containsAny(this::contains, cs, searchCharSequences);
    561    }
    562
    563    /**
    564     * Tests if a CharSequence ends with a specified suffix.
    565     *
    566     * <p>
    567     * Case-sensitive examples
    568     * </p>
    569     *
    570     * <pre>
    571     * Strings.CS.endsWith(null, null)      = true
    572     * Strings.CS.endsWith(null, "def")     = false
    573     * Strings.CS.endsWith("abcdef", null)  = false
    574     * Strings.CS.endsWith("abcdef", "def") = true
    575     * Strings.CS.endsWith("ABCDEF", "def") = false
    576     * Strings.CS.endsWith("ABCDEF", "cde") = false
    577     * Strings.CS.endsWith("ABCDEF", "")    = true
    578     * </pre>
    579     *
    580     * <p>
    581     * Case-insensitive examples
    582     * </p>
    583     *
    584     * <pre>
    585     * Strings.CI.endsWith(null, null)      = true
    586     * Strings.CI.endsWith(null, "def")     = false
    587     * Strings.CI.endsWith("abcdef", null)  = false
    588     * Strings.CI.endsWith("abcdef", "def") = true
    589     * Strings.CI.endsWith("ABCDEF", "def") = true
    590     * Strings.CI.endsWith("ABCDEF", "cde") = false
    591     * </pre>
    592     *
    593     * @param str    the CharSequence to check, may be null.
    594     * @param suffix the suffix to find, may be null.
    595     * @return {@code true} if the CharSequence starts with the prefix or both {@code null}.
    596     * @see String#endsWith(String)
    597     */
    598    public boolean endsWith(final CharSequence str, final CharSequence suffix) {
    599        if (str == null || suffix == null) {
    600            return str == suffix;
    601        }
    602        final int sufLen = suffix.length();
    603        if (sufLen > str.length()) {
    604            return false;
    605        }
    606        return CharSequenceUtils.regionMatches(str, ignoreCase, str.length() - sufLen, suffix, 0, sufLen);
    607    }
    608
    609    /**
    610     * Tests if a CharSequence ends with any of the provided suffixes.
    611     *
    612     * <p>
    613     * Case-sensitive examples
    614     * </p>
    615     *
    616     * <pre>
    617     * Strings.CS.endsWithAny(null, null)                  = false
    618     * Strings.CS.endsWithAny(null, new String[] {"abc"})  = false
    619     * Strings.CS.endsWithAny("abcxyz", null)              = false
    620     * Strings.CS.endsWithAny("abcxyz", new String[] {""}) = true
    621     * Strings.CS.endsWithAny("abcxyz", new String[] {"xyz"}) = true
    622     * Strings.CS.endsWithAny("abcxyz", new String[] {null, "xyz", "abc"}) = true
    623     * Strings.CS.endsWithAny("abcXYZ", "def", "XYZ")      = true
    624     * Strings.CS.endsWithAny("abcXYZ", "def", "xyz")      = false
    625     * </pre>
    626     *
    627     * @param sequence      the CharSequence to check, may be null
    628     * @param searchStrings the CharSequence suffixes to find, may be empty or contain {@code null}
    629     * @see Strings#endsWith(CharSequence, CharSequence)
    630     * @return {@code true} if the input {@code sequence} is {@code null} AND no {@code searchStrings} are provided, or the input {@code sequence} ends in any
    631     *         of the provided {@code searchStrings}.
    632     */
    633    public boolean endsWithAny(final CharSequence sequence, final CharSequence... searchStrings) {
    634        if (StringUtils.isEmpty(sequence) || ArrayUtils.isEmpty(searchStrings)) {
    635            return false;
    636        }
    637        for (final CharSequence searchString : searchStrings) {
    638            if (endsWith(sequence, searchString)) {
    639                return true;
    640            }
    641        }
    642        return false;
    643    }
    644
    645    /**
    646     * Compares two CharSequences, returning {@code true} if they represent equal sequences of characters.
    647     *
    648     * <p>
    649     * {@code null}s are handled without exceptions. Two {@code null} references are considered to be equal.
    650     * </p>
    651     *
    652     * <p>
    653     * Case-sensitive examples
    654     * </p>
    655     *
    656     * <pre>
    657     * Strings.CS.equals(null, null)   = true
    658     * Strings.CS.equals(null, "abc")  = false
    659     * Strings.CS.equals("abc", null)  = false
    660     * Strings.CS.equals("abc", "abc") = true
    661     * Strings.CS.equals("abc", "ABC") = false
    662     * </pre>
    663     * <p>
    664     * Case-insensitive examples
    665     * </p>
    666     *
    667     * <pre>
    668     * Strings.CI.equalsIgnoreCase(null, null)   = true
    669     * Strings.CI.equalsIgnoreCase(null, "abc")  = false
    670     * Strings.CI.equalsIgnoreCase("abc", null)  = false
    671     * Strings.CI.equalsIgnoreCase("abc", "abc") = true
    672     * Strings.CI.equalsIgnoreCase("abc", "ABC") = true
    673     * </pre>
    674     *
    675     * @param cs1 the first CharSequence, may be {@code null}
    676     * @param cs2 the second CharSequence, may be {@code null}
    677     * @return {@code true} if the CharSequences are equal (case-sensitive), or both {@code null}
    678     * @see Object#equals(Object)
    679     * @see String#compareTo(String)
    680     * @see String#equalsIgnoreCase(String)
    681     */
    682    public abstract boolean equals(CharSequence cs1, CharSequence cs2);
    683
    684    /**
    685     * Compares two CharSequences, returning {@code true} if they represent equal sequences of characters.
    686     *
    687     * <p>
    688     * {@code null}s are handled without exceptions. Two {@code null} references are considered to be equal.
    689     * </p>
    690     *
    691     * <p>
    692     * Case-sensitive examples
    693     * </p>
    694     *
    695     * <pre>
    696     * Strings.CS.equals(null, null)   = true
    697     * Strings.CS.equals(null, "abc")  = false
    698     * Strings.CS.equals("abc", null)  = false
    699     * Strings.CS.equals("abc", "abc") = true
    700     * Strings.CS.equals("abc", "ABC") = false
    701     * </pre>
    702     * <p>
    703     * Case-insensitive examples
    704     * </p>
    705     *
    706     * <pre>
    707     * Strings.CI.equalsIgnoreCase(null, null)   = true
    708     * Strings.CI.equalsIgnoreCase(null, "abc")  = false
    709     * Strings.CI.equalsIgnoreCase("abc", null)  = false
    710     * Strings.CI.equalsIgnoreCase("abc", "abc") = true
    711     * Strings.CI.equalsIgnoreCase("abc", "ABC") = true
    712     * </pre>
    713     *
    714     * @param str1 the first CharSequence, may be {@code null}
    715     * @param str2 the second CharSequence, may be {@code null}
    716     * @return {@code true} if the CharSequences are equal (case-sensitive), or both {@code null}
    717     * @see Object#equals(Object)
    718     * @see String#compareTo(String)
    719     * @see String#equalsIgnoreCase(String)
    720     */
    721    public abstract boolean equals(String str1, String str2);
    722
    723    /**
    724     * Compares given {@code string} to a CharSequences vararg of {@code searchStrings}, returning {@code true} if the {@code string} is equal to any of the
    725     * {@code searchStrings}.
    726     *
    727     * <p>
    728     * Case-sensitive examples
    729     * </p>
    730     *
    731     * <pre>
    732     * Strings.CS.equalsAny(null, (CharSequence[]) null) = false
    733     * Strings.CS.equalsAny(null, null, null)    = true
    734     * Strings.CS.equalsAny(null, "abc", "def")  = false
    735     * Strings.CS.equalsAny("abc", null, "def")  = false
    736     * Strings.CS.equalsAny("abc", "abc", "def") = true
    737     * Strings.CS.equalsAny("abc", "ABC", "DEF") = false
    738     * </pre>
    739     * <p>
    740     * Case-insensitive examples
    741     * </p>
    742     *
    743     * <pre>
    744     * Strings.CI.equalsAny(null, (CharSequence[]) null) = false
    745     * Strings.CI.equalsAny(null, null, null)    = true
    746     * Strings.CI.equalsAny(null, "abc", "def")  = false
    747     * Strings.CI.equalsAny("abc", null, "def")  = false
    748     * Strings.CI.equalsAny("abc", "abc", "def") = true
    749     * Strings.CI.equalsAny("abc", "ABC", "DEF") = false
    750     * </pre>
    751     *
    752     * @param string        to compare, may be {@code null}.
    753     * @param searchStrings a vararg of strings, may be {@code null}.
    754     * @return {@code true} if the string is equal (case-sensitive) to any other element of {@code searchStrings}; {@code false} if {@code searchStrings} is
    755     *         null or contains no matches.
    756     */
    757    public boolean equalsAny(final CharSequence string, final CharSequence... searchStrings) {
    758        if (ArrayUtils.isNotEmpty(searchStrings)) {
    759            for (final CharSequence next : searchStrings) {
    760                if (equals(string, next)) {
    761                    return true;
    762                }
    763            }
    764        }
    765        return false;
    766    }
    767
    768    /**
    769     * Finds the first index within a CharSequence, handling {@code null}. This method uses {@link String#indexOf(String, int)} if possible.
    770     *
    771     * <p>
    772     * A {@code null} CharSequence will return {@code -1}.
    773     * </p>
    774     *
    775     * <p>
    776     * Case-sensitive examples
    777     * </p>
    778     *
    779     * <pre>
    780     * Strings.CS.indexOf(null, *)          = -1
    781     * Strings.CS.indexOf(*, null)          = -1
    782     * Strings.CS.indexOf("", "")           = 0
    783     * Strings.CS.indexOf("", *)            = -1 (except when * = "")
    784     * Strings.CS.indexOf("aabaabaa", "a")  = 0
    785     * Strings.CS.indexOf("aabaabaa", "b")  = 2
    786     * Strings.CS.indexOf("aabaabaa", "ab") = 1
    787     * Strings.CS.indexOf("aabaabaa", "")   = 0
    788     * </pre>
    789     * <p>
    790     * Case-insensitive examples
    791     * </p>
    792     *
    793     * <pre>
    794     * Strings.CI.indexOfIgnoreCase(null, *)          = -1
    795     * Strings.CI.indexOfIgnoreCase(*, null)          = -1
    796     * Strings.CI.indexOfIgnoreCase("", "")           = 0
    797     * Strings.CI.indexOfIgnoreCase(" ", " ")         = 0
    798     * Strings.CI.indexOfIgnoreCase("aabaabaa", "a")  = 0
    799     * Strings.CI.indexOfIgnoreCase("aabaabaa", "b")  = 2
    800     * Strings.CI.indexOfIgnoreCase("aabaabaa", "ab") = 1
    801     * </pre>
    802     *
    803     * @param seq       the CharSequence to check, may be null
    804     * @param searchSeq the CharSequence to find, may be null
    805     * @return the first index of the search CharSequence, -1 if no match or {@code null} string input
    806     */
    807    public int indexOf(final CharSequence seq, final CharSequence searchSeq) {
    808        return indexOf(seq, searchSeq, 0);
    809    }
    810
    811    /**
    812     * Finds the first index within a CharSequence, handling {@code null}. This method uses {@link String#indexOf(String, int)} if possible.
    813     *
    814     * <p>
    815     * A {@code null} CharSequence will return {@code -1}. A negative start position is treated as zero. An empty ("") search CharSequence always matches. A
    816     * start position greater than the string length only matches an empty search CharSequence.
    817     * </p>
    818     *
    819     * <p>
    820     * Case-sensitive examples
    821     * </p>
    822     *
    823     * <pre>
    824     * Strings.CS.indexOf(null, *, *)          = -1
    825     * Strings.CS.indexOf(*, null, *)          = -1
    826     * Strings.CS.indexOf("", "", 0)           = 0
    827     * Strings.CS.indexOf("", *, 0)            = -1 (except when * = "")
    828     * Strings.CS.indexOf("aabaabaa", "a", 0)  = 0
    829     * Strings.CS.indexOf("aabaabaa", "b", 0)  = 2
    830     * Strings.CS.indexOf("aabaabaa", "ab", 0) = 1
    831     * Strings.CS.indexOf("aabaabaa", "b", 3)  = 5
    832     * Strings.CS.indexOf("aabaabaa", "b", 9)  = -1
    833     * Strings.CS.indexOf("aabaabaa", "b", -1) = 2
    834     * Strings.CS.indexOf("aabaabaa", "", 2)   = 2
    835     * Strings.CS.indexOf("abc", "", 9)        = 3
    836     * </pre>
    837     * <p>
    838     * Case-insensitive examples
    839     * </p>
    840     *
    841     * <pre>
    842     * Strings.CI.indexOfIgnoreCase(null, *, *)          = -1
    843     * Strings.CI.indexOfIgnoreCase(*, null, *)          = -1
    844     * Strings.CI.indexOfIgnoreCase("", "", 0)           = 0
    845     * Strings.CI.indexOfIgnoreCase("aabaabaa", "A", 0)  = 0
    846     * Strings.CI.indexOfIgnoreCase("aabaabaa", "B", 0)  = 2
    847     * Strings.CI.indexOfIgnoreCase("aabaabaa", "AB", 0) = 1
    848     * Strings.CI.indexOfIgnoreCase("aabaabaa", "B", 3)  = 5
    849     * Strings.CI.indexOfIgnoreCase("aabaabaa", "B", 9)  = -1
    850     * Strings.CI.indexOfIgnoreCase("aabaabaa", "B", -1) = 2
    851     * Strings.CI.indexOfIgnoreCase("aabaabaa", "", 2)   = 2
    852     * Strings.CI.indexOfIgnoreCase("abc", "", 9)        = -1
    853     * </pre>
    854     *
    855     * @param seq       the CharSequence to check, may be null
    856     * @param searchSeq the CharSequence to find, may be null
    857     * @param startPos  the start position, negative treated as zero
    858     * @return the first index of the search CharSequence (always &ge; startPos), -1 if no match or {@code null} string input
    859     */
    860    public abstract int indexOf(CharSequence seq, CharSequence searchSeq, int startPos);
    861
    862    /**
    863     * Tests whether to ignore case.
    864     *
    865     * @return whether to ignore case.
    866     */
    867    public boolean isCaseSensitive() {
    868        return !ignoreCase;
    869    }
    870
    871    /**
    872     * Tests whether null is less when comparing.
    873     *
    874     * @return whether null is less when comparing.
    875     */
    876    boolean isNullIsLess() {
    877        return nullIsLess;
    878    }
    879
    880    /**
    881     * Finds the last index within a CharSequence, handling {@code null}. This method uses {@link String#lastIndexOf(String)} if possible.
    882     *
    883     * <p>
    884     * A {@code null} CharSequence will return {@code -1}.
    885     * </p>
    886     *
    887     * <p>
    888     * Case-sensitive examples
    889     * </p>
    890     *
    891     * <pre>
    892     * Strings.CS.lastIndexOf(null, *)          = -1
    893     * Strings.CS.lastIndexOf(*, null)          = -1
    894     * Strings.CS.lastIndexOf("", "")           = 0
    895     * Strings.CS.lastIndexOf("aabaabaa", "a")  = 7
    896     * Strings.CS.lastIndexOf("aabaabaa", "b")  = 5
    897     * Strings.CS.lastIndexOf("aabaabaa", "ab") = 4
    898     * Strings.CS.lastIndexOf("aabaabaa", "")   = 8
    899     * </pre>
    900     * <p>
    901     * Case-insensitive examples
    902     * </p>
    903     *
    904     * <pre>
    905     * Strings.CI.lastIndexOfIgnoreCase(null, *)          = -1
    906     * Strings.CI.lastIndexOfIgnoreCase(*, null)          = -1
    907     * Strings.CI.lastIndexOfIgnoreCase("aabaabaa", "A")  = 7
    908     * Strings.CI.lastIndexOfIgnoreCase("aabaabaa", "B")  = 5
    909     * Strings.CI.lastIndexOfIgnoreCase("aabaabaa", "AB") = 4
    910     * </pre>
    911     *
    912     * @param str       the CharSequence to check, may be null
    913     * @param searchStr the CharSequence to find, may be null
    914     * @return the last index of the search String, -1 if no match or {@code null} string input
    915     */
    916    public int lastIndexOf(final CharSequence str, final CharSequence searchStr) {
    917        if (str == null) {
    918            return INDEX_NOT_FOUND;
    919        }
    920        return lastIndexOf(str, searchStr, str.length());
    921    }
    922
    923    /**
    924     * Finds the last index within a CharSequence, handling {@code null}. This method uses {@link String#lastIndexOf(String, int)} if possible.
    925     *
    926     * <p>
    927     * A {@code null} CharSequence will return {@code -1}. A negative start position returns {@code -1}. An empty ("") search CharSequence always matches unless
    928     * the start position is negative. A start position greater than the string length searches the whole string. The search starts at the startPos and works
    929     * backwards; matches starting after the start position are ignored.
    930     * </p>
    931     *
    932     * <p>
    933     * Case-sensitive examples
    934     * </p>
    935     *
    936     * <pre>
    937     * Strings.CS.lastIndexOf(null, *, *)          = -1
    938     * Strings.CS.lastIndexOf(*, null, *)          = -1
    939     * Strings.CS.lastIndexOf("aabaabaa", "a", 8)  = 7
    940     * Strings.CS.lastIndexOf("aabaabaa", "b", 8)  = 5
    941     * Strings.CS.lastIndexOf("aabaabaa", "ab", 8) = 4
    942     * Strings.CS.lastIndexOf("aabaabaa", "b", 9)  = 5
    943     * Strings.CS.lastIndexOf("aabaabaa", "b", -1) = -1
    944     * Strings.CS.lastIndexOf("aabaabaa", "a", 0)  = 0
    945     * Strings.CS.lastIndexOf("aabaabaa", "b", 0)  = -1
    946     * Strings.CS.lastIndexOf("aabaabaa", "b", 1)  = -1
    947     * Strings.CS.lastIndexOf("aabaabaa", "b", 2)  = 2
    948     * Strings.CS.lastIndexOf("aabaabaa", "ba", 2)  = 2
    949     * </pre>
    950     * <p>
    951     * Case-insensitive examples
    952     * </p>
    953     *
    954     * <pre>
    955     * Strings.CI.lastIndexOfIgnoreCase(null, *, *)          = -1
    956     * Strings.CI.lastIndexOfIgnoreCase(*, null, *)          = -1
    957     * Strings.CI.lastIndexOfIgnoreCase("aabaabaa", "A", 8)  = 7
    958     * Strings.CI.lastIndexOfIgnoreCase("aabaabaa", "B", 8)  = 5
    959     * Strings.CI.lastIndexOfIgnoreCase("aabaabaa", "AB", 8) = 4
    960     * Strings.CI.lastIndexOfIgnoreCase("aabaabaa", "B", 9)  = 5
    961     * Strings.CI.lastIndexOfIgnoreCase("aabaabaa", "B", -1) = -1
    962     * Strings.CI.lastIndexOfIgnoreCase("aabaabaa", "A", 0)  = 0
    963     * Strings.CI.lastIndexOfIgnoreCase("aabaabaa", "B", 0)  = -1
    964     * </pre>
    965     *
    966     * @param seq       the CharSequence to check, may be null
    967     * @param searchSeq the CharSequence to find, may be null
    968     * @param startPos  the start position, negative treated as zero
    969     * @return the last index of the search CharSequence (always &le; startPos), -1 if no match or {@code null} string input
    970     */
    971    public abstract int lastIndexOf(CharSequence seq, CharSequence searchSeq, int startPos);
    972
    973    /**
    974     * Prepends the prefix to the start of the string if the string does not already start with any of the prefixes.
    975     *
    976     * <p>
    977     * Case-sensitive examples
    978     * </p>
    979     *
    980     * <pre>
    981     * Strings.CS.prependIfMissing(null, null) = null
    982     * Strings.CS.prependIfMissing("abc", null) = "abc"
    983     * Strings.CS.prependIfMissing("", "xyz") = "xyz"
    984     * Strings.CS.prependIfMissing("abc", "xyz") = "xyzabc"
    985     * Strings.CS.prependIfMissing("xyzabc", "xyz") = "xyzabc"
    986     * Strings.CS.prependIfMissing("XYZabc", "xyz") = "xyzXYZabc"
    987     * </pre>
    988     * <p>
    989     * With additional prefixes,
    990     * </p>
    991     *
    992     * <pre>
    993     * Strings.CS.prependIfMissing(null, null, null) = null
    994     * Strings.CS.prependIfMissing("abc", null, null) = "abc"
    995     * Strings.CS.prependIfMissing("", "xyz", null) = "xyz"
    996     * Strings.CS.prependIfMissing("abc", "xyz", new CharSequence[]{null}) = "xyzabc"
    997     * Strings.CS.prependIfMissing("abc", "xyz", "") = "abc"
    998     * Strings.CS.prependIfMissing("abc", "xyz", "mno") = "xyzabc"
    999     * Strings.CS.prependIfMissing("xyzabc", "xyz", "mno") = "xyzabc"
    1000     * Strings.CS.prependIfMissing("mnoabc", "xyz", "mno") = "mnoabc"
    1001     * Strings.CS.prependIfMissing("XYZabc", "xyz", "mno") = "xyzXYZabc"
    1002     * Strings.CS.prependIfMissing("MNOabc", "xyz", "mno") = "xyzMNOabc"
    1003     * </pre>
    1004     *
    1005     * <p>
    1006     * Case-insensitive examples
    1007     * </p>
    1008     *
    1009     * <pre>
    1010     * Strings.CI.prependIfMissingIgnoreCase(null, null) = null
    1011     * Strings.CI.prependIfMissingIgnoreCase("abc", null) = "abc"
    1012     * Strings.CI.prependIfMissingIgnoreCase("", "xyz") = "xyz"
    1013     * Strings.CI.prependIfMissingIgnoreCase("abc", "xyz") = "xyzabc"
    1014     * Strings.CI.prependIfMissingIgnoreCase("xyzabc", "xyz") = "xyzabc"
    1015     * Strings.CI.prependIfMissingIgnoreCase("XYZabc", "xyz") = "XYZabc"
    1016     * </pre>
    1017     * <p>
    1018     * With additional prefixes,
    1019     * </p>
    1020     *
    1021     * <pre>
    1022     * Strings.CI.prependIfMissingIgnoreCase(null, null, null) = null
    1023     * Strings.CI.prependIfMissingIgnoreCase("abc", null, null) = "abc"
    1024     * Strings.CI.prependIfMissingIgnoreCase("", "xyz", null) = "xyz"
    1025     * Strings.CI.prependIfMissingIgnoreCase("abc", "xyz", new CharSequence[]{null}) = "xyzabc"
    1026     * Strings.CI.prependIfMissingIgnoreCase("abc", "xyz", "") = "abc"
    1027     * Strings.CI.prependIfMissingIgnoreCase("abc", "xyz", "mno") = "xyzabc"
    1028     * Strings.CI.prependIfMissingIgnoreCase("xyzabc", "xyz", "mno") = "xyzabc"
    1029     * Strings.CI.prependIfMissingIgnoreCase("mnoabc", "xyz", "mno") = "mnoabc"
    1030     * Strings.CI.prependIfMissingIgnoreCase("XYZabc", "xyz", "mno") = "XYZabc"
    1031     * Strings.CI.prependIfMissingIgnoreCase("MNOabc", "xyz", "mno") = "MNOabc"
    1032     * </pre>
    1033     *
    1034     * @param str      The string.
    1035     * @param prefix   The prefix to prepend to the start of the string.
    1036     * @param prefixes Additional prefixes that are valid.
    1037     * @return A new String if prefix was prepended, the same string otherwise.
    1038     */
    1039    public String prependIfMissing(final String str, final CharSequence prefix, final CharSequence... prefixes) {
    1040        if (str == null || StringUtils.isEmpty(prefix) || startsWith(str, prefix)) {
    1041            return str;
    1042        }
    1043        if (ArrayUtils.isNotEmpty(prefixes)) {
    1044            for (final CharSequence p : prefixes) {
    1045                if (startsWith(str, p)) {
    1046                    return str;
    1047                }
    1048            }
    1049        }
    1050        return prefix + str;
    1051    }
    1052
    1053    /**
    1054     * Removes all occurrences of a substring from within the source string.
    1055     *
    1056     * <p>
    1057     * 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
    1058     * the source string. An empty ("") remove string will return the source string.
    1059     * </p>
    1060     *
    1061     * <p>
    1062     * Case-sensitive examples
    1063     * </p>
    1064     *
    1065     * <pre>
    1066     * Strings.CS.remove(null, *)        = null
    1067     * Strings.CS.remove("", *)          = ""
    1068     * Strings.CS.remove(*, null)        = *
    1069     * Strings.CS.remove(*, "")          = *
    1070     * Strings.CS.remove("queued", "ue") = "qd"
    1071     * Strings.CS.remove("queued", "zz") = "queued"
    1072     * </pre>
    1073     *
    1074     * <p>
    1075     * Case-insensitive examples
    1076     * </p>
    1077     *
    1078     * <pre>
    1079     * Strings.CI.removeIgnoreCase(null, *)        = null
    1080     * Strings.CI.removeIgnoreCase("", *)          = ""
    1081     * Strings.CI.removeIgnoreCase(*, null)        = *
    1082     * Strings.CI.removeIgnoreCase(*, "")          = *
    1083     * Strings.CI.removeIgnoreCase("queued", "ue") = "qd"
    1084     * Strings.CI.removeIgnoreCase("queued", "zz") = "queued"
    1085     * Strings.CI.removeIgnoreCase("quEUed", "UE") = "qd"
    1086     * Strings.CI.removeIgnoreCase("queued", "zZ") = "queued"
    1087     * </pre>
    1088     *
    1089     * @param str    the source String to search, may be null
    1090     * @param remove the String to search for and remove, may be null
    1091     * @return the substring with the string removed if found, {@code null} if null String input
    1092     */
    1093    public String remove(final String str, final String remove) {
    1094        return replace(str, remove, StringUtils.EMPTY, -1);
    1095    }
    1096
    1097    /**
    1098     * Case-insensitive removal of a substring if it is at the end of a source string, otherwise returns the source string.
    1099     *
    1100     * <p>
    1101     * 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
    1102     * the source string.
    1103     * </p>
    1104     *
    1105     * <p>
    1106     * Case-sensitive examples
    1107     * </p>
    1108     *
    1109     * <pre>
    1110     * Strings.CS.removeEnd(null, *)      = null
    1111     * Strings.CS.removeEnd("", *)        = ""
    1112     * Strings.CS.removeEnd(*, null)      = *
    1113     * Strings.CS.removeEnd("www.domain.com", ".com.")  = "www.domain.com"
    1114     * Strings.CS.removeEnd("www.domain.com", ".com")   = "www.domain"
    1115     * Strings.CS.removeEnd("www.domain.com", "domain") = "www.domain.com"
    1116     * Strings.CS.removeEnd("abc", "")    = "abc"
    1117     * </pre>
    1118     * <p>
    1119     * Case-insensitive examples
    1120     * </p>
    1121     *
    1122     * <pre>
    1123     * Strings.CI.removeEndIgnoreCase(null, *)      = null
    1124     * Strings.CI.removeEndIgnoreCase("", *)        = ""
    1125     * Strings.CI.removeEndIgnoreCase(*, null)      = *
    1126     * Strings.CI.removeEndIgnoreCase("www.domain.com", ".com.")  = "www.domain.com"
    1127     * Strings.CI.removeEndIgnoreCase("www.domain.com", ".com")   = "www.domain"
    1128     * Strings.CI.removeEndIgnoreCase("www.domain.com", "domain") = "www.domain.com"
    1129     * Strings.CI.removeEndIgnoreCase("abc", "")    = "abc"
    1130     * Strings.CI.removeEndIgnoreCase("www.domain.com", ".COM") = "www.domain")
    1131     * Strings.CI.removeEndIgnoreCase("www.domain.COM", ".com") = "www.domain")
    1132     * </pre>
    1133     *
    1134     * @param str    the source String to search, may be null
    1135     * @param remove the String to search for (case-insensitive) and remove, may be null
    1136     * @return the substring with the string removed if found, {@code null} if null String input
    1137     */
    1138    public String removeEnd(final String str, final CharSequence remove) {
    1139        if (StringUtils.isEmpty(str) || StringUtils.isEmpty(remove)) {
    1140            return str;
    1141        }
    1142        if (endsWith(str, remove)) {
    1143            return str.substring(0, str.length() - remove.length());
    1144        }
    1145        return str;
    1146    }
    1147
    1148    /**
    1149     * Case-insensitive removal of a substring if it is at the beginning of a source string, otherwise returns the source string.
    1150     *
    1151     * <p>
    1152     * 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
    1153     * the source string.
    1154     * </p>
    1155     *
    1156     * <p>
    1157     * Case-sensitive examples
    1158     * </p>
    1159     *
    1160     * <pre>
    1161     * Strings.CS.removeStart(null, *)      = null
    1162     * Strings.CS.removeStart("", *)        = ""
    1163     * Strings.CS.removeStart(*, null)      = *
    1164     * Strings.CS.removeStart("www.domain.com", "www.")   = "domain.com"
    1165     * Strings.CS.removeStart("domain.com", "www.")       = "domain.com"
    1166     * Strings.CS.removeStart("www.domain.com", "domain") = "www.domain.com"
    1167     * Strings.CS.removeStart("abc", "")    = "abc"
    1168     * </pre>
    1169     * <p>
    1170     * Case-insensitive examples
    1171     * </p>
    1172     *
    1173     * <pre>
    1174     * Strings.CI.removeStartIgnoreCase(null, *)      = null
    1175     * Strings.CI.removeStartIgnoreCase("", *)        = ""
    1176     * Strings.CI.removeStartIgnoreCase(*, null)      = *
    1177     * Strings.CI.removeStartIgnoreCase("www.domain.com", "www.")   = "domain.com"
    1178     * Strings.CI.removeStartIgnoreCase("www.domain.com", "WWW.")   = "domain.com"
    1179     * Strings.CI.removeStartIgnoreCase("domain.com", "www.")       = "domain.com"
    1180     * Strings.CI.removeStartIgnoreCase("www.domain.com", "domain") = "www.domain.com"
    1181     * Strings.CI.removeStartIgnoreCase("abc", "")    = "abc"
    1182     * </pre>
    1183     *
    1184     * @param str    the source String to search, may be null
    1185     * @param remove the String to search for (case-insensitive) and remove, may be null
    1186     * @return the substring with the string removed if found, {@code null} if null String input
    1187     */
    1188    public String removeStart(final String str, final CharSequence remove) {
    1189        if (str != null && startsWith(str, remove)) {
    1190            return str.substring(StringUtils.length(remove));
    1191        }
    1192        return str;
    1193    }
    1194
    1195    /**
    1196     * Case insensitively replaces all occurrences of a String within another String.
    1197     *
    1198     * <p>
    1199     * A {@code null} reference passed to this method is a no-op.
    1200     * </p>
    1201     *
    1202     * <p>
    1203     * Case-sensitive examples
    1204     * </p>
    1205     *
    1206     * <pre>
    1207     * Strings.CS.replace(null, *, *)        = null
    1208     * Strings.CS.replace("", *, *)          = ""
    1209     * Strings.CS.replace("any", null, *)    = "any"
    1210     * Strings.CS.replace("any", *, null)    = "any"
    1211     * Strings.CS.replace("any", "", *)      = "any"
    1212     * Strings.CS.replace("aba", "a", null)  = "aba"
    1213     * Strings.CS.replace("aba", "a", "")    = "b"
    1214     * Strings.CS.replace("aba", "a", "z")   = "zbz"
    1215     * </pre>
    1216     * <p>
    1217     * Case-insensitive examples
    1218     * </p>
    1219     *
    1220     * <pre>
    1221     * Strings.CI.replaceIgnoreCase(null, *, *)        = null
    1222     * Strings.CI.replaceIgnoreCase("", *, *)          = ""
    1223     * Strings.CI.replaceIgnoreCase("any", null, *)    = "any"
    1224     * Strings.CI.replaceIgnoreCase("any", *, null)    = "any"
    1225     * Strings.CI.replaceIgnoreCase("any", "", *)      = "any"
    1226     * Strings.CI.replaceIgnoreCase("aba", "a", null)  = "aba"
    1227     * Strings.CI.replaceIgnoreCase("abA", "A", "")    = "b"
    1228     * Strings.CI.replaceIgnoreCase("aba", "A", "z")   = "zbz"
    1229     * </pre>
    1230     *
    1231     * @see #replace(String text, String searchString, String replacement, int max)
    1232     * @param text         text to search and replace in, may be null
    1233     * @param searchString the String to search for (case-insensitive), may be null
    1234     * @param replacement  the String to replace it with, may be null
    1235     * @return the text with any replacements processed, {@code null} if null String input
    1236     */
    1237    public String replace(final String text, final String searchString, final String replacement) {
    1238        return replace(text, searchString, replacement, -1);
    1239    }
    1240
    1241    /**
    1242     * Replaces a String with another String inside a larger String, for the first {@code max} values of the search String.
    1243     *
    1244     * <p>
    1245     * A {@code null} reference passed to this method is a no-op.
    1246     * </p>
    1247     *
    1248     * <p>
    1249     * Case-sensitive examples
    1250     * </p>
    1251     *
    1252     * <pre>
    1253     * Strings.CS.replace(null, *, *, *)         = null
    1254     * Strings.CS.replace("", *, *, *)           = ""
    1255     * Strings.CS.replace("any", null, *, *)     = "any"
    1256     * Strings.CS.replace("any", *, null, *)     = "any"
    1257     * Strings.CS.replace("any", "", *, *)       = "any"
    1258     * Strings.CS.replace("any", *, *, 0)        = "any"
    1259     * Strings.CS.replace("abaa", "a", null, -1) = "abaa"
    1260     * Strings.CS.replace("abaa", "a", "", -1)   = "b"
    1261     * Strings.CS.replace("abaa", "a", "z", 0)   = "abaa"
    1262     * Strings.CS.replace("abaa", "a", "z", 1)   = "zbaa"
    1263     * Strings.CS.replace("abaa", "a", "z", 2)   = "zbza"
    1264     * Strings.CS.replace("abaa", "a", "z", -1)  = "zbzz"
    1265     * </pre>
    1266     * <p>
    1267     * Case-insensitive examples
    1268     * </p>
    1269     *
    1270     * <pre>
    1271     * Strings.CI.replaceIgnoreCase(null, *, *, *)         = null
    1272     * Strings.CI.replaceIgnoreCase("", *, *, *)           = ""
    1273     * Strings.CI.replaceIgnoreCase("any", null, *, *)     = "any"
    1274     * Strings.CI.replaceIgnoreCase("any", *, null, *)     = "any"
    1275     * Strings.CI.replaceIgnoreCase("any", "", *, *)       = "any"
    1276     * Strings.CI.replaceIgnoreCase("any", *, *, 0)        = "any"
    1277     * Strings.CI.replaceIgnoreCase("abaa", "a", null, -1) = "abaa"
    1278     * Strings.CI.replaceIgnoreCase("abaa", "a", "", -1)   = "b"
    1279     * Strings.CI.replaceIgnoreCase("abaa", "a", "z", 0)   = "abaa"
    1280     * Strings.CI.replaceIgnoreCase("abaa", "A", "z", 1)   = "zbaa"
    1281     * Strings.CI.replaceIgnoreCase("abAa", "a", "z", 2)   = "zbza"
    1282     * Strings.CI.replaceIgnoreCase("abAa", "a", "z", -1)  = "zbzz"
    1283     * </pre>
    1284     *
    1285     * @param text         text to search and replace in, may be null
    1286     * @param searchString the String to search for (case-insensitive), may be null
    1287     * @param replacement  the String to replace it with, may be null
    1288     * @param max          maximum number of values to replace, or {@code -1} if no maximum
    1289     * @return the text with any replacements processed, {@code null} if null String input
    1290     */
    1291    public String replace(final String text, String searchString, final String replacement, int max) {
    1292        if (StringUtils.isEmpty(text) || StringUtils.isEmpty(searchString) || replacement == null || max == 0) {
    1293            return text;
    1294        }
    1295        if (ignoreCase) {
    1296            searchString = searchString.toLowerCase();
    1297        }
    1298        int start = 0;
    1299        int end = indexOf(text, searchString, start);
    1300        if (end == INDEX_NOT_FOUND) {
    1301            return text;
    1302        }
    1303        final int replLength = searchString.length();
    1304        int increase = Math.max(replacement.length() - replLength, 0);
    1305        increase *= max < 0 ? 16 : Math.min(max, 64);
    1306        final StringBuilder buf = new StringBuilder(text.length() + increase);
    1307        while (end != INDEX_NOT_FOUND) {
    1308            buf.append(text, start, end).append(replacement);
    1309            start = end + replLength;
    1310            if (--max == 0) {
    1311                break;
    1312            }
    1313            end = indexOf(text, searchString, start);
    1314        }
    1315        buf.append(text, start, text.length());
    1316        return buf.toString();
    1317    }
    1318
    1319    /**
    1320     * Replaces a String with another String inside a larger String, once.
    1321     *
    1322     * <p>
    1323     * A {@code null} reference passed to this method is a no-op.
    1324     * </p>
    1325     *
    1326     * <p>
    1327     * Case-sensitive examples
    1328     * </p>
    1329     *
    1330     * <pre>
    1331     * Strings.CS.replaceOnce(null, *, *)        = null
    1332     * Strings.CS.replaceOnce("", *, *)          = ""
    1333     * Strings.CS.replaceOnce("any", null, *)    = "any"
    1334     * Strings.CS.replaceOnce("any", *, null)    = "any"
    1335     * Strings.CS.replaceOnce("any", "", *)      = "any"
    1336     * Strings.CS.replaceOnce("aba", "a", null)  = "aba"
    1337     * Strings.CS.replaceOnce("aba", "a", "")    = "ba"
    1338     * Strings.CS.replaceOnce("aba", "a", "z")   = "zba"
    1339     * </pre>
    1340     *
    1341     * <p>
    1342     * Case-insensitive examples
    1343     * </p>
    1344     *
    1345     * <pre>
    1346     * Strings.CI.replaceOnceIgnoreCase(null, *, *)        = null
    1347     * Strings.CI.replaceOnceIgnoreCase("", *, *)          = ""
    1348     * Strings.CI.replaceOnceIgnoreCase("any", null, *)    = "any"
    1349     * Strings.CI.replaceOnceIgnoreCase("any", *, null)    = "any"
    1350     * Strings.CI.replaceOnceIgnoreCase("any", "", *)      = "any"
    1351     * Strings.CI.replaceOnceIgnoreCase("aba", "a", null)  = "aba"
    1352     * Strings.CI.replaceOnceIgnoreCase("aba", "a", "")    = "ba"
    1353     * Strings.CI.replaceOnceIgnoreCase("aba", "a", "z")   = "zba"
    1354     * Strings.CI.replaceOnceIgnoreCase("FoOFoofoo", "foo", "") = "Foofoo"
    1355     * </pre>
    1356     *
    1357     * @see #replace(String text, String searchString, String replacement, int max)
    1358     * @param text         text to search and replace in, may be null
    1359     * @param searchString the String to search for, may be null
    1360     * @param replacement  the String to replace with, may be null
    1361     * @return the text with any replacements processed, {@code null} if null String input
    1362     */
    1363    public String replaceOnce(final String text, final String searchString, final String replacement) {
    1364        return replace(text, searchString, replacement, 1);
    1365    }
    1366
    1367    /**
    1368     * Tests if a CharSequence starts with a specified prefix.
    1369     *
    1370     * <p>
    1371     * {@code null}s are handled without exceptions. Two {@code null} references are considered to be equal.
    1372     * </p>
    1373     *
    1374     * <p>
    1375     * Case-sensitive examples
    1376     * </p>
    1377     *
    1378     * <pre>
    1379     * Strings.CS.startsWith(null, null)      = true
    1380     * Strings.CS.startsWith(null, "abc")     = false
    1381     * Strings.CS.startsWith("abcdef", null)  = false
    1382     * Strings.CS.startsWith("abcdef", "abc") = true
    1383     * Strings.CS.startsWith("ABCDEF", "abc") = false
    1384     * </pre>
    1385     *
    1386     * <p>
    1387     * Case-insensitive examples
    1388     * </p>
    1389     *
    1390     * <pre>
    1391     * Strings.CI.startsWithIgnoreCase(null, null)      = true
    1392     * Strings.CI.startsWithIgnoreCase(null, "abc")     = false
    1393     * Strings.CI.startsWithIgnoreCase("abcdef", null)  = false
    1394     * Strings.CI.startsWithIgnoreCase("abcdef", "abc") = true
    1395     * Strings.CI.startsWithIgnoreCase("ABCDEF", "abc") = true
    1396     * </pre>
    1397     *
    1398     * @see String#startsWith(String)
    1399     * @param str    the CharSequence to check, may be null
    1400     * @param prefix the prefix to find, may be null
    1401     * @return {@code true} if the CharSequence starts with the prefix, case-sensitive, or both {@code null}
    1402     */
    1403    public boolean startsWith(final CharSequence str, final CharSequence prefix) {
    1404        if (str == null || prefix == null) {
    1405            return str == prefix;
    1406        }
    1407        final int preLen = prefix.length();
    1408        if (preLen > str.length()) {
    1409            return false;
    1410        }
    1411        return CharSequenceUtils.regionMatches(str, ignoreCase, 0, prefix, 0, preLen);
    1412    }
    1413
    1414    /**
    1415     * Tests if a CharSequence starts with any of the provided prefixes.
    1416     *
    1417     * <p>
    1418     * Case-sensitive examples
    1419     * </p>
    1420     *
    1421     * <pre>
    1422     * Strings.CS.startsWithAny(null, null)      = false
    1423     * Strings.CS.startsWithAny(null, new String[] {"abc"})  = false
    1424     * Strings.CS.startsWithAny("abcxyz", null)     = false
    1425     * Strings.CS.startsWithAny("abcxyz", new String[] {""}) = true
    1426     * Strings.CS.startsWithAny("abcxyz", new String[] {"abc"}) = true
    1427     * Strings.CS.startsWithAny("abcxyz", new String[] {null, "xyz", "abc"}) = true
    1428     * Strings.CS.startsWithAny("abcxyz", null, "xyz", "ABCX") = false
    1429     * Strings.CS.startsWithAny("ABCXYZ", null, "xyz", "abc") = false
    1430     * </pre>
    1431     *
    1432     * <p>
    1433     * Case-insensitive examples
    1434     * </p>
    1435     *
    1436     * <pre>
    1437     * Strings.CI.startsWithAny(null, null)      = false
    1438     * Strings.CI.startsWithAny(null, new String[] {"aBc"})  = false
    1439     * Strings.CI.startsWithAny("AbCxYz", null)     = false
    1440     * Strings.CI.startsWithAny("AbCxYz", new String[] {""}) = true
    1441     * Strings.CI.startsWithAny("AbCxYz", new String[] {"aBc"}) = true
    1442     * Strings.CI.startsWithAny("AbCxYz", new String[] {null, "XyZ", "aBc"}) = true
    1443     * Strings.CI.startsWithAny("abcxyz", null, "xyz", "ABCX") = true
    1444     * Strings.CI.startsWithAny("ABCXYZ", null, "xyz", "abc") = true
    1445     * </pre>
    1446     *
    1447     * @param sequence      the CharSequence to check, may be null
    1448     * @param searchStrings the CharSequence prefixes, may be empty or contain {@code null}
    1449     * @see Strings#startsWith(CharSequence, CharSequence)
    1450     * @return {@code true} if the input {@code sequence} is {@code null} AND no {@code searchStrings} are provided, or the input {@code sequence} begins with
    1451     *         any of the provided {@code searchStrings}.
    1452     */
    1453    public boolean startsWithAny(final CharSequence sequence, final CharSequence... searchStrings) {
    1454        if (StringUtils.isEmpty(sequence) || ArrayUtils.isEmpty(searchStrings)) {
    1455            return false;
    1456        }
    1457        for (final CharSequence searchString : searchStrings) {
    1458            if (startsWith(sequence, searchString)) {
    1459                return true;
    1460            }
    1461        }
    1462        return false;
    1463    }
    1464
    1465}