2021-02-08 11:24:14 +07:00
|
|
|
package com.cubetiqs.money
|
2020-08-26 20:06:06 +07:00
|
|
|
|
2021-02-09 13:54:59 +07:00
|
|
|
import java.util.*
|
2021-02-09 12:30:24 +07:00
|
|
|
import java.util.concurrent.ConcurrentHashMap
|
|
|
|
import java.util.concurrent.ConcurrentMap
|
|
|
|
|
2020-08-26 20:06:06 +07:00
|
|
|
/**
|
|
|
|
* Default money config in static object.
|
2020-08-26 21:10:52 +07:00
|
|
|
* Sample parse format: USD=1,KHR=4000,EUR=0.99
|
2020-08-26 20:06:06 +07:00
|
|
|
*
|
|
|
|
* @author sombochea
|
|
|
|
* @since 1.0
|
|
|
|
*/
|
|
|
|
object MoneyConfig {
|
2021-02-09 13:54:59 +07:00
|
|
|
private const val CONFIG_INITIAL_SIZE = 10
|
|
|
|
private const val CONFIG_FORMATTER_INITIAL_SIZE = 10
|
|
|
|
private const val LOCALES_INITIAL_SIZE = 5
|
|
|
|
private const val CURRENCIES_INITIAL_SIZE = 5
|
|
|
|
|
2020-08-26 20:06:06 +07:00
|
|
|
/**
|
2020-08-26 20:19:06 +07:00
|
|
|
* All money currencies and its rate are stored in memory state for exchange or compute the value.
|
|
|
|
*
|
2020-08-26 20:06:06 +07:00
|
|
|
* Key is the currency
|
|
|
|
* Value is the rate
|
|
|
|
*/
|
2021-02-09 13:54:59 +07:00
|
|
|
private val configRates: ConcurrentMap<String, Double> = ConcurrentHashMap(CONFIG_INITIAL_SIZE)
|
2020-08-27 08:48:18 +07:00
|
|
|
|
2021-02-09 12:05:02 +07:00
|
|
|
// use to format the money for each value, if have
|
2021-02-09 13:54:59 +07:00
|
|
|
private val configFormatter: ConcurrentMap<String, MoneyFormatProvider> =
|
|
|
|
ConcurrentHashMap(CONFIG_FORMATTER_INITIAL_SIZE)
|
|
|
|
|
|
|
|
// use to custom locales and allow to detect auto formatter, if enable auto locale format
|
|
|
|
private val configLocales: ConcurrentMap<String, Locale> = ConcurrentHashMap(LOCALES_INITIAL_SIZE)
|
|
|
|
|
|
|
|
// use to custom currencies and allow to detect auto, when use auto detect for currencies translate
|
|
|
|
private val configCurrencies: ConcurrentMap<String, Currency> = ConcurrentHashMap(CURRENCIES_INITIAL_SIZE)
|
|
|
|
|
|
|
|
// use to auto format, when this true
|
|
|
|
// and formatter for money value, with current locale or custom set locale
|
|
|
|
private var autoLocaleFormatter: Boolean = false
|
|
|
|
|
|
|
|
// use to auto format, when this true
|
|
|
|
// and formatter for money value, with current currency or custom set currency
|
|
|
|
private var autoCurrencyFormatter: Boolean = false
|
2021-02-09 12:05:02 +07:00
|
|
|
|
|
|
|
// use to identified for config dataset with prefix mode
|
|
|
|
private var configPrefix: String = ""
|
2021-02-09 13:54:59 +07:00
|
|
|
private fun isConfigPrefixValid() = configPrefix.isNotEmpty() && configPrefix.isNotBlank()
|
|
|
|
|
2021-02-09 12:05:02 +07:00
|
|
|
// 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
|
|
|
|
|
2021-02-09 13:54:59 +07:00
|
|
|
fun getConfigs(): Map<String, Any?> {
|
|
|
|
return mapOf(
|
|
|
|
"configRates" to configRates,
|
|
|
|
"configFormatter" to configFormatter,
|
|
|
|
"configLocales" to configLocales,
|
|
|
|
"configCurrencies" to configCurrencies,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// validate the config rates, if have it's valid
|
|
|
|
fun isConfigRatesValid(): Boolean {
|
|
|
|
return configRates.isNotEmpty()
|
2020-08-27 08:48:18 +07:00
|
|
|
}
|
2020-08-26 20:06:06 +07:00
|
|
|
|
2020-08-26 21:10:52 +07:00
|
|
|
/**
|
|
|
|
* Money properties for money config format
|
|
|
|
*/
|
|
|
|
private var properties: MoneyConfigProperties? = null
|
|
|
|
|
2021-02-08 13:09:54 +07:00
|
|
|
private val propertiesBuilder = MoneyConfigProperties.MoneyConfigPropertiesBuilder()
|
2020-08-26 21:10:52 +07:00
|
|
|
|
|
|
|
private fun getProperties(): MoneyConfigProperties {
|
|
|
|
return properties ?: propertiesBuilder.build()
|
|
|
|
}
|
|
|
|
|
|
|
|
fun setProperties(properties: MoneyConfigProperties): MoneyConfig {
|
2021-02-08 11:24:14 +07:00
|
|
|
MoneyConfig.properties = properties
|
2020-08-26 21:10:52 +07:00
|
|
|
return MoneyConfig
|
|
|
|
}
|
|
|
|
|
2021-02-09 12:05:02 +07:00
|
|
|
fun setConfigPrefix(prefix: String): MoneyConfig {
|
|
|
|
configPrefix = prefix
|
|
|
|
return MoneyConfig
|
|
|
|
}
|
|
|
|
|
|
|
|
fun setFallbackRate(fallbackRate: Double) = apply {
|
|
|
|
this.fallbackRate = fallbackRate
|
|
|
|
}
|
|
|
|
|
2021-02-09 13:54:59 +07:00
|
|
|
fun setAutoLocaleFormatter(enabled: Boolean) = apply {
|
|
|
|
this.autoLocaleFormatter = enabled
|
|
|
|
}
|
|
|
|
|
|
|
|
fun setAutoCurrencyFormatter(enabled: Boolean) = apply {
|
|
|
|
this.autoCurrencyFormatter = enabled
|
|
|
|
}
|
|
|
|
|
|
|
|
fun isAutoLocaleFormatterEnabled() = this.autoLocaleFormatter
|
|
|
|
fun isAutoCurrencyFormatterEnabled() = this.autoCurrencyFormatter
|
|
|
|
|
2021-02-09 12:05:02 +07:00
|
|
|
// 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"
|
|
|
|
}
|
|
|
|
|
2020-08-26 20:19:06 +07:00
|
|
|
/**
|
|
|
|
* Parse the config string to currency's map within rates
|
|
|
|
* Key is money's currency (String)
|
|
|
|
* Value is money's value (Double)
|
2021-02-09 08:22:23 +07:00
|
|
|
*
|
|
|
|
* Example for config rules: usd:1,khr:4000
|
2020-08-26 20:19:06 +07:00
|
|
|
*/
|
|
|
|
fun parse(config: String, clearAllStates: Boolean = true) {
|
2021-02-08 13:09:54 +07:00
|
|
|
// remove all states, if needed
|
2020-08-26 20:19:06 +07:00
|
|
|
if (clearAllStates) {
|
2021-02-09 13:54:59 +07:00
|
|
|
if (!isConfigPrefixValid()) {
|
|
|
|
configRates.clear()
|
2021-02-09 12:05:02 +07:00
|
|
|
} else {
|
2021-02-09 13:54:59 +07:00
|
|
|
val keys = configRates.filter { it.key.startsWith(prefix = configPrefix) }.keys
|
2021-02-09 12:05:02 +07:00
|
|
|
keys.forEach { key ->
|
2021-02-09 13:54:59 +07:00
|
|
|
configRates.remove(key)
|
2021-02-09 12:05:02 +07:00
|
|
|
}
|
|
|
|
}
|
2020-08-26 20:19:06 +07:00
|
|
|
}
|
2021-02-08 13:09:54 +07:00
|
|
|
|
2020-08-26 21:10:52 +07:00
|
|
|
val rates = config.split(getProperties().deliSplit)
|
2020-08-26 20:06:06 +07:00
|
|
|
rates.map { i ->
|
2021-02-08 14:50:31 +07:00
|
|
|
val temp = i
|
|
|
|
// remove the quote from string
|
|
|
|
.replace("\"", "")
|
|
|
|
.split(getProperties().deliEqual)
|
2020-08-26 20:06:06 +07:00
|
|
|
if (temp.size == 2) {
|
2021-02-08 14:50:31 +07:00
|
|
|
val currency = temp[0]
|
|
|
|
.toUpperCase()
|
|
|
|
.trim()
|
2021-02-09 12:05:02 +07:00
|
|
|
val key = getConfigKey(currency)
|
2020-08-26 20:06:06 +07:00
|
|
|
val value = temp[1].toDouble()
|
2021-02-09 12:05:02 +07:00
|
|
|
|
|
|
|
// set the value into dataset
|
2021-02-09 13:54:59 +07:00
|
|
|
if (configRates.containsKey(key)) {
|
|
|
|
configRates.replace(key, value)
|
2020-08-26 20:19:06 +07:00
|
|
|
} else {
|
2021-02-09 13:54:59 +07:00
|
|
|
configRates.put(key, value)
|
2020-08-26 20:19:06 +07:00
|
|
|
}
|
2020-08-26 20:06:06 +07:00
|
|
|
} else {
|
2021-02-08 18:53:46 +07:00
|
|
|
throw MoneyCurrencyStateException("money config format $temp is not valid!")
|
2020-08-26 20:06:06 +07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-09 12:05:02 +07:00
|
|
|
// append the rate into dataset
|
|
|
|
// for config key are completed change inside
|
2021-02-08 13:09:54 +07:00
|
|
|
fun appendRate(currency: String, rate: Double) = apply {
|
|
|
|
val currencyKey = currency.toUpperCase().trim()
|
2021-02-09 12:05:02 +07:00
|
|
|
val key = getConfigKey(currencyKey)
|
2021-02-09 13:54:59 +07:00
|
|
|
if (configRates.containsKey(key)) {
|
|
|
|
configRates.replace(key, rate)
|
2021-02-08 13:09:54 +07:00
|
|
|
} else {
|
2021-02-09 13:54:59 +07:00
|
|
|
configRates[key] = rate
|
2021-02-08 13:09:54 +07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-09 12:05:02 +07:00
|
|
|
// append the rate via provider
|
|
|
|
// no need to change currency prefix
|
2021-02-08 14:50:31 +07:00
|
|
|
fun appendRate(provider: MoneyExchangeProvider) = apply {
|
|
|
|
val currency = provider.getCurrency()
|
|
|
|
val rate = provider.getRate()
|
|
|
|
this.appendRate(currency, rate)
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Json Format must be, example below
|
|
|
|
*
|
|
|
|
* {
|
|
|
|
* "USD": 1,
|
|
|
|
* "EUR": 0.99,
|
|
|
|
* "...": ...
|
|
|
|
* }
|
|
|
|
*/
|
2021-02-08 21:34:17 +07:00
|
|
|
fun fromJsonRates(configJson: String, clearAllStates: Boolean = false) {
|
2021-02-08 14:50:31 +07:00
|
|
|
val transformValues = configJson
|
|
|
|
.removePrefix("{")
|
|
|
|
.removeSuffix("}")
|
|
|
|
|
|
|
|
parse(transformValues, clearAllStates)
|
|
|
|
}
|
|
|
|
|
2020-08-26 20:06:06 +07:00
|
|
|
// all currencies with its rate
|
2021-02-09 13:54:59 +07:00
|
|
|
fun getConfigRates() = configRates
|
2020-08-26 20:06:06 +07:00
|
|
|
|
2020-08-26 20:19:06 +07:00
|
|
|
@Throws(MoneyCurrencyStateException::class)
|
2021-02-08 12:39:52 +07:00
|
|
|
fun getRate(currency: StdMoney.Currency): Double {
|
2021-02-09 13:54:59 +07:00
|
|
|
return getConfigRates()[getConfigKey(currency.getCurrency().toUpperCase().trim())]
|
2021-02-09 12:05:02 +07:00
|
|
|
?: if (fallbackRate > 0) fallbackRate else throw MoneyCurrencyStateException("money currency ${currency.getCurrency()} is not valid!")
|
|
|
|
}
|
|
|
|
|
2021-02-09 13:54:59 +07:00
|
|
|
// apply custom locale below
|
|
|
|
fun applyDefaultLocale(locale: Locale = Locale.getDefault()) = apply {
|
|
|
|
configLocales[MoneyFormatter.DEFAULT_LOCALE] = locale
|
|
|
|
}
|
|
|
|
|
|
|
|
// get default locale from system or user defined
|
|
|
|
private fun getDefaultLocale(): Locale {
|
|
|
|
return configLocales[MoneyFormatter.DEFAULT_LOCALE] ?: Locale.getDefault()
|
|
|
|
}
|
|
|
|
|
|
|
|
// add custom locale
|
|
|
|
fun applyLocale(locale: Locale) = apply {
|
|
|
|
if (isConfigPrefixValid()) {
|
|
|
|
if (configLocales.containsKey(configPrefix)) {
|
|
|
|
configLocales.replace(configPrefix, locale)
|
|
|
|
} else {
|
|
|
|
configLocales[configPrefix] = locale
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// get locale by config prefix or default if not found
|
|
|
|
fun getLocale(): Locale {
|
|
|
|
if (isConfigPrefixValid() && configLocales.containsKey(configPrefix)) {
|
|
|
|
return configLocales[configPrefix] ?: getDefaultLocale()
|
|
|
|
}
|
|
|
|
|
|
|
|
return getDefaultLocale()
|
|
|
|
}
|
|
|
|
|
|
|
|
// get default currency from system or user defined
|
|
|
|
private fun getDefaultCurrency(): Currency {
|
|
|
|
return configCurrencies[MoneyFormatter.DEFAULT_CURRENCY] ?: Currency.getInstance(getDefaultLocale())
|
|
|
|
}
|
|
|
|
|
|
|
|
// apply the default currency for system at runtime
|
|
|
|
fun applyDefaultCurrency(currency: StdMoney.Currency) = apply {
|
|
|
|
configCurrencies[MoneyFormatter.DEFAULT_CURRENCY] = currency.findCurrency()
|
|
|
|
}
|
|
|
|
|
|
|
|
// apply the currency into the config for system currency at runtime
|
|
|
|
fun applyCurrency(currency: StdMoney.Currency) = apply {
|
|
|
|
if (isConfigPrefixValid()) {
|
|
|
|
val systemCurrency = currency.findCurrency()
|
|
|
|
if (systemCurrency != null) {
|
|
|
|
if (configCurrencies.containsKey(configPrefix)) {
|
|
|
|
configCurrencies.replace(configPrefix, systemCurrency)
|
|
|
|
} else {
|
|
|
|
configCurrencies[configPrefix] = systemCurrency
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// get currency by config prefix or default if not found
|
|
|
|
fun getCurrency(): Currency {
|
|
|
|
if (isConfigPrefixValid() && configCurrencies.containsKey(configPrefix)) {
|
|
|
|
return configCurrencies[configPrefix] ?: getDefaultCurrency()
|
|
|
|
}
|
|
|
|
|
|
|
|
return getDefaultCurrency()
|
|
|
|
}
|
|
|
|
|
|
|
|
fun getCurrencySymbol(): String {
|
|
|
|
return getCurrency().getSymbol(getDefaultLocale())
|
|
|
|
}
|
|
|
|
|
2021-02-09 12:05:02 +07:00
|
|
|
// 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) }
|
|
|
|
}
|
2020-08-26 20:06:06 +07:00
|
|
|
}
|
2020-08-26 21:10:52 +07:00
|
|
|
|
|
|
|
class MoneyConfigProperties(
|
|
|
|
val deliEqual: Char,
|
|
|
|
val deliSplit: Char,
|
|
|
|
) {
|
|
|
|
class MoneyConfigPropertiesBuilder(
|
|
|
|
private var deliEqual: Char? = null,
|
|
|
|
private var deliSplit: Char? = null,
|
|
|
|
) {
|
|
|
|
fun setDeliEqual(deliEqual: Char) = apply { this.deliEqual = deliEqual }
|
|
|
|
|
|
|
|
private fun getDeliEqual(): Char {
|
|
|
|
return deliEqual ?: '='
|
|
|
|
}
|
|
|
|
|
|
|
|
fun setDeliSplit(deliSplit: Char) = apply { this.deliSplit = deliSplit }
|
|
|
|
|
|
|
|
private fun getDeliSplit(): Char {
|
|
|
|
return deliSplit ?: ','
|
|
|
|
}
|
|
|
|
|
|
|
|
fun build(): MoneyConfigProperties {
|
|
|
|
return MoneyConfigProperties(deliEqual = getDeliEqual(), deliSplit = getDeliSplit())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-02-08 13:09:54 +07:00
|
|
|
|
|
|
|
fun builder() = MoneyConfigProperties.MoneyConfigPropertiesBuilder()
|
2020-08-26 20:06:06 +07:00
|
|
|
}
|