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