Repository: amzn/ion-java (java)

Issue:
Add interface IonNumber above IonDecimal, IonFloat, and IonInt
_Imported from ION-95,IONJAVA-137_


Pull request description:
Adds members to the IonNumber interface
…and makes IonDecimal, IonFloat, and IonInt implement IonNumber

**Issue #, if available:**

Fixes #53 

**Description of changes:**

This adds members to `IonNumber` and makes `IonNumber` the supertype of `IonFloat` and `IonInt`. (It was already the supertype of `IonDecimal`.) I chose `bigDecimalValue()` for the interface because `IonDecimal` and `IonFloat` already have this method. I also chose to add `isNumeric()` so that you can make sure it's not `nan` or `+/-inf` before trying to call `bigDecimalValue()` (since calling that method for `nan`, etc. would result in a `NumberFormatException`).


_By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license._


Patch:
diff --git a/src/com/amazon/ion/IonFloat.java b/src/com/amazon/ion/IonFloat.java
index e4fa2be6..e6890da3 100644
--- a/src/com/amazon/ion/IonFloat.java
+++ b/src/com/amazon/ion/IonFloat.java
@@ -37,7 +37,7 @@ import java.math.BigDecimal;
  * @see IonDecimal
  */
 public interface IonFloat
-    extends IonValue
+    extends IonNumber
 {
     /**
      * Gets the value of this Ion <code>float</code> as a Java
@@ -66,6 +66,8 @@ public interface IonFloat
      * recommended to call {@link IonFloat#isNumericValue()} before calling
      * this method.
      *
+     * If you need negative zeros, use {@link #doubleValue()}.
+     *
      * @return the {@link BigDecimal} value, or {@code null} if
      * {@code this.isNullValue()}.
      *
@@ -73,6 +75,7 @@ public interface IonFloat
      * {@code +inf}, or {@code -inf}, because {@link BigDecimal} cannot
      * represent those values.
      */
+    @Override
     public BigDecimal bigDecimalValue()
         throws NullValueException;
 
diff --git a/src/com/amazon/ion/IonInt.java b/src/com/amazon/ion/IonInt.java
index 2270b0a9..bf8d44a3 100644
--- a/src/com/amazon/ion/IonInt.java
+++ b/src/com/amazon/ion/IonInt.java
@@ -15,6 +15,7 @@
 
 package com.amazon.ion;
 
+import java.math.BigDecimal;
 import java.math.BigInteger;
 
 /**
@@ -24,7 +25,7 @@ import java.math.BigInteger;
  * code outside of this library.
  */
 public interface IonInt
-    extends IonValue
+    extends IonNumber
 {
     /**
      * Gets the content of this Ion <code>int</code> as a Java
@@ -56,6 +57,15 @@ public interface IonInt
      */
     public BigInteger bigIntegerValue();
 
+    /**
+     * Gets the value of this Ion {@code int} as a {@link BigDecimal}.
+     * The scale of the {@code BigDecimal} is zero.
+     *
+     * @return the {@code BigDecimal} value,
+     * or {@code null} if {@code this.isNullValue()}.
+     */
+    public BigDecimal bigDecimalValue();
+
     /**
      * Gets an {@link IntegerSize} representing the smallest-possible
      * Java type of the underlying content, or {@code null} if this is
diff --git a/src/com/amazon/ion/IonNumber.java b/src/com/amazon/ion/IonNumber.java
index dd17c559..88002c68 100644
--- a/src/com/amazon/ion/IonNumber.java
+++ b/src/com/amazon/ion/IonNumber.java
@@ -15,16 +15,45 @@
 
 package com.amazon.ion;
 
+import java.math.BigDecimal;
+
 /**
- * The IonNumber interface is a fore runner of a common base for the
- * ion numeric value types.  Currently only IonDecimal extends this
- * interface. In due course IonFloat and IonInt will be added to the
- * the family.
+ * Common functionality of Ion {@code int}, {@code decimal}, and {@code float}
+ * types.
  * <p>
  * <b>WARNING:</b> This interface should not be implemented or extended by
  * code outside of this library.
  */
-public interface IonNumber // TODO amzn/ion-java/issues/53 Complete this interface
+public interface IonNumber
     extends IonValue
 {
+    /**
+     * Gets the value of this Ion number as a {@link BigDecimal}.
+     *
+     * <p>This method will throw an exception for non-null, non-numeric values.
+     * It's recommended to call {@link #isNumericValue()} before calling
+     * this method.</p>
+     *
+     * <p>Negative zero is supported by {@link IonDecimal} and {@link IonFloat},
+     * but not by {@link BigDecimal}. If you need to distinguish positive and
+     * negative zero, you should call {@link IonDecimal#decimalValue()} or
+     * {@link IonFloat#doubleValue()} after casting to the appropriate type.</p>
+     *
+     * @return the {@link BigDecimal} value, or {@code null} if
+     * {@code this.isNullValue()}.
+     *
+     * @throws NumberFormatException if this value is {@code nan},
+     * {@code +inf}, or {@code -inf}, because {@link BigDecimal} cannot
+     * represent those values.
+     */
+    public BigDecimal bigDecimalValue();
+
+    /**
+     * Determines whether this value is numeric. Returns true if this value
+     * is none of {@code null}, {@code nan}, {@code +inf}, and {@code -inf},
+     * and false if it is any of them.
+     *
+     * @return a checked condition whether this value is numeric.
+     */
+    public boolean isNumericValue();
 }
diff --git a/src/com/amazon/ion/impl/lite/IonDecimalLite.java b/src/com/amazon/ion/impl/lite/IonDecimalLite.java
index 4a8d71e7..63ecea4f 100644
--- a/src/com/amazon/ion/impl/lite/IonDecimalLite.java
+++ b/src/com/amazon/ion/impl/lite/IonDecimalLite.java
@@ -124,6 +124,7 @@ final class IonDecimalLite
         return d;
     }
 
+    @Override
     public BigDecimal bigDecimalValue()
         throws NullValueException
     {
@@ -136,6 +137,11 @@ final class IonDecimalLite
         return Decimal.valueOf(_decimal_value); // Works for null.
     }
 
+    @Override
+    public boolean isNumericValue() {
+        return !isNullValue();
+    }
+
     public void setValue(long value)
     {
         // base setValue will check for the lock
diff --git a/src/com/amazon/ion/impl/lite/IonFloatLite.java b/src/com/amazon/ion/impl/lite/IonFloatLite.java
index 66118c29..551ceb6d 100644
--- a/src/com/amazon/ion/impl/lite/IonFloatLite.java
+++ b/src/com/amazon/ion/impl/lite/IonFloatLite.java
@@ -95,6 +95,7 @@ final class IonFloatLite
         return _float_value.doubleValue();
     }
 
+    @Override
     public BigDecimal bigDecimalValue()
         throws NullValueException
     {
@@ -151,6 +152,7 @@ final class IonFloatLite
         }
     }
 
+    @Override
     public boolean isNumericValue()
     {
         return !(isNullValue() || _float_value.isNaN() || _float_value.isInfinite());
diff --git a/src/com/amazon/ion/impl/lite/IonIntLite.java b/src/com/amazon/ion/impl/lite/IonIntLite.java
index 3fce4810..89747dd6 100644
--- a/src/com/amazon/ion/impl/lite/IonIntLite.java
+++ b/src/com/amazon/ion/impl/lite/IonIntLite.java
@@ -146,6 +146,22 @@ final class IonIntLite
         return _big_int_value;
     }
 
+    @Override
+    public BigDecimal bigDecimalValue() {
+        if (isNullValue()) {
+            return null;
+        } else if (_big_int_value == null) {
+            return BigDecimal.valueOf(_long_value);
+        } else {
+            return new BigDecimal(_big_int_value);
+        }
+    }
+
+    @Override
+    public boolean isNumericValue() {
+        return !isNullValue();
+    }
+
     public void setValue(int value)
     {
         setValue((long)value);


Test patch:
diff --git a/test/com/amazon/ion/DecimalTest.java b/test/com/amazon/ion/DecimalTest.java
index 6d79dacf..eb009d0a 100644
--- a/test/com/amazon/ion/DecimalTest.java
+++ b/test/com/amazon/ion/DecimalTest.java
@@ -31,6 +31,7 @@ public class DecimalTest
     {
         assertSame(IonType.DECIMAL, value.getType());
         assertTrue("isNullValue is false", value.isNullValue());
+        assertFalse(value.isNumericValue());
 
         try
         {
@@ -346,4 +347,13 @@ public class DecimalTest
         checkDecimal(123, 0, value.bigDecimalValue());
     }
 
+    @Test
+    public void testIsNumeric()
+    {
+        IonNumber value = (IonNumber) oneValue("1.23");
+        assertTrue(value.isNumericValue());
+
+        IonNumber nullValue = (IonNumber) oneValue("null.decimal");
+        assertFalse(nullValue.isNumericValue());
+    }
 }
diff --git a/test/com/amazon/ion/IntTest.java b/test/com/amazon/ion/IntTest.java
index d21e2904..96909836 100644
--- a/test/com/amazon/ion/IntTest.java
+++ b/test/com/amazon/ion/IntTest.java
@@ -32,6 +32,7 @@ public class IntTest
     {
         assertSame(IonType.INT, value.getType());
         assertTrue("isNullValue() is false",   value.isNullValue());
+        assertFalse(value.isNumericValue());
 
         try
         {
@@ -48,6 +49,7 @@ public class IntTest
         catch (NullValueException e) { }
 
         assertNull("toBigInteger() isn't null", value.bigIntegerValue());
+        assertNull("bigDecimalValue() isn't null", value.bigDecimalValue());
     }
 
 
@@ -63,6 +65,7 @@ public class IntTest
         value.setValue(A_LONG_INT);
         assertEquals(A_LONG_INT, value.longValue());
         assertEquals(BigInteger.valueOf(A_LONG_INT), value.bigIntegerValue());
+        assertEquals(BigDecimal.valueOf(A_LONG_INT), value.bigDecimalValue());
 
         value.setValue(null);
         checkNullInt(value);
@@ -224,6 +227,9 @@ public class IntTest
         assertEquals(SUPER_BIG_TRUNC_32, val.intValue());
 
         assertEquals(SUPER_BIG.hashCode(), val.hashCode());
+
+        IonNumber result = (IonNumber) reload(val);
+        assertEquals(0, big.compareTo(result.bigDecimalValue()));
     }
 
     @Test
@@ -481,6 +487,26 @@ public class IntTest
         testGetIntegerSizeIntBoundary(Integer.MIN_VALUE);
     }
 
+    @Test
+    public void testIsNumeric()
+    {
+        IonNumber value = (IonNumber) oneValue("1");
+        assertTrue(value.isNumericValue());
+
+        IonNumber nullValue = (IonNumber) oneValue("null.int");
+        assertFalse(nullValue.isNumericValue());
+    }
+
+    @Test
+    public void testBigDecimalValue()
+    {
+        IonNumber value = (IonNumber) oneValue("1");
+        assertEquals(BigDecimal.valueOf(1), value.bigDecimalValue());
+
+        IonNumber nullValue = (IonNumber) oneValue("null.int");
+        assertNull(nullValue.bigDecimalValue());
+    }
+
     private void testGetIntegerSizeLongBoundary(long boundaryValue) {
         BigInteger boundary = BigInteger.valueOf(boundaryValue);
         IonInt boundaryIon = (IonInt)oneValue(boundary.toString());