001/* 002 * Copyright 2002-2019 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.util; 018 019import java.util.ArrayList; 020import java.util.Arrays; 021import java.util.Collection; 022import java.util.Collections; 023import java.util.Enumeration; 024import java.util.Iterator; 025import java.util.LinkedHashSet; 026import java.util.LinkedList; 027import java.util.List; 028import java.util.Locale; 029import java.util.Properties; 030import java.util.Set; 031import java.util.StringTokenizer; 032import java.util.TimeZone; 033 034/** 035 * Miscellaneous {@link String} utility methods. 036 * 037 * <p>Mainly for internal use within the framework; consider 038 * <a href="https://commons.apache.org/proper/commons-lang/">Apache's Commons Lang</a> 039 * for a more comprehensive suite of {@code String} utilities. 040 * 041 * <p>This class delivers some simple functionality that should really be 042 * provided by the core Java {@link String} and {@link StringBuilder} 043 * classes. It also provides easy-to-use methods to convert between 044 * delimited strings, such as CSV strings, and collections and arrays. 045 * 046 * @author Rod Johnson 047 * @author Juergen Hoeller 048 * @author Keith Donald 049 * @author Rob Harrop 050 * @author Rick Evans 051 * @author Arjen Poutsma 052 * @author Sam Brannen 053 * @author Brian Clozel 054 * @since 16 April 2001 055 */ 056public abstract class StringUtils { 057 058 private static final String FOLDER_SEPARATOR = "/"; 059 060 private static final String WINDOWS_FOLDER_SEPARATOR = "\\"; 061 062 private static final String TOP_PATH = ".."; 063 064 private static final String CURRENT_PATH = "."; 065 066 private static final char EXTENSION_SEPARATOR = '.'; 067 068 069 //--------------------------------------------------------------------- 070 // General convenience methods for working with Strings 071 //--------------------------------------------------------------------- 072 073 /** 074 * Check whether the given {@code String} is empty. 075 * <p>This method accepts any Object as an argument, comparing it to 076 * {@code null} and the empty String. As a consequence, this method 077 * will never return {@code true} for a non-null non-String object. 078 * <p>The Object signature is useful for general attribute handling code 079 * that commonly deals with Strings but generally has to iterate over 080 * Objects since attributes may e.g. be primitive value objects as well. 081 * @param str the candidate String 082 * @since 3.2.1 083 */ 084 public static boolean isEmpty(Object str) { 085 return (str == null || "".equals(str)); 086 } 087 088 /** 089 * Check that the given {@code CharSequence} is neither {@code null} nor 090 * of length 0. 091 * <p>Note: this method returns {@code true} for a {@code CharSequence} 092 * that purely consists of whitespace. 093 * <p><pre class="code"> 094 * StringUtils.hasLength(null) = false 095 * StringUtils.hasLength("") = false 096 * StringUtils.hasLength(" ") = true 097 * StringUtils.hasLength("Hello") = true 098 * </pre> 099 * @param str the {@code CharSequence} to check (may be {@code null}) 100 * @return {@code true} if the {@code CharSequence} is not {@code null} and has length 101 * @see #hasText(String) 102 */ 103 public static boolean hasLength(CharSequence str) { 104 return (str != null && str.length() > 0); 105 } 106 107 /** 108 * Check that the given {@code String} is neither {@code null} nor of length 0. 109 * <p>Note: this method returns {@code true} for a {@code String} that 110 * purely consists of whitespace. 111 * @param str the {@code String} to check (may be {@code null}) 112 * @return {@code true} if the {@code String} is not {@code null} and has length 113 * @see #hasLength(CharSequence) 114 * @see #hasText(String) 115 */ 116 public static boolean hasLength(String str) { 117 return (str != null && !str.isEmpty()); 118 } 119 120 /** 121 * Check whether the given {@code CharSequence} contains actual <em>text</em>. 122 * <p>More specifically, this method returns {@code true} if the 123 * {@code CharSequence} is not {@code null}, its length is greater than 124 * 0, and it contains at least one non-whitespace character. 125 * <p><pre class="code"> 126 * StringUtils.hasText(null) = false 127 * StringUtils.hasText("") = false 128 * StringUtils.hasText(" ") = false 129 * StringUtils.hasText("12345") = true 130 * StringUtils.hasText(" 12345 ") = true 131 * </pre> 132 * @param str the {@code CharSequence} to check (may be {@code null}) 133 * @return {@code true} if the {@code CharSequence} is not {@code null}, 134 * its length is greater than 0, and it does not contain whitespace only 135 * @see Character#isWhitespace 136 */ 137 public static boolean hasText(CharSequence str) { 138 return (hasLength(str) && containsText(str)); 139 } 140 141 /** 142 * Check whether the given {@code String} contains actual <em>text</em>. 143 * <p>More specifically, this method returns {@code true} if the 144 * {@code String} is not {@code null}, its length is greater than 0, 145 * and it contains at least one non-whitespace character. 146 * @param str the {@code String} to check (may be {@code null}) 147 * @return {@code true} if the {@code String} is not {@code null}, its 148 * length is greater than 0, and it does not contain whitespace only 149 * @see #hasText(CharSequence) 150 */ 151 public static boolean hasText(String str) { 152 return (hasLength(str) && containsText(str)); 153 } 154 155 private static boolean containsText(CharSequence str) { 156 int strLen = str.length(); 157 for (int i = 0; i < strLen; i++) { 158 if (!Character.isWhitespace(str.charAt(i))) { 159 return true; 160 } 161 } 162 return false; 163 } 164 165 /** 166 * Check whether the given {@code CharSequence} contains any whitespace characters. 167 * @param str the {@code CharSequence} to check (may be {@code null}) 168 * @return {@code true} if the {@code CharSequence} is not empty and 169 * contains at least 1 whitespace character 170 * @see Character#isWhitespace 171 */ 172 public static boolean containsWhitespace(CharSequence str) { 173 if (!hasLength(str)) { 174 return false; 175 } 176 177 int strLen = str.length(); 178 for (int i = 0; i < strLen; i++) { 179 if (Character.isWhitespace(str.charAt(i))) { 180 return true; 181 } 182 } 183 return false; 184 } 185 186 /** 187 * Check whether the given {@code String} contains any whitespace characters. 188 * @param str the {@code String} to check (may be {@code null}) 189 * @return {@code true} if the {@code String} is not empty and 190 * contains at least 1 whitespace character 191 * @see #containsWhitespace(CharSequence) 192 */ 193 public static boolean containsWhitespace(String str) { 194 return containsWhitespace((CharSequence) str); 195 } 196 197 /** 198 * Trim leading and trailing whitespace from the given {@code String}. 199 * @param str the {@code String} to check 200 * @return the trimmed {@code String} 201 * @see java.lang.Character#isWhitespace 202 */ 203 public static String trimWhitespace(String str) { 204 if (!hasLength(str)) { 205 return str; 206 } 207 208 StringBuilder sb = new StringBuilder(str); 209 while (sb.length() > 0 && Character.isWhitespace(sb.charAt(0))) { 210 sb.deleteCharAt(0); 211 } 212 while (sb.length() > 0 && Character.isWhitespace(sb.charAt(sb.length() - 1))) { 213 sb.deleteCharAt(sb.length() - 1); 214 } 215 return sb.toString(); 216 } 217 218 /** 219 * Trim <i>all</i> whitespace from the given {@code String}: 220 * leading, trailing, and in between characters. 221 * @param str the {@code String} to check 222 * @return the trimmed {@code String} 223 * @see java.lang.Character#isWhitespace 224 */ 225 public static String trimAllWhitespace(String str) { 226 if (!hasLength(str)) { 227 return str; 228 } 229 230 int len = str.length(); 231 StringBuilder sb = new StringBuilder(str.length()); 232 for (int i = 0; i < len; i++) { 233 char c = str.charAt(i); 234 if (!Character.isWhitespace(c)) { 235 sb.append(c); 236 } 237 } 238 return sb.toString(); 239 } 240 241 /** 242 * Trim leading whitespace from the given {@code String}. 243 * @param str the {@code String} to check 244 * @return the trimmed {@code String} 245 * @see java.lang.Character#isWhitespace 246 */ 247 public static String trimLeadingWhitespace(String str) { 248 if (!hasLength(str)) { 249 return str; 250 } 251 252 StringBuilder sb = new StringBuilder(str); 253 while (sb.length() > 0 && Character.isWhitespace(sb.charAt(0))) { 254 sb.deleteCharAt(0); 255 } 256 return sb.toString(); 257 } 258 259 /** 260 * Trim trailing whitespace from the given {@code String}. 261 * @param str the {@code String} to check 262 * @return the trimmed {@code String} 263 * @see java.lang.Character#isWhitespace 264 */ 265 public static String trimTrailingWhitespace(String str) { 266 if (!hasLength(str)) { 267 return str; 268 } 269 270 StringBuilder sb = new StringBuilder(str); 271 while (sb.length() > 0 && Character.isWhitespace(sb.charAt(sb.length() - 1))) { 272 sb.deleteCharAt(sb.length() - 1); 273 } 274 return sb.toString(); 275 } 276 277 /** 278 * Trim all occurrences of the supplied leading character from the given {@code String}. 279 * @param str the {@code String} to check 280 * @param leadingCharacter the leading character to be trimmed 281 * @return the trimmed {@code String} 282 */ 283 public static String trimLeadingCharacter(String str, char leadingCharacter) { 284 if (!hasLength(str)) { 285 return str; 286 } 287 288 StringBuilder sb = new StringBuilder(str); 289 while (sb.length() > 0 && sb.charAt(0) == leadingCharacter) { 290 sb.deleteCharAt(0); 291 } 292 return sb.toString(); 293 } 294 295 /** 296 * Trim all occurrences of the supplied trailing character from the given {@code String}. 297 * @param str the {@code String} to check 298 * @param trailingCharacter the trailing character to be trimmed 299 * @return the trimmed {@code String} 300 */ 301 public static String trimTrailingCharacter(String str, char trailingCharacter) { 302 if (!hasLength(str)) { 303 return str; 304 } 305 306 StringBuilder sb = new StringBuilder(str); 307 while (sb.length() > 0 && sb.charAt(sb.length() - 1) == trailingCharacter) { 308 sb.deleteCharAt(sb.length() - 1); 309 } 310 return sb.toString(); 311 } 312 313 /** 314 * Test if the given {@code String} starts with the specified prefix, 315 * ignoring upper/lower case. 316 * @param str the {@code String} to check 317 * @param prefix the prefix to look for 318 * @see java.lang.String#startsWith 319 */ 320 public static boolean startsWithIgnoreCase(String str, String prefix) { 321 return (str != null && prefix != null && str.length() >= prefix.length() && 322 str.regionMatches(true, 0, prefix, 0, prefix.length())); 323 } 324 325 /** 326 * Test if the given {@code String} ends with the specified suffix, 327 * ignoring upper/lower case. 328 * @param str the {@code String} to check 329 * @param suffix the suffix to look for 330 * @see java.lang.String#endsWith 331 */ 332 public static boolean endsWithIgnoreCase(String str, String suffix) { 333 return (str != null && suffix != null && str.length() >= suffix.length() && 334 str.regionMatches(true, str.length() - suffix.length(), suffix, 0, suffix.length())); 335 } 336 337 /** 338 * Test whether the given string matches the given substring 339 * at the given index. 340 * @param str the original string (or StringBuilder) 341 * @param index the index in the original string to start matching against 342 * @param substring the substring to match at the given index 343 */ 344 public static boolean substringMatch(CharSequence str, int index, CharSequence substring) { 345 if (index + substring.length() > str.length()) { 346 return false; 347 } 348 for (int i = 0; i < substring.length(); i++) { 349 if (str.charAt(index + i) != substring.charAt(i)) { 350 return false; 351 } 352 } 353 return true; 354 } 355 356 /** 357 * Count the occurrences of the substring {@code sub} in string {@code str}. 358 * @param str string to search in 359 * @param sub string to search for 360 */ 361 public static int countOccurrencesOf(String str, String sub) { 362 if (!hasLength(str) || !hasLength(sub)) { 363 return 0; 364 } 365 366 int count = 0; 367 int pos = 0; 368 int idx; 369 while ((idx = str.indexOf(sub, pos)) != -1) { 370 ++count; 371 pos = idx + sub.length(); 372 } 373 return count; 374 } 375 376 /** 377 * Replace all occurrences of a substring within a string with another string. 378 * @param inString {@code String} to examine 379 * @param oldPattern {@code String} to replace 380 * @param newPattern {@code String} to insert 381 * @return a {@code String} with the replacements 382 */ 383 public static String replace(String inString, String oldPattern, String newPattern) { 384 if (!hasLength(inString) || !hasLength(oldPattern) || newPattern == null) { 385 return inString; 386 } 387 int index = inString.indexOf(oldPattern); 388 if (index == -1) { 389 // no occurrence -> can return input as-is 390 return inString; 391 } 392 393 int capacity = inString.length(); 394 if (newPattern.length() > oldPattern.length()) { 395 capacity += 16; 396 } 397 StringBuilder sb = new StringBuilder(capacity); 398 399 int pos = 0; // our position in the old string 400 int patLen = oldPattern.length(); 401 while (index >= 0) { 402 sb.append(inString, pos, index); 403 sb.append(newPattern); 404 pos = index + patLen; 405 index = inString.indexOf(oldPattern, pos); 406 } 407 408 // append any characters to the right of a match 409 sb.append(inString, pos, inString.length()); 410 return sb.toString(); 411 } 412 413 /** 414 * Delete all occurrences of the given substring. 415 * @param inString the original {@code String} 416 * @param pattern the pattern to delete all occurrences of 417 * @return the resulting {@code String} 418 */ 419 public static String delete(String inString, String pattern) { 420 return replace(inString, pattern, ""); 421 } 422 423 /** 424 * Delete any character in a given {@code String}. 425 * @param inString the original {@code String} 426 * @param charsToDelete a set of characters to delete. 427 * E.g. "az\n" will delete 'a's, 'z's and new lines. 428 * @return the resulting {@code String} 429 */ 430 public static String deleteAny(String inString, String charsToDelete) { 431 if (!hasLength(inString) || !hasLength(charsToDelete)) { 432 return inString; 433 } 434 435 StringBuilder sb = new StringBuilder(inString.length()); 436 for (int i = 0; i < inString.length(); i++) { 437 char c = inString.charAt(i); 438 if (charsToDelete.indexOf(c) == -1) { 439 sb.append(c); 440 } 441 } 442 return sb.toString(); 443 } 444 445 446 //--------------------------------------------------------------------- 447 // Convenience methods for working with formatted Strings 448 //--------------------------------------------------------------------- 449 450 /** 451 * Quote the given {@code String} with single quotes. 452 * @param str the input {@code String} (e.g. "myString") 453 * @return the quoted {@code String} (e.g. "'myString'"), 454 * or {@code null} if the input was {@code null} 455 */ 456 public static String quote(String str) { 457 return (str != null ? "'" + str + "'" : null); 458 } 459 460 /** 461 * Turn the given Object into a {@code String} with single quotes 462 * if it is a {@code String}; keeping the Object as-is else. 463 * @param obj the input Object (e.g. "myString") 464 * @return the quoted {@code String} (e.g. "'myString'"), 465 * or the input object as-is if not a {@code String} 466 */ 467 public static Object quoteIfString(Object obj) { 468 return (obj instanceof String ? quote((String) obj) : obj); 469 } 470 471 /** 472 * Unqualify a string qualified by a '.' dot character. For example, 473 * "this.name.is.qualified", returns "qualified". 474 * @param qualifiedName the qualified name 475 */ 476 public static String unqualify(String qualifiedName) { 477 return unqualify(qualifiedName, '.'); 478 } 479 480 /** 481 * Unqualify a string qualified by a separator character. For example, 482 * "this:name:is:qualified" returns "qualified" if using a ':' separator. 483 * @param qualifiedName the qualified name 484 * @param separator the separator 485 */ 486 public static String unqualify(String qualifiedName, char separator) { 487 return qualifiedName.substring(qualifiedName.lastIndexOf(separator) + 1); 488 } 489 490 /** 491 * Capitalize a {@code String}, changing the first letter to 492 * upper case as per {@link Character#toUpperCase(char)}. 493 * No other letters are changed. 494 * @param str the {@code String} to capitalize 495 * @return the capitalized {@code String} 496 */ 497 public static String capitalize(String str) { 498 return changeFirstCharacterCase(str, true); 499 } 500 501 /** 502 * Uncapitalize a {@code String}, changing the first letter to 503 * lower case as per {@link Character#toLowerCase(char)}. 504 * No other letters are changed. 505 * @param str the {@code String} to uncapitalize 506 * @return the uncapitalized {@code String} 507 */ 508 public static String uncapitalize(String str) { 509 return changeFirstCharacterCase(str, false); 510 } 511 512 private static String changeFirstCharacterCase(String str, boolean capitalize) { 513 if (!hasLength(str)) { 514 return str; 515 } 516 517 char baseChar = str.charAt(0); 518 char updatedChar; 519 if (capitalize) { 520 updatedChar = Character.toUpperCase(baseChar); 521 } 522 else { 523 updatedChar = Character.toLowerCase(baseChar); 524 } 525 if (baseChar == updatedChar) { 526 return str; 527 } 528 529 char[] chars = str.toCharArray(); 530 chars[0] = updatedChar; 531 return new String(chars, 0, chars.length); 532 } 533 534 /** 535 * Extract the filename from the given Java resource path, 536 * e.g. {@code "mypath/myfile.txt" -> "myfile.txt"}. 537 * @param path the file path (may be {@code null}) 538 * @return the extracted filename, or {@code null} if none 539 */ 540 public static String getFilename(String path) { 541 if (path == null) { 542 return null; 543 } 544 545 int separatorIndex = path.lastIndexOf(FOLDER_SEPARATOR); 546 return (separatorIndex != -1 ? path.substring(separatorIndex + 1) : path); 547 } 548 549 /** 550 * Extract the filename extension from the given Java resource path, 551 * e.g. "mypath/myfile.txt" -> "txt". 552 * @param path the file path (may be {@code null}) 553 * @return the extracted filename extension, or {@code null} if none 554 */ 555 public static String getFilenameExtension(String path) { 556 if (path == null) { 557 return null; 558 } 559 560 int extIndex = path.lastIndexOf(EXTENSION_SEPARATOR); 561 if (extIndex == -1) { 562 return null; 563 } 564 565 int folderIndex = path.lastIndexOf(FOLDER_SEPARATOR); 566 if (folderIndex > extIndex) { 567 return null; 568 } 569 570 return path.substring(extIndex + 1); 571 } 572 573 /** 574 * Strip the filename extension from the given Java resource path, 575 * e.g. "mypath/myfile.txt" -> "mypath/myfile". 576 * @param path the file path 577 * @return the path with stripped filename extension 578 */ 579 public static String stripFilenameExtension(String path) { 580 if (path == null) { 581 return null; 582 } 583 584 int extIndex = path.lastIndexOf(EXTENSION_SEPARATOR); 585 if (extIndex == -1) { 586 return path; 587 } 588 589 int folderIndex = path.lastIndexOf(FOLDER_SEPARATOR); 590 if (folderIndex > extIndex) { 591 return path; 592 } 593 594 return path.substring(0, extIndex); 595 } 596 597 /** 598 * Apply the given relative path to the given Java resource path, 599 * assuming standard Java folder separation (i.e. "/" separators). 600 * @param path the path to start from (usually a full file path) 601 * @param relativePath the relative path to apply 602 * (relative to the full file path above) 603 * @return the full file path that results from applying the relative path 604 */ 605 public static String applyRelativePath(String path, String relativePath) { 606 int separatorIndex = path.lastIndexOf(FOLDER_SEPARATOR); 607 if (separatorIndex != -1) { 608 String newPath = path.substring(0, separatorIndex); 609 if (!relativePath.startsWith(FOLDER_SEPARATOR)) { 610 newPath += FOLDER_SEPARATOR; 611 } 612 return newPath + relativePath; 613 } 614 else { 615 return relativePath; 616 } 617 } 618 619 /** 620 * Normalize the path by suppressing sequences like "path/.." and 621 * inner simple dots. 622 * <p>The result is convenient for path comparison. For other uses, 623 * notice that Windows separators ("\") are replaced by simple slashes. 624 * @param path the original path 625 * @return the normalized path 626 */ 627 public static String cleanPath(String path) { 628 if (path == null) { 629 return null; 630 } 631 String pathToUse = replace(path, WINDOWS_FOLDER_SEPARATOR, FOLDER_SEPARATOR); 632 633 // Strip prefix from path to analyze, to not treat it as part of the 634 // first path element. This is necessary to correctly parse paths like 635 // "file:core/../core/io/Resource.class", where the ".." should just 636 // strip the first "core" directory while keeping the "file:" prefix. 637 int prefixIndex = pathToUse.indexOf(':'); 638 String prefix = ""; 639 if (prefixIndex != -1) { 640 prefix = pathToUse.substring(0, prefixIndex + 1); 641 if (prefix.contains(FOLDER_SEPARATOR)) { 642 prefix = ""; 643 } 644 else { 645 pathToUse = pathToUse.substring(prefixIndex + 1); 646 } 647 } 648 if (pathToUse.startsWith(FOLDER_SEPARATOR)) { 649 prefix = prefix + FOLDER_SEPARATOR; 650 pathToUse = pathToUse.substring(1); 651 } 652 653 String[] pathArray = delimitedListToStringArray(pathToUse, FOLDER_SEPARATOR); 654 List<String> pathElements = new LinkedList<String>(); 655 int tops = 0; 656 657 for (int i = pathArray.length - 1; i >= 0; i--) { 658 String element = pathArray[i]; 659 if (CURRENT_PATH.equals(element)) { 660 // Points to current directory - drop it. 661 } 662 else if (TOP_PATH.equals(element)) { 663 // Registering top path found. 664 tops++; 665 } 666 else { 667 if (tops > 0) { 668 // Merging path element with element corresponding to top path. 669 tops--; 670 } 671 else { 672 // Normal path element found. 673 pathElements.add(0, element); 674 } 675 } 676 } 677 678 // Remaining top paths need to be retained. 679 for (int i = 0; i < tops; i++) { 680 pathElements.add(0, TOP_PATH); 681 } 682 683 return prefix + collectionToDelimitedString(pathElements, FOLDER_SEPARATOR); 684 } 685 686 /** 687 * Compare two paths after normalization of them. 688 * @param path1 first path for comparison 689 * @param path2 second path for comparison 690 * @return whether the two paths are equivalent after normalization 691 */ 692 public static boolean pathEquals(String path1, String path2) { 693 return cleanPath(path1).equals(cleanPath(path2)); 694 } 695 696 /** 697 * Parse the given {@code String} representation into a {@link Locale}. 698 * <p>This is the inverse operation of {@link Locale#toString Locale's toString}. 699 * @param localeString the locale {@code String}: following {@code Locale's} 700 * {@code toString()} format ("en", "en_UK", etc), also accepting spaces as 701 * separators (as an alternative to underscores) 702 * @return a corresponding {@code Locale} instance, or {@code null} if none 703 * @throws IllegalArgumentException in case of an invalid locale specification 704 */ 705 public static Locale parseLocaleString(String localeString) { 706 String[] parts = tokenizeToStringArray(localeString, "_ ", false, false); 707 String language = (parts.length > 0 ? parts[0] : ""); 708 String country = (parts.length > 1 ? parts[1] : ""); 709 710 validateLocalePart(language); 711 validateLocalePart(country); 712 713 String variant = ""; 714 if (parts.length > 2) { 715 // There is definitely a variant, and it is everything after the country 716 // code sans the separator between the country code and the variant. 717 int endIndexOfCountryCode = localeString.indexOf(country, language.length()) + country.length(); 718 // Strip off any leading '_' and whitespace, what's left is the variant. 719 variant = trimLeadingWhitespace(localeString.substring(endIndexOfCountryCode)); 720 if (variant.startsWith("_")) { 721 variant = trimLeadingCharacter(variant, '_'); 722 } 723 } 724 return (language.length() > 0 ? new Locale(language, country, variant) : null); 725 } 726 727 private static void validateLocalePart(String localePart) { 728 for (int i = 0; i < localePart.length(); i++) { 729 char ch = localePart.charAt(i); 730 if (ch != ' ' && ch != '_' && ch != '#' && !Character.isLetterOrDigit(ch)) { 731 throw new IllegalArgumentException( 732 "Locale part \"" + localePart + "\" contains invalid characters"); 733 } 734 } 735 } 736 737 /** 738 * Determine the RFC 3066 compliant language tag, 739 * as used for the HTTP "Accept-Language" header. 740 * @param locale the Locale to transform to a language tag 741 * @return the RFC 3066 compliant language tag as {@code String} 742 */ 743 public static String toLanguageTag(Locale locale) { 744 return locale.getLanguage() + (hasText(locale.getCountry()) ? "-" + locale.getCountry() : ""); 745 } 746 747 /** 748 * Parse the given {@code timeZoneString} value into a {@link TimeZone}. 749 * @param timeZoneString the time zone {@code String}, following {@link TimeZone#getTimeZone(String)} 750 * but throwing {@link IllegalArgumentException} in case of an invalid time zone specification 751 * @return a corresponding {@link TimeZone} instance 752 * @throws IllegalArgumentException in case of an invalid time zone specification 753 */ 754 public static TimeZone parseTimeZoneString(String timeZoneString) { 755 TimeZone timeZone = TimeZone.getTimeZone(timeZoneString); 756 if ("GMT".equals(timeZone.getID()) && !timeZoneString.startsWith("GMT")) { 757 // We don't want that GMT fallback... 758 throw new IllegalArgumentException("Invalid time zone specification '" + timeZoneString + "'"); 759 } 760 return timeZone; 761 } 762 763 764 //--------------------------------------------------------------------- 765 // Convenience methods for working with String arrays 766 //--------------------------------------------------------------------- 767 768 /** 769 * Copy the given {@link Collection} into a {@code String} array. 770 * <p>The {@code Collection} must contain {@code String} elements only. 771 * @param collection the {@code Collection} to copy 772 * @return the resulting {@code String} array 773 */ 774 public static String[] toStringArray(Collection<String> collection) { 775 if (collection == null) { 776 return null; 777 } 778 return collection.toArray(new String[collection.size()]); 779 } 780 781 /** 782 * Copy the given {@link Enumeration} into a {@code String} array. 783 * <p>The {@code Enumeration} must contain {@code String} elements only. 784 * @param enumeration the {@code Enumeration} to copy 785 * @return the resulting {@code String} array 786 */ 787 public static String[] toStringArray(Enumeration<String> enumeration) { 788 if (enumeration == null) { 789 return null; 790 } 791 return toStringArray(Collections.list(enumeration)); 792 } 793 794 /** 795 * Append the given {@code String} to the given {@code String} array, 796 * returning a new array consisting of the input array contents plus 797 * the given {@code String}. 798 * @param array the array to append to (can be {@code null}) 799 * @param str the {@code String} to append 800 * @return the new array (never {@code null}) 801 */ 802 public static String[] addStringToArray(String[] array, String str) { 803 if (ObjectUtils.isEmpty(array)) { 804 return new String[] {str}; 805 } 806 807 String[] newArr = new String[array.length + 1]; 808 System.arraycopy(array, 0, newArr, 0, array.length); 809 newArr[array.length] = str; 810 return newArr; 811 } 812 813 /** 814 * Concatenate the given {@code String} arrays into one, 815 * with overlapping array elements included twice. 816 * <p>The order of elements in the original arrays is preserved. 817 * @param array1 the first array (can be {@code null}) 818 * @param array2 the second array (can be {@code null}) 819 * @return the new array ({@code null} if both given arrays were {@code null}) 820 */ 821 public static String[] concatenateStringArrays(String[] array1, String[] array2) { 822 if (ObjectUtils.isEmpty(array1)) { 823 return array2; 824 } 825 if (ObjectUtils.isEmpty(array2)) { 826 return array1; 827 } 828 829 String[] newArr = new String[array1.length + array2.length]; 830 System.arraycopy(array1, 0, newArr, 0, array1.length); 831 System.arraycopy(array2, 0, newArr, array1.length, array2.length); 832 return newArr; 833 } 834 835 /** 836 * Merge the given {@code String} arrays into one, with overlapping 837 * array elements only included once. 838 * <p>The order of elements in the original arrays is preserved 839 * (with the exception of overlapping elements, which are only 840 * included on their first occurrence). 841 * @param array1 the first array (can be {@code null}) 842 * @param array2 the second array (can be {@code null}) 843 * @return the new array ({@code null} if both given arrays were {@code null}) 844 * @deprecated as of 4.3.15, in favor of manual merging via {@link LinkedHashSet} 845 * (with every entry included at most once, even entries within the first array) 846 */ 847 @Deprecated 848 public static String[] mergeStringArrays(String[] array1, String[] array2) { 849 if (ObjectUtils.isEmpty(array1)) { 850 return array2; 851 } 852 if (ObjectUtils.isEmpty(array2)) { 853 return array1; 854 } 855 856 List<String> result = new ArrayList<String>(); 857 result.addAll(Arrays.asList(array1)); 858 for (String str : array2) { 859 if (!result.contains(str)) { 860 result.add(str); 861 } 862 } 863 return toStringArray(result); 864 } 865 866 /** 867 * Sort the given {@code String} array if necessary. 868 * @param array the original array 869 * @return the sorted array (never {@code null}) 870 */ 871 public static String[] sortStringArray(String[] array) { 872 if (ObjectUtils.isEmpty(array)) { 873 return new String[0]; 874 } 875 876 Arrays.sort(array); 877 return array; 878 } 879 880 /** 881 * Trim the elements of the given {@code String} array, 882 * calling {@code String.trim()} on each of them. 883 * @param array the original {@code String} array 884 * @return the resulting array (of the same size) with trimmed elements 885 */ 886 public static String[] trimArrayElements(String[] array) { 887 if (ObjectUtils.isEmpty(array)) { 888 return new String[0]; 889 } 890 891 String[] result = new String[array.length]; 892 for (int i = 0; i < array.length; i++) { 893 String element = array[i]; 894 result[i] = (element != null ? element.trim() : null); 895 } 896 return result; 897 } 898 899 /** 900 * Remove duplicate strings from the given array. 901 * <p>As of 4.2, it preserves the original order, as it uses a {@link LinkedHashSet}. 902 * @param array the {@code String} array 903 * @return an array without duplicates, in natural sort order 904 */ 905 public static String[] removeDuplicateStrings(String[] array) { 906 if (ObjectUtils.isEmpty(array)) { 907 return array; 908 } 909 910 Set<String> set = new LinkedHashSet<String>(); 911 for (String element : array) { 912 set.add(element); 913 } 914 return toStringArray(set); 915 } 916 917 /** 918 * Split a {@code String} at the first occurrence of the delimiter. 919 * Does not include the delimiter in the result. 920 * @param toSplit the string to split 921 * @param delimiter to split the string up with 922 * @return a two element array with index 0 being before the delimiter, and 923 * index 1 being after the delimiter (neither element includes the delimiter); 924 * or {@code null} if the delimiter wasn't found in the given input {@code String} 925 */ 926 public static String[] split(String toSplit, String delimiter) { 927 if (!hasLength(toSplit) || !hasLength(delimiter)) { 928 return null; 929 } 930 int offset = toSplit.indexOf(delimiter); 931 if (offset < 0) { 932 return null; 933 } 934 935 String beforeDelimiter = toSplit.substring(0, offset); 936 String afterDelimiter = toSplit.substring(offset + delimiter.length()); 937 return new String[] {beforeDelimiter, afterDelimiter}; 938 } 939 940 /** 941 * Take an array of strings and split each element based on the given delimiter. 942 * A {@code Properties} instance is then generated, with the left of the delimiter 943 * providing the key, and the right of the delimiter providing the value. 944 * <p>Will trim both the key and value before adding them to the {@code Properties}. 945 * @param array the array to process 946 * @param delimiter to split each element using (typically the equals symbol) 947 * @return a {@code Properties} instance representing the array contents, 948 * or {@code null} if the array to process was {@code null} or empty 949 */ 950 public static Properties splitArrayElementsIntoProperties(String[] array, String delimiter) { 951 return splitArrayElementsIntoProperties(array, delimiter, null); 952 } 953 954 /** 955 * Take an array of strings and split each element based on the given delimiter. 956 * A {@code Properties} instance is then generated, with the left of the 957 * delimiter providing the key, and the right of the delimiter providing the value. 958 * <p>Will trim both the key and value before adding them to the 959 * {@code Properties} instance. 960 * @param array the array to process 961 * @param delimiter to split each element using (typically the equals symbol) 962 * @param charsToDelete one or more characters to remove from each element 963 * prior to attempting the split operation (typically the quotation mark 964 * symbol), or {@code null} if no removal should occur 965 * @return a {@code Properties} instance representing the array contents, 966 * or {@code null} if the array to process was {@code null} or empty 967 */ 968 public static Properties splitArrayElementsIntoProperties( 969 String[] array, String delimiter, String charsToDelete) { 970 971 if (ObjectUtils.isEmpty(array)) { 972 return null; 973 } 974 975 Properties result = new Properties(); 976 for (String element : array) { 977 if (charsToDelete != null) { 978 element = deleteAny(element, charsToDelete); 979 } 980 String[] splittedElement = split(element, delimiter); 981 if (splittedElement == null) { 982 continue; 983 } 984 result.setProperty(splittedElement[0].trim(), splittedElement[1].trim()); 985 } 986 return result; 987 } 988 989 /** 990 * Tokenize the given {@code String} into a {@code String} array via a 991 * {@link StringTokenizer}. 992 * <p>Trims tokens and omits empty tokens. 993 * <p>The given {@code delimiters} string can consist of any number of 994 * delimiter characters. Each of those characters can be used to separate 995 * tokens. A delimiter is always a single character; for multi-character 996 * delimiters, consider using {@link #delimitedListToStringArray}. 997 * @param str the {@code String} to tokenize 998 * @param delimiters the delimiter characters, assembled as a {@code String} 999 * (each of the characters is individually considered as a delimiter) 1000 * @return an array of the tokens 1001 * @see java.util.StringTokenizer 1002 * @see String#trim() 1003 * @see #delimitedListToStringArray 1004 */ 1005 public static String[] tokenizeToStringArray(String str, String delimiters) { 1006 return tokenizeToStringArray(str, delimiters, true, true); 1007 } 1008 1009 /** 1010 * Tokenize the given {@code String} into a {@code String} array via a 1011 * {@link StringTokenizer}. 1012 * <p>The given {@code delimiters} string can consist of any number of 1013 * delimiter characters. Each of those characters can be used to separate 1014 * tokens. A delimiter is always a single character; for multi-character 1015 * delimiters, consider using {@link #delimitedListToStringArray}. 1016 * @param str the {@code String} to tokenize 1017 * @param delimiters the delimiter characters, assembled as a {@code String} 1018 * (each of the characters is individually considered as a delimiter) 1019 * @param trimTokens trim the tokens via {@link String#trim()} 1020 * @param ignoreEmptyTokens omit empty tokens from the result array 1021 * (only applies to tokens that are empty after trimming; StringTokenizer 1022 * will not consider subsequent delimiters as token in the first place). 1023 * @return an array of the tokens 1024 * @see java.util.StringTokenizer 1025 * @see String#trim() 1026 * @see #delimitedListToStringArray 1027 */ 1028 public static String[] tokenizeToStringArray( 1029 String str, String delimiters, boolean trimTokens, boolean ignoreEmptyTokens) { 1030 1031 if (str == null) { 1032 return null; 1033 } 1034 1035 StringTokenizer st = new StringTokenizer(str, delimiters); 1036 List<String> tokens = new ArrayList<String>(); 1037 while (st.hasMoreTokens()) { 1038 String token = st.nextToken(); 1039 if (trimTokens) { 1040 token = token.trim(); 1041 } 1042 if (!ignoreEmptyTokens || token.length() > 0) { 1043 tokens.add(token); 1044 } 1045 } 1046 return toStringArray(tokens); 1047 } 1048 1049 /** 1050 * Take a {@code String} that is a delimited list and convert it into a 1051 * {@code String} array. 1052 * <p>A single {@code delimiter} may consist of more than one character, 1053 * but it will still be considered as a single delimiter string, rather 1054 * than as bunch of potential delimiter characters, in contrast to 1055 * {@link #tokenizeToStringArray}. 1056 * @param str the input {@code String} 1057 * @param delimiter the delimiter between elements (this is a single delimiter, 1058 * rather than a bunch individual delimiter characters) 1059 * @return an array of the tokens in the list 1060 * @see #tokenizeToStringArray 1061 */ 1062 public static String[] delimitedListToStringArray(String str, String delimiter) { 1063 return delimitedListToStringArray(str, delimiter, null); 1064 } 1065 1066 /** 1067 * Take a {@code String} that is a delimited list and convert it into 1068 * a {@code String} array. 1069 * <p>A single {@code delimiter} may consist of more than one character, 1070 * but it will still be considered as a single delimiter string, rather 1071 * than as bunch of potential delimiter characters, in contrast to 1072 * {@link #tokenizeToStringArray}. 1073 * @param str the input {@code String} 1074 * @param delimiter the delimiter between elements (this is a single delimiter, 1075 * rather than a bunch individual delimiter characters) 1076 * @param charsToDelete a set of characters to delete; useful for deleting unwanted 1077 * line breaks: e.g. "\r\n\f" will delete all new lines and line feeds in a {@code String} 1078 * @return an array of the tokens in the list 1079 * @see #tokenizeToStringArray 1080 */ 1081 public static String[] delimitedListToStringArray(String str, String delimiter, String charsToDelete) { 1082 if (str == null) { 1083 return new String[0]; 1084 } 1085 if (delimiter == null) { 1086 return new String[] {str}; 1087 } 1088 1089 List<String> result = new ArrayList<String>(); 1090 if ("".equals(delimiter)) { 1091 for (int i = 0; i < str.length(); i++) { 1092 result.add(deleteAny(str.substring(i, i + 1), charsToDelete)); 1093 } 1094 } 1095 else { 1096 int pos = 0; 1097 int delPos; 1098 while ((delPos = str.indexOf(delimiter, pos)) != -1) { 1099 result.add(deleteAny(str.substring(pos, delPos), charsToDelete)); 1100 pos = delPos + delimiter.length(); 1101 } 1102 if (str.length() > 0 && pos <= str.length()) { 1103 // Add rest of String, but not in case of empty input. 1104 result.add(deleteAny(str.substring(pos), charsToDelete)); 1105 } 1106 } 1107 return toStringArray(result); 1108 } 1109 1110 /** 1111 * Convert a comma delimited list (e.g., a row from a CSV file) into an 1112 * array of strings. 1113 * @param str the input {@code String} 1114 * @return an array of strings, or the empty array in case of empty input 1115 */ 1116 public static String[] commaDelimitedListToStringArray(String str) { 1117 return delimitedListToStringArray(str, ","); 1118 } 1119 1120 /** 1121 * Convert a comma delimited list (e.g., a row from a CSV file) into a set. 1122 * <p>Note that this will suppress duplicates, and as of 4.2, the elements in 1123 * the returned set will preserve the original order in a {@link LinkedHashSet}. 1124 * @param str the input {@code String} 1125 * @return a set of {@code String} entries in the list 1126 * @see #removeDuplicateStrings(String[]) 1127 */ 1128 public static Set<String> commaDelimitedListToSet(String str) { 1129 Set<String> set = new LinkedHashSet<String>(); 1130 String[] tokens = commaDelimitedListToStringArray(str); 1131 for (String token : tokens) { 1132 set.add(token); 1133 } 1134 return set; 1135 } 1136 1137 /** 1138 * Convert a {@link Collection} to a delimited {@code String} (e.g. CSV). 1139 * <p>Useful for {@code toString()} implementations. 1140 * @param coll the {@code Collection} to convert 1141 * @param delim the delimiter to use (typically a ",") 1142 * @param prefix the {@code String} to start each element with 1143 * @param suffix the {@code String} to end each element with 1144 * @return the delimited {@code String} 1145 */ 1146 public static String collectionToDelimitedString(Collection<?> coll, String delim, String prefix, String suffix) { 1147 if (CollectionUtils.isEmpty(coll)) { 1148 return ""; 1149 } 1150 1151 StringBuilder sb = new StringBuilder(); 1152 Iterator<?> it = coll.iterator(); 1153 while (it.hasNext()) { 1154 sb.append(prefix).append(it.next()).append(suffix); 1155 if (it.hasNext()) { 1156 sb.append(delim); 1157 } 1158 } 1159 return sb.toString(); 1160 } 1161 1162 /** 1163 * Convert a {@code Collection} into a delimited {@code String} (e.g. CSV). 1164 * <p>Useful for {@code toString()} implementations. 1165 * @param coll the {@code Collection} to convert 1166 * @param delim the delimiter to use (typically a ",") 1167 * @return the delimited {@code String} 1168 */ 1169 public static String collectionToDelimitedString(Collection<?> coll, String delim) { 1170 return collectionToDelimitedString(coll, delim, "", ""); 1171 } 1172 1173 /** 1174 * Convert a {@code Collection} into a delimited {@code String} (e.g., CSV). 1175 * <p>Useful for {@code toString()} implementations. 1176 * @param coll the {@code Collection} to convert 1177 * @return the delimited {@code String} 1178 */ 1179 public static String collectionToCommaDelimitedString(Collection<?> coll) { 1180 return collectionToDelimitedString(coll, ","); 1181 } 1182 1183 /** 1184 * Convert a {@code String} array into a delimited {@code String} (e.g. CSV). 1185 * <p>Useful for {@code toString()} implementations. 1186 * @param arr the array to display 1187 * @param delim the delimiter to use (typically a ",") 1188 * @return the delimited {@code String} 1189 */ 1190 public static String arrayToDelimitedString(Object[] arr, String delim) { 1191 if (ObjectUtils.isEmpty(arr)) { 1192 return ""; 1193 } 1194 if (arr.length == 1) { 1195 return ObjectUtils.nullSafeToString(arr[0]); 1196 } 1197 1198 StringBuilder sb = new StringBuilder(); 1199 for (int i = 0; i < arr.length; i++) { 1200 if (i > 0) { 1201 sb.append(delim); 1202 } 1203 sb.append(arr[i]); 1204 } 1205 return sb.toString(); 1206 } 1207 1208 /** 1209 * Convert a {@code String} array into a comma delimited {@code String} 1210 * (i.e., CSV). 1211 * <p>Useful for {@code toString()} implementations. 1212 * @param arr the array to display 1213 * @return the delimited {@code String} 1214 */ 1215 public static String arrayToCommaDelimitedString(Object[] arr) { 1216 return arrayToDelimitedString(arr, ","); 1217 } 1218 1219}