1 /* 2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"). 5 * You may not use this file except in compliance with the License. 6 * A copy of the License is located at 7 * 8 * http://aws.amazon.com/apache2.0 9 * 10 * or in the "license" file accompanying this file. This file is distributed 11 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 12 * express or implied. See the License for the specific language governing 13 * permissions and limitations under the License. 14 */ 15 16 package software.amazon.awssdk.utils; 17 18 import java.io.UncheckedIOException; 19 import java.nio.ByteBuffer; 20 import java.nio.charset.CharacterCodingException; 21 import java.nio.charset.Charset; 22 import java.util.Locale; 23 import software.amazon.awssdk.annotations.SdkProtectedApi; 24 25 /** 26 * <p>Operations on {@link java.lang.String} that are 27 * {@code null} safe.</p> 28 * 29 * <ul> 30 * <li><b>IsEmpty/IsBlank</b> 31 * - checks if a String contains text</li> 32 * <li><b>Trim/Strip</b> 33 * - removes leading and trailing whitespace</li> 34 * <li><b>Equals/Compare</b> 35 * - compares two strings null-safe</li> 36 * <li><b>startsWith</b> 37 * - check if a String starts with a prefix null-safe</li> 38 * <li><b>endsWith</b> 39 * - check if a String ends with a suffix null-safe</li> 40 * <li><b>IndexOf/LastIndexOf/Contains</b> 41 * - null-safe index-of checks 42 * <li><b>IndexOfAny/LastIndexOfAny/IndexOfAnyBut/LastIndexOfAnyBut</b> 43 * - index-of any of a set of Strings</li> 44 * <li><b>ContainsOnly/ContainsNone/ContainsAny</b> 45 * - does String contains only/none/any of these characters</li> 46 * <li><b>Substring/Left/Right/Mid</b> 47 * - null-safe substring extractions</li> 48 * <li><b>SubstringBefore/SubstringAfter/SubstringBetween</b> 49 * - substring extraction relative to other strings</li> 50 * <li><b>Split/Join</b> 51 * - splits a String into an array of substrings and vice versa</li> 52 * <li><b>Remove/Delete</b> 53 * - removes part of a String</li> 54 * <li><b>Replace/Overlay</b> 55 * - Searches a String and replaces one String with another</li> 56 * <li><b>Chomp/Chop</b> 57 * - removes the last part of a String</li> 58 * <li><b>AppendIfMissing</b> 59 * - appends a suffix to the end of the String if not present</li> 60 * <li><b>PrependIfMissing</b> 61 * - prepends a prefix to the start of the String if not present</li> 62 * <li><b>LeftPad/RightPad/Center/Repeat</b> 63 * - pads a String</li> 64 * <li><b>UpperCase/LowerCase/SwapCase/Capitalize/Uncapitalize</b> 65 * - changes the case of a String</li> 66 * <li><b>CountMatches</b> 67 * - counts the number of occurrences of one String in another</li> 68 * <li><b>IsAlpha/IsNumeric/IsWhitespace/IsAsciiPrintable</b> 69 * - checks the characters in a String</li> 70 * <li><b>DefaultString</b> 71 * - protects against a null input String</li> 72 * <li><b>Rotate</b> 73 * - rotate (circular shift) a String</li> 74 * <li><b>Reverse/ReverseDelimited</b> 75 * - reverses a String</li> 76 * <li><b>Abbreviate</b> 77 * - abbreviates a string using ellipsis or another given String</li> 78 * <li><b>Difference</b> 79 * - compares Strings and reports on their differences</li> 80 * <li><b>LevenshteinDistance</b> 81 * - the number of changes needed to change one String into another</li> 82 * </ul> 83 * 84 * <p>The {@code StringUtils} class defines certain words related to 85 * String handling.</p> 86 * 87 * <ul> 88 * <li>null - {@code null}</li> 89 * <li>empty - a zero-length string ({@code ""})</li> 90 * <li>space - the space character ({@code ' '}, char 32)</li> 91 * <li>whitespace - the characters defined by {@link Character#isWhitespace(char)}</li> 92 * <li>trim - the characters <= 32 as in {@link String#trim()}</li> 93 * </ul> 94 * 95 * <p>{@code StringUtils} handles {@code null} input Strings quietly. 96 * That is to say that a {@code null} input will return {@code null}. 97 * Where a {@code boolean} or {@code int} is being returned 98 * details vary by method.</p> 99 * 100 * <p>A side effect of the {@code null} handling is that a 101 * {@code NullPointerException} should be considered a bug in 102 * {@code StringUtils}.</p> 103 * 104 * <p>This class's source was modified from the Apache commons-lang library: https://github.com/apache/commons-lang/</p> 105 * 106 * <p>#ThreadSafe#</p> 107 * @see java.lang.String 108 */ 109 @SdkProtectedApi 110 public final class StringUtils { 111 // Performance testing notes (JDK 1.4, Jul03, scolebourne) 112 // Whitespace: 113 // Character.isWhitespace() is faster than WHITESPACE.indexOf() 114 // where WHITESPACE is a string of all whitespace characters 115 // 116 // Character access: 117 // String.charAt(n) versus toCharArray(), then array[n] 118 // String.charAt(n) is about 15% worse for a 10K string 119 // They are about equal for a length 50 string 120 // String.charAt(n) is about 4 times better for a length 3 string 121 // String.charAt(n) is best bet overall 122 // 123 // Append: 124 // String.concat about twice as fast as StringBuffer.append 125 // (not sure who tested this) 126 127 /** 128 * The empty String {@code ""}. 129 */ 130 private static final String EMPTY = ""; 131 132 /** 133 * <p>{@code StringUtils} instances should NOT be constructed in 134 * standard programming. Instead, the class should be used as 135 * {@code StringUtils.trim(" foo ");}.</p> 136 */ StringUtils()137 private StringUtils() { 138 } 139 140 // Empty checks 141 //----------------------------------------------------------------------- 142 143 /** 144 * <p>Checks if a CharSequence is empty ("") or null.</p> 145 * 146 * <pre> 147 * StringUtils.isEmpty(null) = true 148 * StringUtils.isEmpty("") = true 149 * StringUtils.isEmpty(" ") = false 150 * StringUtils.isEmpty("bob") = false 151 * StringUtils.isEmpty(" bob ") = false 152 * </pre> 153 * 154 * <p>NOTE: This method changed in Lang version 2.0. 155 * It no longer trims the CharSequence. 156 * That functionality is available in isBlank().</p> 157 * 158 * @param cs the CharSequence to check, may be null 159 * @return {@code true} if the CharSequence is empty or null 160 * @since 3.0 Changed signature from isEmpty(String) to isEmpty(CharSequence) 161 */ isEmpty(final CharSequence cs)162 public static boolean isEmpty(final CharSequence cs) { 163 return cs == null || cs.length() == 0; 164 } 165 166 /** 167 * <p>Checks if a CharSequence is empty (""), null or whitespace only.</p> 168 * 169 * <p>Whitespace is defined by {@link Character#isWhitespace(char)}.</p> 170 * 171 * <pre> 172 * StringUtils.isBlank(null) = true 173 * StringUtils.isBlank("") = true 174 * StringUtils.isBlank(" ") = true 175 * StringUtils.isBlank("bob") = false 176 * StringUtils.isBlank(" bob ") = false 177 * </pre> 178 * 179 * @param cs the CharSequence to check, may be null 180 * @return {@code true} if the CharSequence is null, empty or whitespace only 181 * @since 2.0 182 * @since 3.0 Changed signature from isBlank(String) to isBlank(CharSequence) 183 */ isBlank(final CharSequence cs)184 public static boolean isBlank(final CharSequence cs) { 185 if (cs == null || cs.length() == 0) { 186 return true; 187 } 188 for (int i = 0; i < cs.length(); i++) { 189 if (!Character.isWhitespace(cs.charAt(i))) { 190 return false; 191 } 192 } 193 return true; 194 } 195 196 /** 197 * <p>Checks if a CharSequence is not empty (""), not null and not whitespace only.</p> 198 * 199 * <p>Whitespace is defined by {@link Character#isWhitespace(char)}.</p> 200 * 201 * <pre> 202 * StringUtils.isNotBlank(null) = false 203 * StringUtils.isNotBlank("") = false 204 * StringUtils.isNotBlank(" ") = false 205 * StringUtils.isNotBlank("bob") = true 206 * StringUtils.isNotBlank(" bob ") = true 207 * </pre> 208 * 209 * @param cs the CharSequence to check, may be null 210 * @return {@code true} if the CharSequence is not empty and not null and not whitespace only 211 * @since 2.0 212 * @since 3.0 Changed signature from isNotBlank(String) to isNotBlank(CharSequence) 213 */ isNotBlank(final CharSequence cs)214 public static boolean isNotBlank(final CharSequence cs) { 215 return !isBlank(cs); 216 } 217 218 // Trim 219 //----------------------------------------------------------------------- 220 221 /** 222 * <p>Removes control characters (char <= 32) from both 223 * ends of this String, handling {@code null} by returning 224 * {@code null}.</p> 225 * 226 * <p>The String is trimmed using {@link String#trim()}. 227 * Trim removes start and end characters <= 32.</p> 228 * 229 * <pre> 230 * StringUtils.trim(null) = null 231 * StringUtils.trim("") = "" 232 * StringUtils.trim(" ") = "" 233 * StringUtils.trim("abc") = "abc" 234 * StringUtils.trim(" abc ") = "abc" 235 * </pre> 236 * 237 * @param str the String to be trimmed, may be null 238 * @return the trimmed string, {@code null} if null String input 239 */ trim(final String str)240 public static String trim(final String str) { 241 return str == null ? null : str.trim(); 242 } 243 244 /** 245 * <p>Removes control characters (char <= 32) from both 246 * ends of this String returning {@code null} if the String is 247 * empty ("") after the trim or if it is {@code null}. 248 * 249 * <p>The String is trimmed using {@link String#trim()}. 250 * Trim removes start and end characters <= 32.</p> 251 * 252 * <pre> 253 * StringUtils.trimToNull(null) = null 254 * StringUtils.trimToNull("") = null 255 * StringUtils.trimToNull(" ") = null 256 * StringUtils.trimToNull("abc") = "abc" 257 * StringUtils.trimToNull(" abc ") = "abc" 258 * </pre> 259 * 260 * @param str the String to be trimmed, may be null 261 * @return the trimmed String, 262 * {@code null} if only chars <= 32, empty or null String input 263 * @since 2.0 264 */ trimToNull(final String str)265 public static String trimToNull(final String str) { 266 String ts = trim(str); 267 return isEmpty(ts) ? null : ts; 268 } 269 270 /** 271 * <p>Removes control characters (char <= 32) from both 272 * ends of this String returning an empty String ("") if the String 273 * is empty ("") after the trim or if it is {@code null}. 274 * 275 * <p>The String is trimmed using {@link String#trim()}. 276 * Trim removes start and end characters <= 32.</p> 277 * 278 * <pre> 279 * StringUtils.trimToEmpty(null) = "" 280 * StringUtils.trimToEmpty("") = "" 281 * StringUtils.trimToEmpty(" ") = "" 282 * StringUtils.trimToEmpty("abc") = "abc" 283 * StringUtils.trimToEmpty(" abc ") = "abc" 284 * </pre> 285 * 286 * @param str the String to be trimmed, may be null 287 * @return the trimmed String, or an empty String if {@code null} input 288 * @since 2.0 289 */ trimToEmpty(final String str)290 public static String trimToEmpty(final String str) { 291 return str == null ? EMPTY : str.trim(); 292 } 293 294 // Equals 295 //----------------------------------------------------------------------- 296 297 /** 298 * <p>Compares two Strings, returning {@code true} if they represent 299 * equal sequences of characters.</p> 300 * 301 * <p>{@code null}s are handled without exceptions. Two {@code null} 302 * references are considered to be equal. The comparison is case sensitive.</p> 303 * 304 * <pre> 305 * StringUtils.equals(null, null) = true 306 * StringUtils.equals(null, "abc") = false 307 * StringUtils.equals("abc", null) = false 308 * StringUtils.equals("abc", "abc") = true 309 * StringUtils.equals("abc", "ABC") = false 310 * </pre> 311 * 312 * @see Object#equals(Object) 313 * @param cs1 the first String, may be {@code null} 314 * @param cs2 the second String, may be {@code null} 315 * @return {@code true} if the Strings are equal (case-sensitive), or both {@code null} 316 */ equals(final String cs1, final String cs2)317 public static boolean equals(final String cs1, final String cs2) { 318 if (cs1 == null || cs2 == null) { 319 return false; 320 } 321 if (cs1.length() != cs2.length()) { 322 return false; 323 } 324 return cs1.equals(cs2); 325 } 326 327 // Substring 328 //----------------------------------------------------------------------- 329 330 /** 331 * <p>Gets a substring from the specified String avoiding exceptions.</p> 332 * 333 * <p>A negative start position can be used to start {@code n} 334 * characters from the end of the String.</p> 335 * 336 * <p>A {@code null} String will return {@code null}. 337 * An empty ("") String will return "".</p> 338 * 339 * <pre> 340 * StringUtils.substring(null, *) = null 341 * StringUtils.substring("", *) = "" 342 * StringUtils.substring("abc", 0) = "abc" 343 * StringUtils.substring("abc", 2) = "c" 344 * StringUtils.substring("abc", 4) = "" 345 * StringUtils.substring("abc", -2) = "bc" 346 * StringUtils.substring("abc", -4) = "abc" 347 * </pre> 348 * 349 * @param str the String to get the substring from, may be null 350 * @param start the position to start from, negative means count back from the end of the String by this many characters 351 * @return substring from start position, {@code null} if null String input 352 */ substring(final String str, int start)353 public static String substring(final String str, int start) { 354 if (str == null) { 355 return null; 356 } 357 358 // handle negatives, which means last n characters 359 if (start < 0) { 360 start = str.length() + start; // remember start is negative 361 } 362 363 if (start < 0) { 364 start = 0; 365 } 366 if (start > str.length()) { 367 return EMPTY; 368 } 369 370 return str.substring(start); 371 } 372 373 /** 374 * <p>Gets a substring from the specified String avoiding exceptions.</p> 375 * 376 * <p>A negative start position can be used to start/end {@code n} 377 * characters from the end of the String.</p> 378 * 379 * <p>The returned substring starts with the character in the {@code start} 380 * position and ends before the {@code end} position. All position counting is 381 * zero-based -- i.e., to start at the beginning of the string use 382 * {@code start = 0}. Negative start and end positions can be used to 383 * specify offsets relative to the end of the String.</p> 384 * 385 * <p>If {@code start} is not strictly to the left of {@code end}, "" 386 * is returned.</p> 387 * 388 * <pre> 389 * StringUtils.substring(null, *, *) = null 390 * StringUtils.substring("", * , *) = ""; 391 * StringUtils.substring("abc", 0, 2) = "ab" 392 * StringUtils.substring("abc", 2, 0) = "" 393 * StringUtils.substring("abc", 2, 4) = "c" 394 * StringUtils.substring("abc", 4, 6) = "" 395 * StringUtils.substring("abc", 2, 2) = "" 396 * StringUtils.substring("abc", -2, -1) = "b" 397 * StringUtils.substring("abc", -4, 2) = "ab" 398 * </pre> 399 * 400 * @param str the String to get the substring from, may be null 401 * @param start the position to start from, negative means count back from the end of the String by this many characters 402 * @param end the position to end at (exclusive), negative means count back from the end of the String by this many 403 * characters 404 * @return substring from start position to end position, 405 * {@code null} if null String input 406 */ substring(final String str, int start, int end)407 public static String substring(final String str, int start, int end) { 408 if (str == null) { 409 return null; 410 } 411 412 // handle negatives 413 if (end < 0) { 414 end = str.length() + end; // remember end is negative 415 } 416 if (start < 0) { 417 start = str.length() + start; // remember start is negative 418 } 419 420 // check length next 421 if (end > str.length()) { 422 end = str.length(); 423 } 424 425 // if start is greater than end, return "" 426 if (start > end) { 427 return EMPTY; 428 } 429 430 if (start < 0) { 431 start = 0; 432 } 433 if (end < 0) { 434 end = 0; 435 } 436 437 return str.substring(start, end); 438 } 439 440 // Case conversion 441 //----------------------------------------------------------------------- 442 443 /** 444 * <p>Converts a String to upper case as per {@link String#toUpperCase()}.</p> 445 * 446 * <p>A {@code null} input String returns {@code null}.</p> 447 * 448 * <pre> 449 * StringUtils.upperCase(null) = null 450 * StringUtils.upperCase("") = "" 451 * StringUtils.upperCase("aBc") = "ABC" 452 * </pre> 453 * 454 * <p>This uses "ENGLISH" as the locale. 455 * 456 * @param str the String to upper case, may be null 457 * @return the upper cased String, {@code null} if null String input 458 */ upperCase(final String str)459 public static String upperCase(final String str) { 460 if (str == null) { 461 return null; 462 } 463 return str.toUpperCase(Locale.ENGLISH); 464 } 465 466 /** 467 * <p>Converts a String to lower case as per {@link String#toLowerCase()}.</p> 468 * 469 * <p>A {@code null} input String returns {@code null}.</p> 470 * 471 * <pre> 472 * StringUtils.lowerCase(null) = null 473 * StringUtils.lowerCase("") = "" 474 * StringUtils.lowerCase("aBc") = "abc" 475 * </pre> 476 * 477 * <p>This uses "ENGLISH" as the locale. 478 * 479 * @param str the String to lower case, may be null 480 * @return the lower cased String, {@code null} if null String input 481 */ lowerCase(final String str)482 public static String lowerCase(final String str) { 483 if (str == null) { 484 return null; 485 } 486 return str.toLowerCase(Locale.ENGLISH); 487 } 488 489 /** 490 * <p>Capitalizes a String changing the first character to title case as 491 * per {@link Character#toTitleCase(int)}. No other characters are changed.</p> 492 * 493 * <pre> 494 * StringUtils.capitalize(null) = null 495 * StringUtils.capitalize("") = "" 496 * StringUtils.capitalize("cat") = "Cat" 497 * StringUtils.capitalize("cAt") = "CAt" 498 * StringUtils.capitalize("'cat'") = "'cat'" 499 * </pre> 500 * 501 * @param str the String to capitalize, may be null 502 * @return the capitalized String, {@code null} if null String input 503 * @see #uncapitalize(String) 504 * @since 2.0 505 */ capitalize(final String str)506 public static String capitalize(final String str) { 507 if (str == null || str.length() == 0) { 508 return str; 509 } 510 511 int firstCodepoint = str.codePointAt(0); 512 int newCodePoint = Character.toTitleCase(firstCodepoint); 513 if (firstCodepoint == newCodePoint) { 514 // already capitalized 515 return str; 516 } 517 518 int[] newCodePoints = new int[str.length()]; // cannot be longer than the char array 519 int outOffset = 0; 520 newCodePoints[outOffset++] = newCodePoint; // copy the first codepoint 521 for (int inOffset = Character.charCount(firstCodepoint); inOffset < str.length(); ) { 522 int codepoint = str.codePointAt(inOffset); 523 newCodePoints[outOffset++] = codepoint; // copy the remaining ones 524 inOffset += Character.charCount(codepoint); 525 } 526 return new String(newCodePoints, 0, outOffset); 527 } 528 529 /** 530 * <p>Uncapitalizes a String, changing the first character to lower case as 531 * per {@link Character#toLowerCase(int)}. No other characters are changed.</p> 532 * 533 * <pre> 534 * StringUtils.uncapitalize(null) = null 535 * StringUtils.uncapitalize("") = "" 536 * StringUtils.uncapitalize("cat") = "cat" 537 * StringUtils.uncapitalize("Cat") = "cat" 538 * StringUtils.uncapitalize("CAT") = "cAT" 539 * </pre> 540 * 541 * @param str the String to uncapitalize, may be null 542 * @return the uncapitalized String, {@code null} if null String input 543 * @see #capitalize(String) 544 * @since 2.0 545 */ uncapitalize(final String str)546 public static String uncapitalize(final String str) { 547 if (str == null || str.length() == 0) { 548 return str; 549 } 550 551 int firstCodepoint = str.codePointAt(0); 552 int newCodePoint = Character.toLowerCase(firstCodepoint); 553 if (firstCodepoint == newCodePoint) { 554 // already capitalized 555 return str; 556 } 557 558 int[] newCodePoints = new int[str.length()]; // cannot be longer than the char array 559 int outOffset = 0; 560 newCodePoints[outOffset++] = newCodePoint; // copy the first codepoint 561 for (int inOffset = Character.charCount(firstCodepoint); inOffset < str.length(); ) { 562 int codepoint = str.codePointAt(inOffset); 563 newCodePoints[outOffset++] = codepoint; // copy the remaining ones 564 inOffset += Character.charCount(codepoint); 565 } 566 return new String(newCodePoints, 0, outOffset); 567 } 568 569 /** 570 * Encode the given bytes as a string using the given charset 571 * @throws UncheckedIOException with a {@link CharacterCodingException} as the cause if the bytes cannot be encoded using the 572 * provided charset. 573 */ fromBytes(byte[] bytes, Charset charset)574 public static String fromBytes(byte[] bytes, Charset charset) throws UncheckedIOException { 575 try { 576 return charset.newDecoder().decode(ByteBuffer.wrap(bytes)).toString(); 577 } catch (CharacterCodingException e) { 578 throw new UncheckedIOException("Cannot encode string.", e); 579 } 580 } 581 582 /** 583 * Tests if this string starts with the specified prefix ignoring case considerations. 584 * 585 * @param str the string to be tested 586 * @param prefix the prefix 587 * @return true if the string starts with the prefix ignoring case 588 */ startsWithIgnoreCase(String str, String prefix)589 public static boolean startsWithIgnoreCase(String str, String prefix) { 590 return str.regionMatches(true, 0, prefix, 0, prefix.length()); 591 } 592 593 /** 594 * <p>Replaces a String with another String inside a larger String, once.</p> 595 * 596 * <p>A {@code null} reference passed to this method is a no-op.</p> 597 * 598 * <pre> 599 * StringUtils.replaceOnce(null, *, *) = null 600 * StringUtils.replaceOnce("", *, *) = "" 601 * StringUtils.replaceOnce("any", null, *) = "any" 602 * StringUtils.replaceOnce("any", *, null) = "any" 603 * StringUtils.replaceOnce("any", "", *) = "any" 604 * StringUtils.replaceOnce("aba", "a", null) = "aba" 605 * StringUtils.replaceOnce("aba", "a", "") = "ba" 606 * StringUtils.replaceOnce("aba", "a", "z") = "zba" 607 * </pre> 608 * 609 * @see #replace(String text, String searchString, String replacement, int max) 610 * @param text text to search and replace in, may be null 611 * @param searchString the String to search for, may be null 612 * @param replacement the String to replace with, may be null 613 * @return the text with any replacements processed, 614 * {@code null} if null String input 615 */ replaceOnce(String text, String searchString, String replacement)616 public static String replaceOnce(String text, String searchString, String replacement) { 617 return replace(text, searchString, replacement, 1); 618 } 619 620 /** 621 * <p>Replaces a String with another String inside a larger String, 622 * for the first {@code max} values of the search String, 623 * case sensitively/insensitively based on {@code ignoreCase} value.</p> 624 * 625 * <p>A {@code null} reference passed to this method is a no-op.</p> 626 * 627 * <pre> 628 * StringUtils.replace(null, *, *, *, false) = null 629 * StringUtils.replace("", *, *, *, false) = "" 630 * StringUtils.replace("any", null, *, *, false) = "any" 631 * StringUtils.replace("any", *, null, *, false) = "any" 632 * StringUtils.replace("any", "", *, *, false) = "any" 633 * StringUtils.replace("any", *, *, 0, false) = "any" 634 * StringUtils.replace("abaa", "a", null, -1, false) = "abaa" 635 * StringUtils.replace("abaa", "a", "", -1, false) = "b" 636 * StringUtils.replace("abaa", "a", "z", 0, false) = "abaa" 637 * StringUtils.replace("abaa", "A", "z", 1, false) = "abaa" 638 * StringUtils.replace("abaa", "A", "z", 1, true) = "zbaa" 639 * StringUtils.replace("abAa", "a", "z", 2, true) = "zbza" 640 * StringUtils.replace("abAa", "a", "z", -1, true) = "zbzz" 641 * </pre> 642 * 643 * @param text text to search and replace in, may be null 644 * @param searchString the String to search for (case insensitive), may be null 645 * @param replacement the String to replace it with, may be null 646 * @return the text with any replacements processed, 647 * {@code null} if null String input 648 */ replace(String text, String searchString, String replacement)649 public static String replace(String text, String searchString, String replacement) { 650 return replace(text, searchString, replacement, -1); 651 } 652 653 /** 654 * <p>Replaces a String with another String inside a larger String, 655 * for the first {@code max} values of the search String, 656 * case sensitively/insensitively based on {@code ignoreCase} value.</p> 657 * 658 * <p>A {@code null} reference passed to this method is a no-op.</p> 659 * 660 * <pre> 661 * StringUtils.replace(null, *, *, *, false) = null 662 * StringUtils.replace("", *, *, *, false) = "" 663 * StringUtils.replace("any", null, *, *, false) = "any" 664 * StringUtils.replace("any", *, null, *, false) = "any" 665 * StringUtils.replace("any", "", *, *, false) = "any" 666 * StringUtils.replace("any", *, *, 0, false) = "any" 667 * StringUtils.replace("abaa", "a", null, -1, false) = "abaa" 668 * StringUtils.replace("abaa", "a", "", -1, false) = "b" 669 * StringUtils.replace("abaa", "a", "z", 0, false) = "abaa" 670 * StringUtils.replace("abaa", "A", "z", 1, false) = "abaa" 671 * StringUtils.replace("abaa", "A", "z", 1, true) = "zbaa" 672 * StringUtils.replace("abAa", "a", "z", 2, true) = "zbza" 673 * StringUtils.replace("abAa", "a", "z", -1, true) = "zbzz" 674 * </pre> 675 * 676 * @param text text to search and replace in, may be null 677 * @param searchString the String to search for (case insensitive), may be null 678 * @param replacement the String to replace it with, may be null 679 * @param max maximum number of values to replace, or {@code -1} if no maximum 680 * @return the text with any replacements processed, 681 * {@code null} if null String input 682 */ replace(String text, String searchString, String replacement, int max)683 private static String replace(String text, String searchString, String replacement, int max) { 684 if (isEmpty(text) || isEmpty(searchString) || replacement == null || max == 0) { 685 return text; 686 } 687 int start = 0; 688 int end = indexOf(text, searchString, start); 689 if (end == -1) { 690 return text; 691 } 692 int replLength = searchString.length(); 693 int increase = Math.max(replacement.length() - replLength, 0); 694 increase *= max < 0 ? 16 : Math.min(max, 64); 695 StringBuilder buf = new StringBuilder(text.length() + increase); 696 while (end != -1) { 697 buf.append(text, start, end).append(replacement); 698 start = end + replLength; 699 if (--max == 0) { 700 break; 701 } 702 end = indexOf(text, searchString, start); 703 } 704 buf.append(text, start, text.length()); 705 return buf.toString(); 706 } 707 708 /** 709 * <p> 710 * Replaces all occurrences of Strings within another String. 711 * </p> 712 * 713 * <p> 714 * A {@code null} reference passed to this method is a no-op, or if 715 * any "search string" or "string to replace" is null, that replace will be 716 * ignored. This will not repeat. For repeating replaces, call the 717 * overloaded method. 718 * </p> 719 * 720 * <pre> 721 * StringUtils.replaceEach(null, *, *) = null 722 * StringUtils.replaceEach("", *, *) = "" 723 * StringUtils.replaceEach("aba", null, null) = "aba" 724 * StringUtils.replaceEach("aba", new String[0], null) = "aba" 725 * StringUtils.replaceEach("aba", null, new String[0]) = "aba" 726 * StringUtils.replaceEach("aba", new String[]{"a"}, null) = "aba" 727 * StringUtils.replaceEach("aba", new String[]{"a"}, new String[]{""}) = "b" 728 * StringUtils.replaceEach("aba", new String[]{null}, new String[]{"a"}) = "aba" 729 * StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"w", "t"}) = "wcte" 730 * (example of how it does not repeat) 731 * StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "t"}) = "dcte" 732 * </pre> 733 * 734 * @param text 735 * text to search and replace in, no-op if null 736 * @param searchList 737 * the Strings to search for, no-op if null 738 * @param replacementList 739 * the Strings to replace them with, no-op if null 740 * @return the text with any replacements processed, {@code null} if 741 * null String input 742 * @throws IllegalArgumentException 743 * if the lengths of the arrays are not the same (null is ok, 744 * and/or size 0) 745 * @since 2.4 746 */ 747 public static String replaceEach(String text, String[] searchList, String[] replacementList) { 748 // mchyzer Performance note: This creates very few new objects (one major goal) 749 // let me know if there are performance requests, we can create a harness to measure 750 751 if (isEmpty(text)) { 752 return text; 753 } 754 755 int searchLength = searchList.length; 756 int replacementLength = replacementList.length; 757 758 // make sure lengths are ok, these need to be equal 759 if (searchLength != replacementLength) { 760 throw new IllegalArgumentException("Search and Replace array lengths don't match: " 761 + searchLength 762 + " vs " 763 + replacementLength); 764 } 765 766 // keep track of which still have matches 767 boolean[] noMoreMatchesForReplIndex = new boolean[searchLength]; 768 769 // index on index that the match was found 770 int textIndex = -1; 771 int replaceIndex = -1; 772 int tempIndex; 773 774 // index of replace array that will replace the search string found 775 // NOTE: logic duplicated below START 776 for (int i = 0; i < searchLength; i++) { 777 if (noMoreMatchesForReplIndex[i] || isEmpty(searchList[i]) || replacementList[i] == null) { 778 continue; 779 } 780 tempIndex = text.indexOf(searchList[i]); 781 782 // see if we need to keep searching for this 783 if (tempIndex == -1) { 784 noMoreMatchesForReplIndex[i] = true; 785 } else if (textIndex == -1 || tempIndex < textIndex) { 786 textIndex = tempIndex; 787 replaceIndex = i; 788 } 789 } 790 // NOTE: logic mostly below END 791 792 // no search strings found, we are done 793 if (textIndex == -1) { 794 return text; 795 } 796 797 int start = 0; 798 799 // get a good guess on the size of the result buffer so it doesn't have to double if it goes over a bit 800 int increase = 0; 801 802 // count the replacement text elements that are larger than their corresponding text being replaced 803 for (int i = 0; i < searchList.length; i++) { 804 if (searchList[i] == null || replacementList[i] == null) { 805 continue; 806 } 807 int greater = replacementList[i].length() - searchList[i].length(); 808 if (greater > 0) { 809 increase += 3 * greater; // assume 3 matches 810 } 811 } 812 // have upper-bound at 20% increase, then let Java take over 813 increase = Math.min(increase, text.length() / 5); 814 815 StringBuilder buf = new StringBuilder(text.length() + increase); 816 817 while (textIndex != -1) { 818 819 for (int i = start; i < textIndex; i++) { 820 buf.append(text.charAt(i)); 821 } 822 buf.append(replacementList[replaceIndex]); 823 824 start = textIndex + searchList[replaceIndex].length(); 825 826 textIndex = -1; 827 replaceIndex = -1; 828 // find the next earliest match 829 // NOTE: logic mostly duplicated above START 830 for (int i = 0; i < searchLength; i++) { 831 if (noMoreMatchesForReplIndex[i] || searchList[i] == null || 832 searchList[i].isEmpty() || replacementList[i] == null) { 833 continue; 834 } 835 tempIndex = text.indexOf(searchList[i], start); 836 837 // see if we need to keep searching for this 838 if (tempIndex == -1) { 839 noMoreMatchesForReplIndex[i] = true; 840 } else if (textIndex == -1 || tempIndex < textIndex) { 841 textIndex = tempIndex; 842 replaceIndex = i; 843 } 844 } 845 // NOTE: logic duplicated above END 846 847 } 848 int textLength = text.length(); 849 for (int i = start; i < textLength; i++) { 850 buf.append(text.charAt(i)); 851 } 852 return buf.toString(); 853 } 854 855 /** 856 * <p>Finds the first index within a CharSequence, handling {@code null}. 857 * This method uses {@link String#indexOf(String, int)} if possible.</p> 858 * 859 * <p>A {@code null} CharSequence will return {@code -1}.</p> 860 * 861 * <pre> 862 * StringUtils.indexOf(null, *) = -1 863 * StringUtils.indexOf(*, null) = -1 864 * StringUtils.indexOf("", "") = 0 865 * StringUtils.indexOf("", *) = -1 (except when * = "") 866 * StringUtils.indexOf("aabaabaa", "a") = 0 867 * StringUtils.indexOf("aabaabaa", "b") = 2 868 * StringUtils.indexOf("aabaabaa", "ab") = 1 869 * StringUtils.indexOf("aabaabaa", "") = 0 870 * </pre> 871 * 872 * @param seq the CharSequence to check, may be null 873 * @param searchSeq the CharSequence to find, may be null 874 * @return the first index of the search CharSequence, 875 * -1 if no match or {@code null} string input 876 * @since 2.0 877 * @since 3.0 Changed signature from indexOf(String, String) to indexOf(CharSequence, CharSequence) 878 */ 879 private static int indexOf(String seq, String searchSeq, int start) { 880 if (seq == null || searchSeq == null) { 881 return -1; 882 } 883 return seq.indexOf(searchSeq, start); 884 } 885 886 /** 887 * Replace the prefix of the string provided ignoring case considerations. 888 * 889 * <p> 890 * The unmatched part is unchanged. 891 * 892 * 893 * @param str the string to replace 894 * @param prefix the prefix to find 895 * @param replacement the replacement 896 * @return the replaced string 897 */ 898 public static String replacePrefixIgnoreCase(String str, String prefix, String replacement) { 899 return str.replaceFirst("(?i)" + prefix, replacement); 900 } 901 902 903 /** 904 * Searches a string for the first occurrence of a character specified by a list of characters. 905 * @param s The string to search. 906 * @param charsToMatch A list of characters to search the string for. 907 * @return The character that was first matched in the string or null if none of the characters were found. 908 */ 909 public static Character findFirstOccurrence(String s, char ...charsToMatch) { 910 int lowestIndex = Integer.MAX_VALUE; 911 912 for (char toMatch : charsToMatch) { 913 int currentIndex = s.indexOf(toMatch); 914 if (currentIndex != -1 && currentIndex < lowestIndex) { 915 lowestIndex = currentIndex; 916 } 917 } 918 919 return lowestIndex == Integer.MAX_VALUE ? null : s.charAt(lowestIndex); 920 } 921 922 /** 923 * Convert a string to boolean safely (as opposed to the less strict {@link Boolean#parseBoolean(String)}). If a customer 924 * specifies a boolean value it should be "true" or "false" (case insensitive) or an exception will be thrown. 925 */ 926 public static boolean safeStringToBoolean(String value) { 927 if (value.equalsIgnoreCase("true")) { 928 return true; 929 } else if (value.equalsIgnoreCase("false")) { 930 return false; 931 } 932 933 throw new IllegalArgumentException("Value was defined as '" + value + "', but should be 'false' or 'true'"); 934 } 935 936 /** 937 * Returns a string whose value is the concatenation of this string repeated {@code count} times. 938 * <p> 939 * If this string is empty or count is zero then the empty string is returned. 940 * <p> 941 * Logical clone of JDK11's {@link String#repeat(int)}. 942 * 943 * @param value the string to repeat 944 * @param count number of times to repeat 945 * @return A string composed of this string repeated {@code count} times or the empty string if this string is empty or count 946 * is zero 947 * @throws IllegalArgumentException if the {@code count} is negative. 948 */ 949 public static String repeat(String value, int count) { 950 if (count < 0) { 951 throw new IllegalArgumentException("count is negative: " + count); 952 } 953 if (value == null || value.length() == 0 || count == 1) { 954 return value; 955 } 956 if (count == 0) { 957 return ""; 958 } 959 if (value.length() > Integer.MAX_VALUE / count) { 960 throw new OutOfMemoryError("Repeating " + value.length() + " bytes String " + count + 961 " times will produce a String exceeding maximum size."); 962 } 963 int len = value.length(); 964 int limit = len * count; 965 char[] array = new char[limit]; 966 value.getChars(0, len, array, 0); 967 int copied; 968 for (copied = len; copied < limit - copied; copied <<= 1) { 969 System.arraycopy(array, 0, array, copied, copied); 970 } 971 System.arraycopy(array, 0, array, copied, limit - copied); 972 return new String(array); 973 } 974 }