001package com.hfg.util; 002 003 004import java.text.DecimalFormat; 005import java.text.NumberFormat; 006import java.text.ParsePosition; 007import java.util.ArrayList; 008import java.util.HashMap; 009import java.util.HashSet; 010import java.util.List; 011import java.util.Locale; 012import java.util.Map; 013import java.util.Set; 014import java.util.regex.Pattern; 015import java.util.regex.Matcher; 016import java.util.Collection; 017 018import com.hfg.math.Range; 019import com.hfg.util.collection.CollectionUtil; 020 021//------------------------------------------------------------------------------ 022/** 023 * General String utility functions. 024 * 025 * @author J. Alex Taylor, hairyfatguy.com 026 */ 027//------------------------------------------------------------------------------ 028// com.hfg XML/HTML Coding Library 029// 030// This library is free software; you can redistribute it and/or 031// modify it under the terms of the GNU Lesser General Public 032// License as published by the Free Software Foundation; either 033// version 2.1 of the License, or (at your option) any later version. 034// 035// This library is distributed in the hope that it will be useful, 036// but WITHOUT ANY WARRANTY; without even the implied warranty of 037// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 038// Lesser General Public License for more details. 039// 040// You should have received a copy of the GNU Lesser General Public 041// License along with this library; if not, write to the Free Software 042// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 043// 044// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com 045// jataylor@hairyfatguy.com 046//------------------------------------------------------------------------------ 047 048public class StringUtil 049{ 050 private static final Pattern sWhitespacePattern = Pattern.compile("[\\s\u00A0]+"); // \u00A0 is the non-breaking space character 051 private static final Pattern sTrailingWhitespacePattern = Pattern.compile("[\\s\u00A0]*$"); // \u00A0 is the non-breaking space character 052 private static final Pattern sSingleQuotePattern = Pattern.compile("(^|[^\\\\])'"); 053 private static final Pattern sDoubleQuotePattern = Pattern.compile("(^|[^\\\\])\""); 054 private static final Pattern sContainsLowerCasePattern = Pattern.compile("[a-z]"); 055 private static final Pattern sContainsUpperCasePattern = Pattern.compile("[A-Z]"); 056 057 private static final String sLineSeparator = System.getProperty("line.separator"); 058 059 private static Map<Character.UnicodeBlock, Range<Integer>> sUnicodeBlockRangeMap; 060 private static Map<Character.UnicodeScript, List<Integer>> sUnicodeScriptMap; 061 062 //************************************************************************** 063 // PUBLIC FUNCTIONS 064 //************************************************************************** 065 066 //--------------------------------------------------------------------------- 067 public static String getSystemLineSeparator() 068 { 069 return sLineSeparator; 070 } 071 072 //--------------------------------------------------------------------------- 073 /** 074 Returns whether the specified String contains non-whitespace content. 075 */ 076 public static boolean isSet(CharSequence inString) 077 { 078 return (inString != null && inString.toString().trim().length() > 0); 079 } 080 081 //--------------------------------------------------------------------------- 082 /** 083 Joins the elements of a String[] together using a space as the separator. 084 */ 085 public static String join(String[] pieces) 086 { 087 return join(pieces, " "); 088 } 089 090 //--------------------------------------------------------------------------- 091 /** 092 Joins the elements of a String[] together using the specified String. 093 */ 094 public static String join(String[] pieces, String inSeperator) 095 { 096 StringBuffer buffer = new StringBuffer(); 097 098 if (pieces != null) 099 { 100 for (int i = 0; i < pieces.length; i++) 101 { 102 if (i > 0) buffer.append(inSeperator); 103 buffer.append(pieces[i]); 104 } 105 } 106 107 return buffer.toString(); 108 } 109 110 //--------------------------------------------------------------------------- 111 /** 112 Joins the elements of an Object[] together using the specified String. 113 */ 114 public static String join(Object[] inArray, String inSeperator) 115 { 116 StringBuffer buffer = new StringBuffer(); 117 118 if (inArray != null) 119 { 120 for (int i = 0; i < inArray.length; i++) 121 { 122 if (i > 0) buffer.append(inSeperator); 123 buffer.append(inArray[i]); 124 } 125 } 126 127 return buffer.toString(); 128 } 129 130 //--------------------------------------------------------------------------- 131 /** 132 Joins the elements of a int[] together using the specified String. 133 */ 134 public static String join(int[] inArray, String inSeperator) 135 { 136 StringBuffer buffer = new StringBuffer(); 137 138 if (inArray != null) 139 { 140 for (int i = 0; i < inArray.length; i++) 141 { 142 if (i > 0) buffer.append(inSeperator); 143 buffer.append(inArray[i]); 144 } 145 } 146 147 return buffer.toString(); 148 } 149 150 //--------------------------------------------------------------------------- 151 /** 152 Joins the elements of a Collection together using the specified String. 153 */ 154 public static String join(Collection pieces, String inSeperator) 155 { 156 StringBuilder buffer = null; 157 158 if (pieces != null) 159 { 160 for (Object obj : pieces) 161 { 162 if (buffer != null) 163 { 164 buffer.append(inSeperator); 165 } 166 else 167 { 168 buffer = new StringBuilder(); 169 } 170 buffer.append(obj); 171 } 172 } 173 174 return buffer != null ? buffer.toString() : null; 175 } 176 177 //--------------------------------------------------------------------------- 178 /** 179 Joins the elements of a Collection together using the specified String. 180 Enclose each element with single quotation marks. 181 Author: Esther Ting 182 */ 183 public static String joinIncludeSingleQuote(Collection pieces, String inSeperator) 184 { 185 StringBuilder buffer = null; 186 187 if (pieces != null) 188 { 189 for (Object obj : pieces) 190 { 191 if (buffer != null) 192 { 193 buffer.append(inSeperator); 194 } 195 else 196 { 197 buffer = new StringBuilder(); 198 } 199 buffer.append(StringUtil.singleQuote(obj)); 200 } 201 } 202 203 return buffer != null ? buffer.toString() : null; 204 } 205 206 207 //--------------------------------------------------------------------------- 208 /** 209 Returns a String composed of inString repeated inNum times. 210 */ 211 public static String polyString(String inString, int inNum) 212 { 213 StringBuffer buffer = new StringBuffer(inNum); 214 215 for (int i = 0; i < inNum; i++) 216 { 217 buffer.append(inString); 218 } 219 220 return buffer.toString(); 221 } 222 223 //--------------------------------------------------------------------------- 224 /** 225 Returns a String composed of inChar repeated inNum times. 226 */ 227 public static String polyChar(char inChar, int inNum) 228 { 229 StringBuffer buffer = new StringBuffer(inNum); 230 231 for (int i = 0; i < inNum; i++) 232 { 233 buffer.append(inChar); 234 } 235 236 return buffer.toString(); 237 } 238 239 //--------------------------------------------------------------------------- 240 /** 241 Returns a String where all instances of inTarget in inString are replaced by 242 inReplacement. 243 */ 244 public static String replaceAll(CharSequence inString, String inTarget, String inReplacement) 245 { 246 String outString = null; 247 if (inString != null) 248 { 249 outString = inString.toString(); 250 251 if (inTarget != null && inReplacement != null) 252 { 253 int startFromIndex = inString.length(); 254 int index = outString.lastIndexOf(inTarget, startFromIndex); 255 256 // Bail quickly if no replacements are necessary. 257 if (index >= 0) 258 { 259 StringBuilder buffer = new StringBuilder(outString); 260 261 do 262 { 263 buffer.replace(index, index + inTarget.length(), inReplacement); 264 startFromIndex = index - 1; 265 } 266 while ((index = outString.lastIndexOf(inTarget, startFromIndex)) >= 0); 267 268 outString = buffer.toString(); 269 } 270 } 271 } 272 273 return outString; 274 } 275 276 //--------------------------------------------------------------------------- 277 /** 278 Returns a String where all portions of inString matching the inTargetRegexp 279 are replaced by inReplacement. 280 */ 281 public static String replaceAllRegexp(CharSequence inString, String inRegexpTarget, String inReplacement) 282 { 283 String outString = null; 284 if (inString != null) 285 { 286 outString = inString.toString(); 287 288 if (inRegexpTarget != null 289 && inReplacement != null) 290 { 291 Pattern p = Pattern.compile(inRegexpTarget); 292 293 outString = replaceAllRegexp(inString, p, inReplacement); 294 } 295 } 296 297 return outString; 298 } 299 300 //--------------------------------------------------------------------------- 301 /** 302 Returns a String where all portions of inString matching the inPattern 303 are replaced by inReplacement. 304 */ 305 public static String replaceAllRegexp(CharSequence inString, Pattern inPattern, String inReplacement) 306 { 307 String outString = null; 308 if (inString != null) 309 { 310 outString = inString.toString(); 311 312 if (inPattern != null 313 && inReplacement != null) 314 { 315 Matcher m = inPattern.matcher(inString); 316 317 outString = m.replaceAll(inReplacement); 318 } 319 } 320 321 return outString; 322 } 323 324 //--------------------------------------------------------------------------- 325 public static String trimTrailingWhitespace(CharSequence inString) 326 { 327 return replaceAllRegexp(inString, sTrailingWhitespacePattern, ""); 328 } 329 330 //--------------------------------------------------------------------------- 331 public static String removeWhitespace(CharSequence inString) 332 { 333 return replaceAllRegexp(inString, sWhitespacePattern, ""); 334 } 335 336 //--------------------------------------------------------------------------- 337 public static String replaceWhitespace(CharSequence inString, CharSequence inWhitespaceReplacementString) 338 { 339 return replaceAllRegexp(inString, sWhitespacePattern, inWhitespaceReplacementString != null ? inWhitespaceReplacementString.toString() : ""); 340 } 341 342 //--------------------------------------------------------------------------- 343 /** 344 Returns the specified String surrounded by quotes and escaping any internal quotes. 345 */ 346 public static String quote(Object inObject) 347 { 348 StringBuilder buffer = new StringBuilder("\""); 349 if (inObject != null) 350 { 351 buffer.append(replaceAllRegexp(inObject.toString(), sDoubleQuotePattern, "$1\\\\\"")); 352 } 353 354 buffer.append("\""); 355 return buffer.toString(); 356 } 357 358 359 //--------------------------------------------------------------------------- 360 /** 361 Returns the specified String surrounded by single quotes and escaping any internal single quotes. 362 */ 363 public static String singleQuote(Object inObject) 364 { 365 StringBuilder buffer = new StringBuilder("'"); 366 if (inObject != null) 367 { 368 buffer.append(replaceAllRegexp(inObject.toString(), sSingleQuotePattern, "$1\\\\'")); 369 } 370 371 buffer.append("'"); 372 return buffer.toString(); 373 } 374 375 //--------------------------------------------------------------------------- 376 /** 377 Returns whether or not the specified String is contained within surrounding quotes. 378 */ 379 public static boolean isQuoted(CharSequence inString) 380 { 381 boolean result = false; 382 if (inString != null) 383 { 384 String string = inString.toString(); 385 result = ((string.startsWith("\"") 386 && string.endsWith("\"")) 387 || (string.startsWith("\'") 388 && string.endsWith("\'"))); 389 } 390 391 return result; 392 } 393 394 //--------------------------------------------------------------------------- 395 /** 396 Returns the specified String while removing surrounding quotes if present. 397 */ 398 public static String unquote(CharSequence inString) 399 { 400 String result = null; 401 if (inString != null) 402 { 403 result = inString.toString(); 404 405 if (isQuoted(result)) 406 { 407 result = result.substring(1, result.length() - 1); 408 } 409 } 410 411 return result; 412 } 413 414 //--------------------------------------------------------------------------- 415 /** 416 Returns the number of times the specified character appears in the specified String. 417 */ 418 public static int getCharCount(CharSequence inString, char inChar) 419 { 420 int count = 0; 421 422 if (isSet(inString)) 423 { 424 for (int i = 0; i < inString.length(); i++) 425 { 426 if (inString.charAt(i) == inChar) 427 { 428 count++; 429 } 430 } 431 } 432 433 return count; 434 } 435 436 //--------------------------------------------------------------------------- 437 public static String scramble(CharSequence inString) 438 { 439 char[] chars = inString.toString().toCharArray(); 440 ArrayUtil.shuffle(chars); 441 return new String(chars); 442 } 443 444 //--------------------------------------------------------------------------- 445 public static String applySubstitutionMap(CharSequence inString, Map<String, String> inSubstitutionMap) 446 { 447 String substitutedString = inString.toString(); 448 449 for (String key : inSubstitutionMap.keySet()) 450 { 451 substitutedString = substitutedString.replaceAll(key, inSubstitutionMap.get(key)); 452 } 453 454 return substitutedString; 455 } 456 457 //--------------------------------------------------------------------------- 458 public static String applySubstitutionMap(CharSequence inString, Map<String, String> inSubstitutionMap, String inDelimiter) 459 { 460 StringBuilder buffer = new StringBuilder(inString.toString()); 461 462 Pattern pattern = Pattern.compile("(" + inDelimiter + "(.+?)" + inDelimiter + ")"); 463 464 Set<String> unsubstitutedTokens = new HashSet<>(25); 465 466 Matcher m = pattern.matcher(buffer); 467 int index = 0; 468 while (m.find(index)) 469 { 470 String token = m.group(2); 471 if (inSubstitutionMap.containsKey(token)) 472 { 473 String replacementString = inSubstitutionMap.get(token); 474 475 buffer.replace(m.start(1), m.end(1), replacementString); 476 477 index = m.start(1) + replacementString.length(); 478 } 479 else 480 { 481 unsubstitutedTokens.add(token); 482 index = m.end(1) + 1; 483 } 484 } 485 486 if (CollectionUtil.hasValues(unsubstitutedTokens)) 487 { 488 throw new RuntimeException("Unsubstituted tokens: [" + StringUtil.join(unsubstitutedTokens, ", ") + "]!"); 489 } 490 491 return buffer.toString(); 492 } 493 494 //--------------------------------------------------------------------------- 495 public static String stripHTMLTags(String inString) 496 { 497 return (inString != null ? inString.replaceAll("\\<.*?\\>", "") : null); 498 } 499 500 //--------------------------------------------------------------------------- 501 /** 502 If the specified string is longer than the specified maximum length, a truncated 503 string is returned. Otherwise the original string is returned. 504 @param inString the original string value 505 @param inMaxLength the maximum lenght allowed for the string 506 @return the processed string value 507 */ 508 public static String truncate(String inString, int inMaxLength) 509 { 510 if (inMaxLength < 0) 511 { 512 inMaxLength = 0; 513 } 514 515 return (inString != null ? (inString.length() > inMaxLength ? inString.substring(0, inMaxLength) : inString) : null); 516 } 517 518 //--------------------------------------------------------------------------- 519 /** 520 Truncates the string and adds an ellipsis (β¦) if it exceeds the specified length. 521 Otherwise the original string is returned. 522 @param inString the string to be truncated 523 @param inMaxLength the length beyond which the specified string should be truncated 524 @return the truncated string 525 */ 526 public static String truncateWithEllipsis(String inString, int inMaxLength) 527 { 528 String result = null; 529 530 if (StringUtil.isSet(inString)) 531 { 532 if (inString.length() <= inMaxLength) 533 { 534 // No need to truncate 535 result = inString; 536 } 537 else 538 { 539 result = inString.substring(0, inMaxLength) + 'β¦'; 540 } 541 } 542 543 return result; 544 } 545 546 //--------------------------------------------------------------------------- 547 /** 548 Returns whether or not the specified string contains a lower-case letter. 549 @param inString the string value to be checked 550 @return whether or not the specified string contains a lower-case letter 551 */ 552 public static boolean containsLowerCase(String inString) 553 { 554 return (inString != null ? sContainsLowerCasePattern.matcher(inString).find() : false); 555 } 556 557 //--------------------------------------------------------------------------- 558 /** 559 Returns whether or not the specified string contains an upper-case letter. 560 @param inString the string value to be checked 561 @return whether or not the specified string contains an upper-case letter 562 */ 563 public static boolean containsUpperCase(String inString) 564 { 565 return (inString != null ? sContainsUpperCasePattern.matcher(inString).find() : false); 566 } 567 568 //--------------------------------------------------------------------------- 569 /** 570 Returns the input string with the first character capitalized. 571 @param inString the string value to be capitalized 572 @return the input string with the first character capitalized 573 */ 574 public static String capitalize(String inString) 575 { 576 return (isSet(inString) ? Character.toUpperCase(inString.charAt(0)) + (inString.length() > 1 ? inString.substring(1) : "") : inString); 577 } 578 579 580 //--------------------------------------------------------------------------- 581 /** 582 Returns a unicode string for the specified number as a superscript. 583 @param inNumericValue the numeric value to be superscripted 584 @return the unicode string for the specified number as a superscript 585 */ 586 public static String toSuperscript(int inNumericValue) 587 { 588 return toSuperscript(inNumericValue + ""); 589 } 590 591 //--------------------------------------------------------------------------- 592 /** 593 Returns a unicode string for the specified number as a superscript. 594 @param inString the numeric value to be superscripted 595 @return the unicode string for the specified number as a superscript 596 */ 597 public static String toSuperscript(String inString) 598 { 599 StringBuilder buffer = new StringBuilder(); 600 for (int i = 0; i < inString.length(); i++) 601 { 602 char superscript; 603 switch (inString.charAt(i)) 604 { 605 case '0': 606 superscript = '\u2070'; 607 break; 608 case '1': 609 superscript = 0xB9; 610 break; 611 case '2': 612 superscript = 0xB2; 613 break; 614 case '3': 615 superscript = 0xB3; 616 break; 617 case '4': 618 superscript = '\u2074'; 619 break; 620 case '5': 621 superscript = '\u2075'; 622 break; 623 case '6': 624 superscript = '\u2076'; 625 break; 626 case '7': 627 superscript = '\u2077'; 628 break; 629 case '8': 630 superscript = '\u2078'; 631 break; 632 case '9': 633 superscript = '\u2079'; 634 break; 635 case '+': 636 superscript = '\u207A'; 637 break; 638 case '-': 639 superscript = '\u207B'; 640 break; 641 case '=': 642 superscript = '\u207C'; 643 break; 644 case '(': 645 superscript = '\u207D'; 646 break; 647 case ')': 648 superscript = '\u207E'; 649 break; 650 case 'n': 651 superscript = '\u207F'; 652 break; 653 default: 654 throw new RuntimeException("Unsupported superscript char: " + StringUtil.singleQuote(inString.charAt(i)) + "!"); 655 } 656 657 buffer.append(superscript); 658 } 659 660 return buffer.toString(); 661 } 662 663 //--------------------------------------------------------------------------- 664 /** 665 Returns a unicode string for the specified number as a subscript. 666 @param inNumericValue the numeric value to be subscripted 667 @return the unicode string for the specified number as a subscript 668 */ 669 public static String toSubscript(int inNumericValue) 670 { 671 return toSubscript(inNumericValue + ""); 672 } 673 674 //--------------------------------------------------------------------------- 675 /** 676 Returns a unicode string for the specified number as a subscript. 677 @param inString the numeric value to be subscripted 678 @return the unicode string for the specified number as a subscript 679 */ 680 public static String toSubscript(String inString) 681 { 682 StringBuilder buffer = new StringBuilder(); 683 for (int i = 0; i < inString.length(); i++) 684 { 685 char subscript; 686 switch (inString.charAt(i)) 687 { 688 case '0': 689 subscript = '\u2080'; 690 break; 691 case '1': 692 subscript = '\u2081'; 693 break; 694 case '2': 695 subscript = '\u2082'; 696 break; 697 case '3': 698 subscript = '\u2083'; 699 break; 700 case '4': 701 subscript = '\u2084'; 702 break; 703 case '5': 704 subscript = '\u2085'; 705 break; 706 case '6': 707 subscript = '\u2086'; 708 break; 709 case '7': 710 subscript = '\u2087'; 711 break; 712 case '8': 713 subscript = '\u2088'; 714 break; 715 case '9': 716 subscript = '\u2089'; 717 break; 718 case '+': 719 subscript = '\u208A'; 720 break; 721 case '-': 722 subscript = '\u208B'; 723 break; 724 case '=': 725 subscript = '\u208C'; 726 break; 727 case '(': 728 subscript = '\u208D'; 729 break; 730 case ')': 731 subscript = '\u208E'; 732 break; 733 case 'a': 734 subscript = '\u2090'; 735 break; 736 case 'e': 737 subscript = '\u2091'; 738 break; 739 case 'o': 740 subscript = '\u2092'; 741 break; 742 case 'x': 743 subscript = '\u2093'; 744 break; 745 case 'Ι': 746 subscript = '\u2094'; 747 break; 748 case 'h': 749 subscript = '\u2095'; 750 break; 751 case 'k': 752 subscript = '\u2096'; 753 break; 754 case 'l': 755 subscript = '\u2097'; 756 break; 757 case 'm': 758 subscript = '\u2098'; 759 break; 760 case 'n': 761 subscript = '\u2099'; 762 break; 763 case 'p': 764 subscript = '\u209A'; 765 break; 766 case 's': 767 subscript = '\u209B'; 768 break; 769 case 't': 770 subscript = '\u209C'; 771 break; 772 default: 773 throw new RuntimeException("Unsupported subscript char: " + StringUtil.singleQuote(inString.charAt(i)) + "!"); 774 } 775 776 buffer.append(subscript); 777 } 778 779 return buffer.toString(); 780 } 781 782 //--------------------------------------------------------------------------- 783 public static String[] lines(CharSequence inString) 784 { 785 return inString != null ? inString.toString().split("\\r?\\n") : null; 786 } 787 788 //--------------------------------------------------------------------------- 789 public static String wrap(String inString, int inMaxLineLength) 790 { 791 StringBuilderPlus buffer = null; 792 if (inString != null) 793 { 794 buffer = new StringBuilderPlus(inString); 795 796 int index = inMaxLineLength; 797 while (index < buffer.length()) 798 { 799 for (int i = index; i > index - inMaxLineLength; i--) 800 { 801 char theChar = buffer.charAt(i); 802 803 if (theChar == ' ') 804 { 805 buffer.replace(i, i + 1, sLineSeparator); 806 index = i + inMaxLineLength + 1; 807 } 808 else if (theChar == ',' 809 || theChar == ';' 810 || theChar == '.') 811 { 812 buffer.insert(i + 1, sLineSeparator); 813 index = i + inMaxLineLength + 1; 814 } 815 } 816 } 817 818 } 819 820 return buffer != null ? buffer.toString() : null; 821 } 822 823 //--------------------------------------------------------------------------- 824 /** 825 Returns a random string of the specified length using the specified alphabet of characters. 826 @param inAlphabet a string of the allowed characters 827 @param inLength the length of the random string to be composed 828 @return the random string 829 */ 830 public static String generateRandomString(CharSequence inAlphabet, int inLength) 831 { 832 StringBuilderPlus buffer = new StringBuilderPlus(); 833 for (int i = 0; i < inLength; i++) 834 { 835 buffer.append(inAlphabet.charAt((int) Math.floor(Math.random() * inAlphabet.length()))); 836 } 837 838 return buffer.toString(); 839 } 840 841 //--------------------------------------------------------------------------- 842 public static String generateRandomString(Character.UnicodeScript inScript, int inLength) 843 { 844 List<Integer> unicodeCodePoints = getCharactersForUnicodeScript(inScript); 845 846 StringBuilderPlus buffer = new StringBuilderPlus(); 847 for (int i = 0; i < inLength; i++) 848 { 849 int decimalValue = CollectionUtil.getRandomListItem(unicodeCodePoints); 850 851 buffer.append(new String(Character.toChars(decimalValue))); 852 } 853 854 return buffer.toString(); 855 } 856 857 //--------------------------------------------------------------------------- 858 // https://en.wikipedia.org/wiki/Template:Unicode_chart_Egyptian_Hieroglyphs 859 // ππ π·πΌπππππ»π°πͺπΌπ ππππ¨πΉπ»ππ»π¨π³ππ―πΊ 860 public static String generateRandomEgyptianHieroglyphics(int inLength) 861 { 862 return generateRandomString(Character.UnicodeScript.EGYPTIAN_HIEROGLYPHS, inLength); 863 } 864 865 866 //--------------------------------------------------------------------------- 867 public static synchronized Range<Integer> getRangeForUnicodeBlock(Character.UnicodeBlock inBlock) 868 { 869 if (null == sUnicodeBlockRangeMap) 870 { 871 determineUnicodeBlockRanges(); 872 } 873 874 return sUnicodeBlockRangeMap.get(inBlock); 875 } 876 877 //--------------------------------------------------------------------------- 878 public static synchronized List<Integer> getCharactersForUnicodeScript(Character.UnicodeScript inScript) 879 { 880 if (null == sUnicodeScriptMap) 881 { 882 buildUnicodeScriptMap(); 883 } 884 885 return sUnicodeScriptMap.get(inScript); 886 } 887 888 //--------------------------------------------------------------------------- 889 // Use for formatting large numbers with commas. Ex: 20,512 instead of 20512 890 public static synchronized String generateLocalizedNumberString(Number inValue) 891 { 892 return generateLocalizedNumberString(inValue, Locale.getDefault()); 893 } 894 895 //--------------------------------------------------------------------------- 896 // Use for formatting large numbers with commas. Ex: 20,512 instead of 20512 897 public static synchronized String generateLocalizedNumberString(Number inValue, Locale inLocale) 898 { 899 return inValue != null ? NumberFormat.getInstance(inLocale).format(inValue) : null; 900 } 901 902 //--------------------------------------------------------------------------- 903 public static boolean isNumber(CharSequence inString) 904 { 905 boolean result = false; 906 if (inString != null) 907 { 908 String s = inString.toString().trim(); 909 910 try 911 { 912 Double.parseDouble(s); 913 result = true; 914 } 915 catch (NumberFormatException e) 916 { 917 // Attempt to deal with localized number strings 918 NumberFormat format = DecimalFormat.getInstance(); 919 ParsePosition parsePosition = new ParsePosition(0); 920 921 format.parse(s, parsePosition); 922 if (parsePosition.getIndex() == s.length()) 923 { 924 result = true; 925 } 926 } 927 } 928 929 return result; 930 } 931 932 //--------------------------------------------------------------------------- 933 public static boolean allValuesAreEmpty(String[] inArray) 934 { 935 boolean empty = true; 936 937 if (inArray != null) 938 { 939 for (String value : inArray) 940 { 941 if (StringUtil.isSet(value)) 942 { 943 empty = false; 944 break; 945 } 946 } 947 } 948 949 return empty; 950 } 951 952 //--------------------------------------------------------------------------- 953 /** 954 * Calculates the Levenshtein distance between two Strings. 955 * Algorithm based on Apache Commons implementation. 956 * @param inString1 the first string for comparison 957 * @param inString2 the second string for comparison 958 * @return the computed Levenshtein distance 959 */ 960 public static int computeLevenshteinDistance(CharSequence inString1, CharSequence inString2) 961 { 962 int distance = 0; 963 964 if (null == inString1) 965 { 966 if (inString2 != null) 967 { 968 distance = inString2.length(); 969 } 970 } 971 else if (null == inString2) 972 { 973 distance = inString1.length(); 974 } 975 else 976 { 977 // Both strings not null 978 979 // To simplify, string 1 should be the shorter of the two 980 if (inString1.length() > inString2.length()) 981 { 982 // Swap 983 CharSequence tmp = inString1; 984 inString1 = inString2; 985 inString2 = tmp; 986 } 987 988 final int length1 = inString1.length(); 989 final int length2 = inString2.length(); 990 991 final int[] scoreArray = new int[length1 + 1]; 992 993 int upperLeft; 994 int upper; 995 char string2J; 996 int ijComparison; 997 998 // Initialize the scoreArray 999 for (int i = 0; i <= length1; i++) 1000 { 1001 scoreArray[i] = i; 1002 } 1003 1004 // Evaluate 1005 for (int j = 1; j <= length2; j++) 1006 { 1007 upperLeft = scoreArray[0]; 1008 string2J = inString2.charAt(j - 1); 1009 scoreArray[0] = j; 1010 1011 for (int i = 1; i <= length1; i++) 1012 { 1013 upper = scoreArray[i]; 1014 ijComparison = inString1.charAt(i - 1) == string2J ? 0 : 1; 1015 scoreArray[i] = Math.min(Math.min(scoreArray[i - 1] + 1, scoreArray[i] + 1), upperLeft + ijComparison); 1016 upperLeft = upper; 1017 } 1018 } 1019 1020 distance = scoreArray[length1]; 1021 } 1022 1023 return distance; 1024 } 1025 1026 //************************************************************************** 1027 // PRIVATE FUNCTIONS 1028 //************************************************************************** 1029 1030 //--------------------------------------------------------------------------- 1031 private static void determineUnicodeBlockRanges() 1032 { 1033 sUnicodeBlockRangeMap = new HashMap<>(300); 1034 1035 Character.UnicodeBlock currentUnicodeBlock = null; 1036 Range<Integer> currentCodePointRange = null; 1037 for (int decimalValue = Character.MIN_CODE_POINT; decimalValue <= Character.MAX_CODE_POINT; decimalValue++) 1038 { 1039 Character.UnicodeBlock unicodeBlock = Character.UnicodeBlock.of(decimalValue); 1040 if (null == currentUnicodeBlock 1041 || ! currentUnicodeBlock.equals(unicodeBlock)) 1042 { 1043 if (currentCodePointRange != null) 1044 { 1045 currentCodePointRange.setEnd(decimalValue - 1); 1046 sUnicodeBlockRangeMap.put(currentUnicodeBlock, currentCodePointRange); 1047 } 1048 1049 currentUnicodeBlock = unicodeBlock; 1050 currentCodePointRange = new Range<>(); 1051 currentCodePointRange.setStart(decimalValue); 1052 } 1053 } 1054 1055 currentCodePointRange.setEnd(Character.MAX_CODE_POINT); 1056 sUnicodeBlockRangeMap.put(currentUnicodeBlock, currentCodePointRange); 1057 } 1058 1059 //--------------------------------------------------------------------------- 1060 private static void buildUnicodeScriptMap() 1061 { 1062 sUnicodeScriptMap = new HashMap<>(300); 1063 1064 for (int decimalValue = Character.MIN_CODE_POINT; decimalValue <= Character.MAX_CODE_POINT; decimalValue++) 1065 { 1066 Character.UnicodeScript unicodeScript = Character.UnicodeScript.of(decimalValue); 1067 if (unicodeScript != null) 1068 { 1069 List<Integer> codePoints = sUnicodeScriptMap.get(unicodeScript); 1070 if (null == codePoints) 1071 { 1072 codePoints = new ArrayList<>(); 1073 sUnicodeScriptMap.put(unicodeScript, codePoints); 1074 } 1075 1076 codePoints.add(decimalValue); 1077 } 1078 } 1079 1080 // Save memory by trimming the character lists to size 1081 for (Character.UnicodeScript script : sUnicodeScriptMap.keySet()) 1082 { 1083 ((ArrayList) sUnicodeScriptMap.get(script)).trimToSize(); 1084 } 1085 } 1086}