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) {
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>ensitive 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.compare(null, null)   = 0
461      * Strings.CI.compare(null , "a")   < 0
462      * Strings.CI.compare("a", null)    > 0
463      * Strings.CI.compare("abc", "abc") = 0
464      * Strings.CI.compare("abc", "ABC") = 0
465      * Strings.CI.compare("a", "b")     < 0
466      * Strings.CI.compare("b", "a")     > 0
467      * Strings.CI.compare("a", "B")     < 0
468      * Strings.CI.compare("A", "b")     < 0
469      * Strings.CI.compare("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.contains(null, *)    = false
504      * Strings.CI.contains(*, null)    = false
505      * Strings.CI.contains("", "")     = true
506      * Strings.CI.contains("abc", "")  = true
507      * Strings.CI.contains("abc", "a") = true
508      * Strings.CI.contains("abc", "z") = false
509      * Strings.CI.contains("abc", "A") = true
510      * Strings.CI.contains("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.equals(null, null)   = true
669      * Strings.CI.equals(null, "abc")  = false
670      * Strings.CI.equals("abc", null)  = false
671      * Strings.CI.equals("abc", "abc") = true
672      * Strings.CI.equals("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.equals(null, null)   = true
708      * Strings.CI.equals(null, "abc")  = false
709      * Strings.CI.equals("abc", null)  = false
710      * Strings.CI.equals("abc", "abc") = true
711      * Strings.CI.equals("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") = true
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.indexOf(null, *)          = -1
795      * Strings.CI.indexOf(*, null)          = -1
796      * Strings.CI.indexOf("", "")           = 0
797      * Strings.CI.indexOf(" ", " ")         = 0
798      * Strings.CI.indexOf("aabaabaa", "a")  = 0
799      * Strings.CI.indexOf("aabaabaa", "b")  = 2
800      * Strings.CI.indexOf("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.indexOf(null, *, *)          = -1
843      * Strings.CI.indexOf(*, null, *)          = -1
844      * Strings.CI.indexOf("", "", 0)           = 0
845      * Strings.CI.indexOf("aabaabaa", "A", 0)  = 0
846      * Strings.CI.indexOf("aabaabaa", "B", 0)  = 2
847      * Strings.CI.indexOf("aabaabaa", "AB", 0) = 1
848      * Strings.CI.indexOf("aabaabaa", "B", 3)  = 5
849      * Strings.CI.indexOf("aabaabaa", "B", 9)  = -1
850      * Strings.CI.indexOf("aabaabaa", "B", -1) = 2
851      * Strings.CI.indexOf("aabaabaa", "", 2)   = 2
852      * Strings.CI.indexOf("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.lastIndexOf(null, *)          = -1
906      * Strings.CI.lastIndexOf(*, null)          = -1
907      * Strings.CI.lastIndexOf("aabaabaa", "A")  = 7
908      * Strings.CI.lastIndexOf("aabaabaa", "B")  = 5
909      * Strings.CI.lastIndexOf("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.lastIndexOf(null, *, *)          = -1
956      * Strings.CI.lastIndexOf(*, null, *)          = -1
957      * Strings.CI.lastIndexOf("aabaabaa", "A", 8)  = 7
958      * Strings.CI.lastIndexOf("aabaabaa", "B", 8)  = 5
959      * Strings.CI.lastIndexOf("aabaabaa", "AB", 8) = 4
960      * Strings.CI.lastIndexOf("aabaabaa", "B", 9)  = 5
961      * Strings.CI.lastIndexOf("aabaabaa", "B", -1) = -1
962      * Strings.CI.lastIndexOf("aabaabaa", "A", 0)  = 0
963      * Strings.CI.lastIndexOf("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.prependIfMissing(null, null) = null
1011      * Strings.CI.prependIfMissing("abc", null) = "abc"
1012      * Strings.CI.prependIfMissing("", "xyz") = "xyz"
1013      * Strings.CI.prependIfMissing("abc", "xyz") = "xyzabc"
1014      * Strings.CI.prependIfMissing("xyzabc", "xyz") = "xyzabc"
1015      * Strings.CI.prependIfMissing("XYZabc", "xyz") = "XYZabc"
1016      * </pre>
1017      * <p>
1018      * With additional prefixes,
1019      * </p>
1020      *
1021      * <pre>
1022      * Strings.CI.prependIfMissing(null, null, null) = null
1023      * Strings.CI.prependIfMissing("abc", null, null) = "abc"
1024      * Strings.CI.prependIfMissing("", "xyz", null) = "xyz"
1025      * Strings.CI.prependIfMissing("abc", "xyz", new CharSequence[]{null}) = "xyzabc"
1026      * Strings.CI.prependIfMissing("abc", "xyz", "") = "abc"
1027      * Strings.CI.prependIfMissing("abc", "xyz", "mno") = "xyzabc"
1028      * Strings.CI.prependIfMissing("xyzabc", "xyz", "mno") = "xyzabc"
1029      * Strings.CI.prependIfMissing("mnoabc", "xyz", "mno") = "mnoabc"
1030      * Strings.CI.prependIfMissing("XYZabc", "xyz", "mno") = "XYZabc"
1031      * Strings.CI.prependIfMissing("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.remove(null, *)        = null
1080      * Strings.CI.remove("", *)          = ""
1081      * Strings.CI.remove(*, null)        = *
1082      * Strings.CI.remove(*, "")          = *
1083      * Strings.CI.remove("queued", "ue") = "qd"
1084      * Strings.CI.remove("queued", "zz") = "queued"
1085      * Strings.CI.remove("quEUed", "UE") = "qd"
1086      * Strings.CI.remove("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.removeEnd(null, *)      = null
1124      * Strings.CI.removeEnd("", *)        = ""
1125      * Strings.CI.removeEnd(*, null)      = *
1126      * Strings.CI.removeEnd("www.domain.com", ".com.")  = "www.domain.com"
1127      * Strings.CI.removeEnd("www.domain.com", ".com")   = "www.domain"
1128      * Strings.CI.removeEnd("www.domain.com", "domain") = "www.domain.com"
1129      * Strings.CI.removeEnd("abc", "")    = "abc"
1130      * Strings.CI.removeEnd("www.domain.com", ".COM") = "www.domain")
1131      * Strings.CI.removeEnd("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.removeStart(null, *)      = null
1175      * Strings.CI.removeStart("", *)        = ""
1176      * Strings.CI.removeStart(*, null)      = *
1177      * Strings.CI.removeStart("www.domain.com", "www.")   = "domain.com"
1178      * Strings.CI.removeStart("www.domain.com", "WWW.")   = "domain.com"
1179      * Strings.CI.removeStart("domain.com", "www.")       = "domain.com"
1180      * Strings.CI.removeStart("www.domain.com", "domain") = "www.domain.com"
1181      * Strings.CI.removeStart("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.replace(null, *, *)        = null
1222      * Strings.CI.replace("", *, *)          = ""
1223      * Strings.CI.replace("any", null, *)    = "any"
1224      * Strings.CI.replace("any", *, null)    = "any"
1225      * Strings.CI.replace("any", "", *)      = "any"
1226      * Strings.CI.replace("aba", "a", null)  = "aba"
1227      * Strings.CI.replace("abA", "A", "")    = "b"
1228      * Strings.CI.replace("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.replace(null, *, *, *)         = null
1272      * Strings.CI.replace("", *, *, *)           = ""
1273      * Strings.CI.replace("any", null, *, *)     = "any"
1274      * Strings.CI.replace("any", *, null, *)     = "any"
1275      * Strings.CI.replace("any", "", *, *)       = "any"
1276      * Strings.CI.replace("any", *, *, 0)        = "any"
1277      * Strings.CI.replace("abaa", "a", null, -1) = "abaa"
1278      * Strings.CI.replace("abaa", "a", "", -1)   = "b"
1279      * Strings.CI.replace("abaa", "a", "z", 0)   = "abaa"
1280      * Strings.CI.replace("abaa", "A", "z", 1)   = "zbaa"
1281      * Strings.CI.replace("abAa", "a", "z", 2)   = "zbza"
1282      * Strings.CI.replace("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.replaceOnce(null, *, *)        = null
1347      * Strings.CI.replaceOnce("", *, *)          = ""
1348      * Strings.CI.replaceOnce("any", null, *)    = "any"
1349      * Strings.CI.replaceOnce("any", *, null)    = "any"
1350      * Strings.CI.replaceOnce("any", "", *)      = "any"
1351      * Strings.CI.replaceOnce("aba", "a", null)  = "aba"
1352      * Strings.CI.replaceOnce("aba", "a", "")    = "ba"
1353      * Strings.CI.replaceOnce("aba", "a", "z")   = "zba"
1354      * Strings.CI.replaceOnce("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.startsWith(null, null)      = true
1392      * Strings.CI.startsWith(null, "abc")     = false
1393      * Strings.CI.startsWith("abcdef", null)  = false
1394      * Strings.CI.startsWith("abcdef", "abc") = true
1395      * Strings.CI.startsWith("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 }