/**
* 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.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.text.ParseException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.logging.Logger;
import javax.money.CurrencyUnit;
import javax.money.convert.ConversionContextBuilder;
import javax.money.convert.ExchangeRate;
import javax.money.convert.ProviderContext;
import javax.money.convert.RateType;
import org.javamoney.moneta.convert.ExchangeRateBuilder;
import org.javamoney.moneta.spi.DefaultNumberValue;
class IMFRateReadingHandler {
private static final Logger LOG = Logger
.getLogger(IMFRateReadingHandler.class.getName());
private final Map<String, CurrencyUnit> currenciresByName;
private final ProviderContext context;
IMFRateReadingHandler(Map<String, CurrencyUnit> currenciresByName, ProviderContext context) {
this.currenciresByName = currenciresByName;
this.context = context;
}
RateIMFResult read(InputStream inputStream) throws IOException,
ParseException {
Map<CurrencyUnit, List<ExchangeRate>> currencyToSdr = new HashMap<>();
Map<CurrencyUnit, List<ExchangeRate>> sdrToCurrency = new HashMap<>();
BufferedReader reader = new BufferedReader(new InputStreamReader(
inputStream));
String line = reader.readLine();
boolean isCurrencyToSdr = true;
// SDRs per Currency unit (2)
//
// Currency January 31, 2013 January 30, 2013 January 29, 2013
// January 28, 2013 January 25, 2013
// Euro 0.8791080000 0.8789170000 0.8742470000 0.8752180000
// 0.8768020000
// Currency units per SDR(3)
//
// Currency January 31, 2013 January 30, 2013 January 29, 2013
// January 28, 2013 January 25, 2013
// Euro 1.137520 1.137760 1.143840 1.142570 1.140510
List<LocalDate> timestamps = null;
while (Objects.nonNull(line)) {
if (line.trim().isEmpty()) {
line = reader.readLine();
continue;
}
if (line.startsWith("SDRs per Currency unit")) {
isCurrencyToSdr = false;
line = reader.readLine();
continue;
} else if (line.startsWith("Currency units per SDR")) {
isCurrencyToSdr = true;
line = reader.readLine();
continue;
} else if (line.startsWith("Currency")) {
timestamps = readTimestamps(line);
line = reader.readLine();
continue;
}
String[] parts = line.split("\\t");
CurrencyUnit currency = currenciresByName.get(parts[0]);
if (Objects.isNull(currency)) {
LOG.finest(() -> "Uninterpretable data from IMF data feed: "
+ parts[0]);
line = reader.readLine();
continue;
}
saveExchangeRate(currencyToSdr, sdrToCurrency, isCurrencyToSdr,
timestamps, currency, parseValues(parts));
line = reader.readLine();
}
// Cast is save, since contained DefaultExchangeRate is Comparable!
sortResult(currencyToSdr, sdrToCurrency);
return new RateIMFResult(currencyToSdr, sdrToCurrency);
}
@SuppressWarnings("unchecked")
private void sortResult(
Map<CurrencyUnit, List<ExchangeRate>> newCurrencyToSdr,
Map<CurrencyUnit, List<ExchangeRate>> newSdrToCurrency) {
newSdrToCurrency.values().forEach(
(c) -> Collections.sort(List.class.cast(c)));
newCurrencyToSdr.values().forEach(
(c) -> Collections.sort(List.class.cast(c)));
newSdrToCurrency.forEach((c, l) -> LOG.finest(() -> "SDR -> "
+ c.getCurrencyCode() + ": " + l));
newCurrencyToSdr.forEach((c, l) -> LOG.finest(() -> c
.getCurrencyCode() + " -> SDR: " + l));
}
private List<LocalDate> readTimestamps(String line) {
// Currency May 01, 2013 April 30, 2013 April 29, 2013 April 26, 2013
// April 25, 2013
DateTimeFormatter sdf = DateTimeFormatter.ofPattern("MMMM dd, uuuu")
.withLocale(Locale.ENGLISH);
String[] parts = line.split("\\\t");
List<LocalDate> dates = new ArrayList<>(parts.length);
for (int i = 1; i < parts.length; i++) {
dates.add(LocalDate.parse(parts[i], sdf));
}
return dates;
}
private void saveExchangeRate(
Map<CurrencyUnit, List<ExchangeRate>> currencyToSdr,
Map<CurrencyUnit, List<ExchangeRate>> sdrToCurrency,
boolean isCurrencyToSdr, List<LocalDate> timestamps,
CurrencyUnit currency, Double[] values) {
for (int index = 0; index < values.length; index++) {
if (Objects.isNull(values[index])
|| Objects.isNull(getLocalDateFromTS(timestamps, index))) {
continue;
}
LocalDate fromTS = getLocalDateFromTS(timestamps, index);
RateType rateType = getRateType(fromTS);
if (isCurrencyToSdr) {
ExchangeRate rate = new ExchangeRateBuilder(
ConversionContextBuilder.create(context, rateType)
.set(fromTS).build()).setBase(currency)
.setTerm(IMFAbstractRateProvider.SDR)
.setFactor(new DefaultNumberValue(1D / values[index]))
.build();
List<ExchangeRate> rates = currencyToSdr.computeIfAbsent(
currency, c -> new ArrayList<>(5));
rates.add(rate);
} else {
ExchangeRate rate = new ExchangeRateBuilder(
ConversionContextBuilder.create(context, rateType)
.set(fromTS).build()).setBase(IMFAbstractRateProvider.SDR)
.setTerm(currency)
.setFactor(DefaultNumberValue.of(1D / values[index]))
.build();
List<ExchangeRate> rates = sdrToCurrency.computeIfAbsent(
currency, (c) -> new ArrayList<>(5));
rates.add(rate);
}
}
}
private Double[] parseValues(String[] parts) throws ParseException {
ArrayList<Double> result = new ArrayList<>();
int index = 0;
for (String part : parts) {
if(index == 0) {
index++;
continue;
}
if (part.isEmpty() || "NA".equals(part)) {
index++;
result.add(null);
continue;
}
index++;
result.add(Double.valueOf(part.trim().replace(",", "")));
}
return result.toArray(new Double[parts.length - 1]);
}
private LocalDate getLocalDateFromTS(List<LocalDate> timestamps, int index) {
LocalDate fromTS = timestamps != null ? timestamps.get(index) : null;
return fromTS;
}
private RateType getRateType(LocalDate fromTS) {
RateType rateType = RateType.HISTORIC;
if (fromTS.equals(LocalDate.now())) {
rateType = RateType.DEFERRED;
}
return rateType;
}
class RateIMFResult {
private final Map<CurrencyUnit, List<ExchangeRate>> currencyToSdr;
private final Map<CurrencyUnit, List<ExchangeRate>> sdrToCurrency;
RateIMFResult(Map<CurrencyUnit, List<ExchangeRate>> currencyToSdr,
Map<CurrencyUnit, List<ExchangeRate>> sdrToCurrency) {
this.currencyToSdr = currencyToSdr;
this.sdrToCurrency = sdrToCurrency;
}
public Map<CurrencyUnit, List<ExchangeRate>> getCurrencyToSdr() {
return currencyToSdr;
}
public Map<CurrencyUnit, List<ExchangeRate>> getSdrToCurrency() {
return sdrToCurrency;
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(IMFRateReadingHandler.class.getName()).append('{').append(" currenciresByName: ").append(currenciresByName).append(',')
.append(" context: ").append(context).append('}');
return sb.toString();
}
}