/**
* Copyright (c) 2012, 2015, Credit Suisse (Anatole Tresch), Werner Keil and others by the @author tag.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.javamoney.moneta.internal.convert;
import java.io.InputStream;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Collections;
import java.util.Comparator;
import java.util.Currency;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.money.CurrencyContextBuilder;
import javax.money.CurrencyUnit;
import javax.money.Monetary;
import javax.money.MonetaryException;
import javax.money.convert.ConversionContext;
import javax.money.convert.ConversionQuery;
import javax.money.convert.ExchangeRate;
import javax.money.convert.ProviderContext;
import org.javamoney.moneta.CurrencyUnitBuilder;
import org.javamoney.moneta.convert.ExchangeRateBuilder;
import org.javamoney.moneta.internal.convert.IMFRateReadingHandler.RateIMFResult;
import org.javamoney.moneta.spi.AbstractRateProvider;
import org.javamoney.moneta.spi.LoaderService.LoaderListener;
abstract class IMFAbstractRateProvider extends AbstractRateProvider implements LoaderListener {
private static final Logger LOG = Logger.getLogger(IMFAbstractRateProvider.class.getName());
static final Comparator<ExchangeRate> COMPARATOR_EXCHANGE_BY_LOCAL_DATE = Comparator.comparing(c -> c.getContext().get(LocalDate.class));
protected static final Map<String, CurrencyUnit> CURRENCIES_BY_NAME = new HashMap<>();
protected static final CurrencyUnit SDR =
CurrencyUnitBuilder.of("SDR", CurrencyContextBuilder.of(IMFRateProvider.class.getSimpleName()).build())
.setDefaultFractionDigits(3).build(true);
protected Map<CurrencyUnit, List<ExchangeRate>> currencyToSdr = Collections.emptyMap();
protected Map<CurrencyUnit, List<ExchangeRate>> sdrToCurrency = Collections.emptyMap();
protected final IMFRateReadingHandler handler;
private final ProviderContext context;
public IMFAbstractRateProvider(ProviderContext providerContext) {
super(providerContext);
this.context = providerContext;
handler = new IMFRateReadingHandler(CURRENCIES_BY_NAME, context);
}
static {
for (Currency currency : Currency.getAvailableCurrencies()) {
CURRENCIES_BY_NAME.put(currency.getDisplayName(Locale.ENGLISH),
Monetary.getCurrency(currency.getCurrencyCode()));
}
CURRENCIES_BY_NAME.put("U.K. Pound Sterling", Monetary.getCurrency("GBP"));
CURRENCIES_BY_NAME.put("U.S. Dollar", Monetary.getCurrency("USD"));
CURRENCIES_BY_NAME.put("Bahrain Dinar", Monetary.getCurrency("BHD"));
CURRENCIES_BY_NAME.put("Botswana Pula", Monetary.getCurrency("BWP"));
CURRENCIES_BY_NAME.put("Czech Koruna", Monetary.getCurrency("CZK"));
CURRENCIES_BY_NAME.put("Icelandic Krona", Monetary.getCurrency("ISK"));
CURRENCIES_BY_NAME.put("Korean Won", Monetary.getCurrency("KRW"));
CURRENCIES_BY_NAME.put("Rial Omani", Monetary.getCurrency("OMR"));
CURRENCIES_BY_NAME.put("Nuevo Sol", Monetary.getCurrency("PEN"));
CURRENCIES_BY_NAME.put("Qatar Riyal", Monetary.getCurrency("QAR"));
CURRENCIES_BY_NAME.put("Saudi Arabian Riyal", Monetary.getCurrency("SAR"));
CURRENCIES_BY_NAME.put("Sri Lanka Rupee", Monetary.getCurrency("LKR"));
CURRENCIES_BY_NAME.put("Trinidad And Tobago Dollar", Monetary.getCurrency("TTD"));
CURRENCIES_BY_NAME.put("U.A.E. Dirham", Monetary.getCurrency("AED"));
CURRENCIES_BY_NAME.put("Peso Uruguayo", Monetary.getCurrency("UYU"));
CURRENCIES_BY_NAME.put("Bolivar Fuerte", Monetary.getCurrency("VEF"));
}
@Override
public void newDataLoaded(String resourceId, InputStream is) {
try {
RateIMFResult result = handler.read(is);
this.sdrToCurrency = result.getSdrToCurrency();
this.currencyToSdr = result.getCurrencyToSdr();
} catch (Exception e) {
LOG.log(Level.SEVERE, "Error", e);
}
}
@Override
public ExchangeRate getExchangeRate(ConversionQuery conversionQuery) {
if (!isAvailable(conversionQuery)) {
return null;
}
CurrencyUnit base = conversionQuery.getBaseCurrency();
CurrencyUnit term = conversionQuery.getCurrency();
LocalDate[] times = getQueryDates(conversionQuery);
ExchangeRate rate1 = getExchangeRate(currencyToSdr.get(base), times);
ExchangeRate rate2 = getExchangeRate(sdrToCurrency.get(term), times);
if (base.equals(SDR)) {
return rate2;
} else if (term.equals(SDR)) {
return rate1;
}
if (Objects.isNull(rate1) || Objects.isNull(rate2)) {
return null;
}
ConversionContext context = getExchangeContext("imf.digit.fraction");
ExchangeRateBuilder builder =
new ExchangeRateBuilder(context);
builder.setBase(base);
builder.setTerm(term);
builder.setFactor(multiply(rate1.getFactor(), rate2.getFactor()));
builder.setRateChain(rate1, rate2);
return builder.build();
}
private ExchangeRate getExchangeRate(List<ExchangeRate> rates,final LocalDate[] dates) {
if (Objects.isNull(rates) ) {
return null;
}
if (Objects.isNull(dates)) {
return rates.stream().sorted(COMPARATOR_EXCHANGE_BY_LOCAL_DATE.reversed()).findFirst().orElseThrow(() -> new MonetaryException("There is not more recent exchange rate to rate on IMFRateProvider."));
} else {
for (LocalDate localDate : dates) {
Predicate<ExchangeRate> filter = rate -> rate.getContext().get(LocalDate.class).equals(localDate);
Optional<ExchangeRate> exchangeRateOptional = rates.stream().filter(filter).findFirst();
if(exchangeRateOptional.isPresent()) {
return exchangeRateOptional.get();
}
}
String datesOnErros = Stream.of(dates).map(date -> date.format(DateTimeFormatter.ISO_LOCAL_DATE)).collect(Collectors.joining(","));
throw new MonetaryException("There is not exchange on day " + datesOnErros + " to rate to rate on IFMRateProvider.");
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getClass().getName()).append('{')
.append(" context: ").append(context).append('}');
return sb.toString();
}
}