001/* 002 Licensed to the Apache Software Foundation (ASF) under one 003 or more contributor license agreements. See the NOTICE file 004 distributed with this work for additional information 005 regarding copyright ownership. The ASF licenses this file 006 to you under the Apache License, Version 2.0 (the 007 "License"); you may not use this file except in compliance 008 with the License. You may obtain a copy of the License at 009 010 http://www.apache.org/licenses/LICENSE-2.0 011 012 Unless required by applicable law or agreed to in writing, 013 software distributed under the License is distributed on an 014 "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 KIND, either express or implied. See the License for the 016 specific language governing permissions and limitations 017 under the License. 018 */ 019package org.apache.wiki.util; 020 021import org.apache.commons.lang3.StringUtils; 022 023import java.io.File; 024import java.io.IOException; 025import java.nio.charset.Charset; 026import java.nio.charset.StandardCharsets; 027import java.security.SecureRandom; 028import java.util.NoSuchElementException; 029import java.util.Properties; 030import java.util.Random; 031 032 033/** 034 * Contains a number of static utility methods. 035 */ 036public final class TextUtil { 037 038 static final String HEX_DIGITS = "0123456789ABCDEF"; 039 040 /** 041 * Private constructor prevents instantiation. 042 */ 043 private TextUtil() {} 044 045 /** 046 * java.net.URLEncoder.encode() method in JDK < 1.4 is buggy. This duplicates 047 * its functionality. 048 * @param rs the string to encode 049 * @return the URL-encoded string 050 */ 051 protected static String urlEncode( byte[] rs ) { 052 StringBuilder result = new StringBuilder(rs.length*2); 053 054 // Does the URLEncoding. We could use the java.net one, but 055 // it does not eat byte[]s. 056 057 for( int i = 0; i < rs.length; i++ ) { 058 char c = ( char )rs[i]; 059 060 switch( c ) { 061 case '_': 062 case '.': 063 case '*': 064 case '-': 065 case '/': 066 result.append( c ); 067 break; 068 069 case ' ': 070 result.append( '+' ); 071 break; 072 073 default: 074 if( ( c >= 'a' && c <= 'z' ) || 075 ( c >= 'A' && c <= 'Z' ) || 076 ( c >= '0' && c <= '9' ) ) { 077 result.append( c ); 078 } else { 079 result.append( '%' ); 080 result.append( HEX_DIGITS.charAt( ( c & 0xF0 ) >> 4 ) ); 081 result.append( HEX_DIGITS.charAt( c & 0x0F ) ); 082 } 083 } 084 085 } // for 086 087 return result.toString(); 088 } 089 090 /** 091 * URL encoder does not handle all characters correctly. 092 * See <A HREF="http://developer.java.sun.com/developer/bugParade/bugs/4257115.html"> 093 * Bug parade, bug #4257115</A> for more information. 094 * <P> 095 * Thanks to CJB for this fix. 096 * 097 * @param bytes The byte array containing the bytes of the string 098 * @param encoding The encoding in which the string should be interpreted 099 * @return A decoded String 100 * 101 * @throws IllegalArgumentException If the byte array is not a valid string. 102 */ 103 protected static String urlDecode( byte[] bytes, String encoding ) throws IllegalArgumentException { 104 if( bytes == null ) { 105 return null; 106 } 107 108 byte[] decodeBytes = new byte[bytes.length]; 109 int decodedByteCount = 0; 110 111 try { 112 for( int count = 0; count < bytes.length; count++ ) { 113 switch( bytes[count] ) { 114 case '+': 115 decodeBytes[decodedByteCount++] = ( byte ) ' '; 116 break ; 117 118 case '%': 119 decodeBytes[decodedByteCount++] = ( byte )( ( HEX_DIGITS.indexOf( bytes[++count] ) << 4 ) + 120 ( HEX_DIGITS.indexOf( bytes[++count] ) ) ); 121 break ; 122 123 default: 124 decodeBytes[decodedByteCount++] = bytes[count] ; 125 } 126 } 127 128 } catch( IndexOutOfBoundsException ae ) { 129 throw new IllegalArgumentException( "Malformed UTF-8 string?" ); 130 } 131 132 return new String(decodeBytes, 0, decodedByteCount, Charset.forName( encoding ) ); 133 } 134 135 /** 136 * As java.net.URLEncoder class, but this does it in UTF8 character set. 137 * 138 * @param text The text to decode 139 * @return An URLEncoded string. 140 */ 141 public static String urlEncodeUTF8( String text ) { 142 // If text is null, just return an empty string 143 if ( text == null ) { 144 return ""; 145 } 146 147 byte[] rs = text.getBytes( StandardCharsets.UTF_8 ); 148 return urlEncode( rs ); 149 } 150 151 /** 152 * As java.net.URLDecoder class, but for UTF-8 strings. null is a safe value and returns null. 153 * 154 * @param utf8 The UTF-8 encoded string 155 * @return A plain, normal string. 156 */ 157 public static String urlDecodeUTF8( String utf8 ) { 158 if( utf8 == null ) { 159 return null; 160 } 161 162 return urlDecode( utf8.getBytes( StandardCharsets.ISO_8859_1 ), StandardCharsets.UTF_8.toString() ); 163 } 164 165 /** 166 * Provides encoded version of string depending on encoding. Encoding may be UTF-8 or ISO-8859-1 (default). 167 * 168 * <p>This implementation is the same as in FileSystemProvider.mangleName(). 169 * 170 * @param data A string to encode 171 * @param encoding The encoding in which to encode 172 * @return An URL encoded string. 173 */ 174 public static String urlEncode( String data, String encoding ) { 175 // Presumably, the same caveats apply as in FileSystemProvider. 176 // Don't see why it would be horribly kludgy, though. 177 if( StandardCharsets.UTF_8.toString().equals( encoding ) ) { 178 return urlEncodeUTF8( data ); 179 } 180 181 return urlEncode( data.getBytes( Charset.forName( encoding ) ) ); 182 } 183 184 /** 185 * Provides decoded version of string depending on encoding. Encoding may be UTF-8 or ISO-8859-1 (default). 186 * 187 * <p>This implementation is the same as in FileSystemProvider.unmangleName(). 188 * 189 * @param data The URL-encoded string to decode 190 * @param encoding The encoding to use 191 * @return A decoded string. 192 * @throws IllegalArgumentException If the data cannot be decoded. 193 */ 194 public static String urlDecode( String data, String encoding ) throws IllegalArgumentException { 195 // Presumably, the same caveats apply as in FileSystemProvider. 196 // Don't see why it would be horribly kludgy, though. 197 if( "UTF-8".equals( encoding ) ) { 198 return urlDecodeUTF8( data ); 199 } 200 201 return urlDecode( data.getBytes( Charset.forName( encoding ) ), encoding ); 202 } 203 204 /** 205 * Replaces the relevant entities inside the String. All & >, <, and " are replaced by their 206 * respective names. 207 * 208 * @since 1.6.1 209 * @param src The source string. 210 * @return The encoded string. 211 */ 212 public static String replaceEntities( String src ) { 213 src = replaceString( src, "&", "&" ); 214 src = replaceString( src, "<", "<" ); 215 src = replaceString( src, ">", ">" ); 216 src = replaceString( src, "\"", """ ); 217 218 return src; 219 } 220 221 /** 222 * Replaces a string with an other string. 223 * 224 * @param orig Original string. Null is safe. 225 * @param src The string to find. 226 * @param dest The string to replace <I>src</I> with. 227 * @return A string with the replacement done. 228 */ 229 public static String replaceString( String orig, String src, String dest ) { 230 if ( orig == null ) return null; 231 if ( src == null || dest == null ) throw new NullPointerException(); 232 if ( src.length() == 0 ) return orig; 233 234 StringBuilder res = new StringBuilder( orig.length() + 20 ); // Pure guesswork 235 int start = 0; 236 int end = 0; 237 int last = 0; 238 239 while ( ( start = orig.indexOf( src,end ) ) != -1 ) { 240 res.append( orig.substring( last, start ) ); 241 res.append( dest ); 242 end = start + src.length(); 243 last = start + src.length(); 244 } 245 246 res.append( orig.substring( end ) ); 247 248 return res.toString(); 249 } 250 251 /** 252 * Replaces a part of a string with a new String. 253 * 254 * @param start Where in the original string the replacing should start. 255 * @param end Where the replacing should end. 256 * @param orig Original string. Null is safe. 257 * @param text The new text to insert into the string. 258 * @return The string with the orig replaced with text. 259 */ 260 public static String replaceString( String orig, int start, int end, String text ) { 261 if( orig == null ) return null; 262 263 StringBuilder buf = new StringBuilder(orig); 264 buf.replace( start, end, text ); 265 return buf.toString(); 266 } 267 268 /** 269 * Replaces a string with an other string. Case insensitive matching is used 270 * 271 * @param orig Original string. Null is safe. 272 * @param src The string to find. 273 * @param dest The string to replace <I>src</I> with. 274 * @return A string with all instances of src replaced with dest. 275 */ 276 public static String replaceStringCaseUnsensitive( String orig, String src, String dest ) { 277 if( orig == null ) return null; 278 279 StringBuilder res = new StringBuilder(); 280 int start = 0; 281 int end = 0; 282 int last = 0; 283 284 String origCaseUnsn = orig.toLowerCase(); 285 String srcCaseUnsn = src.toLowerCase(); 286 287 while( ( start = origCaseUnsn.indexOf( srcCaseUnsn, end ) ) != -1 ) { 288 res.append( orig.substring( last, start ) ); 289 res.append( dest ); 290 end = start + src.length(); 291 last = start + src.length(); 292 } 293 294 res.append( orig.substring( end ) ); 295 296 return res.toString(); 297 } 298 299 /** 300 * Parses an integer parameter, returning a default value if the value is null or a non-number. 301 * 302 * @param value The value to parse 303 * @param defvalue A default value in case the value is not a number 304 * @return The parsed value (or defvalue). 305 */ 306 public static int parseIntParameter( String value, int defvalue ) { 307 int val = defvalue; 308 309 try { 310 val = Integer.parseInt( value.trim() ); 311 } catch( Exception e ) {} 312 313 return val; 314 } 315 316 /** 317 * Gets an integer-valued property from a standard Properties list. 318 * 319 * Before inspecting the props, we first check if there is a Java System Property with the same name, if it exists 320 * we use that value, if not we check an environment variable with that (almost) same name, almost meaning we replace 321 * dots with underscores. 322 * 323 * If the value does not exist, or is a 324 * non-integer, returns defVal. 325 * 326 * @since 2.1.48. 327 * @param props The property set to look through 328 * @param key The key to look for 329 * @param defVal If the property is not found or is a non-integer, returns this value. 330 * @return The property value as an integer (or defVal). 331 */ 332 public static int getIntegerProperty( Properties props, String key, int defVal ) { 333 String val = System.getProperties().getProperty(key, System.getenv(StringUtils.replace(key,".","_"))); 334 if (val == null) { 335 val = props.getProperty(key); 336 } 337 return parseIntParameter( val, defVal ); 338 } 339 340 /** 341 * Gets a boolean property from a standard Properties list. Returns the default value, in case the key has not 342 * been set. 343 * Before inspecting the props, we first check if there is a Java System Property with the same name, if it exists 344 * we use that value, if not we check an environment variable with that (almost) same name, almost meaning we replace 345 * dots with underscores. 346 * <P> 347 * The possible values for the property are "true"/"false", "yes"/"no", or "on"/"off". Any value not 348 * recognized is always defined as "false". 349 * 350 * @param props A list of properties to search. 351 * @param key The property key. 352 * @param defval The default value to return. 353 * 354 * @return True, if the property "key" was set to "true", "on", or "yes". 355 * 356 * @since 2.0.11 357 */ 358 public static boolean getBooleanProperty( Properties props, String key, boolean defval ) { 359 String val = System.getProperties().getProperty(key, System.getenv(StringUtils.replace(key,".","_"))); 360 if (val == null) { 361 val = props.getProperty(key); 362 } 363 if( val == null ) { 364 return defval; 365 } 366 367 return isPositive( val ); 368 } 369 370 /** 371 * Fetches a String property from the set of Properties. This differs from Properties.getProperty() in a 372 * couple of key respects: First, property value is trim()med (so no extra whitespace back and front). 373 * 374 * Before inspecting the props, we first check if there is a Java System Property with the same name, if it exists 375 * we use that value, if not we check an environment variable with that (almost) same name, almost meaning we replace 376 * dots with underscores. 377 * 378 * @param props The Properties to search through 379 * @param key The property key 380 * @param defval A default value to return, if the property does not exist. 381 * @return The property value. 382 * @since 2.1.151 383 */ 384 public static String getStringProperty(Properties props, String key, String defval) { 385 String val = System.getProperties().getProperty(key, System.getenv(StringUtils.replace(key,".","_"))); 386 if (val == null) { 387 val = props.getProperty(key); 388 } 389 if (val == null) { 390 return defval; 391 } 392 return val.trim(); 393 } 394 395 /** 396 * Throws an exception if a property is not found. 397 * 398 * @param props A set of properties to search the key in. 399 * @param key The key to look for. 400 * @return The required property 401 * 402 * @throws NoSuchElementException If the search key is not in the property set. 403 * @since 2.0.26 (on TextUtils, moved To WikiEngine on 2.11.0-M1 and back to TextUtils on 2.11.0-M6) 404 */ 405 public static String getRequiredProperty( Properties props, String key ) throws NoSuchElementException { 406 String value = getStringProperty( props, key, null ); 407 if( value == null ) { 408 throw new NoSuchElementException( "Required property not found: " + key ); 409 } 410 return value; 411 } 412 413 /** 414 * Fetches a file path property from the set of Properties. 415 * 416 * Before inspecting the props, we first check if there is a Java System Property with the same name, if it exists 417 * we use that value, if not we check an environment variable with that (almost) same name, almost meaning we replace 418 * dots with underscores. 419 * 420 * If the implementation fails to create the canonical path it just returns 421 * the original value of the property which is a bit doggy. 422 * 423 * @param props The Properties to search through 424 * @param key The property key 425 * @param defval A default value to return, if the property does not exist. 426 * @return the canonical path of the file or directory being referenced 427 * @since 2.10.1 428 */ 429 public static String getCanonicalFilePathProperty(Properties props, String key, String defval) { 430 431 String result; 432 String val = System.getProperties().getProperty(key, System.getenv(StringUtils.replace(key,".","_"))); 433 if (val == null) { 434 val = props.getProperty(key); 435 } 436 437 if( val == null ) { 438 val = defval; 439 } 440 441 try { 442 result = new File(new File(val.trim()).getCanonicalPath()).getAbsolutePath(); 443 } 444 catch(IOException e) { 445 result = val.trim(); 446 } 447 return result; 448 } 449 450 /** 451 * Returns true, if the string "val" denotes a positive string. Allowed values are "yes", "on", and "true". 452 * Comparison is case-insignificant. Null values are safe. 453 * 454 * @param val Value to check. 455 * @return True, if val is "true", "on", or "yes"; otherwise false. 456 * 457 * @since 2.0.26 458 */ 459 public static boolean isPositive( String val ) { 460 if( val == null ) { 461 return false; 462 } 463 val = val.trim(); 464 return val.equalsIgnoreCase( "true" ) || val.equalsIgnoreCase( "on" ) || val.equalsIgnoreCase( "yes" ); 465 } 466 467 /** 468 * Makes sure that the POSTed data is conforms to certain rules. These rules are: 469 * <UL> 470 * <LI>The data always ends with a newline (some browsers, such as NS4.x series, does not send a newline at 471 * the end, which makes the diffs a bit strange sometimes. 472 * <LI>The CR/LF/CRLF mess is normalized to plain CRLF. 473 * </UL> 474 * 475 * The reason why we're using CRLF is that most browser already return CRLF since that is the closest thing to 476 * a HTTP standard. 477 * 478 * @param postData The data to normalize 479 * @return Normalized data 480 */ 481 public static String normalizePostData( String postData ) { 482 StringBuilder sb = new StringBuilder(); 483 484 for( int i = 0; i < postData.length(); i++ ) { 485 switch( postData.charAt(i) ) { 486 case 0x0a: // LF, UNIX 487 sb.append( "\r\n" ); 488 break; 489 490 case 0x0d: // CR, either Mac or MSDOS 491 sb.append( "\r\n" ); 492 // If it's MSDOS, skip the LF so that we don't add it again. 493 if( i < postData.length() - 1 && postData.charAt( i + 1 ) == 0x0a ) { 494 i++; 495 } 496 break; 497 498 default: 499 sb.append( postData.charAt(i) ); 500 break; 501 } 502 } 503 504 if( sb.length() < 2 || !sb.substring( sb.length()-2 ).equals( "\r\n" ) ) { 505 sb.append( "\r\n" ); 506 } 507 508 return sb.toString(); 509 } 510 511 private static final int EOI = 0; 512 private static final int LOWER = 1; 513 private static final int UPPER = 2; 514 private static final int DIGIT = 3; 515 private static final int OTHER = 4; 516 private static final Random RANDOM = new SecureRandom(); 517 518 private static int getCharKind( int c ) { 519 if( c == -1 ) { 520 return EOI; 521 } 522 523 char ch = ( char )c; 524 525 if( Character.isLowerCase( ch ) ) { 526 return LOWER; 527 } else if( Character.isUpperCase( ch ) ) { 528 return UPPER; 529 } else if( Character.isDigit( ch ) ) { 530 return DIGIT; 531 } else { 532 return OTHER; 533 } 534 } 535 536 /** 537 * Adds spaces in suitable locations of the input string. This is used to transform a WikiName into a more 538 * readable format. 539 * 540 * @param s String to be beautified. 541 * @return A beautified string. 542 */ 543 public static String beautifyString( String s ) { 544 return beautifyString( s, " " ); 545 } 546 547 /** 548 * Adds spaces in suitable locations of the input string. This is used to transform a WikiName into a more 549 * readable format. 550 * 551 * @param s String to be beautified. 552 * @param space Use this string for the space character. 553 * @return A beautified string. 554 * @since 2.1.127 555 */ 556 public static String beautifyString( String s, String space ) { 557 if( s == null || s.length() == 0 ) { 558 return ""; 559 } 560 561 StringBuilder result = new StringBuilder(); 562 563 int cur = s.charAt( 0 ); 564 int curKind = getCharKind( cur ); 565 566 int prevKind = LOWER; 567 int nextKind = -1; 568 569 int next = -1; 570 int nextPos = 1; 571 572 while( curKind != EOI ) { 573 next = ( nextPos < s.length() ) ? s.charAt( nextPos++ ) : -1; 574 nextKind = getCharKind( next ); 575 576 if( ( prevKind == UPPER ) && ( curKind == UPPER ) && ( nextKind == LOWER ) ) { 577 result.append( space ); 578 result.append( ( char ) cur ); 579 } else { 580 result.append((char) cur ); 581 if( ( ( curKind == UPPER ) && (nextKind == DIGIT) ) 582 || ( ( curKind == LOWER ) && ( ( nextKind == DIGIT ) || ( nextKind == UPPER ) ) ) 583 || ( ( curKind == DIGIT ) && ( ( nextKind == UPPER ) || ( nextKind == LOWER ) ) ) ) { 584 result.append( space ); 585 } 586 } 587 prevKind = curKind; 588 cur = next; 589 curKind = nextKind; 590 } 591 592 return result.toString(); 593 } 594 595 /** 596 * Creates a Properties object based on an array which contains alternatively a key and a value. It is useful 597 * for generating default mappings. For example: 598 * <pre> 599 * String[] properties = { "jspwiki.property1", "value1", 600 * "jspwiki.property2", "value2 }; 601 * 602 * Properties props = TextUtil.createPropertes( values ); 603 * 604 * System.out.println( props.getProperty("jspwiki.property1") ); 605 * </pre> 606 * would output "value1". 607 * 608 * @param values Alternating key and value pairs. 609 * @return Property object 610 * @see java.util.Properties 611 * @throws IllegalArgumentException if the property array is missing a value for a key. 612 * @since 2.2. 613 */ 614 public static Properties createProperties( String[] values ) throws IllegalArgumentException { 615 if( values.length % 2 != 0 ) { 616 throw new IllegalArgumentException( "One value is missing."); 617 } 618 619 Properties props = new Properties(); 620 for( int i = 0; i < values.length; i += 2 ) { 621 props.setProperty( values[i], values[i + 1] ); 622 } 623 624 return props; 625 } 626 627 /** 628 * Counts the number of sections (separated with "----") from the page. 629 * 630 * @param pagedata The WikiText to parse. 631 * @return int Number of counted sections. 632 * @since 2.1.86. 633 */ 634 public static int countSections( String pagedata ) { 635 int tags = 0; 636 int start = 0; 637 638 while( ( start = pagedata.indexOf( "----", start ) ) != -1 ) { 639 tags++; 640 start += 4; // Skip this "----" 641 } 642 643 // 644 // The first section does not get the "----" 645 // 646 return pagedata.length() > 0 ? tags + 1 : 0; 647 } 648 649 /** 650 * Gets the given section (separated with "----") from the page text. 651 * Note that the first section is always #1. If a page has no section markers, 652 * then there is only a single section, #1. 653 * 654 * @param pagedata WikiText to parse. 655 * @param section Which section to get. 656 * @return String The section. 657 * @throws IllegalArgumentException If the page does not contain this many sections. 658 * @since 2.1.86. 659 */ 660 public static String getSection( String pagedata, int section ) throws IllegalArgumentException { 661 int tags = 0; 662 int start = 0; 663 int previous = 0; 664 665 while( ( start = pagedata.indexOf( "----", start ) ) != -1 ) { 666 if( ++tags == section ) { 667 return pagedata.substring( previous, start ); 668 } 669 670 start += 4; // Skip this "----" 671 // allow additional dashes, treat it as if it was a correct 4-dash 672 while (start < pagedata.length() && pagedata.charAt( start ) == '-') { 673 start++; 674 } 675 676 previous = start; 677 } 678 679 if( ++tags == section ) { 680 return pagedata.substring( previous ); 681 } 682 683 throw new IllegalArgumentException( "There is no section no. " + section + " on the page." ); 684 } 685 686 /** 687 * A simple routine which just repeates the arguments. This is useful for creating something like a line or 688 * something. 689 * 690 * @param what String to repeat 691 * @param times How many times to repeat the string. 692 * @return Guess what? 693 * @since 2.1.98. 694 */ 695 public static String repeatString( String what, int times ) { 696 StringBuilder sb = new StringBuilder(); 697 for( int i = 0; i < times; i++ ) { 698 sb.append( what ); 699 } 700 701 return sb.toString(); 702 } 703 704 /** 705 * Converts a string from the Unicode representation into something that can be embedded in a java 706 * properties file. All references outside the ASCII range are replaced with \\uXXXX. 707 * 708 * @param s The string to convert 709 * @return the ASCII string 710 */ 711 public static String native2Ascii( String s ) { 712 StringBuilder sb = new StringBuilder(); 713 for( int i = 0; i < s.length(); i++ ) { 714 char aChar = s.charAt(i); 715 if( ( aChar < 0x0020 ) || ( aChar > 0x007e ) ) { 716 sb.append( '\\'); 717 sb.append( 'u'); 718 sb.append( toHex( ( aChar >> 12 ) & 0xF ) ); 719 sb.append( toHex( ( aChar >> 8 ) & 0xF ) ); 720 sb.append( toHex( ( aChar >> 4 ) & 0xF ) ); 721 sb.append( toHex( aChar & 0xF ) ); 722 } else { 723 sb.append( aChar ); 724 } 725 } 726 return sb.toString(); 727 } 728 729 private static char toHex( int nibble ) { 730 final char[] hexDigit = { 731 '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F' 732 }; 733 return hexDigit[nibble & 0xF]; 734 } 735 736 /** 737 * Generates a hexadecimal string from an array of bytes. For example, if the array contains 738 * { 0x01, 0x02, 0x3E }, the resulting string will be "01023E". 739 * 740 * @param bytes A Byte array 741 * @return A String representation 742 * @since 2.3.87 743 */ 744 public static String toHexString( byte[] bytes ) { 745 StringBuilder sb = new StringBuilder( bytes.length * 2 ); 746 for( int i = 0; i < bytes.length; i++ ) { 747 sb.append( toHex( bytes[i] >> 4 ) ); 748 sb.append( toHex( bytes[i] ) ); 749 } 750 751 return sb.toString(); 752 } 753 754 /** 755 * Returns true, if the argument contains a number, otherwise false. In a quick test this is roughly the same 756 * speed as Integer.parseInt() if the argument is a number, and roughly ten times the speed, if the argument 757 * is NOT a number. 758 * 759 * @since 2.4 760 * @param s String to check 761 * @return True, if s represents a number. False otherwise. 762 */ 763 public static boolean isNumber( String s ) { 764 if( s == null ) { 765 return false; 766 } 767 768 if( s.length() > 1 && s.charAt(0) == '-' ) { 769 s = s.substring(1); 770 } 771 772 for( int i = 0; i < s.length(); i++ ) { 773 if( !Character.isDigit( s.charAt( i ) ) ) { 774 return false; 775 } 776 } 777 778 return true; 779 } 780 781 /** Length of password. @see #generateRandomPassword() */ 782 public static final int PASSWORD_LENGTH = 8; 783 784 /** 785 * Generate a random String suitable for use as a temporary password. 786 * 787 * @return String suitable for use as a temporary password 788 * @since 2.4 789 */ 790 public static String generateRandomPassword() { 791 // Pick from some letters that won't be easily mistaken for each 792 // other. So, for example, omit o O and 0, 1 l and L. 793 String letters = "abcdefghjkmnpqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ23456789+@"; 794 795 String pw = ""; 796 for( int i = 0; i < PASSWORD_LENGTH; i++ ) { 797 int index = ( int )( RANDOM.nextDouble() * letters.length() ); 798 pw += letters.substring( index, index + 1 ); 799 } 800 return pw; 801 } 802 803}