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}