001/**
002 * Copyright (c) 2012, 2014, Credit Suisse (Anatole Tresch), Werner Keil and others by the @author tag.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005 * use this file except in compliance with the License. You may obtain a copy of
006 * the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013 * License for the specific language governing permissions and limitations under
014 * the License.
015 */
016package org.javamoney.moneta.spi;
017
018import javax.money.CurrencyUnit;
019import javax.money.MonetaryAmount;
020import javax.money.MonetaryContext;
021import javax.money.MonetaryException;
022import java.math.BigDecimal;
023import java.math.MathContext;
024import java.math.RoundingMode;
025import java.util.Objects;
026import java.util.Optional;
027
028/**
029 * Platform RI: This utility class simplifies implementing {@link MonetaryAmount},
030 * by providing the common functionality. The different explicitly typed methods
031 * are all reduced to methods using {@link BigDecimal} as input, hereby
032 * performing any conversion to {@link BigDecimal} as needed. Obviously this
033 * takes some time, so implementors that want to avoid this overhead should
034 * implement {@link MonetaryAmount} directly.
035 *
036 * @author Anatole Tresch
037 */
038public final class MoneyUtils {
039
040
041    private MoneyUtils() {
042    }
043
044
045    // Supporting methods
046
047    /**
048     * Creates a {@link BigDecimal} from the given {@link Number} doing the
049     * valid conversion depending the type given.
050     *
051     * @param num the number type
052     * @return the corresponding {@link BigDecimal}
053     */
054    public static BigDecimal getBigDecimal(long num) {
055        return BigDecimal.valueOf(num);
056    }
057
058    /**
059     * Creates a {@link BigDecimal} from the given {@link Number} doing the
060     * valid conversion depending the type given.
061     *
062     * @param num the number type
063     * @return the corresponding {@link BigDecimal}
064     */
065    public static BigDecimal getBigDecimal(double num) {
066        if (num == Double.NaN) {
067            throw new ArithmeticException("Invalid input Double.NaN.");
068        } else if (num == Double.POSITIVE_INFINITY) {
069            throw new ArithmeticException("Invalid input Double.POSITIVE_INFINITY.");
070        } else if (num == Double.NEGATIVE_INFINITY) {
071            throw new ArithmeticException("Invalid input Double.NEGATIVE_INFINITY.");
072        }
073        return new BigDecimal(String.valueOf(num));
074    }
075
076    /**
077     * Creates a {@link BigDecimal} from the given {@link Number} doing the
078     * valid conversion depending the type given.
079     *
080     * @param num the number type
081     * @return the corresponding {@link BigDecimal}
082     */
083    public static BigDecimal getBigDecimal(Number num) {
084        return ConvertBigDecimal.of(num);
085    }
086
087    /**
088     * Creates a {@link BigDecimal} from the given {@link Number} doing the
089     * valid conversion depending the type given, if a {@link MonetaryContext}
090     * is given, it is applied to the number returned.
091     *
092     * @param num the number type
093     * @return the corresponding {@link BigDecimal}
094     */
095    public static BigDecimal getBigDecimal(Number num, MonetaryContext moneyContext) {
096        BigDecimal bd = getBigDecimal(num);
097        if (Objects.nonNull(moneyContext)) {
098            return new BigDecimal(bd.toString(), getMathContext(moneyContext, RoundingMode.HALF_EVEN));
099        }
100        return bd;
101    }
102
103    /**
104     * Evaluates the {@link MathContext} from the given {@link MonetaryContext}.
105     *
106     * @param monetaryContext the {@link MonetaryContext}
107     * @param defaultMode     the default {@link RoundingMode}, to be used if no one is set
108     *                        in {@link MonetaryContext}.
109     * @return the corresponding {@link MathContext}
110     */
111        public static MathContext getMathContext(MonetaryContext monetaryContext, RoundingMode defaultMode) {
112                MathContext ctx = monetaryContext.get(MathContext.class);
113                if (Objects.nonNull(ctx)) {
114                        return ctx;
115                }
116                RoundingMode roundingMode = monetaryContext.get(RoundingMode.class);
117                if (roundingMode == null) {
118                        roundingMode = Optional.ofNullable(defaultMode).orElse(RoundingMode.HALF_EVEN);
119                }
120                return new MathContext(monetaryContext.getPrecision(), roundingMode);
121        }
122
123    /**
124     * Method to check if a currency is compatible with this amount instance.
125     *
126     * @param amount       The monetary amount to be compared to, never null.
127     * @param currencyUnit the currency unit to compare, never null.
128     * @throws MonetaryException If the amount is null, or the amount's {@link CurrencyUnit} is not
129     *                           compatible, meaning has a different value of
130     *                           {@link CurrencyUnit#getCurrencyCode()}).
131     */
132    public static void checkAmountParameter(MonetaryAmount amount, CurrencyUnit currencyUnit) {
133        Objects.requireNonNull(amount, "Amount must not be null.");
134        final CurrencyUnit amountCurrency = amount.getCurrency();
135        if (!(currencyUnit.getCurrencyCode().equals(amountCurrency.getCurrencyCode()))) {
136            throw new MonetaryException("Currency mismatch: " + currencyUnit + '/' + amountCurrency);
137        }
138    }
139
140    /**
141     * Internal method to check for correct number parameter.
142     *
143     * @param number the number to be checked.
144     * @throws IllegalArgumentException If the number is null
145     */
146    public static void checkNumberParameter(Number number) {
147        Objects.requireNonNull(number, "Number is required.");
148    }
149
150}