Money.java

package com.tradecloud.domain.model;

import com.tradecloud.domain.base.utils.MathUtils;
import com.tradecloud.domain.wrapper.CurrencyXmlAdapter;

import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import java.io.Serializable;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Currency;

@Embeddable
@Access(AccessType.FIELD)
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "Money")
public class Money implements Comparable<Money>, Serializable {

    private static final long serialVersionUID = 1L;

    @XmlJavaTypeAdapter(CurrencyXmlAdapter.class)
    private Currency currency;

    @Column(precision = 19, scale = 15)
    private BigDecimal value;

    private static final RoundingMode DEFAULT_ROUNDING_MODE = RoundingMode.HALF_UP;

    public static Money zeroValue(Currency currency) {
        return new Money(BigDecimal.ZERO, currency);
    }

    public static Money valueOf(BigDecimal results, String currency) {
        return new Money(results, Currency.getInstance(currency));
    }

    public Money zeroValue() {
        return zeroValue(currency);
        // return new Money(BigDecimal.ZERO, currency);
    }

    private static void validateAmountLength(String value) {
        if (value != null && value.length() > 30) {
            throw new IllegalArgumentException("Invalid value, amount too big");
        }
    }

    // For JPA only
    protected Money() {
    }

    //copy constructor
    public Money(Money toCopy) {
        this.value = toCopy.getValue();
        this.currency = toCopy.getCurrency();
    }

    public Money(Currency currency) {
        this(BigDecimal.ZERO, currency);
    }

    public Money(String currencyStr) {
        this(BigDecimal.ZERO, Currency.getInstance(currencyStr));
    }

    public Money(com.tradecloud.domain.common.Currency currency) {
        this(Currency.getInstance(currency.getCode()));
    }

    public Money(double value, String currencyStr) {
        this(new BigDecimal(value), Currency.getInstance(currencyStr));
    }

    public Money(BigDecimal value, com.tradecloud.domain.common.Currency currency) {
        this(value, Currency.getInstance(currency.getCode()));
    }

    public Money(BigDecimal value, Currency currency) {
        this(value, currency, null);
    }

    private Money(BigDecimal value, Currency currency, Integer overrideDefaultScale) {
        this.value = setValueScale(value, overrideDefaultScale);
        this.currency = currency;
    }

    public boolean isZero() {
        return value.compareTo(BigDecimal.ZERO) == 0;
    }

    public boolean isNegative() {
        return value.compareTo(BigDecimal.ZERO) == -1;
    }

    public boolean isPositive() {
        return value.compareTo(BigDecimal.ZERO) == 1;
    }

    public static Money valueOf(String value, Currency currency) {
        BigDecimal valueBigD = parseNumber(value);
        return new Money(valueBigD, currency);
    }

    public static Money valueOf(BigDecimal value, Currency currency) {

        return new Money(value, currency);
    }

    public static Money valueOf(String value, com.tradecloud.domain.common.Currency currency) {
        BigDecimal valueBigD = parseNumber(value);
        return new Money(valueBigD, currency);
    }

    private static BigDecimal parseNumber(String value) {
        validateAmountLength(value);
        try {
            return new BigDecimal(removeCommasFrom(value));
        } catch (NumberFormatException exc) {
            throw new RuntimeException("Invalid value, numeric expected [" + value + "]");
        }
    }

    private static String removeCommasFrom(String value) {
        return value.replaceAll(",", "");
    }

    private static BigDecimal setValueScale(BigDecimal value, Integer overrideDefaultScale) {
        int scaleValue = 6;
        if (overrideDefaultScale != null)
            scaleValue = overrideDefaultScale;

        return value.setScale(scaleValue, DEFAULT_ROUNDING_MODE);
    }

    public static BigDecimal parseValue(String amountValue) {
        return setValueScale(parseNumber(amountValue), null);
    }

    public Money divide(Money money) {
        assertThatCurrenciesAreTheSame(money);
        return new Money(this.value.divide(money.getValue(), 15, DEFAULT_ROUNDING_MODE), this.currency);
    }

    public Money divide(int money) {
        BigDecimal divisor = new BigDecimal(money);
        divisor = divisor.setScale(this.getValue().scale());
        return new Money(this.value.divide(divisor, 15, DEFAULT_ROUNDING_MODE), this.currency);
    }

    public Money divideRate(Money money) {
        if (this.currency.equals(money.getCurrency())) {
            throw new IllegalArgumentException("Currencies should not be the same when dividing an amount by a rate : "
                    + this.getCurrency().getCurrencyCode() + " and " + money.getCurrency().getCurrencyCode());
        }
        return new Money(this.value.divide(money.getValue(), 15, DEFAULT_ROUNDING_MODE), money.getCurrency(),15);
    }

    public static boolean areNumbersCloselyEqual(BigDecimal lhsNumber, BigDecimal rhsNumber, int precision) {
        return MathUtils.areNumbersCloselyEqual(lhsNumber, rhsNumber, precision);
    }

    public static boolean areNumbersCloselyEqual(double lhsNumber, double rhsNumber, int precision) {
        return MathUtils.areNumbersCloselyEqual(lhsNumber, rhsNumber, precision);
    }

    public BigDecimal getValue() {
        return value;
    }

    public void setValue(BigDecimal value) {
        this.value = value;
    }

    public String getPrettyValue() {
        return getValue().setScale(2, DEFAULT_ROUNDING_MODE).toString();
    }

    public Currency getCurrency() {
        return currency;
    }

    public Money add(Money money) {
        assertThatCurrenciesAreTheSame(money);
        return new Money(this.value.add(money.getValue()), this.currency);
    }

    public Money subtract(Money money) {
        assertThatCurrenciesAreTheSame(money);
        return new Money(this.value.subtract(money.getValue()), this.currency);
    }

    public Money multiply(Money money) {
        assertThatCurrenciesAreTheSame(money);
        return new Money(this.value.multiply(money.getValue()), this.currency);
    }

    public Money multiply(Money money, int overrideDefaultScale) {
        assertThatCurrenciesAreTheSame(money);
        return new Money(this.value.multiply(money.getValue()), this.currency, overrideDefaultScale);
    }

    public Money multiply(BigDecimal value) {
        return new Money(this.value.multiply(value), currency);
    }

    public Money multiplyRate(Money money) {
        if (this.currency.equals(money.getCurrency())) {
            throw new IllegalArgumentException("Currencies should not be the same when multiplying an amount by a rate : "
                    + this.getCurrency().getCurrencyCode() + " and " + money.getCurrency().getCurrencyCode());
        }
        return new Money(this.value.multiply(money.getValue()), money.getCurrency());
    }

    public Money negate() {
        return new Money(value.multiply(new BigDecimal("-1")), currency);
    }

    private void assertThatCurrenciesAreTheSame(Money money) throws IllegalArgumentException {
        if (!this.currency.equals(money.getCurrency())) {
            throw new IllegalArgumentException("Currency mismatch between " + this.getCurrency().getCurrencyCode() + " and "
                    + money.getCurrency().getCurrencyCode());
        }
    }

    public boolean lessThan(Money rhs) {
        return this.getValue().compareTo(rhs.getValue()) < 0 && this.getCurrency().equals(rhs.getCurrency());
    }

    public boolean lessThanOrEqual(Money rhs) {
        return (this.getValue().compareTo(rhs.getValue()) < 0 || equals(rhs)) && this.getCurrency().equals(rhs.getCurrency());
    }

    public boolean greaterThan(Money rhs) {
        return rhs.lessThan(this);
    }

    public boolean greaterThanOrEqual(Money rhs) {
        return (this.getValue().compareTo(rhs.getValue()) > 0 || equals(rhs)) && this.getCurrency().equals(rhs.getCurrency());
    }

    @Override
    public Money clone() {
        return new Money(value, currency);
    }

    @Override
    public boolean equals(Object obj) {
        // TODO. Use EqualsBuilder
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final Money other = (Money) obj;
        if (!this.currency.equals(other.currency)) {
            return false;
        }
        if (!(this.value.compareTo(other.value) == 0)) {
            return false;
        }
        return true;
    }

    @Override
    public int hashCode() {
        // TODO. Use HashcodeBuilder
        int hash = 3;
        hash = 71 * hash + (this.currency != null ? this.currency.hashCode() : 0);
        hash = 71 * hash + (this.value != null ? this.value.hashCode() : 0);
        return hash;
    }

    @Override
    public int compareTo(Money m) {
        assertThatCurrenciesAreTheSame(m);
        return this.value.compareTo(m.getValue());
    }

    @Override
    public String toString() {
        return currency.getCurrencyCode() + value.toPlainString();
    }

    public Money abs() {
        return new Money(value.abs(), currency);
    }
}