001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * https://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.apache.commons.lang3; 019 020import static org.apache.commons.lang3.StringUtils.INDEX_NOT_FOUND; 021 022import org.apache.commons.lang3.builder.AbstractSupplier; 023import org.apache.commons.lang3.function.ToBooleanBiFunction; 024 025/** 026 * String operations where you choose case-sensitive {@link #CS} vs. case-insensitive {@link #CI} through a singleton instance. 027 * 028 * @see CharSequenceUtils 029 * @see StringUtils 030 * @since 3.18.0 031 */ 032public abstract class Strings { 033 034 /** 035 * Builds {@link Strings} instances. 036 */ 037 public static class Builder extends AbstractSupplier<Strings, Builder, RuntimeException> { 038 039 /** 040 * Ignores case when possible. 041 */ 042 private boolean ignoreCase; 043 044 /** 045 * Compares null as less when possible. 046 */ 047 private boolean nullIsLess; 048 049 /** 050 * Constructs a new instance. 051 */ 052 private Builder() { 053 // empty 054 } 055 056 /** 057 * Gets a new {@link Strings} instance. 058 */ 059 @Override 060 public Strings get() { 061 return ignoreCase ? new CiStrings(nullIsLess) : new CsStrings(nullIsLess); 062 } 063 064 /** 065 * Sets the ignoreCase property for new Strings instances. 066 * 067 * @param ignoreCase the ignoreCase property for new Strings instances. 068 * @return this instance. 069 */ 070 public Builder setIgnoreCase(final boolean ignoreCase) { 071 this.ignoreCase = ignoreCase; 072 return asThis(); 073 } 074 075 /** 076 * Sets the nullIsLess property for new Strings instances. 077 * 078 * @param nullIsLess the nullIsLess property for new Strings instances. 079 * @return this instance. 080 */ 081 public Builder setNullIsLess(final boolean nullIsLess) { 082 this.nullIsLess = nullIsLess; 083 return asThis(); 084 } 085 086 } 087 088 /** 089 * Case-insensitive extension. 090 */ 091 private static final class CiStrings extends Strings { 092 093 private CiStrings(final boolean nullIsLess) { 094 super(true, nullIsLess); 095 } 096 097 @Override 098 public int compare(final String s1, final String s2) { 099 if (s1 == s2) { 100 // Both null or same object 101 return 0; 102 } 103 if (s1 == null) { 104 return isNullIsLess() ? -1 : 1; 105 } 106 if (s2 == null) { 107 return isNullIsLess() ? 1 : -1; 108 } 109 return s1.compareToIgnoreCase(s2); 110 } 111 112 @Override 113 public boolean contains(final CharSequence str, final CharSequence searchStr) { 114 if (str == null || searchStr == null) { 115 return false; 116 } 117 final int len = searchStr.length(); 118 final int max = str.length() - len; 119 for (int i = 0; i <= max; i++) { 120 if (CharSequenceUtils.regionMatches(str, true, i, searchStr, 0, len)) { 121 return true; 122 } 123 } 124 return false; 125 } 126 127 @Override 128 public boolean equals(final CharSequence cs1, final CharSequence cs2) { 129 if (cs1 == cs2) { 130 return true; 131 } 132 if (cs1 == null || cs2 == null) { 133 return false; 134 } 135 if (cs1.length() != cs2.length()) { 136 return false; 137 } 138 return CharSequenceUtils.regionMatches(cs1, true, 0, cs2, 0, cs1.length()); 139 } 140 141 @Override 142 public boolean equals(final String s1, final String s2) { 143 return s1 == null ? s2 == null : s1.equalsIgnoreCase(s2); 144 } 145 146 @Override 147 public int indexOf(final CharSequence str, final CharSequence searchStr, int startPos) { 148 if (str == null || searchStr == null) { 149 return INDEX_NOT_FOUND; 150 } 151 if (startPos < 0) { 152 startPos = 0; 153 } 154 final int endLimit = str.length() - searchStr.length() + 1; 155 if (startPos > endLimit) { 156 return INDEX_NOT_FOUND; 157 } 158 if (searchStr.length() == 0) { 159 return startPos; 160 } 161 for (int i = startPos; i < endLimit; i++) { 162 if (CharSequenceUtils.regionMatches(str, true, i, searchStr, 0, searchStr.length())) { 163 return i; 164 } 165 } 166 return INDEX_NOT_FOUND; 167 } 168 169 @Override 170 public int lastIndexOf(final CharSequence str, final CharSequence searchStr, int startPos) { 171 if (str == null || searchStr == null) { 172 return INDEX_NOT_FOUND; 173 } 174 final int searchStrLength = searchStr.length(); 175 final int strLength = str.length(); 176 if (startPos > strLength - searchStrLength) { 177 startPos = strLength - searchStrLength; 178 } 179 if (startPos < 0) { 180 return INDEX_NOT_FOUND; 181 } 182 if (searchStrLength == 0) { 183 return startPos; 184 } 185 for (int i = startPos; i >= 0; i--) { 186 if (CharSequenceUtils.regionMatches(str, true, i, searchStr, 0, searchStrLength)) { 187 return i; 188 } 189 } 190 return INDEX_NOT_FOUND; 191 } 192 193 } 194 195 /** 196 * Case-sentive extension. 197 */ 198 private static final class CsStrings extends Strings { 199 200 private CsStrings(final boolean nullIsLess) { 201 super(false, nullIsLess); 202 } 203 204 @Override 205 public int compare(final String s1, final String s2) { 206 if (s1 == s2) { 207 // Both null or same object 208 return 0; 209 } 210 if (s1 == null) { 211 return isNullIsLess() ? -1 : 1; 212 } 213 if (s2 == null) { 214 return isNullIsLess() ? 1 : -1; 215 } 216 return s1.compareTo(s2); 217 } 218 219 @Override 220 public boolean contains(final CharSequence seq, final CharSequence searchSeq) { 221 return CharSequenceUtils.indexOf(seq, searchSeq, 0) >= 0; 222 } 223 224 @Override 225 public boolean equals(final CharSequence cs1, final CharSequence cs2) { 226 if (cs1 == cs2) { 227 return true; 228 } 229 if (cs1 == null || cs2 == null) { 230 return false; 231 } 232 if (cs1.length() != cs2.length()) { 233 return false; 234 } 235 if (cs1 instanceof String && cs2 instanceof String) { 236 return cs1.equals(cs2); 237 } 238 // Step-wise comparison 239 final int length = cs1.length(); 240 for (int i = 0; i < length; i++) { 241 if (cs1.charAt(i) != cs2.charAt(i)) { 242 return false; 243 } 244 } 245 return true; 246 } 247 248 @Override 249 public boolean equals(final String s1, final String s2) { 250 return eq(s1, s2); 251 } 252 253 @Override 254 public int indexOf(final CharSequence seq, final CharSequence searchSeq, final int startPos) { 255 return CharSequenceUtils.indexOf(seq, searchSeq, startPos); 256 } 257 258 @Override 259 public int lastIndexOf(final CharSequence seq, final CharSequence searchSeq, final int startPos) { 260 return CharSequenceUtils.lastIndexOf(seq, searchSeq, startPos); 261 } 262 263 } 264 265 /** 266 * The <strong>C</strong>ase-<strong>I</strong>nsensitive singleton instance. 267 */ 268 public static final Strings CI = new CiStrings(true); 269 270 /** 271 * The <strong>C</strong>ase-<strong>S</strong>nsensitive singleton instance. 272 */ 273 public static final Strings CS = new CsStrings(true); 274 275 /** 276 * Constructs a new {@link Builder} instance. 277 * 278 * @return a new {@link Builder} instance. 279 */ 280 public static final Builder builder() { 281 return new Builder(); 282 } 283 284 /** 285 * Tests if the CharSequence contains any of the CharSequences in the given array. 286 * 287 * <p> 288 * A {@code null} {@code cs} CharSequence will return {@code false}. A {@code null} or zero length search array will return {@code false}. 289 * </p> 290 * 291 * @param cs The CharSequence to check, may be null 292 * @param searchCharSequences The array of CharSequences to search for, may be null. Individual CharSequences may be null as well. 293 * @return {@code true} if any of the search CharSequences are found, {@code false} otherwise 294 */ 295 private static boolean containsAny(final ToBooleanBiFunction<CharSequence, CharSequence> test, final CharSequence cs, 296 final CharSequence... searchCharSequences) { 297 if (StringUtils.isEmpty(cs) || ArrayUtils.isEmpty(searchCharSequences)) { 298 return false; 299 } 300 for (final CharSequence searchCharSequence : searchCharSequences) { 301 if (test.applyAsBoolean(cs, searchCharSequence)) { 302 return true; 303 } 304 } 305 return false; 306 } 307 308 /** 309 * Tests for equality in a null-safe manner. 310 * 311 * See JDK-8015417. 312 */ 313 private static boolean eq(final Object o1, final Object o2) { 314 return o1 == null ? o2 == null : o1.equals(o2); 315 } 316 317 /** 318 * Ignores case when possible. 319 */ 320 private final boolean ignoreCase; 321 322 /** 323 * Compares null as less when possible. 324 */ 325 private final boolean nullIsLess; 326 327 /** 328 * Constructs a new instance. 329 * 330 * @param ignoreCase Ignores case when possible. 331 * @param nullIsLess Compares null as less when possible. 332 */ 333 private Strings(final boolean ignoreCase, final boolean nullIsLess) { 334 this.ignoreCase = ignoreCase; 335 this.nullIsLess = nullIsLess; 336 } 337 338 /** 339 * Appends the suffix to the end of the string if the string does not already end with the suffix. 340 * 341 * <p> 342 * Case-sensitive examples 343 * </p> 344 * 345 * <pre> 346 * Strings.CS.appendIfMissing(null, null) = null 347 * Strings.CS.appendIfMissing("abc", null) = "abc" 348 * Strings.CS.appendIfMissing("", "xyz" = "xyz" 349 * Strings.CS.appendIfMissing("abc", "xyz") = "abcxyz" 350 * Strings.CS.appendIfMissing("abcxyz", "xyz") = "abcxyz" 351 * Strings.CS.appendIfMissing("abcXYZ", "xyz") = "abcXYZxyz" 352 * </pre> 353 * <p> 354 * With additional suffixes: 355 * </p> 356 * 357 * <pre> 358 * Strings.CS.appendIfMissing(null, null, null) = null 359 * Strings.CS.appendIfMissing("abc", null, null) = "abc" 360 * Strings.CS.appendIfMissing("", "xyz", null) = "xyz" 361 * Strings.CS.appendIfMissing("abc", "xyz", new CharSequence[]{null}) = "abcxyz" 362 * Strings.CS.appendIfMissing("abc", "xyz", "") = "abc" 363 * Strings.CS.appendIfMissing("abc", "xyz", "mno") = "abcxyz" 364 * Strings.CS.appendIfMissing("abcxyz", "xyz", "mno") = "abcxyz" 365 * Strings.CS.appendIfMissing("abcmno", "xyz", "mno") = "abcmno" 366 * Strings.CS.appendIfMissing("abcXYZ", "xyz", "mno") = "abcXYZxyz" 367 * Strings.CS.appendIfMissing("abcMNO", "xyz", "mno") = "abcMNOxyz" 368 * </pre> 369 * 370 * <p> 371 * Case-insensitive examples 372 * </p> 373 * 374 * <pre> 375 * Strings.CI.appendIfMissing(null, null) = null 376 * Strings.CI.appendIfMissing("abc", null) = "abc" 377 * Strings.CI.appendIfMissing("", "xyz") = "xyz" 378 * Strings.CI.appendIfMissing("abc", "xyz") = "abcxyz" 379 * Strings.CI.appendIfMissing("abcxyz", "xyz") = "abcxyz" 380 * Strings.CI.appendIfMissing("abcXYZ", "xyz") = "abcXYZ" 381 * </pre> 382 * <p> 383 * With additional suffixes: 384 * </p> 385 * 386 * <pre> 387 * Strings.CI.appendIfMissing(null, null, null) = null 388 * Strings.CI.appendIfMissing("abc", null, null) = "abc" 389 * Strings.CI.appendIfMissing("", "xyz", null) = "xyz" 390 * Strings.CI.appendIfMissing("abc", "xyz", new CharSequence[]{null}) = "abcxyz" 391 * Strings.CI.appendIfMissing("abc", "xyz", "") = "abc" 392 * Strings.CI.appendIfMissing("abc", "xyz", "mno") = "abcxyz" 393 * Strings.CI.appendIfMissing("abcxyz", "xyz", "mno") = "abcxyz" 394 * Strings.CI.appendIfMissing("abcmno", "xyz", "mno") = "abcmno" 395 * Strings.CI.appendIfMissing("abcXYZ", "xyz", "mno") = "abcXYZ" 396 * Strings.CI.appendIfMissing("abcMNO", "xyz", "mno") = "abcMNO" 397 * </pre> 398 * 399 * @param str The string. 400 * @param suffix The suffix to append to the end of the string. 401 * @param suffixes Additional suffixes that are valid terminators (optional). 402 * @return A new String if suffix was appended, the same string otherwise. 403 */ 404 public String appendIfMissing(final String str, final CharSequence suffix, final CharSequence... suffixes) { 405 if (str == null || StringUtils.isEmpty(suffix) || endsWith(str, suffix)) { 406 return str; 407 } 408 if (ArrayUtils.isNotEmpty(suffixes)) { 409 for (final CharSequence s : suffixes) { 410 if (endsWith(str, s)) { 411 return str; 412 } 413 } 414 } 415 return str + suffix; 416 } 417 418 /** 419 * Compare two Strings lexicographically, like {@link String#compareTo(String)}. 420 * <p> 421 * The return values are: 422 * </p> 423 * <ul> 424 * <li>{@code int = 0}, if {@code str1} is equal to {@code str2} (or both {@code null})</li> 425 * <li>{@code int < 0}, if {@code str1} is less than {@code str2}</li> 426 * <li>{@code int > 0}, if {@code str1} is greater than {@code str2}</li> 427 * </ul> 428 * 429 * <p> 430 * This is a {@code null} safe version of : 431 * </p> 432 * 433 * <pre> 434 * str1.compareTo(str2) 435 * </pre> 436 * 437 * <p> 438 * {@code null} value is considered less than non-{@code null} value. Two {@code null} references are considered equal. 439 * </p> 440 * 441 * <p> 442 * Case-sensitive examples 443 * </p> 444 * 445 * <pre>{@code 446 * Strings.CS.compare(null, null) = 0 447 * Strings.CS.compare(null , "a") < 0 448 * Strings.CS.compare("a", null) > 0 449 * Strings.CS.compare("abc", "abc") = 0 450 * Strings.CS.compare("a", "b") < 0 451 * Strings.CS.compare("b", "a") > 0 452 * Strings.CS.compare("a", "B") > 0 453 * Strings.CS.compare("ab", "abc") < 0 454 * }</pre> 455 * <p> 456 * Case-insensitive examples 457 * </p> 458 * 459 * <pre>{@code 460 * Strings.CI.compareIgnoreCase(null, null) = 0 461 * Strings.CI.compareIgnoreCase(null , "a") < 0 462 * Strings.CI.compareIgnoreCase("a", null) > 0 463 * Strings.CI.compareIgnoreCase("abc", "abc") = 0 464 * Strings.CI.compareIgnoreCase("abc", "ABC") = 0 465 * Strings.CI.compareIgnoreCase("a", "b") < 0 466 * Strings.CI.compareIgnoreCase("b", "a") > 0 467 * Strings.CI.compareIgnoreCase("a", "B") < 0 468 * Strings.CI.compareIgnoreCase("A", "b") < 0 469 * Strings.CI.compareIgnoreCase("ab", "ABC") < 0 470 * }</pre> 471 * 472 * @see String#compareTo(String) 473 * @param str1 the String to compare from 474 * @param str2 the String to compare to 475 * @return < 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.containsIgnoreCase(null, *) = false 504 * Strings.CI.containsIgnoreCase(*, null) = false 505 * Strings.CI.containsIgnoreCase("", "") = true 506 * Strings.CI.containsIgnoreCase("abc", "") = true 507 * Strings.CI.containsIgnoreCase("abc", "a") = true 508 * Strings.CI.containsIgnoreCase("abc", "z") = false 509 * Strings.CI.containsIgnoreCase("abc", "A") = true 510 * Strings.CI.containsIgnoreCase("abc", "Z") = false 511 * </pre> 512 * 513 * @param seq the CharSequence to check, may be null 514 * @param searchSeq the CharSequence to find, may be null 515 * @return true if the CharSequence contains the search CharSequence, false if not or {@code null} string input 516 */ 517 public abstract boolean contains(CharSequence seq, CharSequence searchSeq); 518 519 /** 520 * Tests if the CharSequence contains any of the CharSequences in the given array. 521 * 522 * <p> 523 * A {@code null} {@code cs} CharSequence will return {@code false}. A {@code null} or zero length search array will return {@code false}. 524 * </p> 525 * 526 * <p> 527 * Case-sensitive examples 528 * </p> 529 * 530 * <pre> 531 * Strings.CS.containsAny(null, *) = false 532 * Strings.CS.containsAny("", *) = false 533 * Strings.CS.containsAny(*, null) = false 534 * Strings.CS.containsAny(*, []) = false 535 * Strings.CS.containsAny("abcd", "ab", null) = true 536 * Strings.CS.containsAny("abcd", "ab", "cd") = true 537 * Strings.CS.containsAny("abc", "d", "abc") = true 538 * </pre> 539 * <p> 540 * Case-insensitive examples 541 * </p> 542 * 543 * <pre> 544 * Strings.CI.containsAny(null, *) = false 545 * Strings.CI.containsAny("", *) = false 546 * Strings.CI.containsAny(*, null) = false 547 * Strings.CI.containsAny(*, []) = false 548 * Strings.CI.containsAny("abcd", "ab", null) = true 549 * Strings.CI.containsAny("abcd", "ab", "cd") = true 550 * Strings.CI.containsAny("abc", "d", "abc") = true 551 * Strings.CI.containsAny("abc", "D", "ABC") = true 552 * Strings.CI.containsAny("ABC", "d", "abc") = true 553 * </pre> 554 * 555 * @param cs The CharSequence to check, may be null 556 * @param searchCharSequences The array of CharSequences to search for, may be null. Individual CharSequences may be null as well. 557 * @return {@code true} if any of the search CharSequences are found, {@code false} otherwise 558 */ 559 public boolean containsAny(final CharSequence cs, final CharSequence... searchCharSequences) { 560 return containsAny(this::contains, cs, searchCharSequences); 561 } 562 563 /** 564 * Tests if a CharSequence ends with a specified suffix. 565 * 566 * <p> 567 * Case-sensitive examples 568 * </p> 569 * 570 * <pre> 571 * Strings.CS.endsWith(null, null) = true 572 * Strings.CS.endsWith(null, "def") = false 573 * Strings.CS.endsWith("abcdef", null) = false 574 * Strings.CS.endsWith("abcdef", "def") = true 575 * Strings.CS.endsWith("ABCDEF", "def") = false 576 * Strings.CS.endsWith("ABCDEF", "cde") = false 577 * Strings.CS.endsWith("ABCDEF", "") = true 578 * </pre> 579 * 580 * <p> 581 * Case-insensitive examples 582 * </p> 583 * 584 * <pre> 585 * Strings.CI.endsWith(null, null) = true 586 * Strings.CI.endsWith(null, "def") = false 587 * Strings.CI.endsWith("abcdef", null) = false 588 * Strings.CI.endsWith("abcdef", "def") = true 589 * Strings.CI.endsWith("ABCDEF", "def") = true 590 * Strings.CI.endsWith("ABCDEF", "cde") = false 591 * </pre> 592 * 593 * @param str the CharSequence to check, may be null. 594 * @param suffix the suffix to find, may be null. 595 * @return {@code true} if the CharSequence starts with the prefix or both {@code null}. 596 * @see String#endsWith(String) 597 */ 598 public boolean endsWith(final CharSequence str, final CharSequence suffix) { 599 if (str == null || suffix == null) { 600 return str == suffix; 601 } 602 final int sufLen = suffix.length(); 603 if (sufLen > str.length()) { 604 return false; 605 } 606 return CharSequenceUtils.regionMatches(str, ignoreCase, str.length() - sufLen, suffix, 0, sufLen); 607 } 608 609 /** 610 * Tests if a CharSequence ends with any of the provided suffixes. 611 * 612 * <p> 613 * Case-sensitive examples 614 * </p> 615 * 616 * <pre> 617 * Strings.CS.endsWithAny(null, null) = false 618 * Strings.CS.endsWithAny(null, new String[] {"abc"}) = false 619 * Strings.CS.endsWithAny("abcxyz", null) = false 620 * Strings.CS.endsWithAny("abcxyz", new String[] {""}) = true 621 * Strings.CS.endsWithAny("abcxyz", new String[] {"xyz"}) = true 622 * Strings.CS.endsWithAny("abcxyz", new String[] {null, "xyz", "abc"}) = true 623 * Strings.CS.endsWithAny("abcXYZ", "def", "XYZ") = true 624 * Strings.CS.endsWithAny("abcXYZ", "def", "xyz") = false 625 * </pre> 626 * 627 * @param sequence the CharSequence to check, may be null 628 * @param searchStrings the CharSequence suffixes to find, may be empty or contain {@code null} 629 * @see Strings#endsWith(CharSequence, CharSequence) 630 * @return {@code true} if the input {@code sequence} is {@code null} AND no {@code searchStrings} are provided, or the input {@code sequence} ends in any 631 * of the provided {@code searchStrings}. 632 */ 633 public boolean endsWithAny(final CharSequence sequence, final CharSequence... searchStrings) { 634 if (StringUtils.isEmpty(sequence) || ArrayUtils.isEmpty(searchStrings)) { 635 return false; 636 } 637 for (final CharSequence searchString : searchStrings) { 638 if (endsWith(sequence, searchString)) { 639 return true; 640 } 641 } 642 return false; 643 } 644 645 /** 646 * Compares two CharSequences, returning {@code true} if they represent equal sequences of characters. 647 * 648 * <p> 649 * {@code null}s are handled without exceptions. Two {@code null} references are considered to be equal. 650 * </p> 651 * 652 * <p> 653 * Case-sensitive examples 654 * </p> 655 * 656 * <pre> 657 * Strings.CS.equals(null, null) = true 658 * Strings.CS.equals(null, "abc") = false 659 * Strings.CS.equals("abc", null) = false 660 * Strings.CS.equals("abc", "abc") = true 661 * Strings.CS.equals("abc", "ABC") = false 662 * </pre> 663 * <p> 664 * Case-insensitive examples 665 * </p> 666 * 667 * <pre> 668 * Strings.CI.equalsIgnoreCase(null, null) = true 669 * Strings.CI.equalsIgnoreCase(null, "abc") = false 670 * Strings.CI.equalsIgnoreCase("abc", null) = false 671 * Strings.CI.equalsIgnoreCase("abc", "abc") = true 672 * Strings.CI.equalsIgnoreCase("abc", "ABC") = true 673 * </pre> 674 * 675 * @param cs1 the first CharSequence, may be {@code null} 676 * @param cs2 the second CharSequence, may be {@code null} 677 * @return {@code true} if the CharSequences are equal (case-sensitive), or both {@code null} 678 * @see Object#equals(Object) 679 * @see String#compareTo(String) 680 * @see String#equalsIgnoreCase(String) 681 */ 682 public abstract boolean equals(CharSequence cs1, CharSequence cs2); 683 684 /** 685 * Compares two CharSequences, returning {@code true} if they represent equal sequences of characters. 686 * 687 * <p> 688 * {@code null}s are handled without exceptions. Two {@code null} references are considered to be equal. 689 * </p> 690 * 691 * <p> 692 * Case-sensitive examples 693 * </p> 694 * 695 * <pre> 696 * Strings.CS.equals(null, null) = true 697 * Strings.CS.equals(null, "abc") = false 698 * Strings.CS.equals("abc", null) = false 699 * Strings.CS.equals("abc", "abc") = true 700 * Strings.CS.equals("abc", "ABC") = false 701 * </pre> 702 * <p> 703 * Case-insensitive examples 704 * </p> 705 * 706 * <pre> 707 * Strings.CI.equalsIgnoreCase(null, null) = true 708 * Strings.CI.equalsIgnoreCase(null, "abc") = false 709 * Strings.CI.equalsIgnoreCase("abc", null) = false 710 * Strings.CI.equalsIgnoreCase("abc", "abc") = true 711 * Strings.CI.equalsIgnoreCase("abc", "ABC") = true 712 * </pre> 713 * 714 * @param str1 the first CharSequence, may be {@code null} 715 * @param str2 the second CharSequence, may be {@code null} 716 * @return {@code true} if the CharSequences are equal (case-sensitive), or both {@code null} 717 * @see Object#equals(Object) 718 * @see String#compareTo(String) 719 * @see String#equalsIgnoreCase(String) 720 */ 721 public abstract boolean equals(String str1, String str2); 722 723 /** 724 * Compares given {@code string} to a CharSequences vararg of {@code searchStrings}, returning {@code true} if the {@code string} is equal to any of the 725 * {@code searchStrings}. 726 * 727 * <p> 728 * Case-sensitive examples 729 * </p> 730 * 731 * <pre> 732 * Strings.CS.equalsAny(null, (CharSequence[]) null) = false 733 * Strings.CS.equalsAny(null, null, null) = true 734 * Strings.CS.equalsAny(null, "abc", "def") = false 735 * Strings.CS.equalsAny("abc", null, "def") = false 736 * Strings.CS.equalsAny("abc", "abc", "def") = true 737 * Strings.CS.equalsAny("abc", "ABC", "DEF") = false 738 * </pre> 739 * <p> 740 * Case-insensitive examples 741 * </p> 742 * 743 * <pre> 744 * Strings.CI.equalsAny(null, (CharSequence[]) null) = false 745 * Strings.CI.equalsAny(null, null, null) = true 746 * Strings.CI.equalsAny(null, "abc", "def") = false 747 * Strings.CI.equalsAny("abc", null, "def") = false 748 * Strings.CI.equalsAny("abc", "abc", "def") = true 749 * Strings.CI.equalsAny("abc", "ABC", "DEF") = false 750 * </pre> 751 * 752 * @param string to compare, may be {@code null}. 753 * @param searchStrings a vararg of strings, may be {@code null}. 754 * @return {@code true} if the string is equal (case-sensitive) to any other element of {@code searchStrings}; {@code false} if {@code searchStrings} is 755 * null or contains no matches. 756 */ 757 public boolean equalsAny(final CharSequence string, final CharSequence... searchStrings) { 758 if (ArrayUtils.isNotEmpty(searchStrings)) { 759 for (final CharSequence next : searchStrings) { 760 if (equals(string, next)) { 761 return true; 762 } 763 } 764 } 765 return false; 766 } 767 768 /** 769 * Finds the first index within a CharSequence, handling {@code null}. This method uses {@link String#indexOf(String, int)} if possible. 770 * 771 * <p> 772 * A {@code null} CharSequence will return {@code -1}. 773 * </p> 774 * 775 * <p> 776 * Case-sensitive examples 777 * </p> 778 * 779 * <pre> 780 * Strings.CS.indexOf(null, *) = -1 781 * Strings.CS.indexOf(*, null) = -1 782 * Strings.CS.indexOf("", "") = 0 783 * Strings.CS.indexOf("", *) = -1 (except when * = "") 784 * Strings.CS.indexOf("aabaabaa", "a") = 0 785 * Strings.CS.indexOf("aabaabaa", "b") = 2 786 * Strings.CS.indexOf("aabaabaa", "ab") = 1 787 * Strings.CS.indexOf("aabaabaa", "") = 0 788 * </pre> 789 * <p> 790 * Case-insensitive examples 791 * </p> 792 * 793 * <pre> 794 * Strings.CI.indexOfIgnoreCase(null, *) = -1 795 * Strings.CI.indexOfIgnoreCase(*, null) = -1 796 * Strings.CI.indexOfIgnoreCase("", "") = 0 797 * Strings.CI.indexOfIgnoreCase(" ", " ") = 0 798 * Strings.CI.indexOfIgnoreCase("aabaabaa", "a") = 0 799 * Strings.CI.indexOfIgnoreCase("aabaabaa", "b") = 2 800 * Strings.CI.indexOfIgnoreCase("aabaabaa", "ab") = 1 801 * </pre> 802 * 803 * @param seq the CharSequence to check, may be null 804 * @param searchSeq the CharSequence to find, may be null 805 * @return the first index of the search CharSequence, -1 if no match or {@code null} string input 806 */ 807 public int indexOf(final CharSequence seq, final CharSequence searchSeq) { 808 return indexOf(seq, searchSeq, 0); 809 } 810 811 /** 812 * Finds the first index within a CharSequence, handling {@code null}. This method uses {@link String#indexOf(String, int)} if possible. 813 * 814 * <p> 815 * A {@code null} CharSequence will return {@code -1}. A negative start position is treated as zero. An empty ("") search CharSequence always matches. A 816 * start position greater than the string length only matches an empty search CharSequence. 817 * </p> 818 * 819 * <p> 820 * Case-sensitive examples 821 * </p> 822 * 823 * <pre> 824 * Strings.CS.indexOf(null, *, *) = -1 825 * Strings.CS.indexOf(*, null, *) = -1 826 * Strings.CS.indexOf("", "", 0) = 0 827 * Strings.CS.indexOf("", *, 0) = -1 (except when * = "") 828 * Strings.CS.indexOf("aabaabaa", "a", 0) = 0 829 * Strings.CS.indexOf("aabaabaa", "b", 0) = 2 830 * Strings.CS.indexOf("aabaabaa", "ab", 0) = 1 831 * Strings.CS.indexOf("aabaabaa", "b", 3) = 5 832 * Strings.CS.indexOf("aabaabaa", "b", 9) = -1 833 * Strings.CS.indexOf("aabaabaa", "b", -1) = 2 834 * Strings.CS.indexOf("aabaabaa", "", 2) = 2 835 * Strings.CS.indexOf("abc", "", 9) = 3 836 * </pre> 837 * <p> 838 * Case-insensitive examples 839 * </p> 840 * 841 * <pre> 842 * Strings.CI.indexOfIgnoreCase(null, *, *) = -1 843 * Strings.CI.indexOfIgnoreCase(*, null, *) = -1 844 * Strings.CI.indexOfIgnoreCase("", "", 0) = 0 845 * Strings.CI.indexOfIgnoreCase("aabaabaa", "A", 0) = 0 846 * Strings.CI.indexOfIgnoreCase("aabaabaa", "B", 0) = 2 847 * Strings.CI.indexOfIgnoreCase("aabaabaa", "AB", 0) = 1 848 * Strings.CI.indexOfIgnoreCase("aabaabaa", "B", 3) = 5 849 * Strings.CI.indexOfIgnoreCase("aabaabaa", "B", 9) = -1 850 * Strings.CI.indexOfIgnoreCase("aabaabaa", "B", -1) = 2 851 * Strings.CI.indexOfIgnoreCase("aabaabaa", "", 2) = 2 852 * Strings.CI.indexOfIgnoreCase("abc", "", 9) = -1 853 * </pre> 854 * 855 * @param seq the CharSequence to check, may be null 856 * @param searchSeq the CharSequence to find, may be null 857 * @param startPos the start position, negative treated as zero 858 * @return the first index of the search CharSequence (always ≥ startPos), -1 if no match or {@code null} string input 859 */ 860 public abstract int indexOf(CharSequence seq, CharSequence searchSeq, int startPos); 861 862 /** 863 * Tests whether to ignore case. 864 * 865 * @return whether to ignore case. 866 */ 867 public boolean isCaseSensitive() { 868 return !ignoreCase; 869 } 870 871 /** 872 * Tests whether null is less when comparing. 873 * 874 * @return whether null is less when comparing. 875 */ 876 boolean isNullIsLess() { 877 return nullIsLess; 878 } 879 880 /** 881 * Finds the last index within a CharSequence, handling {@code null}. This method uses {@link String#lastIndexOf(String)} if possible. 882 * 883 * <p> 884 * A {@code null} CharSequence will return {@code -1}. 885 * </p> 886 * 887 * <p> 888 * Case-sensitive examples 889 * </p> 890 * 891 * <pre> 892 * Strings.CS.lastIndexOf(null, *) = -1 893 * Strings.CS.lastIndexOf(*, null) = -1 894 * Strings.CS.lastIndexOf("", "") = 0 895 * Strings.CS.lastIndexOf("aabaabaa", "a") = 7 896 * Strings.CS.lastIndexOf("aabaabaa", "b") = 5 897 * Strings.CS.lastIndexOf("aabaabaa", "ab") = 4 898 * Strings.CS.lastIndexOf("aabaabaa", "") = 8 899 * </pre> 900 * <p> 901 * Case-insensitive examples 902 * </p> 903 * 904 * <pre> 905 * Strings.CI.lastIndexOfIgnoreCase(null, *) = -1 906 * Strings.CI.lastIndexOfIgnoreCase(*, null) = -1 907 * Strings.CI.lastIndexOfIgnoreCase("aabaabaa", "A") = 7 908 * Strings.CI.lastIndexOfIgnoreCase("aabaabaa", "B") = 5 909 * Strings.CI.lastIndexOfIgnoreCase("aabaabaa", "AB") = 4 910 * </pre> 911 * 912 * @param str the CharSequence to check, may be null 913 * @param searchStr the CharSequence to find, may be null 914 * @return the last index of the search String, -1 if no match or {@code null} string input 915 */ 916 public int lastIndexOf(final CharSequence str, final CharSequence searchStr) { 917 if (str == null) { 918 return INDEX_NOT_FOUND; 919 } 920 return lastIndexOf(str, searchStr, str.length()); 921 } 922 923 /** 924 * Finds the last index within a CharSequence, handling {@code null}. This method uses {@link String#lastIndexOf(String, int)} if possible. 925 * 926 * <p> 927 * A {@code null} CharSequence will return {@code -1}. A negative start position returns {@code -1}. An empty ("") search CharSequence always matches unless 928 * the start position is negative. A start position greater than the string length searches the whole string. The search starts at the startPos and works 929 * backwards; matches starting after the start position are ignored. 930 * </p> 931 * 932 * <p> 933 * Case-sensitive examples 934 * </p> 935 * 936 * <pre> 937 * Strings.CS.lastIndexOf(null, *, *) = -1 938 * Strings.CS.lastIndexOf(*, null, *) = -1 939 * Strings.CS.lastIndexOf("aabaabaa", "a", 8) = 7 940 * Strings.CS.lastIndexOf("aabaabaa", "b", 8) = 5 941 * Strings.CS.lastIndexOf("aabaabaa", "ab", 8) = 4 942 * Strings.CS.lastIndexOf("aabaabaa", "b", 9) = 5 943 * Strings.CS.lastIndexOf("aabaabaa", "b", -1) = -1 944 * Strings.CS.lastIndexOf("aabaabaa", "a", 0) = 0 945 * Strings.CS.lastIndexOf("aabaabaa", "b", 0) = -1 946 * Strings.CS.lastIndexOf("aabaabaa", "b", 1) = -1 947 * Strings.CS.lastIndexOf("aabaabaa", "b", 2) = 2 948 * Strings.CS.lastIndexOf("aabaabaa", "ba", 2) = 2 949 * </pre> 950 * <p> 951 * Case-insensitive examples 952 * </p> 953 * 954 * <pre> 955 * Strings.CI.lastIndexOfIgnoreCase(null, *, *) = -1 956 * Strings.CI.lastIndexOfIgnoreCase(*, null, *) = -1 957 * Strings.CI.lastIndexOfIgnoreCase("aabaabaa", "A", 8) = 7 958 * Strings.CI.lastIndexOfIgnoreCase("aabaabaa", "B", 8) = 5 959 * Strings.CI.lastIndexOfIgnoreCase("aabaabaa", "AB", 8) = 4 960 * Strings.CI.lastIndexOfIgnoreCase("aabaabaa", "B", 9) = 5 961 * Strings.CI.lastIndexOfIgnoreCase("aabaabaa", "B", -1) = -1 962 * Strings.CI.lastIndexOfIgnoreCase("aabaabaa", "A", 0) = 0 963 * Strings.CI.lastIndexOfIgnoreCase("aabaabaa", "B", 0) = -1 964 * </pre> 965 * 966 * @param seq the CharSequence to check, may be null 967 * @param searchSeq the CharSequence to find, may be null 968 * @param startPos the start position, negative treated as zero 969 * @return the last index of the search CharSequence (always ≤ startPos), -1 if no match or {@code null} string input 970 */ 971 public abstract int lastIndexOf(CharSequence seq, CharSequence searchSeq, int startPos); 972 973 /** 974 * Prepends the prefix to the start of the string if the string does not already start with any of the prefixes. 975 * 976 * <p> 977 * Case-sensitive examples 978 * </p> 979 * 980 * <pre> 981 * Strings.CS.prependIfMissing(null, null) = null 982 * Strings.CS.prependIfMissing("abc", null) = "abc" 983 * Strings.CS.prependIfMissing("", "xyz") = "xyz" 984 * Strings.CS.prependIfMissing("abc", "xyz") = "xyzabc" 985 * Strings.CS.prependIfMissing("xyzabc", "xyz") = "xyzabc" 986 * Strings.CS.prependIfMissing("XYZabc", "xyz") = "xyzXYZabc" 987 * </pre> 988 * <p> 989 * With additional prefixes, 990 * </p> 991 * 992 * <pre> 993 * Strings.CS.prependIfMissing(null, null, null) = null 994 * Strings.CS.prependIfMissing("abc", null, null) = "abc" 995 * Strings.CS.prependIfMissing("", "xyz", null) = "xyz" 996 * Strings.CS.prependIfMissing("abc", "xyz", new CharSequence[]{null}) = "xyzabc" 997 * Strings.CS.prependIfMissing("abc", "xyz", "") = "abc" 998 * Strings.CS.prependIfMissing("abc", "xyz", "mno") = "xyzabc" 999 * Strings.CS.prependIfMissing("xyzabc", "xyz", "mno") = "xyzabc" 1000 * Strings.CS.prependIfMissing("mnoabc", "xyz", "mno") = "mnoabc" 1001 * Strings.CS.prependIfMissing("XYZabc", "xyz", "mno") = "xyzXYZabc" 1002 * Strings.CS.prependIfMissing("MNOabc", "xyz", "mno") = "xyzMNOabc" 1003 * </pre> 1004 * 1005 * <p> 1006 * Case-insensitive examples 1007 * </p> 1008 * 1009 * <pre> 1010 * Strings.CI.prependIfMissingIgnoreCase(null, null) = null 1011 * Strings.CI.prependIfMissingIgnoreCase("abc", null) = "abc" 1012 * Strings.CI.prependIfMissingIgnoreCase("", "xyz") = "xyz" 1013 * Strings.CI.prependIfMissingIgnoreCase("abc", "xyz") = "xyzabc" 1014 * Strings.CI.prependIfMissingIgnoreCase("xyzabc", "xyz") = "xyzabc" 1015 * Strings.CI.prependIfMissingIgnoreCase("XYZabc", "xyz") = "XYZabc" 1016 * </pre> 1017 * <p> 1018 * With additional prefixes, 1019 * </p> 1020 * 1021 * <pre> 1022 * Strings.CI.prependIfMissingIgnoreCase(null, null, null) = null 1023 * Strings.CI.prependIfMissingIgnoreCase("abc", null, null) = "abc" 1024 * Strings.CI.prependIfMissingIgnoreCase("", "xyz", null) = "xyz" 1025 * Strings.CI.prependIfMissingIgnoreCase("abc", "xyz", new CharSequence[]{null}) = "xyzabc" 1026 * Strings.CI.prependIfMissingIgnoreCase("abc", "xyz", "") = "abc" 1027 * Strings.CI.prependIfMissingIgnoreCase("abc", "xyz", "mno") = "xyzabc" 1028 * Strings.CI.prependIfMissingIgnoreCase("xyzabc", "xyz", "mno") = "xyzabc" 1029 * Strings.CI.prependIfMissingIgnoreCase("mnoabc", "xyz", "mno") = "mnoabc" 1030 * Strings.CI.prependIfMissingIgnoreCase("XYZabc", "xyz", "mno") = "XYZabc" 1031 * Strings.CI.prependIfMissingIgnoreCase("MNOabc", "xyz", "mno") = "MNOabc" 1032 * </pre> 1033 * 1034 * @param str The string. 1035 * @param prefix The prefix to prepend to the start of the string. 1036 * @param prefixes Additional prefixes that are valid. 1037 * @return A new String if prefix was prepended, the same string otherwise. 1038 */ 1039 public String prependIfMissing(final String str, final CharSequence prefix, final CharSequence... prefixes) { 1040 if (str == null || StringUtils.isEmpty(prefix) || startsWith(str, prefix)) { 1041 return str; 1042 } 1043 if (ArrayUtils.isNotEmpty(prefixes)) { 1044 for (final CharSequence p : prefixes) { 1045 if (startsWith(str, p)) { 1046 return str; 1047 } 1048 } 1049 } 1050 return prefix + str; 1051 } 1052 1053 /** 1054 * Removes all occurrences of a substring from within the source string. 1055 * 1056 * <p> 1057 * A {@code null} source string will return {@code null}. An empty ("") source string will return the empty string. A {@code null} remove string will return 1058 * the source string. An empty ("") remove string will return the source string. 1059 * </p> 1060 * 1061 * <p> 1062 * Case-sensitive examples 1063 * </p> 1064 * 1065 * <pre> 1066 * Strings.CS.remove(null, *) = null 1067 * Strings.CS.remove("", *) = "" 1068 * Strings.CS.remove(*, null) = * 1069 * Strings.CS.remove(*, "") = * 1070 * Strings.CS.remove("queued", "ue") = "qd" 1071 * Strings.CS.remove("queued", "zz") = "queued" 1072 * </pre> 1073 * 1074 * <p> 1075 * Case-insensitive examples 1076 * </p> 1077 * 1078 * <pre> 1079 * Strings.CI.removeIgnoreCase(null, *) = null 1080 * Strings.CI.removeIgnoreCase("", *) = "" 1081 * Strings.CI.removeIgnoreCase(*, null) = * 1082 * Strings.CI.removeIgnoreCase(*, "") = * 1083 * Strings.CI.removeIgnoreCase("queued", "ue") = "qd" 1084 * Strings.CI.removeIgnoreCase("queued", "zz") = "queued" 1085 * Strings.CI.removeIgnoreCase("quEUed", "UE") = "qd" 1086 * Strings.CI.removeIgnoreCase("queued", "zZ") = "queued" 1087 * </pre> 1088 * 1089 * @param str the source String to search, may be null 1090 * @param remove the String to search for and remove, may be null 1091 * @return the substring with the string removed if found, {@code null} if null String input 1092 */ 1093 public String remove(final String str, final String remove) { 1094 return replace(str, remove, StringUtils.EMPTY, -1); 1095 } 1096 1097 /** 1098 * Case-insensitive removal of a substring if it is at the end of a source string, otherwise returns the source string. 1099 * 1100 * <p> 1101 * A {@code null} source string will return {@code null}. An empty ("") source string will return the empty string. A {@code null} search string will return 1102 * the source string. 1103 * </p> 1104 * 1105 * <p> 1106 * Case-sensitive examples 1107 * </p> 1108 * 1109 * <pre> 1110 * Strings.CS.removeEnd(null, *) = null 1111 * Strings.CS.removeEnd("", *) = "" 1112 * Strings.CS.removeEnd(*, null) = * 1113 * Strings.CS.removeEnd("www.domain.com", ".com.") = "www.domain.com" 1114 * Strings.CS.removeEnd("www.domain.com", ".com") = "www.domain" 1115 * Strings.CS.removeEnd("www.domain.com", "domain") = "www.domain.com" 1116 * Strings.CS.removeEnd("abc", "") = "abc" 1117 * </pre> 1118 * <p> 1119 * Case-insensitive examples 1120 * </p> 1121 * 1122 * <pre> 1123 * Strings.CI.removeEndIgnoreCase(null, *) = null 1124 * Strings.CI.removeEndIgnoreCase("", *) = "" 1125 * Strings.CI.removeEndIgnoreCase(*, null) = * 1126 * Strings.CI.removeEndIgnoreCase("www.domain.com", ".com.") = "www.domain.com" 1127 * Strings.CI.removeEndIgnoreCase("www.domain.com", ".com") = "www.domain" 1128 * Strings.CI.removeEndIgnoreCase("www.domain.com", "domain") = "www.domain.com" 1129 * Strings.CI.removeEndIgnoreCase("abc", "") = "abc" 1130 * Strings.CI.removeEndIgnoreCase("www.domain.com", ".COM") = "www.domain") 1131 * Strings.CI.removeEndIgnoreCase("www.domain.COM", ".com") = "www.domain") 1132 * </pre> 1133 * 1134 * @param str the source String to search, may be null 1135 * @param remove the String to search for (case-insensitive) and remove, may be null 1136 * @return the substring with the string removed if found, {@code null} if null String input 1137 */ 1138 public String removeEnd(final String str, final CharSequence remove) { 1139 if (StringUtils.isEmpty(str) || StringUtils.isEmpty(remove)) { 1140 return str; 1141 } 1142 if (endsWith(str, remove)) { 1143 return str.substring(0, str.length() - remove.length()); 1144 } 1145 return str; 1146 } 1147 1148 /** 1149 * Case-insensitive removal of a substring if it is at the beginning of a source string, otherwise returns the source string. 1150 * 1151 * <p> 1152 * A {@code null} source string will return {@code null}. An empty ("") source string will return the empty string. A {@code null} search string will return 1153 * the source string. 1154 * </p> 1155 * 1156 * <p> 1157 * Case-sensitive examples 1158 * </p> 1159 * 1160 * <pre> 1161 * Strings.CS.removeStart(null, *) = null 1162 * Strings.CS.removeStart("", *) = "" 1163 * Strings.CS.removeStart(*, null) = * 1164 * Strings.CS.removeStart("www.domain.com", "www.") = "domain.com" 1165 * Strings.CS.removeStart("domain.com", "www.") = "domain.com" 1166 * Strings.CS.removeStart("www.domain.com", "domain") = "www.domain.com" 1167 * Strings.CS.removeStart("abc", "") = "abc" 1168 * </pre> 1169 * <p> 1170 * Case-insensitive examples 1171 * </p> 1172 * 1173 * <pre> 1174 * Strings.CI.removeStartIgnoreCase(null, *) = null 1175 * Strings.CI.removeStartIgnoreCase("", *) = "" 1176 * Strings.CI.removeStartIgnoreCase(*, null) = * 1177 * Strings.CI.removeStartIgnoreCase("www.domain.com", "www.") = "domain.com" 1178 * Strings.CI.removeStartIgnoreCase("www.domain.com", "WWW.") = "domain.com" 1179 * Strings.CI.removeStartIgnoreCase("domain.com", "www.") = "domain.com" 1180 * Strings.CI.removeStartIgnoreCase("www.domain.com", "domain") = "www.domain.com" 1181 * Strings.CI.removeStartIgnoreCase("abc", "") = "abc" 1182 * </pre> 1183 * 1184 * @param str the source String to search, may be null 1185 * @param remove the String to search for (case-insensitive) and remove, may be null 1186 * @return the substring with the string removed if found, {@code null} if null String input 1187 */ 1188 public String removeStart(final String str, final CharSequence remove) { 1189 if (str != null && startsWith(str, remove)) { 1190 return str.substring(StringUtils.length(remove)); 1191 } 1192 return str; 1193 } 1194 1195 /** 1196 * Case insensitively replaces all occurrences of a String within another String. 1197 * 1198 * <p> 1199 * A {@code null} reference passed to this method is a no-op. 1200 * </p> 1201 * 1202 * <p> 1203 * Case-sensitive examples 1204 * </p> 1205 * 1206 * <pre> 1207 * Strings.CS.replace(null, *, *) = null 1208 * Strings.CS.replace("", *, *) = "" 1209 * Strings.CS.replace("any", null, *) = "any" 1210 * Strings.CS.replace("any", *, null) = "any" 1211 * Strings.CS.replace("any", "", *) = "any" 1212 * Strings.CS.replace("aba", "a", null) = "aba" 1213 * Strings.CS.replace("aba", "a", "") = "b" 1214 * Strings.CS.replace("aba", "a", "z") = "zbz" 1215 * </pre> 1216 * <p> 1217 * Case-insensitive examples 1218 * </p> 1219 * 1220 * <pre> 1221 * Strings.CI.replaceIgnoreCase(null, *, *) = null 1222 * Strings.CI.replaceIgnoreCase("", *, *) = "" 1223 * Strings.CI.replaceIgnoreCase("any", null, *) = "any" 1224 * Strings.CI.replaceIgnoreCase("any", *, null) = "any" 1225 * Strings.CI.replaceIgnoreCase("any", "", *) = "any" 1226 * Strings.CI.replaceIgnoreCase("aba", "a", null) = "aba" 1227 * Strings.CI.replaceIgnoreCase("abA", "A", "") = "b" 1228 * Strings.CI.replaceIgnoreCase("aba", "A", "z") = "zbz" 1229 * </pre> 1230 * 1231 * @see #replace(String text, String searchString, String replacement, int max) 1232 * @param text text to search and replace in, may be null 1233 * @param searchString the String to search for (case-insensitive), may be null 1234 * @param replacement the String to replace it with, may be null 1235 * @return the text with any replacements processed, {@code null} if null String input 1236 */ 1237 public String replace(final String text, final String searchString, final String replacement) { 1238 return replace(text, searchString, replacement, -1); 1239 } 1240 1241 /** 1242 * Replaces a String with another String inside a larger String, for the first {@code max} values of the search String. 1243 * 1244 * <p> 1245 * A {@code null} reference passed to this method is a no-op. 1246 * </p> 1247 * 1248 * <p> 1249 * Case-sensitive examples 1250 * </p> 1251 * 1252 * <pre> 1253 * Strings.CS.replace(null, *, *, *) = null 1254 * Strings.CS.replace("", *, *, *) = "" 1255 * Strings.CS.replace("any", null, *, *) = "any" 1256 * Strings.CS.replace("any", *, null, *) = "any" 1257 * Strings.CS.replace("any", "", *, *) = "any" 1258 * Strings.CS.replace("any", *, *, 0) = "any" 1259 * Strings.CS.replace("abaa", "a", null, -1) = "abaa" 1260 * Strings.CS.replace("abaa", "a", "", -1) = "b" 1261 * Strings.CS.replace("abaa", "a", "z", 0) = "abaa" 1262 * Strings.CS.replace("abaa", "a", "z", 1) = "zbaa" 1263 * Strings.CS.replace("abaa", "a", "z", 2) = "zbza" 1264 * Strings.CS.replace("abaa", "a", "z", -1) = "zbzz" 1265 * </pre> 1266 * <p> 1267 * Case-insensitive examples 1268 * </p> 1269 * 1270 * <pre> 1271 * Strings.CI.replaceIgnoreCase(null, *, *, *) = null 1272 * Strings.CI.replaceIgnoreCase("", *, *, *) = "" 1273 * Strings.CI.replaceIgnoreCase("any", null, *, *) = "any" 1274 * Strings.CI.replaceIgnoreCase("any", *, null, *) = "any" 1275 * Strings.CI.replaceIgnoreCase("any", "", *, *) = "any" 1276 * Strings.CI.replaceIgnoreCase("any", *, *, 0) = "any" 1277 * Strings.CI.replaceIgnoreCase("abaa", "a", null, -1) = "abaa" 1278 * Strings.CI.replaceIgnoreCase("abaa", "a", "", -1) = "b" 1279 * Strings.CI.replaceIgnoreCase("abaa", "a", "z", 0) = "abaa" 1280 * Strings.CI.replaceIgnoreCase("abaa", "A", "z", 1) = "zbaa" 1281 * Strings.CI.replaceIgnoreCase("abAa", "a", "z", 2) = "zbza" 1282 * Strings.CI.replaceIgnoreCase("abAa", "a", "z", -1) = "zbzz" 1283 * </pre> 1284 * 1285 * @param text text to search and replace in, may be null 1286 * @param searchString the String to search for (case-insensitive), may be null 1287 * @param replacement the String to replace it with, may be null 1288 * @param max maximum number of values to replace, or {@code -1} if no maximum 1289 * @return the text with any replacements processed, {@code null} if null String input 1290 */ 1291 public String replace(final String text, String searchString, final String replacement, int max) { 1292 if (StringUtils.isEmpty(text) || StringUtils.isEmpty(searchString) || replacement == null || max == 0) { 1293 return text; 1294 } 1295 if (ignoreCase) { 1296 searchString = searchString.toLowerCase(); 1297 } 1298 int start = 0; 1299 int end = indexOf(text, searchString, start); 1300 if (end == INDEX_NOT_FOUND) { 1301 return text; 1302 } 1303 final int replLength = searchString.length(); 1304 int increase = Math.max(replacement.length() - replLength, 0); 1305 increase *= max < 0 ? 16 : Math.min(max, 64); 1306 final StringBuilder buf = new StringBuilder(text.length() + increase); 1307 while (end != INDEX_NOT_FOUND) { 1308 buf.append(text, start, end).append(replacement); 1309 start = end + replLength; 1310 if (--max == 0) { 1311 break; 1312 } 1313 end = indexOf(text, searchString, start); 1314 } 1315 buf.append(text, start, text.length()); 1316 return buf.toString(); 1317 } 1318 1319 /** 1320 * Replaces a String with another String inside a larger String, once. 1321 * 1322 * <p> 1323 * A {@code null} reference passed to this method is a no-op. 1324 * </p> 1325 * 1326 * <p> 1327 * Case-sensitive examples 1328 * </p> 1329 * 1330 * <pre> 1331 * Strings.CS.replaceOnce(null, *, *) = null 1332 * Strings.CS.replaceOnce("", *, *) = "" 1333 * Strings.CS.replaceOnce("any", null, *) = "any" 1334 * Strings.CS.replaceOnce("any", *, null) = "any" 1335 * Strings.CS.replaceOnce("any", "", *) = "any" 1336 * Strings.CS.replaceOnce("aba", "a", null) = "aba" 1337 * Strings.CS.replaceOnce("aba", "a", "") = "ba" 1338 * Strings.CS.replaceOnce("aba", "a", "z") = "zba" 1339 * </pre> 1340 * 1341 * <p> 1342 * Case-insensitive examples 1343 * </p> 1344 * 1345 * <pre> 1346 * Strings.CI.replaceOnceIgnoreCase(null, *, *) = null 1347 * Strings.CI.replaceOnceIgnoreCase("", *, *) = "" 1348 * Strings.CI.replaceOnceIgnoreCase("any", null, *) = "any" 1349 * Strings.CI.replaceOnceIgnoreCase("any", *, null) = "any" 1350 * Strings.CI.replaceOnceIgnoreCase("any", "", *) = "any" 1351 * Strings.CI.replaceOnceIgnoreCase("aba", "a", null) = "aba" 1352 * Strings.CI.replaceOnceIgnoreCase("aba", "a", "") = "ba" 1353 * Strings.CI.replaceOnceIgnoreCase("aba", "a", "z") = "zba" 1354 * Strings.CI.replaceOnceIgnoreCase("FoOFoofoo", "foo", "") = "Foofoo" 1355 * </pre> 1356 * 1357 * @see #replace(String text, String searchString, String replacement, int max) 1358 * @param text text to search and replace in, may be null 1359 * @param searchString the String to search for, may be null 1360 * @param replacement the String to replace with, may be null 1361 * @return the text with any replacements processed, {@code null} if null String input 1362 */ 1363 public String replaceOnce(final String text, final String searchString, final String replacement) { 1364 return replace(text, searchString, replacement, 1); 1365 } 1366 1367 /** 1368 * Tests if a CharSequence starts with a specified prefix. 1369 * 1370 * <p> 1371 * {@code null}s are handled without exceptions. Two {@code null} references are considered to be equal. 1372 * </p> 1373 * 1374 * <p> 1375 * Case-sensitive examples 1376 * </p> 1377 * 1378 * <pre> 1379 * Strings.CS.startsWith(null, null) = true 1380 * Strings.CS.startsWith(null, "abc") = false 1381 * Strings.CS.startsWith("abcdef", null) = false 1382 * Strings.CS.startsWith("abcdef", "abc") = true 1383 * Strings.CS.startsWith("ABCDEF", "abc") = false 1384 * </pre> 1385 * 1386 * <p> 1387 * Case-insensitive examples 1388 * </p> 1389 * 1390 * <pre> 1391 * Strings.CI.startsWithIgnoreCase(null, null) = true 1392 * Strings.CI.startsWithIgnoreCase(null, "abc") = false 1393 * Strings.CI.startsWithIgnoreCase("abcdef", null) = false 1394 * Strings.CI.startsWithIgnoreCase("abcdef", "abc") = true 1395 * Strings.CI.startsWithIgnoreCase("ABCDEF", "abc") = true 1396 * </pre> 1397 * 1398 * @see String#startsWith(String) 1399 * @param str the CharSequence to check, may be null 1400 * @param prefix the prefix to find, may be null 1401 * @return {@code true} if the CharSequence starts with the prefix, case-sensitive, or both {@code null} 1402 */ 1403 public boolean startsWith(final CharSequence str, final CharSequence prefix) { 1404 if (str == null || prefix == null) { 1405 return str == prefix; 1406 } 1407 final int preLen = prefix.length(); 1408 if (preLen > str.length()) { 1409 return false; 1410 } 1411 return CharSequenceUtils.regionMatches(str, ignoreCase, 0, prefix, 0, preLen); 1412 } 1413 1414 /** 1415 * Tests if a CharSequence starts with any of the provided prefixes. 1416 * 1417 * <p> 1418 * Case-sensitive examples 1419 * </p> 1420 * 1421 * <pre> 1422 * Strings.CS.startsWithAny(null, null) = false 1423 * Strings.CS.startsWithAny(null, new String[] {"abc"}) = false 1424 * Strings.CS.startsWithAny("abcxyz", null) = false 1425 * Strings.CS.startsWithAny("abcxyz", new String[] {""}) = true 1426 * Strings.CS.startsWithAny("abcxyz", new String[] {"abc"}) = true 1427 * Strings.CS.startsWithAny("abcxyz", new String[] {null, "xyz", "abc"}) = true 1428 * Strings.CS.startsWithAny("abcxyz", null, "xyz", "ABCX") = false 1429 * Strings.CS.startsWithAny("ABCXYZ", null, "xyz", "abc") = false 1430 * </pre> 1431 * 1432 * <p> 1433 * Case-insensitive examples 1434 * </p> 1435 * 1436 * <pre> 1437 * Strings.CI.startsWithAny(null, null) = false 1438 * Strings.CI.startsWithAny(null, new String[] {"aBc"}) = false 1439 * Strings.CI.startsWithAny("AbCxYz", null) = false 1440 * Strings.CI.startsWithAny("AbCxYz", new String[] {""}) = true 1441 * Strings.CI.startsWithAny("AbCxYz", new String[] {"aBc"}) = true 1442 * Strings.CI.startsWithAny("AbCxYz", new String[] {null, "XyZ", "aBc"}) = true 1443 * Strings.CI.startsWithAny("abcxyz", null, "xyz", "ABCX") = true 1444 * Strings.CI.startsWithAny("ABCXYZ", null, "xyz", "abc") = true 1445 * </pre> 1446 * 1447 * @param sequence the CharSequence to check, may be null 1448 * @param searchStrings the CharSequence prefixes, may be empty or contain {@code null} 1449 * @see Strings#startsWith(CharSequence, CharSequence) 1450 * @return {@code true} if the input {@code sequence} is {@code null} AND no {@code searchStrings} are provided, or the input {@code sequence} begins with 1451 * any of the provided {@code searchStrings}. 1452 */ 1453 public boolean startsWithAny(final CharSequence sequence, final CharSequence... searchStrings) { 1454 if (StringUtils.isEmpty(sequence) || ArrayUtils.isEmpty(searchStrings)) { 1455 return false; 1456 } 1457 for (final CharSequence searchString : searchStrings) { 1458 if (startsWith(sequence, searchString)) { 1459 return true; 1460 } 1461 } 1462 return false; 1463 } 1464 1465}