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.logging.Logger;
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     * The logger used.
041     */
042    private static final Logger LOG = Logger.getLogger(MoneyUtils.class.getName());
043
044    private MoneyUtils() {
045    }
046
047
048    // Supporting methods
049
050    /**
051     * Creates a {@link BigDecimal} from the given {@link Number} doing the
052     * valid conversion depending the type given.
053     *
054     * @param num the number type
055     * @return the corresponding {@link BigDecimal}
056     */
057    public static BigDecimal getBigDecimal(long num) {
058        return BigDecimal.valueOf(num);
059    }
060
061    /**
062     * Creates a {@link BigDecimal} from the given {@link Number} doing the
063     * valid conversion depending the type given.
064     *
065     * @param num the number type
066     * @return the corresponding {@link BigDecimal}
067     */
068    public static BigDecimal getBigDecimal(double num) {
069        if (Double.isNaN(num)) {
070            throw new ArithmeticException("Invalid input Double.NaN.");
071        } else if(Double.isInfinite(num)) {
072            throw new ArithmeticException("Invalid input Double.xxx_INFINITY.");
073        }
074        return new BigDecimal(String.valueOf(num));
075    }
076
077    /**
078     * Creates a {@link BigDecimal} from the given {@link Number} doing the
079     * valid conversion depending the type given.
080     *
081     * @param num the number type
082     * @return the corresponding {@link BigDecimal}
083     */
084    public static BigDecimal getBigDecimal(Number num) {
085        return ConvertBigDecimal.of(num);
086    }
087
088    /**
089     * Creates a {@link BigDecimal} from the given {@link Number} doing the
090     * valid conversion depending the type given, if a {@link MonetaryContext}
091     * is given, it is applied to the number returned.
092     *
093     * @param num the number type
094     * @return the corresponding {@link BigDecimal}
095     */
096    public static BigDecimal getBigDecimal(Number num, MonetaryContext moneyContext) {
097        BigDecimal bd = getBigDecimal(num);
098        if (moneyContext!=null) {
099            MathContext mc = getMathContext(moneyContext, RoundingMode.HALF_EVEN);
100            bd = new BigDecimal(bd.toString(), mc);
101            if (moneyContext.getMaxScale() > 0) {
102                LOG.fine(String.format("Got Max Scale %s", moneyContext.getMaxScale()));
103                bd = bd.setScale(moneyContext.getMaxScale(), mc.getRoundingMode());
104            }
105        }
106        return bd;
107    }
108
109    /**
110     * Evaluates the {@link MathContext} from the given {@link MonetaryContext}.
111     *
112     * @param monetaryContext the {@link MonetaryContext}
113     * @param defaultMode     the default {@link RoundingMode}, to be used if no one is set
114     *                        in {@link MonetaryContext}.
115     * @return the corresponding {@link MathContext}
116     */
117    public static MathContext getMathContext(MonetaryContext monetaryContext, RoundingMode defaultMode) {
118        MathContext ctx = monetaryContext.get(MathContext.class);
119        if (ctx!=null) {
120            return ctx;
121        }
122        RoundingMode roundingMode = monetaryContext.get(RoundingMode.class);
123        if (roundingMode == null) {
124            roundingMode = defaultMode;
125        }
126        if (roundingMode == null) {
127            roundingMode = RoundingMode.HALF_EVEN;
128        }
129        return new MathContext(monetaryContext.getPrecision(), roundingMode);
130    }
131
132    /**
133     * Method to check if a currency is compatible with this amount instance.
134     *
135     * @param amount       The monetary amount to be compared to, never null.
136     * @param currencyUnit the currency unit to compare, never null.
137     * @throws MonetaryException If the amount is null, or the amount's {@link CurrencyUnit} is not
138     *                           compatible, meaning has a different value of
139     *                           {@link CurrencyUnit#getCurrencyCode()}).
140     */
141    public static void checkAmountParameter(MonetaryAmount amount, CurrencyUnit currencyUnit) {
142        Objects.requireNonNull(amount, "Amount must not be null.");
143        final CurrencyUnit amountCurrency = amount.getCurrency();
144        if (!(currencyUnit.getCurrencyCode().equals(amountCurrency.getCurrencyCode()))) {
145            throw new MonetaryException("Currency mismatch: " + currencyUnit + '/' + amountCurrency);
146        }
147    }
148
149    /**
150     * Internal method to check for correct number parameter.
151     *
152     * @param number the number to be checked.
153     * @throws IllegalArgumentException If the number is null
154     */
155    public static void checkNumberParameter(Number number) {
156        Objects.requireNonNull(number, "Number is required.");
157    }
158
159}