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