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

org.javamoney.moneta.Money Maven / Gradle / Ivy

Go to download

JSR 354 provides an API for representing, transporting, and performing comprehensive calculations with Money and Currency. This module implements JSR 354.

There is a newer version: 1.4.4
Show newest version
/**
 * Copyright (c) 2012, 2014, Credit Suisse (Anatole Tresch), Werner Keil and others by the @author tag.
 *
 * 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.
 */
package org.javamoney.moneta;

import java.io.Serializable;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.util.Objects;

import javax.money.CurrencyUnit;
import javax.money.MonetaryAmount;
import javax.money.MonetaryAmountFactory;
import javax.money.MonetaryContext;
import javax.money.Monetary;
import javax.money.MonetaryException;
import javax.money.MonetaryOperator;
import javax.money.MonetaryQuery;
import javax.money.NumberValue;
import javax.money.UnknownCurrencyException;
import javax.money.format.MonetaryAmountFormat;

import org.javamoney.moneta.ToStringMonetaryAmountFormat.ToStringMonetaryAmountFormatStyle;
import org.javamoney.moneta.internal.MoneyAmountBuilder;
import org.javamoney.moneta.spi.DefaultNumberValue;
import org.javamoney.moneta.spi.MoneyUtils;

/**
 * Default immutable implementation of {@link MonetaryAmount} based
 * on {@link BigDecimal} as numeric representation.
 * 

* As required by {@link MonetaryAmount} this class is final, thread-safe, * immutable and serializable. *

* This class can be configured with an arbitrary {@link MonetaryContext}. The * default {@link MonetaryContext} used models by default the same settings as * {@link MathContext#DECIMAL64} . This default {@link MonetaryContext} can also * be reconfigured by adding a file {@code /javamoney.properties} to the * classpath, with the following content: *

*
 * # Default MathContext for Money
 * #-------------------------------
 * # Custom MonetaryContext, overrides default entries from
 * # org.javamoney.moneta.Money.monetaryContext
 * # RoundingMode hereby is optional (default = HALF_EVEN)
 * org.javamoney.moneta.Money.defaults.precision=256
 * org.javamoney.moneta.Money.defaults.roundingMode=HALF_EVEN
 * 
* * @author Anatole Tresch * @author Werner Keil * @version 0.7 */ public final class Money implements MonetaryAmount, Comparable, Serializable { /** * serialVersionUID. */ private static final long serialVersionUID = -7565813772046251748L; /** * The default {@link MonetaryContext} applied, if not set explicitly on * creation. */ public static final MonetaryContext DEFAULT_MONETARY_CONTEXT = new DefaultMonetaryContextFactory().getContext(); /** * The currency of this amount. */ private final CurrencyUnit currency; /** * the {@link MonetaryContext} used by this instance, e.g. on division. */ private final MonetaryContext monetaryContext; /** * The numeric part of this amount. */ private final BigDecimal number; /** * Creates a new instance os {@link Money}. * * @param currency the currency, not null. * @param number the amount, not null. * @throws ArithmeticException If the number exceeds the capabilities of the default * {@link MonetaryContext}. */ private Money(BigDecimal number, CurrencyUnit currency) { this(number, currency, null); } /** * Creates a new instance of {@link Money}. * * @param currency the currency, not {@code null}. * @param number the amount, not {@code null}. * @param monetaryContext the {@link MonetaryContext}, if {@code null}, the default is * used. * @throws ArithmeticException If the number exceeds the capabilities of the * {@link MonetaryContext} used. */ private Money(BigDecimal number, CurrencyUnit currency, MonetaryContext monetaryContext) { Objects.requireNonNull(currency, "Currency is required."); this.currency = currency; if (Objects.nonNull(monetaryContext)) { this.monetaryContext = monetaryContext; } else { this.monetaryContext = DEFAULT_MONETARY_CONTEXT; } Objects.requireNonNull(number, "Number is required."); this.number = MoneyUtils.getBigDecimal(number, monetaryContext); } /** * Returns the amount’s currency, modelled as {@link CurrencyUnit}. * Implementations may co-variantly change the return type to a more * specific implementation of {@link CurrencyUnit} if desired. * * @return the currency, never {@code null} * @see javax.money.MonetaryAmount#getCurrency() */ @Override public CurrencyUnit getCurrency() { return currency; } /** * Access the {@link MonetaryContext} used by this instance. * * @return the {@link MonetaryContext} used, never null. * @see javax.money.MonetaryAmount#getContext() */ @Override public MonetaryContext getContext() { return monetaryContext; } /** * Gets the number representation of the numeric value of this item. * * @return The {@link Number} representation matching best. */ @Override public NumberValue getNumber() { return new DefaultNumberValue(number); } /** * Method that returns BigDecimal.ZERO, if {@link #isZero()}, and * {@link #number #stripTrailingZeros()} in all other cases. * * @return the stripped number value. */ public BigDecimal getNumberStripped() { if (isZero()) { return BigDecimal.ZERO; } return this.number.stripTrailingZeros(); } /* * @see java.lang.Comparable#compareTo(java.lang.Object) */ @Override public int compareTo(MonetaryAmount o) { Objects.requireNonNull(o); int compare = getCurrency().getCurrencyCode().compareTo(o.getCurrency().getCurrencyCode()); if (compare == 0) { compare = this.number.compareTo(Money.from(o).number); } return compare; } // Arithmetic Operations /* * (non-Javadoc) * * @see javax.money.MonetaryAmount#abs() */ @Override public Money abs() { if (this.isPositiveOrZero()) { return this; } return negate(); } /* * (non-Javadoc) * * @see javax.money.MonetaryAmount#divide(javax.money.MonetaryAmount) */ @Override public Money divide(long divisor) { if (divisor == 1L) { return this; } return divide(BigDecimal.valueOf(divisor)); } /* * (non-Javadoc) * * @see javax.money.MonetaryAmount#divide(javax.money.MonetaryAmount) */ @Override public Money divide(double divisor) { if (isInfinityAndNotNaN(divisor)) { return Money.of(0, getCurrency()); } if (divisor == 1.0) { return this; } return divide(new BigDecimal(String.valueOf(divisor))); } /* * (non-Javadoc) * * @see * javax.money.MonetaryAmount#divideAndRemainder(javax.money.MonetaryAmount) */ @Override public Money[] divideAndRemainder(long divisor) { if (divisor == 1L) { return new Money[]{this, Money.of(0L, getCurrency())}; } return divideAndRemainder(BigDecimal.valueOf(divisor)); } /* * (non-Javadoc) * * @see * javax.money.MonetaryAmount#divideAndRemainder(javax.money.MonetaryAmount) */ @Override public Money[] divideAndRemainder(double divisor) { if (isInfinityAndNotNaN(divisor)) { Money zero = Money.of(0, getCurrency()); return new Money[]{zero, zero}; } return divideAndRemainder(new BigDecimal(String.valueOf(divisor))); } /* * (non-Javadoc) * * @see javax.money.MonetaryAmount#multiply(Number) */ @Override public Money multiply(long multiplicand) { if (multiplicand == 1L) { return this; } return multiply(BigDecimal.valueOf(multiplicand)); } /* * (non-Javadoc) * * @see javax.money.MonetaryAmount#multiply(Number) */ @Override public Money multiply(double multiplicand) { checkNoInfinityOrNaN(multiplicand); if (multiplicand == 1.0d) { return this; } return multiply(new BigDecimal(String.valueOf(multiplicand))); } /* * (non-Javadoc) * * @see javax.money.MonetaryAmount#remainder(Number) */ @Override public Money remainder(long divisor) { if (divisor == 1L) { return this; } return remainder(BigDecimal.valueOf(divisor)); } /* * (non-Javadoc) * * @see javax.money.MonetaryAmount#remainder(Number) */ @Override public Money remainder(double divisor) { if (isInfinityAndNotNaN(divisor)) { return Money.of(0, getCurrency()); } return remainder(new BigDecimal(String.valueOf(divisor))); } /* * (non-Javadoc) * * @see javax.money.MonetaryAmount#isZero() */ @Override public boolean isZero() { return signum() == 0; } /* * (non-Javadoc) * * @see javax.money.MonetaryAmount#isPositive() */ @Override public boolean isPositive() { return signum() == 1; } /* * (non-Javadoc) * * @see javax.money.MonetaryAmount#isPositiveOrZero() */ @Override public boolean isPositiveOrZero() { return signum() >= 0; } /* * (non-Javadoc) * * @see javax.money.MonetaryAmount#isNegative() */ @Override public boolean isNegative() { return signum() == -1; } /* * (non-Javadoc) * * @see javax.money.MonetaryAmount#isNegativeOrZero() */ @Override public boolean isNegativeOrZero() { return signum() <= 0; } /* * }(non-Javadoc) * * @see javax.money.MonetaryAmount#query(javax.money.MonetaryQuery) */ @Override public R query(MonetaryQuery query) { Objects.requireNonNull(query); try { return query.queryFrom(this); } catch (MonetaryException e) { throw e; } catch (Exception e) { throw new MonetaryException("Query failed: " + query, e); } } /* * (non-Javadoc) * * @see javax.money.MonetaryAmount#with(javax.money.MonetaryOperator) */ @Override public Money with(MonetaryOperator operator) { Objects.requireNonNull(operator); try { return Money.class.cast(operator.apply(this)); } catch (MonetaryException e) { throw e; } catch (Exception e) { throw new MonetaryException("Operator failed: " + operator, e); } } /* * (non-Javadoc) * * @see javax.money.MonetaryAmount#add(javax.money.MonetaryAmount) */ @Override public Money add(MonetaryAmount amount) { MoneyUtils.checkAmountParameter(amount, this.currency); if (amount.isZero()) { return this; } return new Money(this.number.add(amount.getNumber().numberValue(BigDecimal.class)), getCurrency()); } /* * (non-Javadoc) * * @see javax.money.MonetaryAmount#divide(java.lang.Number) */ @Override public Money divide(Number divisor) { if (isInfinityAndNotNaN(divisor)) { return Money.of(0, getCurrency()); } BigDecimal divisorBD = MoneyUtils.getBigDecimal(divisor); if (divisorBD.equals(BigDecimal.ONE)) { return this; } BigDecimal dec = this.number.divide(divisorBD, MoneyUtils.getMathContext(getContext(), RoundingMode.HALF_EVEN)); return new Money(dec, getCurrency()); } @Override public Money[] divideAndRemainder(Number divisor) { if (isInfinityAndNotNaN(divisor)) { Money zero = Money.of(0, getCurrency()); return new Money[]{zero, zero}; } BigDecimal divisorBD = MoneyUtils.getBigDecimal(divisor); if (divisorBD.equals(BigDecimal.ONE)) { return new Money[]{this, new Money(BigDecimal.ZERO, getCurrency())}; } BigDecimal[] dec = this.number.divideAndRemainder(divisorBD); return new Money[]{new Money(dec[0], getCurrency()), new Money(dec[1], getCurrency())}; } /* * (non-Javadoc) * * @see * org.javamoney.moneta.AbstractMoney#divideToIntegralValue(java.lang.Number * ) */ @Override public Money divideToIntegralValue(long divisor) { return divideToIntegralValue(MoneyUtils.getBigDecimal(divisor)); } @Override public Money divideToIntegralValue(double divisor) { if (isInfinityAndNotNaN(divisor)) { return Money.of(0, getCurrency()); } return divideToIntegralValue(MoneyUtils.getBigDecimal(divisor)); } @Override public Money divideToIntegralValue(Number divisor) { if (isInfinityAndNotNaN(divisor)) { return Money.of(0, getCurrency()); } BigDecimal divisorBD = MoneyUtils.getBigDecimal(divisor); BigDecimal dec = this.number.divideToIntegralValue(divisorBD); return new Money(dec, getCurrency()); } /* * (non-Javadoc) * * @see org.javamoney.moneta.AbstractMoney#multiply(java.lang.Number) */ @Override public Money multiply(Number multiplicand) { checkNoInfinityOrNaN(multiplicand); BigDecimal multiplicandBD = MoneyUtils.getBigDecimal(multiplicand); if (multiplicandBD.equals(BigDecimal.ONE)) { return this; } BigDecimal dec = this.number.multiply(multiplicandBD); return new Money(dec, getCurrency()); } /* * (non-Javadoc) * * @see javax.money.MonetaryAmount#negate() */ @Override public Money negate() { return new Money(this.number.negate(), getCurrency()); } /* * (non-Javadoc) * * @see javax.money.MonetaryAmount#plus() */ @Override public Money plus() { return new Money(this.number.plus(), getCurrency()); } /* * (non-Javadoc) * * @see javax.money.MonetaryAmount#subtract(javax.money.MonetaryAmount) */ @Override public Money subtract(MonetaryAmount amount) { MoneyUtils.checkAmountParameter(amount, this.currency); if (amount.isZero()) { return this; } return new Money(this.number.subtract(amount.getNumber().numberValue(BigDecimal.class)), getCurrency()); } /* * (non-Javadoc) * * @see javax.money.MonetaryAmount#stripTrailingZeros() */ @Override public Money stripTrailingZeros() { if (isZero()) { return new Money(BigDecimal.ZERO, getCurrency()); } return new Money(this.number.stripTrailingZeros(), getCurrency()); } /* * (non-Javadoc) * * @see org.javamoney.moneta.AbstractMoney#remainder(java.math.BigDecimal) */ @Override public Money remainder(Number divisor) { if (isInfinityAndNotNaN(divisor)) { return new Money(BigDecimal.ZERO, getCurrency()); } BigDecimal bd = MoneyUtils.getBigDecimal(divisor); return new Money(this.number.remainder(bd), getCurrency()); } /* * (non-Javadoc) * * @see javax.money.MonetaryAmount#scaleByPowerOfTen(int) */ @Override public Money scaleByPowerOfTen(int power) { return new Money(this.number.scaleByPowerOfTen(power), getCurrency()); } /* * (non-Javadoc) * * @see javax.money.MonetaryAmount#signum() */ @Override public int signum() { return this.number.signum(); } /* * (non-Javadoc) * * @see javax.money.MonetaryAmount#isLessThan(javax.money.MonetaryAmount) */ @Override public boolean isLessThan(MonetaryAmount amount) { MoneyUtils.checkAmountParameter(amount, this.currency); return number.stripTrailingZeros() .compareTo(amount.getNumber().numberValue(BigDecimal.class).stripTrailingZeros()) < 0; } /* * (non-Javadoc) * * @see * javax.money.MonetaryAmount#isLessThanOrEqualTo(javax.money.MonetaryAmount * ) */ @Override public boolean isLessThanOrEqualTo(MonetaryAmount amount) { MoneyUtils.checkAmountParameter(amount, this.currency); return number.stripTrailingZeros() .compareTo(amount.getNumber().numberValue(BigDecimal.class).stripTrailingZeros()) <= 0; } /* * (non-Javadoc) * * @see javax.money.MonetaryAmount#isGreaterThan(javax.money.MonetaryAmount) */ @Override public boolean isGreaterThan(MonetaryAmount amount) { MoneyUtils.checkAmountParameter(amount, this.currency); return number.stripTrailingZeros() .compareTo(amount.getNumber().numberValue(BigDecimal.class).stripTrailingZeros()) > 0; } /* * (non-Javadoc) * * @see * javax.money.MonetaryAmount#isGreaterThanOrEqualTo(javax.money.MonetaryAmount * ) #see */ @Override public boolean isGreaterThanOrEqualTo(MonetaryAmount amount) { MoneyUtils.checkAmountParameter(amount, this.currency); return number.stripTrailingZeros() .compareTo(amount.getNumber().numberValue(BigDecimal.class).stripTrailingZeros()) >= 0; } /* * (non-Javadoc) * * @see javax.money.MonetaryAmount#isEqualTo(javax.money.MonetaryAmount) */ @Override public boolean isEqualTo(MonetaryAmount amount) { MoneyUtils.checkAmountParameter(amount, this.currency); return number.stripTrailingZeros() .compareTo(amount.getNumber().numberValue(BigDecimal.class).stripTrailingZeros()) == 0; } /* * (non-Javadoc) * * @see javax.money.MonetaryAmount#getFactory() */ @Override public MonetaryAmountFactory getFactory() { return new MoneyAmountBuilder().setAmount(this); } /* * (non-Javadoc) * * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj instanceof Money) { Money other = (Money) obj; return Objects.equals(getCurrency(), other.getCurrency()) && Objects.equals(getNumberStripped(), other.getNumberStripped()); } return false; } /* * (non-Javadoc) * * @see java.lang.Object#toString() */ @Override public String toString() { return getCurrency().getCurrencyCode() + ' ' + number.toString(); } /* * (non-Javadoc) * * @see java.lang.Object#hashCode() */ @Override public int hashCode() { return Objects.hash(getCurrency(), getNumberStripped()); } /** * Creates a new instance of {@link Money}, using the default * {@link MonetaryContext}. * * @param number numeric value, not {@code null}. * @param currency currency unit, not {@code null}. * @return a {@code Money} combining the numeric value and currency unit. * @throws ArithmeticException If the number exceeds the capabilities of the default * {@link MonetaryContext} used. */ public static Money of(BigDecimal number, CurrencyUnit currency) { return new Money(number, currency); } /** * Creates a new instance of {@link Money}, using an explicit * {@link MonetaryContext}. * * @param number numeric value, not {@code null}. * @param currency currency unit, not {@code null}. * @param monetaryContext the {@link MonetaryContext} to be used, if {@code null} the * default {@link MonetaryContext} is used. * @return a {@code Money} instance based on the monetary context with the * given numeric value, currency unit. * @throws ArithmeticException If the number exceeds the capabilities of the * {@link MonetaryContext} used. */ public static Money of(BigDecimal number, CurrencyUnit currency, MonetaryContext monetaryContext) { return new Money(number, currency, monetaryContext); } /** * Creates a new instance of {@link Money}, using the default * {@link MonetaryContext}. * * @param currency The target currency, not null. * @param number The numeric part, not null. * @return A new instance of {@link Money}. * @throws ArithmeticException If the number exceeds the capabilities of the default * {@link MonetaryContext} used. */ public static Money of(Number number, CurrencyUnit currency) { return new Money(MoneyUtils.getBigDecimal(number), currency); } /** * Creates a new instance of {@link Money}, using an explicit * {@link MonetaryContext}. * * @param currency The target currency, not null. * @param number The numeric part, not null. * @param monetaryContext the {@link MonetaryContext} to be used, if {@code null} the * default {@link MonetaryContext} is used. * @return A new instance of {@link Money}. * @throws ArithmeticException If the number exceeds the capabilities of the * {@link MonetaryContext} used. */ public static Money of(Number number, CurrencyUnit currency, MonetaryContext monetaryContext) { return new Money(MoneyUtils.getBigDecimal(number), currency, monetaryContext); } /** * Static factory method for creating a new instance of {@link Money}. * * @param currencyCode The target currency as ISO currency code. * @param number The numeric part, not null. * @return A new instance of {@link Money}. */ public static Money of(Number number, String currencyCode) { return new Money(MoneyUtils.getBigDecimal(number), Monetary.getCurrency(currencyCode)); } /** * Static factory method for creating a new instance of {@link Money}. * * @param currencyCode The target currency as ISO currency code. * @param number The numeric part, not null. * @return A new instance of {@link Money}. */ public static Money of(BigDecimal number, String currencyCode) { return new Money(number, Monetary.getCurrency(currencyCode)); } /** * Static factory method for creating a new instance of {@link Money}. * * @param currencyCode The target currency as ISO currency code. * @param number The numeric part, not null. * @param monetaryContext the {@link MonetaryContext} to be used, if {@code null} the * default {@link MonetaryContext} is used. * @return A new instance of {@link Money}. */ public static Money of(Number number, String currencyCode, MonetaryContext monetaryContext) { return new Money(MoneyUtils.getBigDecimal(number), Monetary.getCurrency(currencyCode), monetaryContext); } /** * Static factory method for creating a new instance of {@link Money}. * * @param currencyCode The target currency as ISO currency code. * @param number The numeric part, not null. * @param monetaryContext the {@link MonetaryContext} to be used, if {@code null} the * default {@link MonetaryContext} is used. * @return A new instance of {@link Money}. */ public static Money of(BigDecimal number, String currencyCode, MonetaryContext monetaryContext) { return new Money(number, Monetary.getCurrency(currencyCode), monetaryContext); } /** * Converts (if necessary) the given {@link MonetaryAmount} to a * {@link Money} instance. The {@link MonetaryContext} will be adapted as * necessary, if the precision of the given amount exceeds the capabilities * of the default {@link MonetaryContext}. * * @param amt the amount to be converted * @return an according Money instance. */ public static Money from(MonetaryAmount amt) { if (amt.getClass() == Money.class) { return (Money) amt; } return Money.of(amt.getNumber().numberValue(BigDecimal.class), amt.getCurrency(), amt.getContext()); } /** * Obtains an instance of Money from a text string such as 'EUR 25.25'. * * @param text the text to parse not null * @return Money instance * @throws NullPointerException * @throws NumberFormatException * @throws UnknownCurrencyException */ public static Money parse(CharSequence text) { return parse(text, DEFAULT_FORMATTER); } /** * Obtains an instance of Money from a text using specific formatter. * * @param text the text to parse not null * @param formatter the formatter to use not null * @return Money instance */ public static Money parse(CharSequence text, MonetaryAmountFormat formatter) { return from(formatter.parse(text)); } private static final ToStringMonetaryAmountFormat DEFAULT_FORMATTER = ToStringMonetaryAmountFormat .of(ToStringMonetaryAmountFormatStyle.MONEY); public static void checkNoInfinityOrNaN(Number number) { if (Double.class == number.getClass() || Float.class == number.getClass()) { double dValue = number.doubleValue(); if (Double.isNaN(dValue)) { throw new ArithmeticException("Not a valid input: NaN."); } else if (Double.isInfinite(dValue)) { throw new ArithmeticException("Not a valid input: INFINITY: " + dValue); } } } public static boolean isInfinityAndNotNaN(Number number) { if (Double.class == number.getClass() || Float.class == number.getClass()) { double dValue = number.doubleValue(); if (Double.isNaN(dValue)) { throw new ArithmeticException("Not a valid input: NaN."); } else if (Double.isInfinite(dValue)) { return true; } } return false; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy