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