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