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.MonetaryOperator;
021import javax.money.NumberValue;
022import javax.money.convert.ConversionContext;
023import javax.money.convert.CurrencyConversion;
024import javax.money.convert.CurrencyConversionException;
025import javax.money.convert.ExchangeRate;
026
027import org.javamoney.moneta.function.MonetaryOperators;
028
029import java.math.MathContext;
030import java.math.RoundingMode;
031import java.util.Objects;
032
033/**
034 * Abstract base class used for implementing currency conversion.
035 *
036 * @author Anatole Tresch
037 * @author Werner Keil
038 */
039public abstract class AbstractCurrencyConversion implements CurrencyConversion {
040
041    private final CurrencyUnit termCurrency;
042    private final ConversionContext conversionContext;
043
044    public static final String KEY_SCALE = "exchangeRateScale";
045
046    public AbstractCurrencyConversion(CurrencyUnit termCurrency, ConversionContext conversionContext) {
047        Objects.requireNonNull(termCurrency);
048        Objects.requireNonNull(conversionContext);
049        this.termCurrency = termCurrency;
050        this.conversionContext = conversionContext;
051    }
052
053    /**
054     * Access the terminating {@link CurrencyUnit} of this conversion instance.
055     *
056     * @return the terminating {@link CurrencyUnit} , never {@code null}.
057     */
058    @Override
059    public CurrencyUnit getCurrency() {
060        return termCurrency;
061    }
062
063    /**
064     * Access the target {@link ConversionContext} of this conversion instance.
065     *
066     * @return the target {@link ConversionContext}.
067     */
068    @Override
069    public ConversionContext getContext() {
070        return conversionContext;
071    }
072
073    /**
074     * Get the exchange rate type that this {@link MonetaryOperator} instance is
075     * using for conversion.
076     *
077     * @return the {@link ExchangeRate} to be used, or null, if this conversion
078     * is not supported (will lead to a
079     * {@link CurrencyConversionException}.
080     * @see #apply(MonetaryAmount)
081     */
082    @Override
083    public abstract ExchangeRate getExchangeRate(MonetaryAmount sourceAmount);
084
085    /*
086     * (non-Javadoc)
087     * @see javax.money.convert.CurrencyConversion#with(javax.money.convert.ConversionContext)
088     */
089    public abstract CurrencyConversion with(ConversionContext conversionContext);
090
091    /**
092     * Method that converts the source {@link MonetaryAmount} to an
093     * {@link MonetaryAmount} based on the {@link ExchangeRate} of this
094     * conversion.
095     *
096     * @param amount The source amount
097     * @return The converted amount, never null.
098     * @throws CurrencyConversionException if conversion failed, or the required data is not available.
099     * @see #getExchangeRate(MonetaryAmount)
100     */
101    @Override
102    public MonetaryAmount apply(MonetaryAmount amount) {
103        if (termCurrency.equals(Objects.requireNonNull(amount).getCurrency())) {
104            return amount;
105        }
106        ExchangeRate rate = getExchangeRate(amount);
107        if (Objects.isNull(rate) || !amount.getCurrency().equals(rate.getBaseCurrency())) {
108            throw new CurrencyConversionException(amount.getCurrency(),
109                    Objects.isNull(rate) ? null : rate.getCurrency(), null);
110        }
111
112        NumberValue factor = rate.getFactor();
113        factor = roundFactor(amount, factor);
114
115        Integer scale = rate.getContext().get(KEY_SCALE, Integer.class);
116        if(Objects.isNull(scale) || scale < 0) {
117                return amount.multiply(factor).getFactory().setCurrency(rate.getCurrency()).create();
118        } else {
119                return amount.multiply(factor).getFactory().setCurrency(rate.getCurrency()).create().with(MonetaryOperators.rounding(scale));
120        }
121    }
122
123    /**
124     * Optionally rounds the factor to be used. By default this method will only round
125     * as much as its is needed, so the factor can be handled by the target amount instance based on its
126     * numeric capabilities.
127     *
128     * @param amount the amount, not null.
129     * @param factor the factor
130     * @return the new NumberValue, never null.
131     */
132    protected NumberValue roundFactor(MonetaryAmount amount, NumberValue factor) {
133        if (amount.getContext().getMaxScale() > 0) {
134            if (factor.getScale() > amount.getContext().getMaxScale()) {
135                return factor.round(new MathContext(amount.getContext().getMaxScale(), RoundingMode.HALF_EVEN));
136            }
137        }
138        return factor;
139    }
140
141
142    /*
143     * (non-Javadoc)
144     *
145     * @see java.lang.Object#toString()
146     */
147    @Override
148    public String toString() {
149        return getClass().getName() + " [MonetaryAmount -> MonetaryAmount" + ']';
150    }
151
152}