diff --git a/core/src/main/kotlin/net/corda/core/contracts/FinanceTypes.kt b/core/src/main/kotlin/net/corda/core/contracts/FinanceTypes.kt index 0b66c5e1b6..9266f366dc 100644 --- a/core/src/main/kotlin/net/corda/core/contracts/FinanceTypes.kt +++ b/core/src/main/kotlin/net/corda/core/contracts/FinanceTypes.kt @@ -10,6 +10,7 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize import com.fasterxml.jackson.databind.annotation.JsonSerialize import com.google.common.annotations.VisibleForTesting import java.math.BigDecimal +import java.math.BigInteger import java.time.DayOfWeek import java.time.LocalDate import java.time.format.DateTimeFormatter @@ -34,6 +35,19 @@ import java.util.* * @param T the type of the token, for example [Currency]. */ data class Amount(val quantity: Long, val token: T) : Comparable> { + companion object { + /** + * Build an amount from a decimal representation. For example, with an input of "12.34" GBP, + * returns an amount with a quantity of "1234". + * + * @see Amount.toDecimal + */ + fun fromDecimal(quantity: BigDecimal, currency: Currency) : Amount { + val longQuantity = quantity.movePointRight(currency.defaultFractionDigits).toLong() + return Amount(longQuantity, currency) + } + } + init { // Negative amounts are of course a vital part of any ledger, but negative values are only valid in certain // contexts: you cannot send a negative amount of cash, but you can (sometimes) have a negative balance. @@ -41,7 +55,12 @@ data class Amount(val quantity: Long, val token: T) : Comparable> { require(quantity >= 0) { "Negative amounts are not allowed: $quantity" } } - constructor(amount: BigDecimal, currency: T) : this(amount.toLong(), currency) + /** + * Construct the amount using the given decimal value as quantity. Any fractional part + * is discarded. To convert and use the fractional part, see [fromDecimal]. + */ + constructor(quantity: BigDecimal, token: T) : this(quantity.toLong(), token) + constructor(quantity: BigInteger, token: T) : this(quantity.toLong(), token) operator fun plus(other: Amount): Amount { checkCurrency(other) @@ -70,6 +89,14 @@ data class Amount(val quantity: Long, val token: T) : Comparable> { } } +/** + * Convert a currency [Amount] to a decimal representation. For example, with an amount with a quantity + * of "1234" GBP, returns "12.34". + * + * @see Amount.Companion.fromDecimal + */ +fun Amount.toDecimal() : BigDecimal = BigDecimal(quantity).movePointLeft(token.defaultFractionDigits) + fun Iterable>.sumOrNull() = if (!iterator().hasNext()) null else sumOrThrow() fun Iterable>.sumOrThrow() = reduce { left, right -> left + right } fun Iterable>.sumOrZero(currency: T) = if (iterator().hasNext()) sumOrThrow() else Amount(0, currency) diff --git a/core/src/test/kotlin/net/corda/core/contracts/AmountTests.kt b/core/src/test/kotlin/net/corda/core/contracts/AmountTests.kt new file mode 100644 index 0000000000..00a7ca8d2f --- /dev/null +++ b/core/src/test/kotlin/net/corda/core/contracts/AmountTests.kt @@ -0,0 +1,26 @@ +package net.corda.core.contracts + +import org.junit.Test +import java.math.BigDecimal +import kotlin.test.assertEquals + +/** + * Tests of the [Amount] class. + */ +class AmountTests { + @Test + fun basicCurrency() { + val expected = 1000L + val amount = Amount(expected, GBP) + assertEquals(expected, amount.quantity) + } + + @Test + fun decimalConversion() { + val quantity = 1234L + val amount = Amount(quantity, GBP) + val expected = BigDecimal("12.34") + assertEquals(expected, amount.toDecimal()) + assertEquals(amount, Amount.fromDecimal(amount.toDecimal(), amount.token)) + } +} \ No newline at end of file