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 amount); 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 (rate==null || !amount.getCurrency().equals(rate.getBaseCurrency())) { 108 throw new CurrencyConversionException(amount.getCurrency(), 109 this.termCurrency, this.conversionContext); 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(scale==null || 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 it is needed, so the factor can be handled by the target amount instance based on its 126 * numeric capabilities. Rounding is applied only if {@code amount.getContext().getMaxScale() > 0} as follows: 127 * <ul> 128 * <li>If the amount provides a {@link MathContext} as context property this is used.</li> 129 * <li>If the amount provides a {@link RoundingMode}, this is used (default is 130 * {@code RoundingMode.HALF_EVEN}).</li> 131 * <li>By default the scale used is scale of the conversion factor. If the acmount allows a higher 132 * scale based on {@code amount.getContext().getMaxScale()}, this higher scale is used.</li> 133 * </ul> 134 * 135 * @param amount the amount, not null. 136 * @param factor the factor 137 * @return the new NumberValue, never null. 138 */ 139 protected NumberValue roundFactor(MonetaryAmount amount, NumberValue factor) { 140 if (amount.getContext().getMaxScale() > 0) { 141 MathContext mathContext = amount.getContext().get(MathContext.class); 142 if(mathContext==null){ 143 int scale = factor.getScale(); 144 if (factor.getScale() > amount.getContext().getMaxScale()) { 145 scale = amount.getContext().getMaxScale(); 146 } 147 RoundingMode roundingMode = amount.getContext().get(RoundingMode.class); 148 if(roundingMode==null){ 149 roundingMode = RoundingMode.HALF_EVEN; 150 } 151 mathContext = new MathContext(scale, roundingMode); 152 } 153 return factor.round(mathContext); 154 } 155 return factor; 156 } 157 158 159 /* 160 * (non-Javadoc) 161 * 162 * @see java.lang.Object#toString() 163 */ 164 @Override 165 public String toString() { 166 return getClass().getName() + " [MonetaryAmount -> MonetaryAmount" + ']'; 167 } 168 169}