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