LANG-805 RandomStringUtils.random(count, 0, 0, false, false, universe, random) always throws java.lang.ArrayIndexOutOfBoundsException
Merge fixes from LANG3 code

git-svn-id: https://svn.apache.org/repos/asf/commons/proper/lang/branches/LANG_2_X@1348424 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/main/java/org/apache/commons/lang/RandomStringUtils.java b/src/main/java/org/apache/commons/lang/RandomStringUtils.java
index facbd1e..f7e2a4e 100644
--- a/src/main/java/org/apache/commons/lang/RandomStringUtils.java
+++ b/src/main/java/org/apache/commons/lang/RandomStringUtils.java
@@ -17,10 +17,11 @@
 package org.apache.commons.lang;
 
 import java.util.Random;
+
 /**
- * <p>Operations for random <code>String</code>s.</p>
+ * <p>Operations for random {@code String}s.</p>
  * <p>Currently <em>private high surrogate</em> characters are ignored. 
- * These are unicode characters that fall between the values 56192 (db80)
+ * These are Unicode characters that fall between the values 56192 (db80)
  * and 56319 (dbff) as we don't know how to handle them. 
  * High and low surrogates are correctly dealt with - that is if a 
  * high surrogate is randomly chosen, 55296 (d800) to 56191 (db7f) 
@@ -29,10 +30,6 @@
  * chosen high surrogate. </p>
  *
  * <p>#ThreadSafe#</p>
- * @author Apache Software Foundation
- * @author <a href="mailto:steven@caswell.name">Steven Caswell</a>
- * @author Gary Gregory
- * @author Phil Steitz
  * @since 1.0
  * @version $Id$
  */
@@ -46,9 +43,9 @@
     private static final Random RANDOM = new Random();
 
     /**
-     * <p><code>RandomStringUtils</code> instances should NOT be constructed in
+     * <p>{@code RandomStringUtils} instances should NOT be constructed in
      * standard programming. Instead, the class should be used as
-     * <code>RandomStringUtils.random(5);</code>.</p>
+     * {@code RandomStringUtils.random(5);}.</p>
      *
      * <p>This constructor is public to permit tools that require a JavaBean instance
      * to operate.</p>
@@ -77,7 +74,7 @@
      * specified.</p>
      *
      * <p>Characters will be chosen from the set of characters whose
-     * ASCII value is between <code>32</code> and <code>126</code> (inclusive).</p>
+     * ASCII value is between {@code 32} and {@code 126} (inclusive).</p>
      *
      * @param count  the length of random string to create
      * @return the random string
@@ -136,9 +133,9 @@
      * characters as indicated by the arguments.</p>
      *
      * @param count  the length of random string to create
-     * @param letters  if <code>true</code>, generated string will include
+     * @param letters  if {@code true}, generated string will include
      *  alphabetic characters
-     * @param numbers  if <code>true</code>, generated string will include
+     * @param numbers  if {@code true}, generated string will include
      *  numeric characters
      * @return the random string
      */
@@ -156,9 +153,9 @@
      * @param count  the length of random string to create
      * @param start  the position in set of chars to start at
      * @param end  the position in set of chars to end before
-     * @param letters  if <code>true</code>, generated string will include
+     * @param letters  if {@code true}, generated string will include
      *  alphabetic characters
-     * @param numbers  if <code>true</code>, generated string will include
+     * @param numbers  if {@code true}, generated string will include
      *  numeric characters
      * @return the random string
      */
@@ -181,10 +178,10 @@
      * @param letters  only allow letters?
      * @param numbers  only allow numbers?
      * @param chars  the set of chars to choose randoms from.
-     *  If <code>null</code>, then it will use the set of all chars.
+     *  If {@code null}, then it will use the set of all chars.
      * @return the random string
      * @throws ArrayIndexOutOfBoundsException if there are not
-     *  <code>(end - start) + 1</code> characters in the set array.
+     *  {@code (end - start) + 1} characters in the set array.
      */
     public static String random(int count, int start, int end, boolean letters, boolean numbers, char[] chars) {
         return random(count, start, end, letters, numbers, chars, RANDOM);
@@ -194,13 +191,13 @@
      * <p>Creates a random string based on a variety of options, using
      * supplied source of randomness.</p>
      *
-     * <p>If start and end are both <code>0</code>, start and end are set
-     * to <code>' '</code> and <code>'z'</code>, the ASCII printable
+     * <p>If start and end are both {@code 0}, start and end are set
+     * to {@code ' '} and {@code 'z'}, the ASCII printable
      * characters, will be used, unless letters and numbers are both
-     * <code>false</code>, in which case, start and end are set to
-     * <code>0</code> and <code>Integer.MAX_VALUE</code>.
+     * {@code false}, in which case, start and end are set to
+     * {@code 0} and {@code Integer.MAX_VALUE}.
      *
-     * <p>If set is not <code>null</code>, characters between start and
+     * <p>If set is not {@code null}, characters between start and
      * end are chosen.</p>
      *
      * <p>This method accepts a user-supplied {@link Random}
@@ -214,13 +211,13 @@
      * @param end  the position in set of chars to end before
      * @param letters  only allow letters?
      * @param numbers  only allow numbers?
-     * @param chars  the set of chars to choose randoms from.
-     *  If <code>null</code>, then it will use the set of all chars.
+     * @param chars  the set of chars to choose randoms from, must not be empty.
+     *  If {@code null}, then it will use the set of all chars.
      * @param random  a source of randomness.
      * @return the random string
      * @throws ArrayIndexOutOfBoundsException if there are not
-     *  <code>(end - start) + 1</code> characters in the set array.
-     * @throws IllegalArgumentException if <code>count</code> &lt; 0.
+     *  {@code (end - start) + 1} characters in the set array.
+     * @throws IllegalArgumentException if {@code count} &lt; 0 or the provided chars array is empty.
      * @since 2.0
      */
     public static String random(int count, int start, int end, boolean letters, boolean numbers,
@@ -230,12 +227,20 @@
         } else if (count < 0) {
             throw new IllegalArgumentException("Requested random string length " + count + " is less than 0.");
         }
-        if ((start == 0) && (end == 0)) {
-            end = 'z' + 1;
-            start = ' ';
-            if (!letters && !numbers) {
-                start = 0;
-                end = Integer.MAX_VALUE;
+        if (chars != null && chars.length == 0) {
+            throw new IllegalArgumentException("The chars array must not be empty");
+        }
+
+        if (start == 0 && end == 0) {
+            if (chars != null) {
+                end = chars.length;
+            } else {
+                if (!letters && !numbers) {
+                    end = Integer.MAX_VALUE;
+                } else {
+                    end = 'z' + 1;
+                    start = ' ';                
+                }
             }
         }
 
@@ -249,10 +254,9 @@
             } else {
                 ch = chars[random.nextInt(gap) + start];
             }
-            if ((letters && Character.isLetter(ch))
-                || (numbers && Character.isDigit(ch))
-                || (!letters && !numbers)) 
-            {
+            if (letters && Character.isLetter(ch)
+                    || numbers && Character.isDigit(ch)
+                    || !letters && !numbers) {
                 if(ch >= 56320 && ch <= 57343) {
                     if(count == 0) {
                         count++;
@@ -289,13 +293,14 @@
      * specified.</p>
      *
      * <p>Characters will be chosen from the set of characters
-     * specified.</p>
+     * specified by the string, must not be empty. 
+     * If null, the set of all characters is used.</p>
      *
      * @param count  the length of random string to create
      * @param chars  the String containing the set of characters to use,
-     *  may be null
+     *  may be null, but must not be empty
      * @return the random string
-     * @throws IllegalArgumentException if <code>count</code> &lt; 0.
+     * @throws IllegalArgumentException if {@code count} &lt; 0 or the string is empty.
      */
     public static String random(int count, String chars) {
         if (chars == null) {
@@ -314,7 +319,7 @@
      * @param chars  the character array containing the set of characters to use,
      *  may be null
      * @return the random string
-     * @throws IllegalArgumentException if <code>count</code> &lt; 0.
+     * @throws IllegalArgumentException if {@code count} &lt; 0.
      */
     public static String random(int count, char[] chars) {
         if (chars == null) {
diff --git a/src/site/changes/changes.xml b/src/site/changes/changes.xml
index 7f5c3f4..be5bf49 100644
--- a/src/site/changes/changes.xml
+++ b/src/site/changes/changes.xml
@@ -21,6 +21,7 @@
   </properties>
   <body>
   <release version="2.7" date="TBA" description="TBA (requires minimum of Java 1.3)">
+    <action issue="LANG-805" type="fix">RandomStringUtils.random(count, 0, 0, false, false, universe, random) always throws java.lang.ArrayIndexOutOfBoundsException</action>
     <action issue="LANG-803" type="fix">LocaleUtils - DCL idiom is not thread-safe.</action>
     <action issue="LANG-677" type="fix">DateUtils isSameLocalTime() compares the hour using 12hour Calendar.HOUR instead of 24hour Calendar.HOUR_OF_DAY</action>
   </release>
diff --git a/src/test/java/org/apache/commons/lang/RandomStringUtilsTest.java b/src/test/java/org/apache/commons/lang/RandomStringUtilsTest.java
index 966b973..1172b1a 100644
--- a/src/test/java/org/apache/commons/lang/RandomStringUtilsTest.java
+++ b/src/test/java/org/apache/commons/lang/RandomStringUtilsTest.java
@@ -20,13 +20,9 @@
 import java.lang.reflect.Modifier;
 import java.util.Random;
 
-
 /**
  * Unit tests {@link org.apache.commons.lang.RandomStringUtils}.
  *
- * @author <a href="mailto:steven@caswell.name">Steven Caswell</a>
- * @author <a href="mailto:ridesmet@users.sourceforge.net">Ringo De Smet</a>
- * @author Phil Steitz
  * @version $Id$
  */
 public class RandomStringUtilsTest extends junit.framework.TestCase {
@@ -127,9 +123,15 @@
 
         r1 = RandomStringUtils.random(0);
         assertEquals("random(0).equals(\"\")", "", r1);
-
     }
+
+    public void testLANG805() {
+        long seed = System.currentTimeMillis();
+        assertEquals("aaa", RandomStringUtils.random(3,0,0,false,false,new char[]{'a'},new Random(seed)));
+    }
+
     public void testExceptions() {
+        final char[] DUMMY = new char[]{'a'}; // valid char array
         try {
             RandomStringUtils.random(-1);
             fail();
@@ -139,7 +141,11 @@
             fail();
         } catch (IllegalArgumentException ex) {}
         try {
-            RandomStringUtils.random(-1, new char[0]);
+            RandomStringUtils.random(-1, DUMMY);
+            fail();
+        } catch (IllegalArgumentException ex) {}
+        try {
+            RandomStringUtils.random(1, new char[0]); // must not provide empty array => IAE
             fail();
         } catch (IllegalArgumentException ex) {}
         try {
@@ -147,15 +153,19 @@
             fail();
         } catch (IllegalArgumentException ex) {}
         try {
+            RandomStringUtils.random(-1, (String)null);
+            fail();
+        } catch (IllegalArgumentException ex) {}
+        try {
             RandomStringUtils.random(-1, 'a', 'z', false, false);
             fail();
         } catch (IllegalArgumentException ex) {}
         try {
-            RandomStringUtils.random(-1, 'a', 'z', false, false, new char[0]);
+            RandomStringUtils.random(-1, 'a', 'z', false, false, DUMMY);
             fail();
         } catch (IllegalArgumentException ex) {}
         try {
-            RandomStringUtils.random(-1, 'a', 'z', false, false, new char[0], new Random());
+            RandomStringUtils.random(-1, 'a', 'z', false, false, DUMMY, new Random());
             fail();
         } catch (IllegalArgumentException ex) {}
     }
@@ -290,8 +300,8 @@
         double sumSq = 0.0d;
         double dev = 0.0d;
         for (int i = 0; i < observed.length; i++) {
-            dev = (double) (observed[i] - expected[i]);
-            sumSq += dev * dev / (double) expected[i];
+            dev = observed[i] - expected[i];
+            sumSq += dev * dev / expected[i];
         }
         return sumSq;
     }           
@@ -315,8 +325,8 @@
         for (int i=0; i < orig.length() && i < copy.length(); i++) {
             char o = orig.charAt(i);
             char c = copy.charAt(i);
-            assertEquals("differs at " + i + "(" + Integer.toHexString((new Character(o)).hashCode()) + "," +
-            Integer.toHexString((new Character(c)).hashCode()) + ")", o, c);
+            assertEquals("differs at " + i + "(" + Integer.toHexString(new Character(o).hashCode()) + "," +
+            Integer.toHexString(new Character(c).hashCode()) + ")", o, c);
         }
         // compare length also
         assertEquals(orig.length(), copy.length());