X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;f=yang%2Fyang-common%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fyangtools%2Fyang%2Fcommon%2FDecimal64.java;h=78354f391594807493c9ff3d191ca9265c75b13c;hb=c6e7dcaa59ffa31d3b252b4392399f917fcc260e;hp=5b7bc508be3279ed29b10e2d8c5b097ca248e959;hpb=928be5f194f2638cc2da1bdb072c60fe00f814fe;p=yangtools.git diff --git a/yang/yang-common/src/main/java/org/opendaylight/yangtools/yang/common/Decimal64.java b/yang/yang-common/src/main/java/org/opendaylight/yangtools/yang/common/Decimal64.java index 5b7bc508be..78354f3915 100644 --- a/yang/yang-common/src/main/java/org/opendaylight/yangtools/yang/common/Decimal64.java +++ b/yang/yang-common/src/main/java/org/opendaylight/yangtools/yang/common/Decimal64.java @@ -13,9 +13,11 @@ 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 java.util.Optional; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.opendaylight.yangtools.concepts.Variant; /** * Dedicated type for YANG's 'type decimal64' type. This class is similar to {@link BigDecimal}, but provides more @@ -24,7 +26,116 @@ import org.opendaylight.yangtools.concepts.Immutable; * @author Robert Varga */ @Beta -public final class Decimal64 extends Number implements Comparable, Immutable { +@NonNullByDefault +public class Decimal64 extends Number implements CanonicalValue { + public static final class Support extends AbstractCanonicalValueSupport { + public Support() { + super(Decimal64.class); + } + + @Override + public Variant fromString(final String str) { + // https://tools.ietf.org/html/rfc6020#section-9.3.1 + // + // A decimal64 value is lexically represented as an optional sign ("+" + // or "-"), followed by a sequence of decimal digits, optionally + // followed by a period ('.') as a decimal indicator and a sequence of + // decimal digits. If no sign is specified, "+" is assumed. + if (str.isEmpty()) { + return CanonicalValueViolation.variantOf("Empty string is not a valid decimal64 representation"); + } + + // Deal with optional sign + final boolean negative; + int idx; + switch (str.charAt(0)) { + case '-': + negative = true; + idx = 1; + break; + case '+': + negative = false; + idx = 1; + break; + default: + negative = false; + idx = 0; + } + + // Sanity check length + if (idx == str.length()) { + return CanonicalValueViolation.variantOf("Missing digits after sign"); + } + + // Character limit, used for caching and cutting trailing zeroes + int limit = str.length() - 1; + + // Skip any leading zeroes, but leave at least one + for (; idx < limit && str.charAt(idx) == '0'; idx++) { + final char ch = str.charAt(idx + 1); + if (ch < '0' || ch > '9') { + break; + } + } + + // Integer part and its length + int intLen = 0; + long intPart = 0; + + for (; idx <= limit; idx++, intLen++) { + final char ch = str.charAt(idx); + if (ch == '.') { + // Fractions are next + break; + } + if (intLen == MAX_FRACTION_DIGITS) { + return CanonicalValueViolation.variantOf( + "Integer part is longer than " + MAX_FRACTION_DIGITS + " digits"); + } + + intPart = 10 * intPart + toInt(ch, idx); + } + + if (idx > limit) { + // No fraction digits, we are done + return Variant.ofFirst(new Decimal64((byte)1, intPart, 0, negative)); + } + + // Bump index to skip over period and check the remainder + idx++; + if (idx > limit) { + return CanonicalValueViolation.variantOf("Value '" + str + "' is missing fraction digits"); + } + + // Trim trailing zeroes, if any + while (idx < limit && str.charAt(limit) == '0') { + limit--; + } + + final int fracLimit = MAX_FRACTION_DIGITS - intLen; + byte fracLen = 0; + long fracPart = 0; + for (; idx <= limit; idx++, fracLen++) { + final char ch = str.charAt(idx); + if (fracLen == fracLimit) { + return CanonicalValueViolation.variantOf("Fraction part longer than " + fracLimit + " digits"); + } + + fracPart = 10 * fracPart + toInt(ch, idx); + } + + return Variant.ofFirst(new Decimal64(fracLen, intPart, fracPart, negative)); + } + + private static int toInt(final char ch, final int index) { + if (ch < '0' || ch > '9') { + throw new NumberFormatException("Illegal character at offset " + index); + } + return ch - '0'; + } + } + + private static final CanonicalValueSupport SUPPORT = new Support(); private static final long serialVersionUID = 1L; private static final int MAX_FRACTION_DIGITS = 18; @@ -66,6 +177,11 @@ public final class Decimal64 extends Number implements Comparable, 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 +191,7 @@ public final class Decimal64 extends Number implements Comparable, 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) { @@ -103,160 +219,120 @@ public final class Decimal64 extends Number implements Comparable, Im * @throws NumberFormatException if the string does not contain a parsable decimal64. */ public static Decimal64 valueOf(final String str) { - // https://tools.ietf.org/html/rfc6020#section-9.3.1 - // - // A decimal64 value is lexically represented as an optional sign ("+" - // or "-"), followed by a sequence of decimal digits, optionally - // followed by a period ('.') as a decimal indicator and a sequence of - // decimal digits. If no sign is specified, "+" is assumed. - if (str.isEmpty()) { - throw new NumberFormatException("Empty string is not a valid decimal64 representation"); - } - - // Deal with optional sign - final boolean negative; - int idx; - switch (str.charAt(0)) { - case '-': - negative = true; - idx = 1; - break; - case '+': - negative = false; - idx = 1; - break; - default: - negative = false; - idx = 0; + final Variant variant = SUPPORT.fromString(str); + final Optional value = variant.tryFirst(); + if (value.isPresent()) { + return value.get(); } - - // Sanity check length - if (idx == str.length()) { - throw new NumberFormatException("Missing digits after sign"); - } - - // Character limit, used for caching and cutting trailing zeroes - int limit = str.length() - 1; - - // Skip any leading zeroes, but leave at least one - for (; idx < limit && str.charAt(idx) == '0'; idx++) { - final char ch = str.charAt(idx + 1); - if (ch < '0' || ch > '9') { - break; - } - } - - // Integer part and its length - int intLen = 0; - long intPart = 0; - - for (; idx <= limit; idx++, intLen++) { - final char ch = str.charAt(idx); - if (ch == '.') { - // Fractions are next - break; - } - if (intLen == MAX_FRACTION_DIGITS) { - throw new NumberFormatException("Integer part is longer than " + MAX_FRACTION_DIGITS + " digits"); - } - - intPart = 10 * intPart + toInt(ch, idx); - } - - if (idx > limit) { - // No fraction digits, we are done - return new Decimal64((byte)1, intPart, 0, negative); - } - - // Bump index to skip over period and check the remainder - idx++; - if (idx > limit) { - throw new NumberFormatException("Value '" + str + "' is missing fraction digits"); - } - - // Trim trailing zeroes, if any - while (idx < limit && str.charAt(limit) == '0') { - limit--; - } - - final int fracLimit = MAX_FRACTION_DIGITS - intLen; - byte fracLen = 0; - long fracPart = 0; - for (; idx <= limit; idx++, fracLen++) { - final char ch = str.charAt(idx); - if (fracLen == fracLimit) { - throw new NumberFormatException("Fraction part longer than " + fracLimit + " digits"); - } - - fracPart = 10 * fracPart + toInt(ch, idx); - } - - return new Decimal64(fracLen, intPart, fracPart, negative); + final Optional message = variant.getSecond().getMessage(); + throw message.isPresent() ? new NumberFormatException(message.get()) : new NumberFormatException(); } - 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]; } - @Override - @SuppressWarnings("checkstyle:parameterName") - public int compareTo(final Decimal64 o) { - if (this == o) { - return 0; + /** + * 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"); } - if (scaleOffset == o.scaleOffset) { - return Long.compare(value, o.value); + 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; + } - // XXX: we could do something smarter here - return Double.compare(doubleValue(), o.doubleValue()); + /** + * 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; } - @Override - public int hashCode() { - // We need to normalize the results in order to be consistent with equals() - return Longs.hashCode(intPart()) * 31 + Long.hashCode(fracPart()); + /** + * 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 - public boolean equals(final Object obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof Decimal64)) { - return false; + @SuppressWarnings("checkstyle:parameterName") + public final int compareTo(final Decimal64 o) { + if (this == o) { + return 0; } - final Decimal64 other = (Decimal64) obj; - if (scaleOffset == other.scaleOffset) { - return value == other.value; + if (scaleOffset == o.scaleOffset) { + return Long.compare(value, o.value); } - // We need to normalize both - return intPart() == other.intPart() && fracPart() == fracPart(); + // XXX: we could do something smarter here + return Double.compare(doubleValue(), o.doubleValue()); } @Override - public String toString() { + 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 @@ -276,6 +352,43 @@ public final class Decimal64 extends Number implements Comparable, Im return sb.toString(); } + @Override + public final CanonicalValueSupport support() { + return SUPPORT; + } + + @Override + public final int hashCode() { + // We need to normalize the results in order to be consistent with equals() + return Long.hashCode(intPart()) * 31 + Long.hashCode(fracPart()); + } + + @Override + public final boolean equals(final @Nullable Object obj) { + return this == obj || obj instanceof Decimal64 && equalsImpl((Decimal64) obj); + } + + /** + * A slightly faster version of {@link #equals(Object)}. + * + * @param obj Decimal64 object + * @return {@code true} if this object is the same as the obj argument; {@code false} otherwise. + */ + public final boolean equals(final @Nullable Decimal64 obj) { + return this == obj || obj != null && equalsImpl(obj); + } + + @Override + public final String toString() { + return toCanonicalString(); + } + + private boolean equalsImpl(final Decimal64 other) { + return scaleOffset == other.scaleOffset ? value == other.value + // We need to normalize both + : intPart() == other.intPart() && fracPart() == other.fracPart(); + } + private long intPart() { return value / SCALE[scaleOffset]; } @@ -283,11 +396,4 @@ public final class Decimal64 extends Number implements Comparable, Im private long fracPart() { return Math.abs(value % SCALE[scaleOffset]); } - - private static int toInt(final char ch, final int index) { - if (ch < '0' || ch > '9') { - throw new NumberFormatException("Illegal character at offset " + index); - } - return ch - '0'; - } }