View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.lang3;
19  
20  import static org.apache.commons.lang3.StringUtils.INDEX_NOT_FOUND;
21  
22  import org.apache.commons.lang3.builder.AbstractSupplier;
23  import org.apache.commons.lang3.function.ToBooleanBiFunction;
24  
25  /**
26   * String operations where you choose case-sensitive {@link #CS} vs. case-insensitive {@link #CI} through a singleton instance.
27   *
28   * @see CharSequenceUtils
29   * @see StringUtils
30   * @since 3.18.0
31   */
32  public abstract class Strings {
33  
34      /**
35       * Builds {@link Strings} instances.
36       */
37      public static class Builder extends AbstractSupplier<Strings, Builder, RuntimeException> {
38  
39          /**
40           * Ignores case when possible.
41           */
42          private boolean ignoreCase;
43  
44          /**
45           * Compares null as less when possible.
46           */
47          private boolean nullIsLess;
48  
49          /**
50           * Constructs a new instance.
51           */
52          private Builder() {
53              // empty
54          }
55  
56          /**
57           * Gets a new {@link Strings} instance.
58           */
59          @Override
60          public Strings get() {
61              return ignoreCase ? new CiStrings(nullIsLess) : new CsStrings(nullIsLess);
62          }
63  
64          /**
65           * Sets the ignoreCase property for new Strings instances.
66           *
67           * @param ignoreCase The ignoreCase property for new Strings instances.
68           * @return {@code this} instance.
69           */
70          public Builder setIgnoreCase(final boolean ignoreCase) {
71              this.ignoreCase = ignoreCase;
72              return asThis();
73          }
74  
75          /**
76           * Sets the nullIsLess property for new Strings instances.
77           *
78           * @param nullIsLess The nullIsLess property for new Strings instances.
79           * @return {@code this} instance.
80           */
81          public Builder setNullIsLess(final boolean nullIsLess) {
82              this.nullIsLess = nullIsLess;
83              return asThis();
84          }
85  
86      }
87  
88      /**
89       * Case-insensitive extension.
90       */
91      private static final class CiStrings extends Strings {
92  
93          private CiStrings(final boolean nullIsLess) {
94              super(true, nullIsLess);
95          }
96  
97          @Override
98          public int compare(final String s1, final String s2) {
99              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 }