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