All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.jfachwert.bank.Geldbetrag.kt Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2024 by Oliver Boehm
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * (c)reated 29.07.2024 by oboehm ([email protected])
 */
package de.jfachwert.bank

import com.fasterxml.jackson.databind.annotation.JsonSerialize
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer
import de.jfachwert.KFachwert
import de.jfachwert.KSimpleValidator
import de.jfachwert.bank.Waehrung.Companion.getSymbol
import de.jfachwert.bank.Waehrung.Companion.toCurrency
import de.jfachwert.money.internal.GeldbetragFormatter
import de.jfachwert.money.internal.Zahlenwert
import de.jfachwert.money.pruefung.exception.LocalizedMonetaryException
import de.jfachwert.pruefung.NumberValidator
import de.jfachwert.pruefung.exception.InvalidValueException
import de.jfachwert.pruefung.exception.LocalizedArithmeticException
import java.math.BigDecimal
import java.math.RoundingMode
import java.text.DecimalFormat
import java.util.*
import javax.money.*
import javax.money.format.MonetaryAmountFormat

/**
 * Diese Klasse wurde ins money-Package verschoben. Sie ist nur noch aus
 * Kompatibiltaetsgruenden fuer eine Uebergangszeit im bank-Package.
 *
 * @deprecated: durch Geldbetrag ersetzt
 */
@JsonSerialize(using = ToStringSerializer::class)
open class Geldbetrag @JvmOverloads constructor(betrag: Number, currency: CurrencyUnit, context: MonetaryContext = FACTORY.getMonetaryContextOf(betrag)) : MonetaryAmount, Comparable,
    KFachwert {

    private val betrag: BigDecimal
    private val context: MonetaryContext

    // Eine Umstellung auf 'Waehrung' oder 'Currency' fuehrt leider dazu, dass
    // dann das TCK zu JSR-354 fehlschlaegt, da Waehrung nicht final und damit
    // potentiell nicht immutable ist. Daher unterdruecken wir jetzt die
    // Sonar-Warnung "Make "currency" transient or serializable".
    @SuppressWarnings("squid:S1948")
    private val currency: CurrencyUnit

    /**
     * Erzeugt einen Geldbetrag in der aktuellen Landeswaehrung.
     *
     * @param betrag Geldbetrag, z.B. 1
     */
    constructor(betrag: Long) : this(BigDecimal.valueOf(betrag))

    /**
     * Erzeugt einen Geldbetrag in der aktuellen Landeswaehrung.
     *
     * @param betrag Geldbetrag, z.B. 1.00
     */
    constructor(betrag: Double) : this(BigDecimal.valueOf(betrag))

    /**
     * Erzeugt einen Geldbetrag in der aktuellen Landeswaehrung.
     *
     * @param betrag Geldbetrag, z.B. "1"
     */
    constructor(betrag: String) : this(valueOf(betrag))

    /**
     * Dies ist zum einen der CopyConstructor als Ersatz fuer eine
     * clone-Methode, zum anderen wandelt es einen [MonetaryAmount]
     * in ein GeldBetrag-Objekt.
     *
     * @param other der andere Geldbetrag
     */
    constructor(other: MonetaryAmount) : this(other.number, Currency.getInstance(other.currency.currencyCode))

    /**
     * Erzeugt einen Geldbetrag in der angegebenen Waehrung.
     *
     * @param betrag   Geldbetrag, z.B. 1.00
     * @param currency Waehrung, z.B. Euro
     */
    constructor(betrag: Number, currency: Currency? = Waehrung.DEFAULT_CURRENCY) : this(betrag, Waehrung.of(currency!!))

    /**
     * Liefert einen Geldbetrag mit der neuen gewuenschten Waehrung zurueck.
     * Dabei findet **keine** Umrechnung statt.
     *
     * Anmerkung: Der Prefix "with" kommt von der Namenskonvention in Scala
     * fuer immutable Objekte.
     *
     * @param unit die Waehrungseinheit
     * @return Geldbetrag mit neuer Waehrung
     */
    fun withCurrency(unit: CurrencyUnit): Geldbetrag {
        return withCurrency(unit.currencyCode)
    }

    /**
     * Liefert einen Geldbetrag mit der neuen gewuenschten Waehrung zurueck.
     * Dabei findet **keine** Umrechnung statt.
     *
     * Anmerkung: Der Prefix "with" kommt von der Namenskonvention in Scala
     * fuer immutable Objekte.
     *
     * @param waehrung Waehrung
     * @return Geldbetrag mit neuer Waehrung
     */
    fun withCurrency(waehrung: String): Geldbetrag {
        var normalized = waehrung.uppercase().trim { it <= ' ' }
        if ("DM".equals(normalized, ignoreCase = true)) {
            normalized = "DEM"
        }
        return withCurrency(Currency.getInstance(normalized))
    }

    /**
     * Liefert einen Geldbetrag mit der neuen gewuenschten Waehrung zurueck.
     * Dabei findet **keine** Umrechnung statt.
     *
     * Anmerkung: Der Prefix "with" kommt von der Namenskonvention in Scala
     * fuer immutable Objekte.
     *
     * @param currency Waehrung
     * @return Geldbetrag mit neuer Waehrung
     */
    fun withCurrency(currency: Currency?): Geldbetrag {
        return Geldbetrag(this.number, currency)
    }

    /**
     * Gibt den [MonetaryContext] des Geldbetrags zurueck. Der
     * [MonetaryContext] enthaelt Informationen ueber numerische
     * Eigenschaften wie Anzahl Nachkommastellen oder Rundungsinformation.
     *
     * @return den [MonetaryContext] zum Geldbetrag
     */
    override fun getContext(): MonetaryContext {
        return context
    }

    /**
     * Erzeugt eine neue @code GeldbetragFactory}, die @link CurrencyUnit}, den
     * numerischen Werte und den aktuellen [MonetaryContext] verwendet.
     *
     * @return eine `GeldbetragFactory`, mit dem ein neuer (gleicher)
     * Geldbetrag erzeugt werden kann.
     */
    override fun getFactory(): GeldbetragFactory {
        return GeldbetragFactory().setCurrency(currency).setNumber(betrag).setContext(context)
    }

    /**
     * Vergleicht zwei Instanzen von [MonetaryAmount]. Nicht signifikante
     * Nachkommastellen werden dabei ignoriert.
     *
     * @param amount Betrag eines `MonetaryAmount`, mit dem verglichen werid
     * @return `true` falls `amount > this`.
     * @throws MonetaryException bei unterschiedlichen Waehrungen.
     */
    override fun isGreaterThan(amount: MonetaryAmount): Boolean {
        return this.compareTo(amount) > 0
    }

    /**
     * Vergleicht zwei Instanzen von [MonetaryAmount]. Nicht signifikante
     * Nachkommastellen werden dabei ignoriert.
     *
     * @param amount Betrag eines `MonetaryAmount`, mit dem verglichen werid
     * @return `true` falls `amount >= this`.
     * @throws MonetaryException bei unterschiedlichen Waehrungen.
     */
    override fun isGreaterThanOrEqualTo(amount: MonetaryAmount): Boolean {
        return this.compareTo(amount) >= 0
    }

    /**
     * Vergleicht zwei Instanzen von [MonetaryAmount]. Nicht signifikante
     * Nachkommastellen werden dabei ignoriert.
     *
     * @param amount Betrag eines `MonetaryAmount`, mit dem verglichen werid
     * @return `true` falls `amount < this`.
     * @throws MonetaryException bei unterschiedlichen Waehrungen.
     */
    override fun isLessThan(amount: MonetaryAmount): Boolean {
        return this.compareTo(amount) < 0
    }

    /**
     * Vergleicht zwei Instanzen von [MonetaryAmount]. Nicht signifikante
     * Nachkommastellen werden dabei ignoriert.
     *
     * @param amount Betrag eines `MonetaryAmount`, mit dem verglichen werid
     * @return `true` falls `amount <= this`.
     * @throws MonetaryException bei unterschiedlichen Waehrungen.
     */
    override fun isLessThanOrEqualTo(amount: MonetaryAmount): Boolean {
        return this.compareTo(amount) <= 0
    }

    /**
     * Zwei Geldbetraege sind nur dann gleich, wenn sie die gleiche Waehrung
     * und den gleichen Betrag haben. Im Unterschied zu [.equals]
     * muessen die Betraege exakt gleich sein.
     *
     * @param other der andere Geldbetrag oder MonetaryAmount
     * @return true, falls Waehrung und Betrag gleich ist
     * @throws MonetaryException wenn die Waehrungen nicht uebereinstimmen
     */
    override fun isEqualTo(other: MonetaryAmount): Boolean {
        checkCurrency(other)
        return isNumberEqualTo(other.number)
    }

    private fun isNumberEqualTo(value: NumberValue): Boolean {
        val otherValue = toBigDecimal(value, context)
        return betrag.compareTo(otherValue) == 0
    }

    /**
     * Testet, ob der Betrag negativ ist.
     *
     * @return true bei negativen Betraegen
     */
    override fun isNegative(): Boolean {
        return betrag.compareTo(BigDecimal.ZERO) < 0
    }

    /**
     * Testet, ob der Betrag negativ oder Null ist.
     *
     * @return false bei positiven Betraegen
     */
    override fun isNegativeOrZero(): Boolean {
        return betrag.compareTo(BigDecimal.ZERO) <= 0
    }

    /**
     * Testet, ob der Betrag positiv ist.
     *
     * @return true bei positiven Betraegen
     */
    override fun isPositive(): Boolean {
        return betrag.compareTo(BigDecimal.ZERO) > 0
    }

    /**
     * Testet, ob der Betrag positiv oder Null ist.
     *
     * @return false bei negativen Betraegen
     */
    override fun isPositiveOrZero(): Boolean {
        return betrag.compareTo(BigDecimal.ZERO) >= 0
    }

    /**
     * Tested, ob der Betrag null ist.
     *
     * @return true, falls Betrag == 0
     */
    override fun isZero(): Boolean {
        return betrag.compareTo(BigDecimal.ZERO) == 0
    }

    /**
     * Returns the signum function of this `MonetaryAmount`.
     *
     * @return -1, 0, or 1 as the value of this `MonetaryAmount` is negative, zero, or
     * positive.
     */
    override fun signum(): Int {
        return toBigDecimal(number).signum()
    }

    /**
     * Liefert die Summe mit dem anderen Gelbetrag zurueck. Vorausgesetzt,
     * beide Betraege haben die gleichen Waehrungen. Einzige Ausnahem davon
     * ist die Addition von 0, da hier die Waehrung egal ist (neutrale
     * Operation).
     *
     * @param other value to be added to this `MonetaryAmount`.
     * @return `this + amount`
     * @throws ArithmeticException if the result exceeds the numeric capabilities
     * of this implementation class, i.e. the [MonetaryContext] cannot be adapted
     * as required.
     */
    override fun add(other: MonetaryAmount?): Geldbetrag {
        if (betrag.compareTo(BigDecimal.ZERO) == 0) {
            return valueOf(other!!)
        }
        val n = toBigDecimal(other!!.number, context)
        if (n.compareTo(BigDecimal.ZERO) == 0) {
            return this
        }
        checkCurrency(other)
        return valueOf(betrag.add(n), currency)
    }

    /**
     * Returns a `MonetaryAmount` whose value is `this -
     * amount`, and whose scale is `max(this.scale(),
     * subtrahend.scale()`.
     *
     * @param amount value to be subtracted from this `MonetaryAmount`.
     * @return `this - amount`
     * @throws ArithmeticException if the result exceeds the numeric capabilities of this implementation class, i.e.
     * the [MonetaryContext] cannot be adapted as required.
     */
    override fun subtract(amount: MonetaryAmount?): Geldbetrag {
        return add(amount!!.negate())
    }

    /**
     * Returns a `MonetaryAmount` whose value is (this
     * multiplicand), and whose scale is `this.scale() +
     * multiplicand.scale()`.
     *
     * @param multiplicand value to be multiplied by this `MonetaryAmount`.
     * @return `this * multiplicand`
     * @throws ArithmeticException if the result exceeds the numeric capabilities of this implementation class, i.e.
     * the [MonetaryContext] cannot be adapted as required.
     */
    override fun multiply(multiplicand: Long): MonetaryAmount {
        return multiply(BigDecimal.valueOf(multiplicand))
    }

    /**
     * Liefert einen GeldBetrag, desseen Wert (this
     * multiplicand) und desse Genauigkeit (scale)
     * `this.scale() + multiplicand.scale()` entspricht.
     *
     * @param multiplicand Multiplikant (wird evtl. gerundet, wenn die
     * Genauigkeit zu hoch ist
     * @return `this * multiplicand`
     * @throws ArithmeticException bei "unendlich" oder "NaN" als Mulitiplikant
     */
    override fun multiply(multiplicand: Double): MonetaryAmount {
        return multiply(toBigDecimal(multiplicand))
    }

    /**
     * Returns a `MonetaryAmount` whose value is (this
     * multiplicand), and whose scale is `this.scale() +
     * multiplicand.scale()`.
     *
     * @param multiplicand value to be multiplied by this `MonetaryAmount`. If the multiplicand's scale exceeds
     * the
     * capabilities of the implementation, it may be rounded implicitly.
     * @return `this * multiplicand`
     * @throws ArithmeticException if the result exceeds the numeric capabilities of this implementation class, i.e.
     * the [MonetaryContext] cannot be adapted as required.
     */
    override fun multiply(multiplicand: Number?): MonetaryAmount {
        val d = toBigDecimal(multiplicand!!, context)
        if (BigDecimal.ONE.compareTo(d) == 0) {
            return this
        }
        val multiplied = betrag.multiply(d)
        return valueOf(multiplied, currency)
    }

    /**
     * Returns a `MonetaryAmount` whose value is `this /
     * divisor`, and whose preferred scale is `this.scale() -
     * divisor.scale()`; if the exact quotient cannot be represented an `ArithmeticException`
     * is thrown.
     *
     * @param divisor value by which this `MonetaryAmount` is to be divided.
     * @return `this / divisor`
     * @throws ArithmeticException if the exact quotient does not have a terminating decimal expansion, or if the
     * result exceeds the numeric capabilities of this implementation class, i.e. the
     * [MonetaryContext] cannot be adapted as required.
     */
    override fun divide(divisor: Long): Geldbetrag {
        return divide(BigDecimal.valueOf(divisor))
    }

    /**
     * Returns a `MonetaryAmount` whose value is `this /
     * divisor`, and whose preferred scale is `this.scale() -
     * divisor.scale()`; if the exact quotient cannot be represented an `ArithmeticException`
     * is thrown.
     *
     * @param divisor value by which this `MonetaryAmount` is to be divided.
     * @return `this / divisor`
     * @throws ArithmeticException if the exact quotient does not have a terminating decimal expansion, or if the
     * result exceeds the numeric capabilities of this implementation class, i.e. the
     * [MonetaryContext] cannot be adapted as required.
     */
    override fun divide(divisor: Double): MonetaryAmount {
        return if (isInfinite(divisor)) {
            valueOf(BigDecimal.ZERO, currency)
        } else divide(BigDecimal.valueOf(divisor))
    }

    /**
     * Returns a `MonetaryAmount` whose value is `this /
     * divisor`, and whose preferred scale is `this.scale() -
     * divisor.scale()`; if the exact quotient cannot be represented an `ArithmeticException`
     * is thrown.
     *
     * @param divisor value by which this `MonetaryAmount` is to be divided.
     * @return `this / divisor`
     * @throws ArithmeticException if the exact quotient does not have a terminating decimal expansion, or if the
     * result exceeds the numeric capabilities of this implementation class, i.e. the
     * [MonetaryContext] cannot be adapted as required.
     */
    override fun divide(divisor: Number?): Geldbetrag {
        val d = toBigDecimal(divisor!!, context)
        return if (BigDecimal.ONE.compareTo(d) == 0) {
            this
        } else valueOf(betrag.setScale(4, RoundingMode.HALF_UP).divide(d, RoundingMode.HALF_UP), currency)
    }

    /**
     * Returns a `MonetaryAmount` whose value is `this % divisor`.
     *
     * The remainder is given by
     * `this.subtract(this.divideToIntegralValue(divisor).multiply(divisor)` . Note that this
     * is not the modulo operation (the result can be negative).
     *
     * @param divisor value by which this `MonetaryAmount` is to be divided.
     * @return `this % divisor`.
     * @throws ArithmeticException if `divisor==0`, or if the result exceeds the numeric capabilities of this
     * implementation class, i.e. the [MonetaryContext] cannot be adapted as
     * required.
     */
    override fun remainder(divisor: Long): Geldbetrag {
        return remainder(BigDecimal.valueOf(divisor))
    }

    /**
     * Liefert eine @code Geldbetrag} zurueck, dessen Wert
     * `this % divisor` entspricht. Der Betrag kann auch
     * negativ sein (im Gegensatz zur Modulo-Operation).
     *
     * @param divisor Wert, durch den der `Geldbetrag` geteilt wird.
     * @return `this % divisor`.
     */
    override fun remainder(divisor: Double): Geldbetrag {
        return if (isInfinite(divisor)) {
            valueOf(0, currency)
        } else remainder(toBigDecimal(divisor))
    }

    /**
     * Returns a `MonetaryAmount` whose value is `this % divisor`.
     *
     * The remainder is given by
     * `this.subtract(this.divideToIntegralValue(divisor).multiply(divisor)` . Note that this
     * is not the modulo operation (the result can be negative).
     *
     * @param divisor value by which this `MonetaryAmount` is to be divided.
     * @return `this % divisor`.
     * @throws ArithmeticException if `divisor==0`, or if the result exceeds the numeric capabilities of this
     * implementation class, i.e. the [MonetaryContext] cannot be adapted as
     * required.
     */
    override fun remainder(divisor: Number?): Geldbetrag {
        return valueOf(betrag.remainder(toBigDecimal(divisor!!, context)), currency)
    }

    /**
     * Liefert ein zwei-elementiges `Geldbatrag`-Array mit dem Ergebnis
     * `divideToIntegralValue` und`remainder`.
     *
     * @param divisor Teiler
     * @return ein zwei-elementiges `Geldbatrag`-Array
     * @throws ArithmeticException bei `divisor==0`
     * @see .divideToIntegralValue
     * @see .remainder
     */
    override fun divideAndRemainder(divisor: Long): Array {
        return divideAndRemainder(BigDecimal.valueOf(divisor))
    }

    /**
     * Liefert ein zwei-elementiges `Geldbatrag`-Array mit dem Ergebnis
     * `divideToIntegralValue` und`remainder`.
     *
     * @param divisor Teiler
     * @return ein zwei-elementiges `Geldbatrag`-Array
     * @throws ArithmeticException bei `divisor==0`
     * @see .divideToIntegralValue
     * @see .remainder
     */
    override fun divideAndRemainder(divisor: Double): Array {
        return if (isInfinite(divisor)) {
            toGeldbetragArray(BigDecimal.ZERO, BigDecimal.ZERO)
        } else divideAndRemainder(BigDecimal.valueOf(divisor))
    }

    /**
     * Liefert ein zwei-elementiges `Geldbetrag`-Array mit dem Ergebnis
     * `divideToIntegralValue` und`remainder`.
     *
     * @param divisor Teiler
     * @return ein zwei-elementiges `Geldbatrag`-Array
     * @throws ArithmeticException bei `divisor==0`
     * @see .divideToIntegralValue
     * @see .remainder
     */
    override fun divideAndRemainder(divisor: Number?): Array {
        val numbers = betrag.divideAndRemainder(toBigDecimal(divisor!!, context))
        return toGeldbetragArray(*numbers)
    }

    private fun toGeldbetragArray(vararg numbers: BigDecimal): Array {
        val betraege = Array(numbers.size) { i -> valueOf(numbers[i], currency) }
        return betraege
    }

    /**
     * Liefert den Integer-Teil des Quotienten `this / divisor`
     * (abgerundet).
     *
     * @param divisor Teiler
     * @return Integer-Teil von `this / divisor`.
     * @throws ArithmeticException falls `divisor==0`
     * @see BigDecimal.divideToIntegralValue
     */
    override fun divideToIntegralValue(divisor: Long): Geldbetrag {
        return divideToIntegralValue(BigDecimal.valueOf(divisor))
    }

    /**
     * Liefert den Integer-Teil des Quotienten `this / divisor`
     * (abgerundet).
     *
     * @param divisor Teiler
     * @return Integer-Teil von `this / divisor`.
     * @throws ArithmeticException falls `divisor==0`
     * @see BigDecimal.divideToIntegralValue
     */
    override fun divideToIntegralValue(divisor: Double): Geldbetrag {
        return divideToIntegralValue(BigDecimal.valueOf(divisor))
    }

    /**
     * Liefert den Integer-Teil des Quotienten `this / divisor`
     * (abgerundet).
     *
     * @param divisor Teiler
     * @return Integer-Teil von `this / divisor`.
     * @throws ArithmeticException falls `divisor==0`
     * @see BigDecimal.divideToIntegralValue
     */
    override fun divideToIntegralValue(divisor: Number): Geldbetrag {
        return valueOf(betrag.divideToIntegralValue(toBigDecimal(divisor, context)), currency)
    }

    /**
     * Liefert eine `Geldbetrag`, dessen Wert (`this` * 10n)
     * entspricht.
     *
     * @param power 10er-Potenz (z.B. 3 fuer 1000)
     * @return berechneter Geldbetrag
     */
    override fun scaleByPowerOfTen(power: Int): Geldbetrag {
        //val scaled = betrag.scaleByPowerOfTen(power).setScale(context.maxScale, context.get(RoundingMode::class.java))
        val scaled = betrag.scaleByPowerOfTen(power)
        return roundedValueOf(scaled, getCurrency(), context)
    }

    /**
     * Returns a `MonetaryAmount` whose value is the absolute value of this
     * `MonetaryAmount`, and whose scale is `this.scale()`.
     *
     * @return `abs(this)`
     */
    override fun abs(): Geldbetrag {
        return if (betrag.compareTo(BigDecimal.ZERO) < 0) {
            negate()
        } else {
            this
        }
    }

    /**
     * Returns a `MonetaryAmount` whose value is `-this`, and whose scale is
     * `this.scale()`.
     *
     * @return `-this`.
     */
    override fun negate(): Geldbetrag {
        return valueOf(betrag.negate(), currency)
    }

    /**
     * Liefert immer eine positiven Geldbetrag.
     *
     * @return positiver Geldbetrag
     * @see BigDecimal.plus
     */
    override fun plus(): Geldbetrag {
        return if (betrag.compareTo(BigDecimal.ZERO) < 0) {
            negate()
        } else {
            this
        }
    }

    /**
     * Liefert einen `Geldbetrag`, der numerisch dem gleichen Wert
     * entspricht, aber ohne Nullen in den Nachkommastellen.
     *
     * @return im Priip der gleiche `Geldbetrag`, nur wird die Zahl
     * intern anders repraesentiert.
     */
    override fun stripTrailingZeros(): Geldbetrag {
        return if (isZero) {
            valueOf(BigDecimal.ZERO, getCurrency())
        } else valueOf(betrag.stripTrailingZeros(), getCurrency(), context)
    }

    /**
     * Vergleicht die Zahlenwerter der beiden Geldbetraege. Aber nur, wenn es
     * sich um die gleiche Waehrung handelt. Ansonsten wird die Waehrung als
     * Vergleichswert herangezogen. Dies fuehrt dazu, dass "CHF 1 < GBP 0" ist.
     * Dies ist leider durch das TCK so vorgegeben :-(
     *
     * @param other der andere Geldbetrag
     * @return 0 bei Gleicheit; negative Zahl, wenn dieser Geldbetrag kleiner
     * als der andere ist; sonst positive Zahl.
     */
    override fun compareTo(other: MonetaryAmount): Int {
        val compare = getCurrency().currencyCode.compareTo(other.currency.currencyCode)
        if (compare == 0) {
            val n = toBigDecimal(other.number)
            return betrag.compareTo(n)
        }
        return compare
    }

    /**
     * Vergleicht nur den Zahlenwert und ignoriert die Waehrung. Diese Methode
     * ist aus Kompatibiltaetsgruenden zur BigDecimal-Klasse enthalten.
     *
     * @param other der andere Betrag
     * @return 0 bei Gleicheit; negative Zahl, wenn die Zahle kleiner als die
     * andere ist, sonst positive Zahl.
     */
    operator fun compareTo(other: Number): Int {
        return this.compareTo(valueOf(other, currency))
    }

    /**
     * Liefert die entsprechende Waehrungseinheit ([CurrencyUnit]).
     *
     * @return die entsprechende [CurrencyUnit], not null.
     */
    override fun getCurrency(): CurrencyUnit {
        return currency
    }

    /**
     * Liefert den entsprechenden [NumberValue].
     *
     * @return der entsprechende [NumberValue], not null.
     */
    override fun getNumber(): NumberValue {
        return Zahlenwert(betrag)
    }

    /**
     * Liefert nur die Zahl als 'double' zurueck. Sie entspricht der
     * gleichnamigen Methode aus [BigDecimal].
     *
     * @return Zahl als 'double'
     * @see BigDecimal.toDouble
     */
    fun doubleValue(): Double {
        return betrag.toDouble()
    }

    /**
     * Hash-Code.
     *
     * @return a hash code value for this object.
     * @see Object.equals
     * @see System.identityHashCode
     */
    override fun hashCode(): Int {
        return betrag.hashCode()
    }

    /**
     * Zwei Betraege sind gleich, wenn Betrag und Waehrung gleich sind. Im
     * Unterschied zu [.isEqualTo] wird hier nur der
     * sichtbare Teil fuer den Vergleich herangezogen, d.h. Rundungsdifferenzen
     * spielen beim Vergleich keine Rolle.
     *
     * @param other der Geldbetrag, mit dem verglichen wird
     * @return true, falls (optisch) gleich
     */
    override fun equals(other: Any?): Boolean {
        if (other !is Geldbetrag) {
            return false
        }
        return if (!hasSameCurrency(other)) {
            false
        } else this.toString() == other.toString()
    }

    private fun hasSameCurrency(other: MonetaryAmount): Boolean {
        return Waehrung.of(getCurrency()) == Waehrung.of(other.currency)
    }

    private fun checkCurrency(other: MonetaryAmount) {
        if (!hasSameCurrency(other)) throw LocalizedMonetaryException("different currencies", this, other)
    }

    /**
     * Liefert das Ergebnis des Operator **vom selben Typ**.
     *
     * @param operator Operator (nicht null)
     * @return ein Objekt desselben Typs (nicht null)
     * @see javax.money.MonetaryAmount.with
     */
    override fun with(operator: MonetaryOperator?): Geldbetrag {
        Objects.requireNonNull(operator)
        return try {
            operator!!.apply(this) as Geldbetrag
        } catch (ex: MonetaryException) {
            throw ex
        } catch (ex: RuntimeException) {
            throw LocalizedMonetaryException("operator failed", operator, ex)
        }
    }

    /**
     * Fraegt einen Wert an.
     *
     * @param query Anrfage (nicht null)
     * @return Ergebnis der Anfrage (kann null sein)
     * @see javax.money.MonetaryAmount.query
     */
    override fun  query(query: MonetaryQuery?): R {
        Objects.requireNonNull(query)
        return try {
            query!!.queryFrom(this)
        } catch (ex: MonetaryException) {
            throw ex
        } catch (ex: RuntimeException) {
            throw LocalizedMonetaryException("query failed", query, ex)
        }
    }

    /**
     * Gibt den Betrag in Kurz-Format aus: ohne Nachkommastellen und mit dem
     * Waehrungssymbol.
     *
     * @return z.B. "$19"
     */
    fun toShortString(): String {
        return getSymbol(currency) + betrag.setScale(0, RoundingMode.HALF_UP)
    }

    /**
     * Um anzuzeigen, dass es ein Geldbtrag ist, wird zusaetzlich noch das
     * Waehrungszeichen (abhaengig von der eingestellten Locale) ausgegeben.
     *
     * @return z.B. "19.00 USD"
     * @see java.math.BigDecimal.toString
     */
    override fun toString(): String {
        return DEFAULT_FORMATTER.format(this)
    }

    /**
     * Hier wird der Geldbetrag mit voller Genauigkeit ausgegeben.
     *
     * @return z.B. "19.0012 USD"
     */
    fun toLongString(): String {
        val formatter = DecimalFormat.getInstance()
        formatter.minimumFractionDigits = context.maxScale
        formatter.minimumFractionDigits = context.maxScale
        return formatter.format(betrag) + " " + currency
    }



    /**
     * Dieser Validator ist fuer die Ueberpruefung von Geldbetraegen vorgesehen.
     *
     * @since 3.0
     */
    class Validator : KSimpleValidator {
        /**
         * Validiert die uebergebene Zahl, ob sie sich als Geldbetrag eignet.
         *
         * @param value als String
         * @return die Zahl zur Weitervarabeitung
         */
        override fun validate(value: String): String {
            return try {
                valueOf(value).toString()
            } catch (ex: IllegalArgumentException) {
                throw InvalidValueException(value, "money_amount", ex)
            }
        }
    }



    companion object {

        private val FACTORY = GeldbetragFactory()
        private val DEFAULT_FORMATTER = GeldbetragFormatter()
        private val NUMBER_VALIDATOR = NumberValidator()
        private val VALIDATOR: KSimpleValidator = Validator()

        /** Da 0-Betraege relativ haeufig vorkommen, spendieren wir dafuer eine eigene Konstante.  */
        @JvmField
        val ZERO = Geldbetrag(BigDecimal.ZERO)

        /** Der minimale Betrag, den wir unterstuetzen.  */
        @JvmField
        val MIN_VALUE = Geldbetrag(BigDecimal.valueOf(Long.MIN_VALUE))

        /** Der maximale Betrag, den wir unterstuetzen.  */
        @JvmField
        val MAX_VALUE = Geldbetrag(BigDecimal.valueOf(Long.MAX_VALUE))

        /** Null-Konstante fuer Initialisierungen.  */
        @JvmField
        val NULL = ZERO

        /**
         * Hierueber kann eine Geldbetrag ueber die Anzahl an Cents angelegt
         * werden.
         *
         * @param cents Cent-Betrag, z.B. 42
         * @return Geldbetrag, z.B. 0.42$
         */
        @JvmStatic
        fun fromCent(cents: Long): Geldbetrag {
            return ofMinor(Waehrung.of("EUR"), cents)
        }

        /**
         * Legt einen Geldbetrag unter Angabe der Unter-Einheit an. So liefert
         * `ofMinor(EUR, 12345)` die Instanz fuer '123,45 EUR' zurueck.
         *
         * Die Methode wurde aus Kompatibitaetsgrunden zur Money-Klasse
         * hinzugefuegt.
         *
         * @param currency Waehrung
         * @param amountMinor Betrag der Unter-Einzeit (z.B. 12345 Cents)
         * @param fractionDigits Anzahl der Nachkommastellen
         * @return Geldbetrag
         * @since 1.0.1
         */
        @JvmOverloads
        @JvmStatic
        fun ofMinor(currency: CurrencyUnit, amountMinor: Long, fractionDigits: Int = currency.defaultFractionDigits): Geldbetrag {
            return of(BigDecimal.valueOf(amountMinor, fractionDigits), currency)
        }

        /**
         * Wandelt den angegebenen MonetaryAmount in einen Geldbetrag um. Um die
         * Anzahl von Objekten gering zu halten, wird nur dann tatsaechlich eine
         * neues Objekt erzeugt, wenn es sich nicht vermeiden laesst.
         *
         * Diese Methode ist identisch mit der entsprechenden valueOf(..)-Methode.
         *
         * @param other the other
         * @return ein Geldbetrag
         */
        @JvmStatic
        fun of(other: String): Geldbetrag {
            return valueOf(other)
        }

        /**
         * Wandelt den angegebenen MonetaryAmount in einen Geldbetrag um. Um die
         * Anzahl von Objekten gering zu halten, wird nur dann tatsaechlich eine
         * neues Objekt erzeugt, wenn es sich nicht vermeiden laesst.
         *
         * In Anlehnung an [BigDecimal] heisst die Methode "valueOf".
         *
         * @param other the other
         * @return ein Geldbetrag
         */
        @JvmStatic
        fun valueOf(other: String): Geldbetrag {
            val b = de.jfachwert.money.Geldbetrag.valueOf(other)
            return Geldbetrag(b.number, b.currency)
        }

        /**
         * Wandelt den angegebenen MonetaryAmount in einen Geldbetrag um. Um die
         * Anzahl von Objekten gering zu halten, wird nur dann tatsaechlich eine
         * neues Objekt erzeugt, wenn es sich nicht vermeiden laesst.
         *
         * Diese Methode ist identisch mit der entsprechenden valueOf(..)-Methode.
         *
         * @param value Wert des andere Geldbetrags
         * @return ein Geldbetrag
         */
        @JvmStatic
        fun of(value: Long): Geldbetrag {
            return valueOf(Geldbetrag(value))
        }

        /**
         * Wandelt den angegebenen MonetaryAmount in einen Geldbetrag um. Um die
         * Anzahl von Objekten gering zu halten, wird nur dann tatsaechlich eine
         * neues Objekt erzeugt, wenn es sich nicht vermeiden laesst.
         *
         * In Anlehnung an [BigDecimal] heisst die Methode "valueOf".
         *
         * @param value Wert des andere Geldbetrags
         * @return ein Geldbetrag
         */
        @JvmStatic
        fun valueOf(value: Long): Geldbetrag {
            return valueOf(Geldbetrag(value))
        }

        /**
         * Wandelt den angegebenen MonetaryAmount in einen Geldbetrag um. Um die
         * Anzahl von Objekten gering zu halten, wird nur dann tatsaechlich eine
         * neues Objekt erzeugt, wenn es sich nicht vermeiden laesst.
         *
         * Diese Methode ist identisch mit der entsprechenden valueOf(..)-Methode.
         *
         * @param value Wert des andere Geldbetrags
         * @return ein Geldbetrag
         */
        @JvmStatic
        fun of(value: Double): Geldbetrag {
            return valueOf(Geldbetrag(value))
        }

        /**
         * Wandelt den angegebenen MonetaryAmount in einen Geldbetrag um. Um die
         * Anzahl von Objekten gering zu halten, wird nur dann tatsaechlich eine
         * neues Objekt erzeugt, wenn es sich nicht vermeiden laesst.
         *
         * In Anlehnung an [BigDecimal] heisst die Methode "valueOf".
         *
         * @param value Wert des andere Geldbetrags
         * @return ein Geldbetrag
         */
        @JvmStatic
        fun valueOf(value: Double): Geldbetrag {
            return valueOf(Geldbetrag(value))
        }

        /**
         * Wandelt den angegebenen MonetaryAmount in einen Geldbetrag um. Um die
         * Anzahl von Objekten gering zu halten, wird nur dann tatsaechlich eine
         * neues Objekt erzeugt, wenn es sich nicht vermeiden laesst.
         *
         * Diese Methode ist identisch mit der entsprechenden valueOf(..)-Methode.
         *
         * @param value Wert des andere Geldbetrags
         * @param currency Waehrung des anderen Geldbetrags
         * @return ein Geldbetrag
         */
        @JvmStatic
        fun of(value: Number, currency: String): Geldbetrag {
            return valueOf(value, currency)
        }

        /**
         * Wandelt den angegebenen MonetaryAmount in einen Geldbetrag um. Um die
         * Anzahl von Objekten gering zu halten, wird nur dann tatsaechlich eine
         * neues Objekt erzeugt, wenn es sich nicht vermeiden laesst.
         *
         * In Anlehnung an [BigDecimal] heisst die Methode "valueOf".
         *
         * @param value Wert des andere Geldbetrags
         * @param currency Waehrung des anderen Geldbetrags
         * @return ein Geldbetrag
         */
        @JvmStatic
        fun valueOf(value: Number, currency: String): Geldbetrag {
            return valueOf(value, toCurrency(currency))
        }

        /**
         * Wandelt den angegebenen MonetaryAmount in einen Geldbetrag um. Um die
         * Anzahl von Objekten gering zu halten, wird nur dann tatsaechlich eine
         * neues Objekt erzeugt, wenn es sich nicht vermeiden laesst.
         *
         * Diese Methode ist identisch mit der entsprechenden valueOf(..)-Methode.
         *
         * @param value Wert des andere Geldbetrags
         * @param currency Waehrung des anderen Geldbetrags
         * @return ein Geldbetrag
         */
        @JvmStatic
        fun of(value: Number, currency: Currency): Geldbetrag {
            return valueOf(value, currency)
        }

        /**
         * Wandelt den angegebenen MonetaryAmount in einen Geldbetrag um. Um die
         * Anzahl von Objekten gering zu halten, wird nur dann tatsaechlich eine
         * neues Objekt erzeugt, wenn es sich nicht vermeiden laesst.
         *
         * In Anlehnung an [BigDecimal] heisst die Methode "valueOf".
         *
         * @param value Wert des andere Geldbetrags
         * @param currency Waehrung des anderen Geldbetrags
         * @return ein Geldbetrag
         */
        @JvmStatic
        fun valueOf(value: Number, currency: Currency): Geldbetrag {
            return valueOf(Geldbetrag(value, currency))
        }

        /**
         * Wandelt den angegebenen MonetaryAmount in einen Geldbetrag um. Um die
         * Anzahl von Objekten gering zu halten, wird nur dann tatsaechlich eine
         * neues Objekt erzeugt, wenn es sich nicht vermeiden laesst.
         *
         * Diese Methode ist identisch mit der entsprechenden valueOf(..)-Methode.
         *
         * @param value Wert des andere Geldbetrags
         * @param currency Waehrung des anderen Geldbetrags
         * @return ein Geldbetrag
         */
        @JvmStatic
        fun of(value: Number, currency: CurrencyUnit): Geldbetrag {
            return valueOf(value, currency)
        }

        /**
         * Wandelt den angegebenen MonetaryAmount in einen Geldbetrag um. Um die
         * Anzahl von Objekten gering zu halten, wird nur dann tatsaechlich eine
         * neues Objekt erzeugt, wenn es sich nicht vermeiden laesst.
         *
         * In Anlehnung an [BigDecimal] heisst die Methode "valueOf".
         *
         * @param value Wert des andere Geldbetrags
         * @param currency Waehrung des anderen Geldbetrags
         * @return ein Geldbetrag
         */
        @JvmStatic
        fun valueOf(value: Number, currency: CurrencyUnit): Geldbetrag {
            return valueOf(Geldbetrag(value, currency))
        }

        /**
         * Wandelt den angegebenen MonetaryAmount in einen Geldbetrag um. Um die
         * Anzahl von Objekten gering zu halten, wird nur dann tatsaechlich eine
         * neues Objekt erzeugt, wenn es sich nicht vermeiden laesst.
         *
         * Diese Methode ist identisch mit der entsprechenden valueOf(..)-Methode.
         *
         * @param value Wert des andere Geldbetrags
         * @param currency Waehrung des anderen Geldbetrags
         * @param monetaryContext Kontext des anderen Geldbetrags
         * @return ein Geldbetrag
         */
        @JvmStatic
        fun of(value: Number, currency: String, monetaryContext: MonetaryContext): Geldbetrag {
            return valueOf(value, currency, monetaryContext)
        }

        /**
         * Wandelt den angegebenen MonetaryAmount in einen Geldbetrag um. Um die
         * Anzahl von Objekten gering zu halten, wird nur dann tatsaechlich eine
         * neues Objekt erzeugt, wenn es sich nicht vermeiden laesst.
         *
         * In Anlehnung an [BigDecimal] heisst die Methode "valueOf".
         *
         * @param value Wert des andere Geldbetrags
         * @param currency Waehrung des anderen Geldbetrags
         * @param monetaryContext Kontext des anderen Geldbetrags
         * @return ein Geldbetrag
         */
        @JvmStatic
        fun valueOf(value: Number, currency: String, monetaryContext: MonetaryContext): Geldbetrag {
            return valueOf(value, Waehrung.of(currency), monetaryContext)
        }

        /**
         * Wandelt den angegebenen MonetaryAmount in einen Geldbetrag um. Um die
         * Anzahl von Objekten gering zu halten, wird nur dann tatsaechlich eine
         * neues Objekt erzeugt, wenn es sich nicht vermeiden laesst.
         *
         * Diese Methode ist identisch mit der entsprechenden valueOf(..)-Methode.
         *
         * @param value Wert des andere Geldbetrags
         * @param currency Waehrung des anderen Geldbetrags
         * @param monetaryContext Kontext des anderen Geldbetrags
         * @return ein Geldbetrag
         */
        @JvmStatic
        fun of(value: Number, currency: CurrencyUnit, monetaryContext: MonetaryContext): Geldbetrag {
            return valueOf(value, currency, monetaryContext)
        }

        /**
         * Wandelt den angegebenen MonetaryAmount in einen Geldbetrag um. Um die
         * Anzahl von Objekten gering zu halten, wird nur dann tatsaechlich eine
         * neues Objekt erzeugt, wenn es sich nicht vermeiden laesst.
         *
         * In Anlehnung an [BigDecimal] heisst die Methode "valueOf".
         *
         * @param value Wert des andere Geldbetrags
         * @param currency Waehrung des anderen Geldbetrags
         * @param monetaryContext Kontext des anderen Geldbetrags
         * @return ein Geldbetrag
         */
        @JvmStatic
        fun valueOf(value: Number, currency: CurrencyUnit, monetaryContext: MonetaryContext): Geldbetrag {
            return valueOf(Geldbetrag(value, currency, monetaryContext))
        }

        /**
         * Im Gegensatz zu valueOf wird hier keine [ArithmeticException]
         * geworfen, wenn Genauigkeit verloren geht. Stattdessen wird der
         * Wert gerundet.
         *
         * @param value Wert des andere Geldbetrags
         * @param currency Waehrung des anderen Geldbetrags
         * @param monetaryContext Kontext des anderen Geldbetrags
         * @return ein Geldbetrag
         * @since 4.0
         */
        @JvmStatic
        fun roundedValueOf(value: Number, currency: CurrencyUnit, monetaryContext: MonetaryContext): Geldbetrag {
            val roundedValue = toBigDecimalRounded(value, monetaryContext)
            return valueOf(Geldbetrag(roundedValue, currency, monetaryContext))
        }

        /**
         * Erzeugt einen Geldbetrag anhand des uebergebenen Textes und mittels
         * des uebergebenen Formatters.
         *
         * @param text z.B. "12,25 EUR"
         * @param formatter Formatter
         * @return Geldbetrag
         */
        @JvmOverloads
        @JvmStatic
        fun parse(text: CharSequence?, formatter: MonetaryAmountFormat = DEFAULT_FORMATTER): Geldbetrag {
            return from(formatter.parse(text))
        }

        /**
         * Wandelt den angegebenen MonetaryAmount in einen Geldbetrag um. Um die
         * Anzahl von Objekten gering zu halten, wird nur dann tatsaechlich eine
         * neues Objekt erzeugt, wenn es sich nicht vermeiden laesst.
         *
         * Diese Methode ist identisch mit der entsprechenden of(..)-Methode und
         * wurde eingefuehrt, um mit der Money-Klasse aus "org.javamoney.moneta"
         * kompatibel zu sein.
         *
         * @param other the other
         * @return ein Geldbetrag
         */
        @JvmStatic
        fun from(other: MonetaryAmount): Geldbetrag {
            return of(other)
        }

        /**
         * Wandelt den angegebenen MonetaryAmount in einen Geldbetrag um. Um die
         * Anzahl von Objekten gering zu halten, wird nur dann tatsaechlich eine
         * neues Objekt erzeugt, wenn es sich nicht vermeiden laesst.
         *
         * Diese Methode ist identisch mit der entsprechenden valueOf(..)-Methode.
         *
         * @param other the other
         * @return ein Geldbetrag
         */
        @JvmStatic
        fun of(other: MonetaryAmount): Geldbetrag {
            return valueOf(other)
        }

        /**
         * Wandelt den angegebenen MonetaryAmount in einen Geldbetrag um. Um die
         * Anzahl von Objekten gering zu halten, wird nur dann tatsaechlich eine
         * neues Objekt erzeugt, wenn es sich nicht vermeiden laesst.
         *
         * In Anlehnung an [BigDecimal] heisst die Methode "valueOf" .
         *
         * @param other the other
         * @return ein Geldbetrag
         */
        @JvmStatic
        fun valueOf(other: MonetaryAmount): Geldbetrag {
            if (other is Geldbetrag) {
                return other
            }
            val value = other.number.numberValue(BigDecimal::class.java)
            return if (value == BigDecimal.ZERO) {
                ZERO
            } else Geldbetrag(value).withCurrency(other.currency)
        }

        /**
         * Validiert die uebergebene Zahl, ob sie sich als Geldbetrag eignet.
         *
         * @param zahl als String
         * @return die Zahl zur Weitervarabeitung
         */
        @JvmStatic
        fun validate(zahl: String): String {
            return VALIDATOR.validate(zahl)
        }

        private fun toBigDecimal(value: NumberValue): BigDecimal {
            return value.numberValue(BigDecimal::class.java)
        }

        private fun toBigDecimal(value: NumberValue, mc: MonetaryContext): BigDecimal {
            val n: Number = toBigDecimal(value)
            return toBigDecimal(n, mc)
        }

        private fun toBigDecimal(value: Double): BigDecimal {
            NUMBER_VALIDATOR.verifyNumber(value)
            return BigDecimal.valueOf(value)
        }

        private fun isInfinite(divisor: Double): Boolean {
            if (divisor == Double.POSITIVE_INFINITY || divisor == Double.NEGATIVE_INFINITY) {
                return true
            }
            if (java.lang.Double.isNaN(divisor)) {
                throw ArithmeticException("invalid number: NaN")
            }
            return false
        }

        private fun toBigDecimal(value: Number, monetaryContext: MonetaryContext): BigDecimal {
            val n: BigDecimal = toBigDecimal(value)
            val rounded: BigDecimal = toBigDecimalRounded(value, monetaryContext)
            if (n.compareTo(rounded) != 0) {
                throw LocalizedArithmeticException(value, "lost_precision")
            }
            return n
        }

        private fun toBigDecimalRounded(value: Number, monetaryContext: MonetaryContext): BigDecimal {
            val n: BigDecimal = toBigDecimal(value)
            var roundingMode = monetaryContext.get(RoundingMode::class.java)
            if (roundingMode == null) {
                roundingMode = RoundingMode.HALF_UP
            }
            val scale = monetaryContext.maxScale
            return if (scale <= 0) {
                n
            } else {
                val scaled = n.setScale(scale, roundingMode)
                scaled
            }
        }

        private fun toBigDecimal(value: Number): BigDecimal {
            if (value is BigDecimal) {
                return value
            } else if (value is Zahlenwert) {
                return value.numberValue(BigDecimal::class.java)
            }
            return BigDecimal.valueOf(value.toDouble())
        }

    }

    /**
     * Erzeugt einen Geldbetrag in der angegebenen Waehrung.
     */
    init {
        this.betrag = toBigDecimal(betrag, context)
        this.currency = currency
        this.context = context
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy