aboutsummaryrefslogtreecommitdiff
path: root/taler-kotlin-common/src/commonMain
diff options
context:
space:
mode:
Diffstat (limited to 'taler-kotlin-common/src/commonMain')
-rw-r--r--taler-kotlin-common/src/commonMain/kotlin/net/taler/common/Amount.kt198
-rw-r--r--taler-kotlin-common/src/commonMain/kotlin/net/taler/common/Time.kt106
-rw-r--r--taler-kotlin-common/src/commonMain/kotlin/net/taler/common/Version.kt70
3 files changed, 0 insertions, 374 deletions
diff --git a/taler-kotlin-common/src/commonMain/kotlin/net/taler/common/Amount.kt b/taler-kotlin-common/src/commonMain/kotlin/net/taler/common/Amount.kt
deleted file mode 100644
index 84d10c5..0000000
--- a/taler-kotlin-common/src/commonMain/kotlin/net/taler/common/Amount.kt
+++ /dev/null
@@ -1,198 +0,0 @@
-/*
- * This file is part of GNU Taler
- * (C) 2020 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-package net.taler.common
-
-import kotlinx.serialization.Decoder
-import kotlinx.serialization.Encoder
-import kotlinx.serialization.KSerializer
-import kotlinx.serialization.Serializable
-import kotlinx.serialization.Serializer
-import kotlin.math.floor
-import kotlin.math.pow
-import kotlin.math.roundToInt
-
-class AmountParserException(msg: String? = null, cause: Throwable? = null) : Exception(msg, cause)
-class AmountOverflowException(msg: String? = null, cause: Throwable? = null) : Exception(msg, cause)
-
-@Serializable(with = KotlinXAmountSerializer::class)
-data class Amount(
- /**
- * name of the currency using either a three-character ISO 4217 currency code,
- * or a regional currency identifier starting with a "*" followed by at most 10 characters.
- * ISO 4217 exponents in the name are not supported,
- * although the "fraction" is corresponds to an ISO 4217 exponent of 6.
- */
- val currency: String,
-
- /**
- * The integer part may be at most 2^52.
- * Note that "1" here would correspond to 1 EUR or 1 USD, depending on currency, not 1 cent.
- */
- val value: Long,
-
- /**
- * Unsigned 32 bit fractional value to be added to value representing
- * an additional currency fraction, in units of one hundred millionth (1e-8)
- * of the base currency value. For example, a fraction
- * of 50_000_000 would correspond to 50 cents.
- */
- val fraction: Int
-) : Comparable<Amount> {
-
- companion object {
-
- private const val FRACTIONAL_BASE: Int = 100000000 // 1e8
-
- private val REGEX_CURRENCY = Regex("""^[-_*A-Za-z0-9]{1,12}$""")
- val MAX_VALUE = 2.0.pow(52).toLong()
- private const val MAX_FRACTION_LENGTH = 8
- const val MAX_FRACTION = 99_999_999
-
- fun zero(currency: String): Amount {
- return Amount(checkCurrency(currency), 0, 0)
- }
-
- fun fromJSONString(str: String): Amount {
- val split = str.split(":")
- if (split.size != 2) throw AmountParserException("Invalid Amount Format")
- return fromString(split[0], split[1])
- }
-
- fun fromString(currency: String, str: String): Amount {
- // value
- val valueSplit = str.split(".")
- val value = checkValue(valueSplit[0].toLongOrNull())
- // fraction
- val fraction: Int = if (valueSplit.size > 1) {
- val fractionStr = valueSplit[1]
- if (fractionStr.length > MAX_FRACTION_LENGTH)
- throw AmountParserException("Fraction $fractionStr too long")
- val fraction = "0.$fractionStr".toDoubleOrNull()
- ?.times(FRACTIONAL_BASE)
- ?.roundToInt()
- checkFraction(fraction)
- } else 0
- return Amount(checkCurrency(currency), value, fraction)
- }
-
- fun min(currency: String): Amount = Amount(currency, 0, 1)
- fun max(currency: String): Amount = Amount(currency, MAX_VALUE, MAX_FRACTION)
-
-
- internal fun checkCurrency(currency: String): String {
- if (!REGEX_CURRENCY.matches(currency))
- throw AmountParserException("Invalid currency: $currency")
- return currency
- }
-
- internal fun checkValue(value: Long?): Long {
- if (value == null || value > MAX_VALUE)
- throw AmountParserException("Value $value greater than $MAX_VALUE")
- return value
- }
-
- internal fun checkFraction(fraction: Int?): Int {
- if (fraction == null || fraction > MAX_FRACTION)
- throw AmountParserException("Fraction $fraction greater than $MAX_FRACTION")
- return fraction
- }
-
- }
-
- val amountStr: String
- get() = if (fraction == 0) "$value" else {
- var f = fraction
- var fractionStr = ""
- while (f > 0) {
- fractionStr += f / (FRACTIONAL_BASE / 10)
- f = (f * 10) % FRACTIONAL_BASE
- }
- "$value.$fractionStr"
- }
-
- operator fun plus(other: Amount): Amount {
- check(currency == other.currency) { "Can only subtract from same currency" }
- val resultValue = value + other.value + floor((fraction + other.fraction).toDouble() / FRACTIONAL_BASE).toLong()
- if (resultValue > MAX_VALUE)
- throw AmountOverflowException()
- val resultFraction = (fraction + other.fraction) % FRACTIONAL_BASE
- return Amount(currency, resultValue, resultFraction)
- }
-
- operator fun times(factor: Int): Amount {
- // TODO consider replacing with a faster implementation
- if (factor == 0) return zero(currency)
- var result = this
- for (i in 1 until factor) result += this
- return result
- }
-
- operator fun minus(other: Amount): Amount {
- check(currency == other.currency) { "Can only subtract from same currency" }
- var resultValue = value
- var resultFraction = fraction
- if (resultFraction < other.fraction) {
- if (resultValue < 1L)
- throw AmountOverflowException()
- resultValue--
- resultFraction += FRACTIONAL_BASE
- }
- check(resultFraction >= other.fraction)
- resultFraction -= other.fraction
- if (resultValue < other.value)
- throw AmountOverflowException()
- resultValue -= other.value
- return Amount(currency, resultValue, resultFraction)
- }
-
- fun isZero(): Boolean {
- return value == 0L && fraction == 0
- }
-
- fun toJSONString(): String {
- return "$currency:$amountStr"
- }
-
- override fun toString(): String {
- return "$amountStr $currency"
- }
-
- override fun compareTo(other: Amount): Int {
- check(currency == other.currency) { "Can only compare amounts with the same currency" }
- when {
- value == other.value -> {
- if (fraction < other.fraction) return -1
- if (fraction > other.fraction) return 1
- return 0
- }
- value < other.value -> return -1
- else -> return 1
- }
- }
-
-}
-
-@Serializer(forClass = Amount::class)
-object KotlinXAmountSerializer: KSerializer<Amount> {
- override fun serialize(encoder: Encoder, value: Amount) {
- encoder.encodeString(value.toJSONString())
- }
-
- override fun deserialize(decoder: Decoder): Amount {
- return Amount.fromJSONString(decoder.decodeString())
- }
-}
diff --git a/taler-kotlin-common/src/commonMain/kotlin/net/taler/common/Time.kt b/taler-kotlin-common/src/commonMain/kotlin/net/taler/common/Time.kt
deleted file mode 100644
index 37b6606..0000000
--- a/taler-kotlin-common/src/commonMain/kotlin/net/taler/common/Time.kt
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * This file is part of GNU Taler
- * (C) 2020 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-package net.taler.common
-
-import kotlinx.serialization.SerialName
-import kotlinx.serialization.Serializable
-import kotlinx.serialization.builtins.serializer
-import kotlinx.serialization.json.JsonElement
-import kotlinx.serialization.json.JsonPrimitive
-import kotlinx.serialization.json.JsonTransformingSerializer
-import kotlinx.serialization.json.contentOrNull
-import kotlinx.serialization.json.longOrNull
-import net.taler.common.Duration.Companion.FOREVER
-import kotlin.math.max
-
-expect fun nowMillis(): Long
-
-@Serializable
-data class Timestamp(
- @SerialName("t_ms")
- @Serializable(NeverSerializer::class)
- val ms: Long
-) : Comparable<Timestamp> {
-
- companion object {
- const val NEVER: Long = -1
- fun now(): Timestamp = Timestamp(nowMillis())
- }
-
- /**
- * Returns a copy of this [Timestamp] rounded to seconds.
- */
- fun truncateSeconds(): Timestamp {
- if (ms == NEVER) return Timestamp(ms)
- return Timestamp((ms / 1000L) * 1000L)
- }
-
- operator fun minus(other: Timestamp): Duration = when {
- ms == NEVER -> Duration(FOREVER)
- other.ms == NEVER -> throw Error("Invalid argument for timestamp comparision")
- ms < other.ms -> Duration(0)
- else -> Duration(ms - other.ms)
- }
-
- operator fun minus(other: Duration): Timestamp = when {
- ms == NEVER -> this
- other.ms == FOREVER -> Timestamp(0)
- else -> Timestamp(max(0, ms - other.ms))
- }
-
- override fun compareTo(other: Timestamp): Int {
- return if (ms == NEVER) {
- if (other.ms == NEVER) 0
- else 1
- } else {
- if (other.ms == NEVER) -1
- else ms.compareTo(other.ms)
- }
- }
-
-}
-
-@Serializable
-data class Duration(
- /**
- * Duration in milliseconds.
- */
- @SerialName("d_ms")
- @Serializable(ForeverSerializer::class)
- val ms: Long
-) {
- companion object {
- const val FOREVER: Long = -1
- }
-}
-
-abstract class MinusOneSerializer(private val keyword: String) :
- JsonTransformingSerializer<Long>(Long.serializer(), keyword) {
-
- override fun readTransform(element: JsonElement): JsonElement {
- return if (element.contentOrNull == keyword) return JsonPrimitive(-1)
- else super.readTransform(element)
- }
-
- override fun writeTransform(element: JsonElement): JsonElement {
- return if (element.longOrNull == -1L) return JsonPrimitive(keyword)
- else element
- }
-}
-
-object NeverSerializer : MinusOneSerializer("never")
-object ForeverSerializer : MinusOneSerializer("forever")
diff --git a/taler-kotlin-common/src/commonMain/kotlin/net/taler/common/Version.kt b/taler-kotlin-common/src/commonMain/kotlin/net/taler/common/Version.kt
deleted file mode 100644
index 8774115..0000000
--- a/taler-kotlin-common/src/commonMain/kotlin/net/taler/common/Version.kt
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * This file is part of GNU Taler
- * (C) 2020 Taler Systems S.A.
- *
- * GNU Taler is free software; you can redistribute it and/or modify it under the
- * terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 3, or (at your option) any later version.
- *
- * GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-package net.taler.common
-
-import kotlin.math.sign
-
-/**
- * Semantic versioning, but libtool-style.
- * See https://www.gnu.org/software/libtool/manual/html_node/Libtool-versioning.html
- */
-data class Version(
- val current: Int,
- val revision: Int,
- val age: Int
-) {
- companion object {
- fun parse(v: String): Version? {
- val elements = v.split(":")
- if (elements.size != 3) return null
- val (currentStr, revisionStr, ageStr) = elements
- val current = currentStr.toIntOrNull()
- val revision = revisionStr.toIntOrNull()
- val age = ageStr.toIntOrNull()
- if (current == null || revision == null || age == null) return null
- return Version(current, revision, age)
- }
- }
-
- /**
- * Compare two libtool-style versions.
- *
- * Returns a [VersionMatchResult] or null if the given version was null.
- */
- fun compare(other: Version?): VersionMatchResult? {
- if (other == null) return null
- val compatible = current - age <= other.current &&
- current >= other.current - other.age
- val currentCmp = sign((current - other.current).toDouble()).toInt()
- return VersionMatchResult(compatible, currentCmp)
- }
-
- /**
- * Result of comparing two libtool versions.
- */
- data class VersionMatchResult(
- /**
- * Is the first version compatible with the second?
- */
- val compatible: Boolean,
- /**
- * Is the first version older (-1), newer (+1) or identical (0)?
- */
- val currentCmp: Int
- )
-
-}