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