Add adavanced money formatter and decimal functions for money format and add function builder and money extension updated and add money view for money module
Add money formatter provider for general provider formatter
This commit is contained in:
parent
7497e11ee3
commit
003e1a59db
61
src/main/kotlin/com/cubetiqs/money/DecimalUtils.kt
Normal file
61
src/main/kotlin/com/cubetiqs/money/DecimalUtils.kt
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
package com.cubetiqs.money
|
||||||
|
|
||||||
|
import java.text.DecimalFormat
|
||||||
|
import java.lang.StringBuilder
|
||||||
|
import java.math.RoundingMode
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Java Decimal utils
|
||||||
|
*
|
||||||
|
* @author sombochea
|
||||||
|
* @since 1.0
|
||||||
|
*/
|
||||||
|
object DecimalUtils {
|
||||||
|
private const val DECIMAL_PATTERN = "#.##"
|
||||||
|
private const val ROUNDING_DECIMAL_PATTERN = "##0"
|
||||||
|
fun toStringDecimal(value: Number?, pattern: String?): String {
|
||||||
|
var _pattern = pattern
|
||||||
|
if (_pattern == null || _pattern.isEmpty()) {
|
||||||
|
_pattern = DECIMAL_PATTERN
|
||||||
|
}
|
||||||
|
return DecimalFormat(_pattern).format(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toStringDecimal(value: Number?, pattern: String?, roundingMode: RoundingMode?): String {
|
||||||
|
var _pattern = pattern
|
||||||
|
if (_pattern == null || _pattern.isEmpty()) {
|
||||||
|
_pattern = DECIMAL_PATTERN
|
||||||
|
}
|
||||||
|
if (roundingMode == null) {
|
||||||
|
return toStringDecimal(value, _pattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
val formatter = DecimalFormat(_pattern)
|
||||||
|
formatter.roundingMode = roundingMode
|
||||||
|
return formatter.format(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toDecimalPrecision(value: Number?, precision: Int? = null, roundingMode: RoundingMode? = null): String? {
|
||||||
|
var _precision = precision ?: -1
|
||||||
|
if (value == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val pattern = StringBuilder(ROUNDING_DECIMAL_PATTERN)
|
||||||
|
if (_precision > 0) {
|
||||||
|
pattern.append(".")
|
||||||
|
}
|
||||||
|
|
||||||
|
while (_precision > 0) {
|
||||||
|
pattern.append("0")
|
||||||
|
_precision--
|
||||||
|
}
|
||||||
|
|
||||||
|
val decimalFormat = DecimalFormat(pattern.toString())
|
||||||
|
if (roundingMode != null) {
|
||||||
|
decimalFormat.roundingMode = roundingMode
|
||||||
|
}
|
||||||
|
|
||||||
|
return decimalFormat.format(value)
|
||||||
|
}
|
||||||
|
}
|
21
src/main/kotlin/com/cubetiqs/money/FunctionBuilderInline.kt
Normal file
21
src/main/kotlin/com/cubetiqs/money/FunctionBuilderInline.kt
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package com.cubetiqs.money
|
||||||
|
|
||||||
|
inline fun buildMoneyConfigProperties(
|
||||||
|
builderAction: MoneyConfig.MoneyConfigProperties.MoneyConfigPropertiesBuilder.() -> Unit
|
||||||
|
): MoneyConfig.MoneyConfigProperties {
|
||||||
|
return MoneyConfig
|
||||||
|
.builder().apply(builderAction)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun applyMoneyConfig(
|
||||||
|
builderAction: MoneyConfig.() -> Unit,
|
||||||
|
) {
|
||||||
|
MoneyConfig.apply(builderAction)
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun buildMoneyFormatter(
|
||||||
|
builderAction: MoneyFormatter.() -> Unit
|
||||||
|
): MoneyFormatter {
|
||||||
|
return MoneyFormatter().apply(builderAction)
|
||||||
|
}
|
@ -16,6 +16,16 @@ object MoneyConfig {
|
|||||||
*/
|
*/
|
||||||
private val config: MutableMap<String, Double> = mutableMapOf()
|
private val config: MutableMap<String, Double> = mutableMapOf()
|
||||||
|
|
||||||
|
// use to format the money for each value, if have
|
||||||
|
private val configFormatter: MutableMap<String, MoneyFormatProvider> = mutableMapOf()
|
||||||
|
|
||||||
|
// use to identified for config dataset with prefix mode
|
||||||
|
private var configPrefix: String = ""
|
||||||
|
// use to fallback, if the currency not found
|
||||||
|
// if the fallback greater than ZERO, then called it
|
||||||
|
// else throws
|
||||||
|
private var fallbackRate: Double = 0.0
|
||||||
|
|
||||||
// validate the config, if have it's valid
|
// validate the config, if have it's valid
|
||||||
fun isValid(): Boolean {
|
fun isValid(): Boolean {
|
||||||
return config.isNotEmpty()
|
return config.isNotEmpty()
|
||||||
@ -37,6 +47,24 @@ object MoneyConfig {
|
|||||||
return MoneyConfig
|
return MoneyConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setConfigPrefix(prefix: String): MoneyConfig {
|
||||||
|
configPrefix = prefix
|
||||||
|
return MoneyConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setFallbackRate(fallbackRate: Double) = apply {
|
||||||
|
this.fallbackRate = fallbackRate
|
||||||
|
}
|
||||||
|
|
||||||
|
// get custom config key within currency generally
|
||||||
|
// example: myOwned_usd
|
||||||
|
private fun getConfigKey(key: String): String {
|
||||||
|
if (configPrefix.isEmpty() || configPrefix.isBlank()) {
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
return "${configPrefix}_$key"
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse the config string to currency's map within rates
|
* Parse the config string to currency's map within rates
|
||||||
* Key is money's currency (String)
|
* Key is money's currency (String)
|
||||||
@ -47,7 +75,14 @@ object MoneyConfig {
|
|||||||
fun parse(config: String, clearAllStates: Boolean = true) {
|
fun parse(config: String, clearAllStates: Boolean = true) {
|
||||||
// remove all states, if needed
|
// remove all states, if needed
|
||||||
if (clearAllStates) {
|
if (clearAllStates) {
|
||||||
|
if (configPrefix.isEmpty() || config.isBlank()) {
|
||||||
MoneyConfig.config.clear()
|
MoneyConfig.config.clear()
|
||||||
|
} else {
|
||||||
|
val keys = MoneyConfig.config.filter { it.key.startsWith(prefix = configPrefix) }.keys
|
||||||
|
keys.forEach { key ->
|
||||||
|
MoneyConfig.config.remove(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val rates = config.split(getProperties().deliSplit)
|
val rates = config.split(getProperties().deliSplit)
|
||||||
@ -60,11 +95,14 @@ object MoneyConfig {
|
|||||||
val currency = temp[0]
|
val currency = temp[0]
|
||||||
.toUpperCase()
|
.toUpperCase()
|
||||||
.trim()
|
.trim()
|
||||||
|
val key = getConfigKey(currency)
|
||||||
val value = temp[1].toDouble()
|
val value = temp[1].toDouble()
|
||||||
if (MoneyConfig.config.containsKey(currency)) {
|
|
||||||
MoneyConfig.config.replace(currency, value)
|
// set the value into dataset
|
||||||
|
if (MoneyConfig.config.containsKey(key)) {
|
||||||
|
MoneyConfig.config.replace(key, value)
|
||||||
} else {
|
} else {
|
||||||
MoneyConfig.config.put(currency, value)
|
MoneyConfig.config.put(key, value)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw MoneyCurrencyStateException("money config format $temp is not valid!")
|
throw MoneyCurrencyStateException("money config format $temp is not valid!")
|
||||||
@ -72,15 +110,20 @@ object MoneyConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// append the rate into dataset
|
||||||
|
// for config key are completed change inside
|
||||||
fun appendRate(currency: String, rate: Double) = apply {
|
fun appendRate(currency: String, rate: Double) = apply {
|
||||||
val currencyKey = currency.toUpperCase().trim()
|
val currencyKey = currency.toUpperCase().trim()
|
||||||
if (config.containsKey(currencyKey)) {
|
val key = getConfigKey(currencyKey)
|
||||||
config.replace(currencyKey, rate)
|
if (config.containsKey(key)) {
|
||||||
|
config.replace(key, rate)
|
||||||
} else {
|
} else {
|
||||||
config[currencyKey] = rate
|
config[key] = rate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// append the rate via provider
|
||||||
|
// no need to change currency prefix
|
||||||
fun appendRate(provider: MoneyExchangeProvider) = apply {
|
fun appendRate(provider: MoneyExchangeProvider) = apply {
|
||||||
val currency = provider.getCurrency()
|
val currency = provider.getCurrency()
|
||||||
val rate = provider.getRate()
|
val rate = provider.getRate()
|
||||||
@ -109,8 +152,43 @@ object MoneyConfig {
|
|||||||
|
|
||||||
@Throws(MoneyCurrencyStateException::class)
|
@Throws(MoneyCurrencyStateException::class)
|
||||||
fun getRate(currency: StdMoney.Currency): Double {
|
fun getRate(currency: StdMoney.Currency): Double {
|
||||||
return getConfig()[currency.getCurrency().toUpperCase().trim()]
|
return getConfig()[getConfigKey(currency.getCurrency().toUpperCase().trim())]
|
||||||
?: throw MoneyCurrencyStateException("money currency ${currency.getCurrency()} is not valid!")
|
?: if (fallbackRate > 0) fallbackRate else throw MoneyCurrencyStateException("money currency ${currency.getCurrency()} is not valid!")
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply default formatter for all not exists
|
||||||
|
fun applyDefaultFormatter(
|
||||||
|
provider: MoneyFormatProvider? = null
|
||||||
|
) = apply {
|
||||||
|
configFormatter[MoneyFormatter.DEFAULT_FORMATTER] = buildMoneyFormatter {
|
||||||
|
setProvider(provider)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add money formatter by currency of each money value
|
||||||
|
fun addFormatter(currency: String, formatter: MoneyFormatProvider) = apply {
|
||||||
|
val key = getConfigKey(currency.toUpperCase().trim())
|
||||||
|
if (configFormatter.containsKey(key)) {
|
||||||
|
configFormatter.replace(key, formatter)
|
||||||
|
} else {
|
||||||
|
configFormatter[key] = formatter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get formatter by currency or default
|
||||||
|
fun getFormatter(currency: String? = null): MoneyFormatter {
|
||||||
|
// apply default formatter
|
||||||
|
val formatter = (if (!currency.isNullOrEmpty()) {
|
||||||
|
val key = getConfigKey(currency.toUpperCase().trim())
|
||||||
|
configFormatter[key]
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}) ?: configFormatter[MoneyFormatter.DEFAULT_FORMATTER]
|
||||||
|
|
||||||
|
return when (formatter) {
|
||||||
|
is MoneyFormatter -> formatter
|
||||||
|
else -> buildMoneyFormatter { setProvider(provider = formatter) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MoneyConfigProperties(
|
class MoneyConfigProperties(
|
||||||
|
@ -73,7 +73,7 @@ infix fun Number.withCurrency(currency: String): StdMoney = this withCurrency ob
|
|||||||
|
|
||||||
// toString function for StdMoney interface
|
// toString function for StdMoney interface
|
||||||
fun StdMoney.asString(): String = "StdMoney(value=${getValue()}, currency=${getCurrency().getCurrency()})"
|
fun StdMoney.asString(): String = "StdMoney(value=${getValue()}, currency=${getCurrency().getCurrency()})"
|
||||||
fun StdMoney.asMoneyString(): String = "${getValue()}:${getCurrency().getCurrency()}"
|
fun StdMoney.asMoneyString(deli: Char? = ':'): String = "${getValue()}${deli ?: ':'}${getCurrency().getCurrency()}"
|
||||||
fun String?.fromStringToMoney(): StdMoney {
|
fun String?.fromStringToMoney(): StdMoney {
|
||||||
val values = this?.split(":")
|
val values = this?.split(":")
|
||||||
if (values.isNullOrEmpty()) {
|
if (values.isNullOrEmpty()) {
|
||||||
@ -110,16 +110,20 @@ fun StdMoney.tryToCastToMixin(): MoneyMixin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun buildMoneyConfigProperties(
|
// transfer std money to money view
|
||||||
builderAction: MoneyConfig.MoneyConfigProperties.MoneyConfigPropertiesBuilder.() -> Unit
|
fun StdMoney.asMoneyView(): MoneyView {
|
||||||
): MoneyConfig.MoneyConfigProperties {
|
return MoneyView(this)
|
||||||
return MoneyConfig
|
|
||||||
.builder().apply(builderAction)
|
|
||||||
.build()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun applyMoneyConfig(
|
// transfer money view to std money
|
||||||
builderAction: MoneyConfig.() -> Unit,
|
fun MoneyView.asStdMoney(): StdMoney {
|
||||||
) {
|
return object : StdMoney {
|
||||||
MoneyConfig.apply(builderAction)
|
override fun getCurrency(): StdMoney.Currency {
|
||||||
|
return StdMoney.initCurrency(this@asStdMoney.getCurrency())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getValue(): Double {
|
||||||
|
return this@asStdMoney.getValue()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
17
src/main/kotlin/com/cubetiqs/money/MoneyFormatProvider.kt
Normal file
17
src/main/kotlin/com/cubetiqs/money/MoneyFormatProvider.kt
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package com.cubetiqs.money
|
||||||
|
|
||||||
|
import java.math.RoundingMode
|
||||||
|
|
||||||
|
interface MoneyFormatProvider {
|
||||||
|
fun getPattern(): String? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getPrecision(): Int? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getRoundingMode(): RoundingMode? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
61
src/main/kotlin/com/cubetiqs/money/MoneyFormatter.kt
Normal file
61
src/main/kotlin/com/cubetiqs/money/MoneyFormatter.kt
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
package com.cubetiqs.money
|
||||||
|
|
||||||
|
import java.io.Serializable
|
||||||
|
import java.math.RoundingMode
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Money Formatter (Final class)
|
||||||
|
*
|
||||||
|
* @see MoneyConfig for format properties for each of value within currency
|
||||||
|
* @see DecimalUtils for Utils formatter with number
|
||||||
|
*/
|
||||||
|
class MoneyFormatter(
|
||||||
|
private var pattern: String? = null,
|
||||||
|
private var precision: Int? = null,
|
||||||
|
private var roundingMode: RoundingMode? = null,
|
||||||
|
) : Serializable, StdMoneyFormation, MoneyFormatProvider {
|
||||||
|
fun setPattern(pattern: String?) = apply { this.pattern = pattern }
|
||||||
|
fun setPrecision(precision: Int?) = apply { this.precision = precision }
|
||||||
|
fun setRoundingMode(roundingMode: RoundingMode?) = apply { this.roundingMode = roundingMode }
|
||||||
|
fun setProvider(provider: MoneyFormatProvider?) = apply {
|
||||||
|
if (provider != null) {
|
||||||
|
this.pattern = provider.getPattern()
|
||||||
|
this.precision = provider.getPrecision()
|
||||||
|
this.roundingMode = provider.getRoundingMode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// when want to format the value for each of them, need to parse the money value here
|
||||||
|
private var value: StdMoney? = null
|
||||||
|
fun setValue(value: StdMoney?) = apply { this.value = value }
|
||||||
|
|
||||||
|
constructor(value: StdMoney?) : this() {
|
||||||
|
this.value = value
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getPattern() = pattern?.trim()
|
||||||
|
override fun getPrecision() = precision ?: -1
|
||||||
|
override fun getRoundingMode() = roundingMode
|
||||||
|
|
||||||
|
override fun format(): String {
|
||||||
|
value?.getValue() ?: return ""
|
||||||
|
|
||||||
|
if (getPattern() == null && getPrecision() < 0 && getRoundingMode() == null) {
|
||||||
|
return value?.getValue().toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getPrecision() > -1) {
|
||||||
|
return DecimalUtils.toDecimalPrecision(value?.getValue() ?: 0, getPrecision(), getRoundingMode()) ?: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return DecimalUtils.toStringDecimal(value?.getValue() ?: 0, getPattern(), getRoundingMode())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toMoneyString(overrideSymbol: Char?): String {
|
||||||
|
return value?.asMoneyString(overrideSymbol) ?: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val DEFAULT_FORMATTER = "defaultFormatter"
|
||||||
|
}
|
||||||
|
}
|
26
src/main/kotlin/com/cubetiqs/money/MoneyView.kt
Normal file
26
src/main/kotlin/com/cubetiqs/money/MoneyView.kt
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package com.cubetiqs.money
|
||||||
|
|
||||||
|
open class MoneyView(
|
||||||
|
private var value: Number? = null,
|
||||||
|
private var currency: String? = null,
|
||||||
|
) : MoneyMixin {
|
||||||
|
constructor(money: StdMoney) : this() {
|
||||||
|
this.value = money.getValue()
|
||||||
|
this.currency = money.getCurrency().getCurrency()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getValue(): Double {
|
||||||
|
return value?.toDouble() ?: 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getCurrency(): String? {
|
||||||
|
return currency
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getFormat(): String {
|
||||||
|
return MoneyConfig
|
||||||
|
.getFormatter(getCurrency())
|
||||||
|
.setValue(this.asStdMoney())
|
||||||
|
.format()
|
||||||
|
}
|
||||||
|
}
|
@ -135,4 +135,9 @@ class MoneyTests {
|
|||||||
Assert.assertEquals(expected1, result1.getValue(), 0.0)
|
Assert.assertEquals(expected1, result1.getValue(), 0.0)
|
||||||
Assert.assertEquals(expected2, result2.getValue(), 0.0)
|
Assert.assertEquals(expected2, result2.getValue(), 0.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun moneyFormatterTest() {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user