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 < 0, 0, > 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 ≥ 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 ≤ 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 }