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}