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