Fixes issue #907 ("Float.valueOf() Acceptes Alpha Strings")

Patch by: bobv
Review by: tobyr, bruce


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@968 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/super/com/google/gwt/emul/java/lang/Byte.java b/user/super/com/google/gwt/emul/java/lang/Byte.java
index 2c0b658..b610af4 100644
--- a/user/super/com/google/gwt/emul/java/lang/Byte.java
+++ b/user/super/com/google/gwt/emul/java/lang/Byte.java
@@ -1,12 +1,12 @@
 /*
- * Copyright 2006 Google Inc.
- * 
+ * Copyright 2007 Google Inc.
+ *
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
  * the License at
- * 
+ *
  * http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -23,12 +23,7 @@
   public static final byte MAX_VALUE = (byte) 0x7F;
 
   public static Byte decode(String s) throws NumberFormatException {
-    long x = __parseLongInfer(s);
-    if (__isLongNaN(x)) {
-      throw new NumberFormatException(s);
-    } else {
-      return new Byte((byte) x);
-    }
+    return new Byte((byte)__decodeAndValidateLong(s, MIN_VALUE, MAX_VALUE));
   }
 
   public static byte parseByte(String s) throws NumberFormatException {
@@ -38,12 +33,7 @@
 
   public static byte parseByte(String s, int radix)
       throws NumberFormatException {
-    long x = __parseLongRadix(s, radix);
-    if (__isLongNaN(x)) {
-      throw new NumberFormatException(s);
-    } else {
-      return (byte) x;
-    }
+    return (byte)__parseAndValidateLong(s, radix, MIN_VALUE, MAX_VALUE);
   }
 
   public static String toString(byte b) {
diff --git a/user/super/com/google/gwt/emul/java/lang/Double.java b/user/super/com/google/gwt/emul/java/lang/Double.java
index 66dcabd..c33d36a 100644
--- a/user/super/com/google/gwt/emul/java/lang/Double.java
+++ b/user/super/com/google/gwt/emul/java/lang/Double.java
@@ -44,12 +44,7 @@
   }-*/;
 
   public static double parseDouble(String s) throws NumberFormatException {
-    double x = __parseDouble(s);
-    if (isNaN(x)) {
-      throw new NumberFormatException(s);
-    } else {
-      return x;
-    }
+    return __parseAndValidateDouble(s);
   }
 
   public static String toString(double b) {
diff --git a/user/super/com/google/gwt/emul/java/lang/Float.java b/user/super/com/google/gwt/emul/java/lang/Float.java
index d7e69f3..d01dd1f 100644
--- a/user/super/com/google/gwt/emul/java/lang/Float.java
+++ b/user/super/com/google/gwt/emul/java/lang/Float.java
@@ -44,12 +44,7 @@
   }-*/;
 
   public static float parseFloat(String s) throws NumberFormatException {
-    float x = __parseFloat(s);
-    if (isNaN(x)) {
-      throw new NumberFormatException(s);
-    } else {
-      return x;
-    }
+    return (float)__parseAndValidateDouble(s);
   }
 
   public static String toString(float b) {
diff --git a/user/super/com/google/gwt/emul/java/lang/Integer.java b/user/super/com/google/gwt/emul/java/lang/Integer.java
index 6c197f3..9640d8f 100644
--- a/user/super/com/google/gwt/emul/java/lang/Integer.java
+++ b/user/super/com/google/gwt/emul/java/lang/Integer.java
@@ -1,12 +1,12 @@
 /*
- * Copyright 2006 Google Inc.
- * 
+ * Copyright 2007 Google Inc.
+ *
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
  * the License at
- * 
+ *
  * http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -23,12 +23,7 @@
   public static final int MAX_VALUE = 0x7fffffff;
 
   public static Integer decode(String s) throws NumberFormatException {
-    long x = __parseLongInfer(s);
-    if (__isLongNaN(x)) {
-      throw new NumberFormatException(s);
-    } else {
-      return new Integer((int) x);
-    }
+    return new Integer((int)__decodeAndValidateLong(s, MIN_VALUE, MAX_VALUE));
   }
 
   public static int parseInt(String s) throws NumberFormatException {
@@ -36,12 +31,7 @@
   }
 
   public static int parseInt(String s, int radix) throws NumberFormatException {
-    long x = __parseLongRadix(s, radix);
-    if (__isLongNaN(x)) {
-      throw new NumberFormatException(s);
-    } else {
-      return (int) x;
-    }
+    return (int)__parseAndValidateLong(s, radix, MIN_VALUE, MAX_VALUE);
   }
 
   public static String toBinaryString(int x) {
diff --git a/user/super/com/google/gwt/emul/java/lang/Long.java b/user/super/com/google/gwt/emul/java/lang/Long.java
index f5f0d09..eb321ff 100644
--- a/user/super/com/google/gwt/emul/java/lang/Long.java
+++ b/user/super/com/google/gwt/emul/java/lang/Long.java
@@ -1,12 +1,12 @@
 /*
- * Copyright 2006 Google Inc.
- * 
+ * Copyright 2007 Google Inc.
+ *
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
  * the License at
- * 
+ *
  * http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -23,12 +23,7 @@
   public static final long MAX_VALUE = 0x7fffffffffffffffL;
 
   public static Long decode(String s) throws NumberFormatException {
-    long x = __parseLongInfer(s);
-    if (__isLongNaN(x)) {
-      throw new NumberFormatException(s);
-    } else {
-      return new Long(x);
-    }
+    return new Long(__decodeAndValidateLong(s, MIN_VALUE, MAX_VALUE));
   }
 
   public static long parseLong(String s) throws NumberFormatException {
@@ -37,12 +32,7 @@
 
   public static long parseLong(String s, int radix)
       throws NumberFormatException {
-    long x = __parseLongRadix(s, radix);
-    if (__isLongNaN(x)) {
-      throw new NumberFormatException(s);
-    } else {
-      return x;
-    }
+    return __parseAndValidateLong(s, radix, MIN_VALUE, MAX_VALUE);
   }
 
   public static String toBinaryString(long x) {
diff --git a/user/super/com/google/gwt/emul/java/lang/Number.java b/user/super/com/google/gwt/emul/java/lang/Number.java
index 71ebf84..c848490 100644
--- a/user/super/com/google/gwt/emul/java/lang/Number.java
+++ b/user/super/com/google/gwt/emul/java/lang/Number.java
@@ -1,12 +1,12 @@
 /*
- * Copyright 2006 Google Inc.
- * 
+ * Copyright 2007 Google Inc.
+ *
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
  * the License at
- * 
+ *
  * http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -15,11 +15,18 @@
  */
 package java.lang;
 
+import com.google.gwt.core.client.JavaScriptObject;
+
 /**
  * Abstract base class for numberic wrapper classes.
  */
 public abstract class Number {
 
+  /**
+   *  Stores a regular expression object to verify format of float values.
+   */
+  protected static JavaScriptObject floatRegex;
+
   // CHECKSTYLE_OFF: A special need to use unusual identifiers to avoid
   // introducing name collisions.
 
@@ -30,43 +37,149 @@
       "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d",
       "e", "f"};
 
+  static {
+    initNative();
+  }
+
+  private static native void initNative() /*-{
+    @java.lang.Number::floatRegex = /^[+-]?\d*\.?\d*(e[+-]?\d+)?$/i;
+  }-*/;
+
+  /**
+   * @skip
+   *
+   * This function will determine the radix that the string is expressed in
+   * based on the parsing rules defined in the Javadocs for Integer.decode() and
+   * invoke __parseAndValidateLong.
+   */
+  protected static long __decodeAndValidateLong(String s, long lowerBound,
+      long upperBound) throws NumberFormatException {
+    final boolean negative;
+    if (s.startsWith("-")) {
+      negative = true;
+      s = s.substring(1);
+    } else {
+      negative = false;
+    }
+
+    final int radix;
+    if (s.startsWith("0x") || s.startsWith("0X")) {
+      s = s.substring(2);
+      radix = 16;
+    } else if (s.startsWith("#")) {
+      s = s.substring(1);
+      radix = 16;
+    } else if (s.startsWith("0")) {
+      radix = 8;
+    } else {
+      radix = 10;
+    }
+    
+    if (negative) {
+      s = "-" + s;
+    }
+    
+    return __parseAndValidateLong(s, radix, lowerBound, upperBound);
+  }
+
+  /**
+   * @skip
+   *
+   * This function contains common logic for parsing a String in a given
+   * radix and validating the result.
+   */
+  protected static long __parseAndValidateLong(String s, int radix,
+      long lowerBound, long upperBound) throws NumberFormatException {
+  
+    int length = s.length();
+    int startIndex = (length > 0) && (s.charAt(0) == '-') ? 1 : 0;
+  
+    for (int i = startIndex; i < length; i++) {
+      if (Character.digit(s.charAt(i), radix) == -1) {
+        throw new NumberFormatException("Could not parse " + s +
+            " in radix " + radix);
+      }
+    }
+
+    long toReturn =  __parseInt(s, radix);
+    if (__isLongNaN(toReturn)) {
+      throw new NumberFormatException("Unable to parse " + s);
+    } else if (toReturn < lowerBound || toReturn > upperBound) {
+      throw new NumberFormatException(
+        "The string " + s + " exceeds the range for the requested data type");
+    }
+    
+    return toReturn;
+  }
+
+  /**
+   * @skip
+   *
+   * This function contains common logic for parsing a String as a floating-
+   * point number and validating the range.
+   */
+  protected static double __parseAndValidateDouble(String s)
+      throws NumberFormatException {
+
+    double toReturn = __parseDouble(s);
+
+    if (__isDoubleNaN(toReturn)) {
+      throw new NumberFormatException("Unable to parse " + s);
+    }
+    
+    return toReturn;
+  }
+
   /**
    * @skip
    */
-  protected static native boolean __isLongNaN(long x) /*-{
+  private static native boolean __isDoubleNaN(double x) /*-{
     return isNaN(x);
   }-*/;
 
   /**
    * @skip
    */
-  protected static native long __parseLongRadix(String s, int radix) /*-{
+  private static native boolean __isLongNaN(long x) /*-{
+    return isNaN(x);
+  }-*/;
+
+  /**
+   * @skip
+   *
+   * Invokes the global JS function <code>parseInt()</code>.
+   */
+  private static native long __parseInt(String s, int radix) /*-{
     return parseInt(s, radix);
   }-*/;
-
+  
   /**
    * @skip
+   *
+   * @return The floating-point representation of <code>str</code> or
+   *           <code>Number.NaN</code> if the string does not match
+   *           {@link floatRegex}.
    */
-  protected static native long __parseLongInfer(String s) /*-{
-    return parseInt(s);
-  }-*/;
-
-  /**
-   * @skip
-   */
-  protected static native double __parseDouble(String str) /*-{
-    return parseFloat(str);
-  }-*/;
-
-  /**
-   * @skip
-   */
-  static native float __parseFloat(String str) /*-{
-    return parseFloat(str);
+  private static native double __parseDouble(String str) /*-{
+    if (@java.lang.Number::floatRegex.test(str)) {
+      return parseFloat(str);
+    } else {
+      return Number.NaN;
+    }
   }-*/;
 
   // CHECKSTYLE_ON
 
+  /**
+   *  Used by JSNI methods to report badly formatted strings.
+   *  @param s the unparseable string
+   *  @throws NumberFormatException every time
+   */
+  private static void throwNumberFormatException(String s)
+      throws NumberFormatException {
+    throw new NumberFormatException("Could not parse " + s);
+  }
+
   public abstract byte byteValue();
 
   public abstract double doubleValue();
diff --git a/user/super/com/google/gwt/emul/java/lang/Short.java b/user/super/com/google/gwt/emul/java/lang/Short.java
index 46e5939..53c3e18 100644
--- a/user/super/com/google/gwt/emul/java/lang/Short.java
+++ b/user/super/com/google/gwt/emul/java/lang/Short.java
@@ -1,12 +1,12 @@
 /*
- * Copyright 2006 Google Inc.
- * 
+ * Copyright 2007 Google Inc.
+ *
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
  * the License at
- * 
+ *
  * http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -23,12 +23,7 @@
   public static final short MAX_VALUE = (short) 0x7fff;
 
   public static Short decode(String s) throws NumberFormatException {
-    long x = __parseLongInfer(s);
-    if (__isLongNaN(x)) {
-      throw new NumberFormatException(s);
-    } else {
-      return new Short((short) x);
-    }
+    return new Short((short)__decodeAndValidateLong(s, MIN_VALUE, MAX_VALUE));
   }
 
   public static short parseShort(String s) throws NumberFormatException {
@@ -37,12 +32,7 @@
 
   public static short parseShort(String s, int radix)
       throws NumberFormatException {
-    long x = __parseLongRadix(s, radix);
-    if (__isLongNaN(x)) {
-      throw new NumberFormatException(s);
-    } else {
-      return (short) x;
-    }
+    return (short)__parseAndValidateLong(s, radix, MIN_VALUE, MAX_VALUE);
   }
 
   public static String toString(short b) {
diff --git a/user/test/com/google/gwt/emultest/java/lang/DoubleTest.java b/user/test/com/google/gwt/emultest/java/lang/DoubleTest.java
index 88756c5..4d6edaa 100644
--- a/user/test/com/google/gwt/emultest/java/lang/DoubleTest.java
+++ b/user/test/com/google/gwt/emultest/java/lang/DoubleTest.java
@@ -1,12 +1,12 @@
 /*
  * Copyright 2007 Google Inc.
- * 
+ *
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
  * the License at
- * 
+ *
  * http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -27,12 +27,49 @@
     return "com.google.gwt.emultest.EmulSuite";
   }
 
+  public void testBadStrings() {
+    try {
+      new Double("0.0e");
+      fail("constructor");
+    } catch (NumberFormatException e) {
+      // Expected behavior
+    }
+
+    try {
+      Double.parseDouble("0.0e");
+      fail("parse");
+    } catch (NumberFormatException e) {
+      // Expected behavior
+    }
+
+    try {
+      Double.valueOf("0x0e");
+      fail("valueOf");
+    } catch (NumberFormatException e) {
+      // Expected behavior
+    }
+  }
+
   public void testDoubleConstants() {
     assertTrue(Double.isNaN(Double.NaN));
     assertTrue(Double.isInfinite(Double.NEGATIVE_INFINITY));
     assertTrue(Double.isInfinite(Double.POSITIVE_INFINITY));
     assertTrue(Double.NEGATIVE_INFINITY < Double.POSITIVE_INFINITY);
+    assertTrue(Double.MIN_VALUE < Double.MAX_VALUE);
     assertFalse(Double.NaN == Double.NaN);
   }
 
+  public void testParse() {
+    assertTrue(0 == Double.parseDouble("0"));
+    assertTrue(-1.5 == Double.parseDouble("-1.5"));
+    assertTrue(3.0 == Double.parseDouble("3."));
+    assertTrue(0.5 == Double.parseDouble(".5"));
+    assertTrue(2.98e8 == Double.parseDouble("2.98e8"));
+    assertTrue(-2.98e-8 == Double.parseDouble("-2.98e-8"));
+    assertTrue(+2.98E+8 == Double.parseDouble("+2.98E+8"));
+    assertTrue("Can't parse MIN_VALUE",
+      Double.MIN_VALUE == Double.parseDouble(String.valueOf(Double.MIN_VALUE)));
+    assertTrue("Can't parse MAX_VALUE",
+      Double.MAX_VALUE == Double.parseDouble(String.valueOf(Double.MAX_VALUE)));
+  }
 }
diff --git a/user/test/com/google/gwt/emultest/java/lang/FloatTest.java b/user/test/com/google/gwt/emultest/java/lang/FloatTest.java
index 1d7faa6..5c145d2 100644
--- a/user/test/com/google/gwt/emultest/java/lang/FloatTest.java
+++ b/user/test/com/google/gwt/emultest/java/lang/FloatTest.java
@@ -1,12 +1,12 @@
 /*
  * Copyright 2007 Google Inc.
- * 
+ *
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
  * the License at
- * 
+ *
  * http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -27,12 +27,46 @@
     return "com.google.gwt.emultest.EmulSuite";
   }
 
+  public void testBadStrings() {
+    try {
+      new Float("0.0e");
+      fail("constructor");
+    } catch (NumberFormatException e) {
+      // Expected behavior
+    }
+
+    try {
+      Float.parseFloat("0.0e");
+      fail("parse");
+    } catch (NumberFormatException e) {
+      // Expected behavior
+    }
+
+    try {
+      Float.valueOf("0x0e");
+      fail("valueOf");
+    } catch (NumberFormatException e) {
+      // Expected behavior
+    }
+  }
+
   public void testFloatConstants() {
     assertTrue(Float.isNaN(Float.NaN));
     assertTrue(Float.isInfinite(Float.NEGATIVE_INFINITY));
     assertTrue(Float.isInfinite(Float.POSITIVE_INFINITY));
     assertTrue(Float.NEGATIVE_INFINITY < Float.POSITIVE_INFINITY);
+    assertTrue(Float.MIN_VALUE < Float.MAX_VALUE);
     assertFalse(Float.NaN == Float.NaN);
   }
 
+  public void testParse() {
+    assertTrue(0 == Float.parseFloat("0"));
+    assertTrue(-1.5 == Float.parseFloat("-1.5"));
+    assertTrue(3.0 == Float.parseFloat("3."));
+    assertTrue(0.5 == Float.parseFloat(".5"));
+    assertTrue("Can't parse MAX_VALUE",
+        Float.MAX_VALUE == Float.parseFloat(String.valueOf(Float.MAX_VALUE)));
+    assertTrue("Can't parse MIN_VALUE",
+        Float.MIN_VALUE == Float.parseFloat(String.valueOf(Float.MIN_VALUE)));
+  }
 }
diff --git a/user/test/com/google/gwt/emultest/java/lang/IntegerTest.java b/user/test/com/google/gwt/emultest/java/lang/IntegerTest.java
index a16f6dc..a53c08a 100644
--- a/user/test/com/google/gwt/emultest/java/lang/IntegerTest.java
+++ b/user/test/com/google/gwt/emultest/java/lang/IntegerTest.java
@@ -1,12 +1,12 @@
 /*
  * Copyright 2007 Google Inc.
- * 
+ *
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
  * the License at
- * 
+ *
  * http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -36,6 +36,57 @@
     assertEquals("-12345", new Integer("-12345").toString());
   }
 
+  public void testBadStrings() {
+    try {
+      new Integer("05abcd");
+      fail("Constructor should have thrown NumberFormatException");
+    } catch (NumberFormatException e) {
+      // Expected behavior
+    }
+
+    try {
+      Integer.decode("05abcd");
+      fail("Decode should have thrown NumberFormatException");
+    } catch (NumberFormatException e) {
+      // Expected behavior
+    }
+
+    try {
+      Integer.parseInt("05abcd");
+      fail("parseInt should have thrown NumberFormatException");
+    } catch (NumberFormatException e) {
+      // Expected behavior
+    }
+    
+    try {
+      Integer.parseInt(String.valueOf(Long.MAX_VALUE));
+      fail("parseInt should reject numbers greater than the range of int");
+    } catch (NumberFormatException e) {
+      // Expected behavior
+    }
+    
+    try {
+      Integer.parseInt(String.valueOf(Long.MIN_VALUE));
+      fail("parseInt should reject numbers less than the range of int");
+    } catch (NumberFormatException e) {
+      // Expected behavior
+    }
+    
+    try {
+      Integer.parseInt(String.valueOf((long)Integer.MAX_VALUE + 1));
+      fail("parseInt should reject numbers greater than the range of int");
+    } catch (NumberFormatException e) {
+      // Expected behavior
+    }
+    
+    try {
+      Integer.parseInt(String.valueOf((long)Integer.MIN_VALUE - 1));
+      fail("parseInt should reject numbers less than the range of int");
+    } catch (NumberFormatException e) {
+      // Expected behavior
+    }
+  }
+
   public void testCompareTo() {
     assertEquals(-1, new Integer(12345).compareTo(new Integer(12346)));
     assertEquals(1, new Integer("12345").compareTo(new Integer(12344)));
@@ -48,7 +99,15 @@
   }
 
   public void testDecode() {
+    assertEquals(Integer.MAX_VALUE,
+        Integer.decode(String.valueOf(Integer.MAX_VALUE)).intValue());
+    assertEquals(Integer.MIN_VALUE,
+        Integer.decode(String.valueOf(Integer.MIN_VALUE)).intValue());
     assertEquals(12345, Integer.decode("12345").intValue());
+    assertEquals(31, Integer.decode("0x1f").intValue());
+    assertEquals(-31, Integer.decode("-0X1F").intValue());
+    assertEquals(31, Integer.decode("#1f").intValue());
+    assertEquals(10, Integer.decode("012").intValue());
     try {
       Integer.decode("abx");
       fail();