Convert base types to implement CanonicalValue
[yangtools.git] / yang / yang-common / src / main / java / org / opendaylight / yangtools / yang / common / Decimal64.java
index 5b7bc508be3279ed29b10e2d8c5b097ca248e959..5fba2e0e3fe8e700c621d38f3e90c770a3707ade 100644 (file)
@@ -13,9 +13,9 @@ import static com.google.common.base.Verify.verify;
 import com.google.common.annotations.Beta;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Strings;
-import com.google.common.primitives.Longs;
 import java.math.BigDecimal;
-import org.opendaylight.yangtools.concepts.Immutable;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
 
 /**
  * Dedicated type for YANG's 'type decimal64' type. This class is similar to {@link BigDecimal}, but provides more
@@ -24,7 +24,20 @@ import org.opendaylight.yangtools.concepts.Immutable;
  * @author Robert Varga
  */
 @Beta
-public final class Decimal64 extends Number implements Comparable<Decimal64>, Immutable {
+@NonNullByDefault
+public class Decimal64 extends Number implements CanonicalValue<Decimal64> {
+    private static final class Support extends AbstractCanonicalValueSupport<Decimal64> {
+        Support() {
+            super(Decimal64.class);
+        }
+
+        @Override
+        public Decimal64 fromString(final String str) {
+            return Decimal64.valueOf(str);
+        }
+    }
+
+    private static final CanonicalValueSupport<Decimal64> SUPPORT = new Support();
     private static final long serialVersionUID = 1L;
 
     private static final int MAX_FRACTION_DIGITS = 18;
@@ -66,6 +79,11 @@ public final class Decimal64 extends Number implements Comparable<Decimal64>, Im
         this.value = negative ? -bits : bits;
     }
 
+    protected Decimal64(final Decimal64 other) {
+        this.scaleOffset = other.scaleOffset;
+        this.value = other.value;
+    }
+
     public static Decimal64 valueOf(final byte byteVal) {
         return byteVal < 0 ? new Decimal64(1, -byteVal, 0, true) : new Decimal64(1, byteVal, 0, false);
     }
@@ -75,7 +93,7 @@ public final class Decimal64 extends Number implements Comparable<Decimal64>, Im
     }
 
     public static Decimal64 valueOf(final int intVal) {
-        return intVal < 0 ? new Decimal64(1, -intVal, 0, true) : new Decimal64(1, intVal, 0, false);
+        return intVal < 0 ? new Decimal64(1, - (long)intVal, 0, true) : new Decimal64(1, intVal, 0, false);
     }
 
     public static Decimal64 valueOf(final long longVal) {
@@ -194,33 +212,98 @@ public final class Decimal64 extends Number implements Comparable<Decimal64>, Im
         return new Decimal64(fracLen, intPart, fracPart, negative);
     }
 
-    public BigDecimal decimalValue() {
+    public final BigDecimal decimalValue() {
         return BigDecimal.valueOf(value, scaleOffset + 1);
     }
 
     @Override
-    public int intValue() {
+    public final int intValue() {
         return (int) intPart();
     }
 
     @Override
-    public long longValue() {
+    public final long longValue() {
         return intPart();
     }
 
     @Override
-    public float floatValue() {
+    public final float floatValue() {
         return (float) doubleValue();
     }
 
     @Override
-    public double doubleValue() {
+    public final double doubleValue() {
         return 1.0 * value / SCALE[scaleOffset];
     }
 
+    /**
+     * Converts this {@code BigDecimal} to a {@code byte}, checking for lost information. If this {@code Decimal64} has
+     * a nonzero fractional part or is out of the possible range for a {@code byte} result then
+     * an {@code ArithmeticException} is thrown.
+     *
+     * @return this {@code Decimal64} converted to a {@code byte}.
+     * @throws ArithmeticException if {@code this} has a nonzero fractional part, or will not fit in a {@code byte}.
+     */
+    public final byte byteValueExact() {
+        final long val = longValueExact();
+        final byte ret = (byte) val;
+        if (val != ret) {
+            throw new ArithmeticException("Value " + val + " is outside of byte range");
+        }
+        return ret;
+    }
+
+    /**
+     * Converts this {@code BigDecimal} to a {@code short}, checking for lost information. If this {@code Decimal64} has
+     * a nonzero fractional part or is out of the possible range for a {@code short} result then
+     * an {@code ArithmeticException} is thrown.
+     *
+     * @return this {@code Decimal64} converted to a {@code short}.
+     * @throws ArithmeticException if {@code this} has a nonzero fractional part, or will not fit in a {@code short}.
+     */
+    public final short shortValueExact() {
+        final long val = longValueExact();
+        final short ret = (short) val;
+        if (val != ret) {
+            throw new ArithmeticException("Value " + val + " is outside of short range");
+        }
+        return ret;
+    }
+
+    /**
+     * Converts this {@code BigDecimal} to an {@code int}, checking for lost information. If this {@code Decimal64} has
+     * a nonzero fractional part or is out of the possible range for an {@code int} result then
+     * an {@code ArithmeticException} is thrown.
+     *
+     * @return this {@code Decimal64} converted to an {@code int}.
+     * @throws ArithmeticException if {@code this} has a nonzero fractional part, or will not fit in an {@code int}.
+     */
+    public final int intValueExact() {
+        final long val = longValueExact();
+        final int ret = (int) val;
+        if (val != ret) {
+            throw new ArithmeticException("Value " + val + " is outside of integer range");
+        }
+        return ret;
+    }
+
+    /**
+     * Converts this {@code BigDecimal} to a {@code long}, checking for lost information.  If this {@code Decimal64} has
+     * a nonzero fractional part then an {@code ArithmeticException} is thrown.
+     *
+     * @return this {@code Decimal64} converted to a {@code long}.
+     * @throws ArithmeticException if {@code this} has a nonzero fractional part.
+     */
+    public final long longValueExact() {
+        if (fracPart() != 0) {
+            throw new ArithmeticException("Conversion of " + this + " would lose fraction");
+        }
+        return intPart();
+    }
+
     @Override
     @SuppressWarnings("checkstyle:parameterName")
-    public int compareTo(final Decimal64 o) {
+    public final int compareTo(final Decimal64 o) {
         if (this == o) {
             return 0;
         }
@@ -233,13 +316,39 @@ public final class Decimal64 extends Number implements Comparable<Decimal64>, Im
     }
 
     @Override
-    public int hashCode() {
+    public final String toCanonicalString() {
+        // https://tools.ietf.org/html/rfc6020#section-9.3.2
+        //
+        // The canonical form of a positive decimal64 does not include the sign
+        // "+".  The decimal point is required.  Leading and trailing zeros are
+        // prohibited, subject to the rule that there MUST be at least one digit
+        // before and after the decimal point.  The value zero is represented as
+        // "0.0".
+        final StringBuilder sb = new StringBuilder(21).append(intPart()).append('.');
+        final long fracPart = fracPart();
+        if (fracPart != 0) {
+            // We may need to zero-pad the fraction part
+            sb.append(Strings.padStart(Long.toString(fracPart), scaleOffset + 1, '0'));
+        } else {
+            sb.append('0');
+        }
+
+        return sb.toString();
+    }
+
+    @Override
+    public final CanonicalValueSupport<Decimal64> support() {
+        return SUPPORT;
+    }
+
+    @Override
+    public final int hashCode() {
         // We need to normalize the results in order to be consistent with equals()
-        return Longs.hashCode(intPart()) * 31 + Long.hashCode(fracPart());
+        return Long.hashCode(intPart()) * 31 + Long.hashCode(fracPart());
     }
 
     @Override
-    public boolean equals(final Object obj) {
+    public final boolean equals(final @Nullable Object obj) {
         if (this == obj) {
             return true;
         }
@@ -256,24 +365,8 @@ public final class Decimal64 extends Number implements Comparable<Decimal64>, Im
     }
 
     @Override
-    public String toString() {
-        // https://tools.ietf.org/html/rfc6020#section-9.3.2
-        //
-        // The canonical form of a positive decimal64 does not include the sign
-        // "+".  The decimal point is required.  Leading and trailing zeros are
-        // prohibited, subject to the rule that there MUST be at least one digit
-        // before and after the decimal point.  The value zero is represented as
-        // "0.0".
-        final StringBuilder sb = new StringBuilder(21).append(intPart()).append('.');
-        final long fracPart = fracPart();
-        if (fracPart != 0) {
-            // We may need to zero-pad the fraction part
-            sb.append(Strings.padStart(Long.toString(fracPart), scaleOffset + 1, '0'));
-        } else {
-            sb.append('0');
-        }
-
-        return sb.toString();
+    public final String toString() {
+        return toCanonicalString();
     }
 
     private long intPart() {