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