001 package edu.nrao.sss.util; 002 003 import java.math.BigDecimal; 004 import java.math.RoundingMode; 005 import java.text.DecimalFormat; 006 import java.util.ArrayList; 007 import java.util.Collection; 008 import java.util.regex.Pattern; 009 import java.util.regex.PatternSyntaxException; 010 011 import org.apache.axis.utils.XMLChar; 012 import org.apache.log4j.Logger; 013 014 import edu.nrao.sss.math.MathUtil; 015 016 /** 017 * A utility class for manipulating text strings. 018 * <p> 019 * <b>Version Info:</b> 020 * <table style="margin-left:2em"> 021 * <tr><td>$Revision: 1351 $</td> 022 * <tr><td>$Date: 2008-06-13 14:11:55 -0600 (Fri, 13 Jun 2008) $</td> 023 * <tr><td>$Author: dharland $</td> 024 * </table></p> 025 * 026 * @author David M. Harland 027 * @since 2006-02-23 028 */ 029 public class StringUtil 030 { 031 private static final Logger log = Logger.getLogger(StringUtil.class); 032 033 /** Returns the end-of-line string. */ 034 public static final String EOL = System.getProperty("line.separator"); 035 036 //This formatter is used so that we can avoid java's version of 037 //scientific notation. NOTE: the documentation on this class 038 //mentions that it is not thread safe. If we every multithread 039 //our stuff, we will need to change our usage here a bit. 040 private DecimalFormat numberFormatter = new DecimalFormat("0.0"); 041 042 private StringUtil() 043 { 044 numberFormatter.setMaximumFractionDigits(1000); //an arbitrarily high number 045 } 046 047 private static StringUtil SINGLETON; 048 049 /** 050 * Returns an instance of this string utility. 051 * 052 * @return a string utility. 053 */ 054 public static StringUtil getInstance() 055 { 056 //Just-in-time creation 057 if (SINGLETON == null) 058 SINGLETON = new StringUtil(); 059 060 return SINGLETON; 061 } 062 063 /** 064 * Returns a normalized version of <code>str</code>. 065 * <p> 066 * Normalization means the following: 067 * <ol> 068 * <li>If <code>str</code> is <i>null</i>, 069 * the empty string (<code>""</code>) is returned.</li> 070 * <li>Otherwise, all leading and trailing whitespace is removed.</li> 071 * </ol> 072 * @param str the string to be normalized. 073 * @return a normalized copy of <code>str</code>. 074 * @see java.lang.String#trim() 075 */ 076 public String normalizeString(String str) 077 { 078 String nStr = ""; 079 if (str != null) 080 { 081 nStr = str.trim(); 082 } 083 return nStr; 084 } 085 086 /** 087 * Returns a string based on {@code str} that is in lower case, except for 088 * the first letters of each word, which are in upper case. 089 * <p> 090 * <b><u>Example:</u></b><br/> 091 * <div style="margin-left:2em"><tt> 092 * INPUT: hANd OvER THe moNeY and NO OnE geTS HURT.<br/> 093 * OUTPUT: Hand Over The Money And No One Gets Hurt. 094 * </tt></div> 095 * 096 * @param str the input string. 097 * 098 * @return a string where each word is in lower case but begins with a 099 * capital letter. 100 * 101 * @see #capitalizeFirstLetterOfEachWord(String) 102 */ 103 public String lowerAndCapitalizeFirstLetterOfEachWord(String str) 104 { 105 return capitalizeFirstLetterOfEachWord(str.toLowerCase()); 106 } 107 108 /** 109 * Returns a string based on {@code str} where 110 * the first letters of each word are converted to upper case. 111 * <p> 112 * <b><u>Example:</u></b><br/> 113 * <div style="margin-left : 2em"><tt> 114 * INPUT: hANd OvER THe moNeY and NO OnE geTS HURT.<br/> 115 * OUTPUT: HANd OvER THe MoNeY And NO OnE GeTS HURT. 116 * </tt></div> 117 * 118 * @param str the input string. 119 * 120 * @return a string where each word begins with a capital letter. 121 * 122 * @see #lowerAndCapitalizeFirstLetterOfEachWord(String) 123 */ 124 public String capitalizeFirstLetterOfEachWord(String str) 125 { 126 //Quick exit is input is null 127 if (str == null) 128 return null; 129 130 StringBuilder buff = new StringBuilder(str); 131 132 //Set to true so that we can capitalize first letter 133 boolean previousCharWasSpace = true; 134 135 //Check each character. We do this instead of splitting string on 136 //whitespace so that we keep original whitespace chars, including 137 //runs of consecutive white space chars. 138 for (int index = 0; index < buff.length(); index++) 139 { 140 char currChar = buff.charAt(index); 141 142 //No need to capitalize unless char is not whitespace 143 if (!Character.isWhitespace(currChar)) 144 { 145 //Capitalize only when previous char was whitespace 146 if (previousCharWasSpace) 147 { 148 buff.setCharAt(index, Character.toUpperCase(currChar)); 149 } 150 previousCharWasSpace = false; 151 } 152 //Curr char is whitespace 153 else 154 { 155 previousCharWasSpace = true; 156 } 157 } 158 159 return buff.toString(); 160 } 161 162 /** 163 * @return a String containing multiplier consequitive tab characters. 164 */ 165 public String getSpacer(int multiplier) 166 { 167 StringBuilder sp = new StringBuilder(multiplier); 168 for (int i = 0; i < multiplier; i++) 169 sp.append("\t"); 170 171 return sp.toString(); 172 } 173 174 /** 175 * Returns a text representation of {@code number}. 176 * The returned text is similar to that provided by 177 * {@code Double.toString(double)}, except that the 178 * range of numbers where scientific notation is not 179 * used is given by the other parameters. 180 * <p> 181 * If {@code number} is such that<pre> 182 * stdBeg <= number <= stdEnd 183 * </pre> 184 * scientific notation will not be used.</p> 185 * 186 * @param number the number for which a text representation is needed. 187 * 188 * @param stdBeg the minimum absolute value where regular notation 189 * should be preferred to scientific notation. 190 * This should be a non-negative number. 191 * 192 * @param stdEnd the maximum absolute value where regular notation 193 * should be preferred to scientific notation. 194 * This should be a non-negative number. 195 * 196 * @return a text representation of {@code number}. 197 */ 198 public String format(double number, double stdBeg, double stdEnd) 199 { 200 double absVal = Math.abs(number); 201 202 if ((stdBeg <= absVal) && (absVal <= stdEnd)) 203 return numberFormatter.format(number); 204 else 205 return Double.toString(number); 206 } 207 208 /** 209 * Returns a text representation of {@code number}. 210 * The returned text is similar to that provided by 211 * {@code Double.toString(double)}, except that 212 * scientific notation is never used. 213 * 214 * @param number the number for which a text representation is needed. 215 * 216 * @return a text representation of {@code number}. 217 */ 218 public String formatNoScientificNotation(double number) 219 { 220 if (Double.isInfinite(number) || Double.isNaN(number)) 221 return Double.toString(number); 222 else 223 return numberFormatter.format(number); 224 } 225 226 /** 227 * Returns a text representation of {@code number}. 228 * The returned text is similar to that provided by 229 * {@code Double.toString(double)}, except that 230 * scientific notation is never used. 231 * 232 * @param number the number for which a text representation is needed. 233 * 234 * @param minFracDigits the minimum number of digits after the decimal point. 235 * 236 * @param maxFracDigits the maximum number of digits after the decimal point. 237 * 238 * @return a text representation of {@code number}. 239 */ 240 public String formatNoScientificNotation(double number, int minFracDigits, 241 int maxFracDigits) 242 { 243 String result; 244 245 if (Double.isInfinite(number) || Double.isNaN(number)) 246 { 247 result = Double.toString(number); 248 } 249 else 250 { 251 //Cache existing values, set new based on params 252 int oldMin = numberFormatter.getMinimumFractionDigits(); 253 int oldMax = numberFormatter.getMaximumFractionDigits(); 254 numberFormatter.setMinimumFractionDigits(minFracDigits); 255 numberFormatter.setMaximumFractionDigits(maxFracDigits); 256 257 result = numberFormatter.format(number); 258 259 //Restore original values 260 numberFormatter.setMinimumFractionDigits(oldMin); 261 numberFormatter.setMaximumFractionDigits(oldMax); 262 } 263 264 return result; 265 } 266 267 /** 268 * Returns a text representation of {@code number}. 269 * There will always be at least one digit after the decimal 270 * point. 271 * <p> 272 * If {@code number} is determined by 273 * {@link MathUtil#doubleValueIsInfinite(BigDecimal)} to 274 * be infinite, the returned text will be either 275 * "+infinity" or "-infinity".</p> 276 * 277 * @param number the number for which a text representation is needed. 278 * 279 * @return a text representation of {@code number}. 280 */ 281 public String formatNoScientificNotation(BigDecimal number) 282 { 283 //Quick exit for infinite values 284 if (MathUtil.doubleValueIsInfinite(number)) 285 { 286 return number.signum() > 0 ? "+infinity" : "-infinity"; 287 } 288 289 number = number.stripTrailingZeros(); 290 291 //The portion of the test after the || should not be necessary, 292 //but it is a work around for a BigDecimal bug reported here: 293 //http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6480539. 294 if (number.scale() < 1 || number.compareTo(BigDecimal.ZERO)==0) 295 number = number.setScale(1); 296 297 return number.toPlainString(); 298 } 299 300 /** 301 * Returns a text representation of {@code number}. 302 * 303 * @param number the number for which a text representation is needed. 304 * 305 * @param minFracDigits the minimum number of digits after the decimal point. 306 * 307 * @param maxFracDigits the maximum number of digits after the decimal point. 308 * 309 * @return a text representation of {@code number}. 310 */ 311 public String formatNoScientificNotation(BigDecimal number, int minFracDigits, 312 int maxFracDigits) 313 { 314 //Quick exit for infinite values 315 if (MathUtil.doubleValueIsInfinite(number)) 316 { 317 return number.signum() > 0 ? "+infinity" : "-infinity"; 318 } 319 320 //Adjust the number's scale to fall w/in parameter range 321 number = number.stripTrailingZeros(); 322 int scale = number.scale(); 323 324 //This if-statement should not be necessary, 325 //but it is a work around for a BigDecimal bug reported here: 326 //http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6480539. 327 if (number.compareTo(BigDecimal.ZERO)==0) 328 number = number.setScale(minFracDigits); 329 330 if (scale < minFracDigits) 331 { 332 number = number.setScale(minFracDigits); 333 } 334 else if (scale > maxFracDigits) 335 { 336 number = number.setScale(maxFracDigits, RoundingMode.HALF_UP); 337 } 338 339 return number.toPlainString(); 340 } 341 342 /** 343 * Returns a string that is the result of calling {@code toString()} on every 344 * member of {@code source} and inserting {@code separator} between each 345 * member. A <i>null</i> member will be represented by the empty string. 346 * <p> 347 * The returned string does not contain a trailing separator 348 * (unless the final member of {@code source} was the empty string or ended 349 * with the separator). This is a convenience method that is equivalent 350 * to calling {@link #fromCollection(Collection, String, boolean) 351 * fromCollection(source, separator, false)}.</p> 352 * <p> 353 * It is the client's duty to select a separator that will not be present 354 * in the string representation of any of the members of the source 355 * collection.</p> 356 * 357 * @param source a collection of objects that will be turned into a single 358 * string. 359 * 360 * @param separator the text used to separate one member of the collection 361 * from another. 362 * 363 * @return a string representation of {@code source}. If the collection is 364 * empty, the empty string (<tt>""</tt>) is returned. 365 */ 366 public String fromCollection(Collection<?> source, String separator) 367 { 368 return fromCollection(source, separator, false); 369 } 370 371 /** 372 * Returns a string that is the result of calling {@code toString()} on every 373 * member of {@code source} and inserting {@code separator} between each 374 * member. A <i>null</i> member will be represented by the empty string. 375 * <p> 376 * It is the client's duty to select a separator that will not be present 377 * in the string representation of any of the members of the source 378 * collection.</p> 379 * 380 * @param source a collection of objects that will be turned into a single 381 * string. 382 * 383 * @param separator the text used to separate one member of the collection 384 * from another. 385 * 386 * @param appendSeparatorToEnd if <i>true</i>, this method adds a final 387 * separator at the end of the returned string. 388 * 389 * @return a string representation of {@code source}. If the collection is 390 * empty, the empty string (<tt>""</tt>) is returned. 391 */ 392 public String fromCollection(Collection<?> source, String separator, 393 boolean appendSeparatorToEnd) 394 { 395 StringBuilder buff = new StringBuilder(); 396 397 for (Object value : source) 398 buff.append(value==null ? "" : value.toString()).append(separator); 399 400 //Remove the final separator? 401 if (!appendSeparatorToEnd) 402 { 403 int buffSize = buff.length(); 404 if (buffSize > 0) 405 { 406 int separatorSize = separator.length(); 407 buff.delete(buffSize-separatorSize, buffSize); 408 } 409 } 410 411 return buff.toString(); 412 } 413 414 /** 415 * Returns a collection of strings that was built by parsing {@code source}, 416 * using {@code separator} as a delimiter. 417 * <p> 418 * <b>Note:</b> the {@code separator} is <i>not</i> interpreted as a 419 * regular expression, but just as a simple string. This allows a client 420 * to call {@code toString} and {@code fromString} with the same delimiter 421 * and obtain sensible results. Also, a final delimiter is assumed, if 422 * not already present. That is, using ";" as a delimiter, the following 423 * text would result in the set of integers from one through five: 424 * <tt>1;2;3;4;5</tt>. Consecutive delimiters will result in empty-string 425 * (<tt>""</tt>) elements. 426 * This is a convenience method that is equivalent to calling 427 * {@link #toCollection(String, String, Collection, boolean) 428 * toCollection(source, separator, desination, true)}.</p> 429 * 430 * @param source a string representation of a collection. 431 * 432 * @param separator text that separates one member of a collection from 433 * another in the {@code source} text. 434 * 435 * @param destination the collection into which the members created from 436 * {@code source} are added. If this parameter is 437 * <i>null</i>, a new collection will be created and 438 * used. 439 * 440 * @return a collection of strings built by parsing {@code source}, 441 * using {@code separator} as a delimiter. 442 */ 443 public Collection<String> toCollection(String source, String separator, 444 Collection<String> destination) 445 { 446 return toCollection(source, separator, destination, true); 447 } 448 449 /** 450 * Returns a collection of strings that was built by parsing {@code source}, 451 * using {@code separator} as a delimiter. 452 * <p> 453 * <b>Note:</b> the {@code separator} is <i>not</i> interpreted as a 454 * regular expression, but just as a simple string. This allows a client 455 * to call {@code toString} and {@code fromString} with the same delimiter 456 * and obtain sensible results. 457 * Consecutive delimiters will result in empty-string (<tt>""</tt>) 458 * elements.</p> 459 * 460 * @param source a string representation of a collection. 461 * 462 * @param separator text that separates one member of a collection from 463 * another in the {@code source} text. 464 * 465 * @param destination the collection into which the members created from 466 * {@code source} are added. If this parameter is 467 * <i>null</i>, a new collection will be created and 468 * used. 469 * 470 * @param addMissingFinalSeparator if <i>true</i>, any characters in 471 * {@code source} between the final separator and the end of the 472 * string will be treated as the final string added to 473 * {@code destination}. If <i>false</i>, any characters after 474 * the final separator are discarded. 475 * 476 * @return a collection of strings built by parsing {@code source}, 477 * using {@code separator} as a delimiter. 478 */ 479 public Collection<String> toCollection(String source, String separator, 480 Collection<String> destination, 481 boolean addMissingFinalSeparator) 482 { 483 if (destination == null) 484 destination = new ArrayList<String>(); 485 486 if (source != null) 487 { 488 StringBuilder buff = new StringBuilder(source); 489 int sepLen = separator.length(); 490 int sepPos; 491 492 while ((sepPos = buff.indexOf(separator)) > -1) 493 { 494 destination.add(buff.substring(0, sepPos)); 495 buff.delete(0, sepPos+sepLen); 496 } 497 498 //Assume a final delimiter if there are characters remaining 499 if (addMissingFinalSeparator && (buff.length() > 0)) 500 destination.add(buff.toString()); 501 } 502 503 return destination; 504 } 505 506 //============================================================================ 507 // XML Strings (Create separate class?) 508 //============================================================================ 509 510 /** 511 * Returns a valid XML Name by replacing illegal characters in {@code name} 512 * with {@code replacementForIllegalChars}. 513 * If there are no illegal characters, {@code name} is returned unaltered. 514 * If the first character of {@code name} is normally a legal value, but is 515 * not legal as the first character in an XML Name, the replacement 516 * character is <i>inserted</i> prior to the first character in 517 * {@code name}. 518 * 519 * @param replacementForIllegalChars a character to use as a replacement for 520 * illegal characters in {@code name}. 521 * 522 * @return a valid XML name based on {@code name}. 523 */ 524 public String toXmlName(String name, char replacementForIllegalChars) 525 { 526 String xmlName; 527 528 if (!XMLChar.isValidName(name)) 529 { 530 //Quick exit if replacement char is illegal 531 if (!XMLChar.isName(replacementForIllegalChars)) 532 throwIllegalXmlChar(replacementForIllegalChars, "Name", false); 533 534 StringBuilder buff = new StringBuilder(name); 535 536 int charCount = name.length(); 537 538 if (charCount == 0) 539 throw new 540 IllegalArgumentException("Name must have at least one character."); 541 542 //Special test for 1st char 543 if (!XMLChar.isNameStart(buff.charAt(0))) 544 { 545 //Make sure we can use the replacement as the 1st char 546 if (!XMLChar.isNameStart(replacementForIllegalChars)) 547 throwIllegalXmlChar(replacementForIllegalChars, "Name", true); 548 549 //If 1st char is not legal at all, replace it. 550 //Otherwise, insert replacement char at 1st position. 551 if (!XMLChar.isName(buff.charAt(0))) 552 buff.setCharAt(0, replacementForIllegalChars); 553 else 554 buff.insert(0, replacementForIllegalChars); 555 } 556 557 //Replace all illegal chars from 2nd position on 558 for (int i=1; i < charCount; i++) 559 if (!XMLChar.isName(buff.charAt(i))) 560 buff.setCharAt(i, replacementForIllegalChars); 561 562 xmlName = buff.toString(); 563 } 564 else 565 { 566 xmlName = name; 567 } 568 569 return xmlName; 570 } 571 572 /** 573 * Returns a valid XML non-colonized name by replacing illegal characters 574 * in {@code name} with {@code replacementForIllegalChars}. 575 * If there are no illegal characters, {@code name} is returned unaltered. 576 * If the first character of {@code name} is normally a legal value, but is 577 * not legal as the first character in an XML NCName, the replacement 578 * character is <i>inserted</i> prior to the first character in 579 * {@code name}. 580 * <p> 581 * XML Schema's ID and IDREF types are subtypes of NCName. That is, 582 * any string used as an ID must be a legal NCName.</p> 583 * 584 * @param replacementForIllegalChars a character to use as a replacement for 585 * illegal characters in {@code name}. 586 * 587 * @return a valid XML name based on {@code name}. 588 */ 589 public String toXmlNCName(String name, char replacementForIllegalChars) 590 { 591 String xmlNCName; 592 593 if (!XMLChar.isValidNCName(name)) 594 { 595 //Quick exit if replacement char is illegal 596 if (!XMLChar.isNCName(replacementForIllegalChars)) 597 throwIllegalXmlChar(replacementForIllegalChars, "NCName", false); 598 599 StringBuilder buff = new StringBuilder(name); 600 601 int charCount = name.length(); 602 603 if (charCount == 0) 604 throw new 605 IllegalArgumentException("Name must have at least one character."); 606 607 //Special test for 1st char 608 if (!XMLChar.isNCNameStart(buff.charAt(0))) 609 { 610 //Make sure we can use the replacement as the 1st char 611 if (!XMLChar.isNCNameStart(replacementForIllegalChars)) 612 throwIllegalXmlChar(replacementForIllegalChars, "NCName", true); 613 614 //If 1st char is not legal at all, replace it. 615 //Otherwise, insert replacement char at 1st position. 616 if (!XMLChar.isNCName(buff.charAt(0))) 617 buff.setCharAt(0, replacementForIllegalChars); 618 else 619 buff.insert(0, replacementForIllegalChars); 620 } 621 622 //Replace all illegal chars from 2nd position on 623 for (int i=1; i < charCount; i++) 624 if (!XMLChar.isNCName(buff.charAt(i))) 625 buff.setCharAt(i, replacementForIllegalChars); 626 627 xmlNCName = buff.toString(); 628 } 629 else 630 { 631 xmlNCName = name; 632 } 633 634 return xmlNCName; 635 } 636 637 private void throwIllegalXmlChar(char c, String type, boolean start) 638 { 639 StringBuilder errMsg = 640 new StringBuilder("The value of replacementForIllegalChars, "); 641 642 errMsg.append(c).append(", is not a legal character for "); 643 644 if (start) 645 errMsg.append("the start of "); 646 647 errMsg.append("an XML ").append(type).append('.'); 648 649 throw new IllegalArgumentException(errMsg.toString()); 650 } 651 652 /** 653 * Converts a glob expression to an SQL LIKE expression. For the purposes of 654 * this method, globs have 2 special operators: 655 * 656 * <ul> 657 * <li><code>*</code> -- matches 0 or more of any character</li> 658 * <li><code>?</code> -- matches 1 of any character</li> 659 * </ul> 660 */ 661 public String globToSqlLike(String glob, char escape) 662 { 663 StringBuilder sql = new StringBuilder(""); 664 char[] chars = glob.toCharArray(); 665 666 for (int i = 0; i < chars.length; i++) 667 { 668 //May be escaping a '*' or '?' 669 if (chars[i] == '\\') 670 { 671 if (i < (chars.length - 1) && (chars[i + 1] == '*' || chars[i + 1] == '?')) 672 i++; 673 674 sql.append(chars[i]); 675 } 676 677 else if (chars[i] == '%') 678 { 679 sql.append(escape); 680 sql.append('%'); 681 } 682 683 else if (chars[i] == '_') 684 { 685 sql.append(escape); 686 sql.append('_'); 687 } 688 689 else if (chars[i] == '*') 690 { 691 sql.append("%"); 692 } 693 694 else if (chars[i] == '?') 695 { 696 sql.append("_"); 697 } 698 699 else if (chars[i] == '\'') 700 { 701 sql.append("''"); 702 } 703 704 else if (chars[i] == '"') 705 { 706 sql.append("''''"); 707 } 708 709 else 710 { 711 sql.append(chars[i]); 712 } 713 } 714 715 return sql.toString(); 716 } 717 718 /** 719 * Converts a glob expression to a regex. For the purposes of this method, 720 * globs have 2 special operators: 721 * 722 * <ul> 723 * <li><code>*</code> -- matches 0 or more of any character</li> 724 * <li><code>?</code> -- matches 1 of any character</li> 725 * </ul> 726 * 727 * Note also that the returned regex will only match a whole string. (i.e. 728 * it starts with '^' and ends with '$') 729 * 730 * @return an regular expression equivalent to the glob <code>glob</code>. 731 */ 732 public String globToRegex(String glob) 733 { 734 StringBuilder regex = new StringBuilder("^"); 735 char[] chars = glob.toCharArray(); 736 737 for (int i = 0; i < chars.length; i++) 738 { 739 //May be escaping a '*' or '?' 740 if (chars[i] == '\\') 741 { 742 if (i < (chars.length - 1) && (chars[i + 1] == '*' || chars[i + 1] == '?')) 743 { 744 i++; 745 regex.append("\\"); 746 regex.append(chars[i]); 747 } 748 749 else 750 { 751 regex.append("\\\\"); 752 } 753 } 754 755 else if (mustBeEscapedForRegex(chars[i])) 756 { 757 regex.append("\\"); 758 regex.append(chars[i]); 759 } 760 761 else if (chars[i] == '*') 762 { 763 regex.append(".*"); 764 } 765 766 else if (chars[i] == '?') 767 { 768 regex.append("."); 769 } 770 771 else 772 { 773 regex.append(chars[i]); 774 } 775 } 776 777 regex.append("$"); 778 779 return regex.toString(); 780 } 781 782 /** 783 * Calls {@link #globToRegex} to create a regex string, then compiles that regex and 784 * returns the resultant pattern. The Pattern has the CASE_INSENSITIVE and 785 * UNICODE_CASE flags set. 786 * 787 * @throws RuntimeException if the value returned by globToRegex is not a 788 * valid regex. Note: this should never happen but may if there's a 789 * programmer error in globToRegex. 790 */ 791 public Pattern globToPattern(String glob) 792 { 793 try 794 { 795 return Pattern.compile( 796 globToRegex(glob), 797 Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE 798 ); 799 } 800 catch (PatternSyntaxException e) 801 { 802 log.error("Programmer Error: ", e); 803 throw new RuntimeException("Programmer Error: ", e); 804 } 805 } 806 807 /** 808 * Used by {@link #globToRegex(String)}. 809 * @return true if the character {@code c} must be escaped in order to be 810 * added to a regular expression as a literal. NOTE: '\', '*', and '?' are not 811 * checked for here because they are treated specially in globToRegex() (i.e. 812 * not merely escaped). 813 */ 814 private boolean mustBeEscapedForRegex(char c) 815 { 816 final char[] replace = {'^', '$', '(', ')', '{', '}', '[', ']', '+', '|', '.'}; 817 818 for (char r : replace) 819 { 820 if (c == r) 821 return true; 822 } 823 824 return false; 825 } 826 827 //============================================================================ 828 // 829 //============================================================================ 830 /* 831 public static void main(String[] args) throws Exception 832 { 833 StringUtil util = StringUtil.getInstance(); 834 char c = '_'; 835 836 String name = "abc123"; 837 System.out.println("text="+name+", xs:Name="+util.toXmlName(name, c)+ 838 ", xs:NCName="+util.toXmlNCName(name, c)); 839 name = "abc 123"; 840 System.out.println("text="+name+", xs:Name="+util.toXmlName(name, c)+ 841 ", xs:NCName="+util.toXmlNCName(name, c)); 842 name = "123abc"; 843 System.out.println("text="+name+", xs:Name="+util.toXmlName(name, c)+ 844 ", xs:NCName="+util.toXmlNCName(name, c)); 845 name = "abc:123"; 846 System.out.println("text="+name+", xs:Name="+util.toXmlName(name, c)+ 847 ", xs:NCName="+util.toXmlNCName(name, c)); 848 name = "abc&123"; 849 System.out.println("text="+name+", xs:Name="+util.toXmlName(name, c)+ 850 ", xs:NCName="+util.toXmlNCName(name, c)); 851 name = "abc:1:2.3"; 852 System.out.println("text="+name+", xs:Name="+util.toXmlName(name, c)+ 853 ", xs:NCName="+util.toXmlNCName(name, c)); 854 name = "J0123+5432"; 855 System.out.println("text="+name+", xs:Name="+util.toXmlName(name, c)+ 856 ", xs:NCName="+util.toXmlNCName(name, c)); 857 name = "J0123-5432"; 858 System.out.println("text="+name+", xs:Name="+util.toXmlName(name, c)+ 859 ", xs:NCName="+util.toXmlNCName(name, c)); 860 861 for (String arg : args) 862 System.out.println("text="+arg+", xs:Name="+util.toXmlName(arg, c)+ 863 ", xs:NCName="+util.toXmlNCName(arg, c)); 864 } 865 */ 866 //This is here for quick & dirty testing 867 /* 868 public static void main(String[] args) 869 { 870 double[] numbers = {0.0, -0.0, 0.00001, -0.00001, 123456789.876, 871 1e19, 1e20, 1e21, 872 1e-9, 1e-10, 1e-11, 873 Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, 874 Double.NaN, Double.MAX_VALUE, Double.MIN_VALUE}; 875 876 for (double number : numbers) 877 { 878 System.out.println("java: " + number); 879 System.out.println("format: " + StringUtil.getInstance().format(number, 1e-10, 1e20)); 880 System.out.println("format-no-SN: " + StringUtil.getInstance().formatNoScientificNotation(number)); 881 System.out.println(); 882 } 883 884 System.out.println(); 885 String msg = " hANd OvER THe moNeY\t and NO OnE geTS HURT."; 886 System.out.println("INPUT: " + msg); 887 System.out.println("OUTPUT 1: " + StringUtil.getInstance().capitalizeFirstLetterOfEachWord(msg)); 888 System.out.println("OUTPUT 2: " + StringUtil.getInstance().lowerAndCapitalizeFirstLetterOfEachWord(msg)); 889 msg = StringUtil.getInstance().normalizeString(msg); 890 System.out.println("OUTPUT 3: " + StringUtil.getInstance().lowerAndCapitalizeFirstLetterOfEachWord(msg)); 891 } 892 */ 893 /* 894 public static void main(String[] args) 895 { 896 StringUtil util = StringUtil.getInstance(); 897 898 String text = "a;b;c;d;e;f"; 899 System.out.println(util.toCollection(text, ";", null)); 900 System.out.println(util.toCollection(text, ";", null, false)); 901 System.out.println(util.toCollection(text, ";", null, true)); 902 903 String delim = " | "; 904 ArrayList<Integer> ints = new ArrayList<Integer>(); 905 for (int i=1; i <= 20; i++) 906 ints.add(i); 907 System.out.println(); 908 text = util.fromCollection(ints, delim); 909 System.out.println(text); 910 System.out.println(util.toCollection(text, delim, null)); 911 System.out.println(); 912 text = util.fromCollection(ints, delim, false); 913 System.out.println(text); 914 System.out.println(util.toCollection(text, delim, null, true)); 915 System.out.println(); 916 text = util.fromCollection(ints, delim, true); 917 System.out.println(text); 918 System.out.println(util.toCollection(text, delim, null, false)); 919 } 920 */ 921 /* 922 public static void main(String[] args) 923 { 924 Random generator = new Random(); 925 926 final int minFrac = 3; 927 final int maxFrac = 7; 928 929 final StringUtil util = StringUtil.getInstance(); 930 931 for (int i=1; i < 50; i++) 932 { 933 double num = generator.nextDouble() * generator.nextInt(); 934 935 BigDecimal number = new BigDecimal(num); 936 number = number.setScale(generator.nextInt(8), RoundingMode.HALF_UP); 937 938 System.out.print(util.formatNoScientificNotation(number, minFrac, maxFrac)); 939 System.out.println(", " + number.toPlainString()); 940 } 941 } 942 */ 943 }