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 }