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;
026
027/**
028 * Platform RI: This utility class simplifies implementing {@link MonetaryAmount},
029 * by providing the common functionality. The different explicitly typed methods
030 * are all reduced to methods using {@link BigDecimal} as input, hereby
031 * performing any conversion to {@link BigDecimal} as needed. Obviously this
032 * takes some time, so implementors that want to avoid this overhead should
033 * implement {@link MonetaryAmount} directly.
034 *
035 * @author Anatole Tresch
036 */
037public final class MoneyUtils {
038
039
040    private MoneyUtils() {
041    }
042
043
044    // Supporting methods
045
046    /**
047     * Creates a {@link BigDecimal} from the given {@link Number} doing the
048     * valid conversion depending the type given.
049     *
050     * @param num the number type
051     * @return the corresponding {@link BigDecimal}
052     */
053    public static BigDecimal getBigDecimal(long num) {
054        return BigDecimal.valueOf(num);
055    }
056
057    /**
058     * Creates a {@link BigDecimal} from the given {@link Number} doing the
059     * valid conversion depending the type given.
060     *
061     * @param num the number type
062     * @return the corresponding {@link BigDecimal}
063     */
064    public static BigDecimal getBigDecimal(double num) {
065        if (num == Double.NaN) {
066            throw new ArithmeticException("Invalid input Double.NaN.");
067        } else if (num == Double.POSITIVE_INFINITY) {
068            throw new ArithmeticException("Invalid input Double.POSITIVE_INFINITY.");
069        } else if (num == Double.NEGATIVE_INFINITY) {
070            throw new ArithmeticException("Invalid input Double.NEGATIVE_INFINITY.");
071        }
072        return new BigDecimal(String.valueOf(num));
073    }
074
075    /**
076     * Creates a {@link BigDecimal} from the given {@link Number} doing the
077     * valid conversion depending the type given.
078     *
079     * @param num the number type
080     * @return the corresponding {@link BigDecimal}
081     */
082    public static BigDecimal getBigDecimal(Number num) {
083        return ConvertBigDecimal.of(num);
084    }
085
086    /**
087     * Creates a {@link BigDecimal} from the given {@link Number} doing the
088     * valid conversion depending the type given, if a {@link MonetaryContext}
089     * is given, it is applied to the number returned.
090     *
091     * @param num the number type
092     * @return the corresponding {@link BigDecimal}
093     */
094    public static BigDecimal getBigDecimal(Number num, MonetaryContext moneyContext) {
095        BigDecimal bd = getBigDecimal(num);
096        if (Objects.nonNull(moneyContext)) {
097            return new BigDecimal(bd.toString(), getMathContext(moneyContext, RoundingMode.HALF_EVEN));
098        }
099        return bd;
100    }
101
102    /**
103     * Evaluates the {@link MathContext} from the given {@link MonetaryContext}.
104     *
105     * @param monetaryContext the {@link MonetaryContext}
106     * @param defaultMode     the default {@link RoundingMode}, to be used if no one is set
107     *                        in {@link MonetaryContext}.
108     * @return the corresponding {@link MathContext}
109     */
110    public static MathContext getMathContext(MonetaryContext monetaryContext, RoundingMode defaultMode) {
111        MathContext ctx = monetaryContext.get(MathContext.class);
112        if (Objects.nonNull(ctx)) {
113            return ctx;
114        }
115        RoundingMode roundingMode = monetaryContext.get(RoundingMode.class);
116        if (roundingMode == null) {
117            roundingMode = defaultMode;
118        }
119        if (roundingMode == null) {
120            roundingMode = RoundingMode.HALF_EVEN;
121        }
122        return new MathContext(monetaryContext.getPrecision(), roundingMode);
123    }
124
125    /**
126     * Method to check if a currency is compatible with this amount instance.
127     *
128     * @param amount       The monetary amount to be compared to, never null.
129     * @param currencyUnit the currency unit to compare, never null.
130     * @throws MonetaryException If the amount is null, or the amount's {@link CurrencyUnit} is not
131     *                           compatible, meaning has a different value of
132     *                           {@link CurrencyUnit#getCurrencyCode()}).
133     */
134    public static void checkAmountParameter(MonetaryAmount amount, CurrencyUnit currencyUnit) {
135        Objects.requireNonNull(amount, "Amount must not be null.");
136        final CurrencyUnit amountCurrency = amount.getCurrency();
137        if (!(currencyUnit.getCurrencyCode().equals(amountCurrency.getCurrencyCode()))) {
138            throw new MonetaryException("Currency mismatch: " + currencyUnit + '/' + amountCurrency);
139        }
140    }
141
142    /**
143     * Internal method to check for correct number parameter.
144     *
145     * @param number the number to be checked.
146     * @throws IllegalArgumentException If the number is null
147     */
148    public static void checkNumberParameter(Number number) {
149        Objects.requireNonNull(number, "Number is required.");
150    }
151
152}