package name.abuchen.portfolio.money.impl;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.text.MessageFormat;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.core.runtime.IProgressMonitor;
import org.osgi.framework.Bundle;
import org.osgi.framework.FrameworkUtil;
import com.thoughtworks.xstream.XStream;
import name.abuchen.portfolio.Messages;
import name.abuchen.portfolio.money.ExchangeRate;
import name.abuchen.portfolio.money.ExchangeRateProvider;
import name.abuchen.portfolio.money.ExchangeRateTimeSeries;
import name.abuchen.portfolio.util.XStreamLocalDateConverter;
/**
* Load and manage exchanges rates provided by the European Central Bank (ECB).
* <p/>
* Because the {@link #load(IProgressMonitor)},
* {@link #update(IProgressMonitor)}, and {@link #save(IProgressMonitor)}
* methods are run as background operations, the following strategy is used for
* synchronization:
* <ul>
* <li>all state is held in a {@link ECBData} object</li>
* <li>all update operations manipulate a deep copy of the data object</li>
* </ul>
*/
public class ECBExchangeRateProvider implements ExchangeRateProvider
{
public static final String EUR = "EUR"; //$NON-NLS-1$
private static final String FILE_STORAGE = "ecb_exchange_rates.xml"; //$NON-NLS-1$
private static final String FILE_SUMMARY = "ecb_exchange_rates_summary.xml"; //$NON-NLS-1$
private volatile XStream xstream;
private ECBData data = new ECBData();
public ECBExchangeRateProvider()
{
fillInDefaultData(data);
}
/* testing */ ECBExchangeRateProvider(ECBData data)
{
this.data = data;
}
@Override
public String getName()
{
return Messages.LabelEuropeanCentralBank;
}
@Override
public synchronized void load(IProgressMonitor monitor) throws IOException
{
monitor.beginTask(MessageFormat.format(Messages.MsgLoadingExchangeRates, getName()), 2);
// read summary first (contains only latest rates, but is fast)
File file = getStorageFile(FILE_SUMMARY);
if (file.exists())
{
@SuppressWarnings("unchecked")
Map<String, ExchangeRate> loaded = (Map<String, ExchangeRate>) xstream().fromXML(file);
ECBData summary = new ECBData();
for (Map.Entry<String, ExchangeRate> entry : loaded.entrySet())
{
ExchangeRateTimeSeriesImpl s = new ExchangeRateTimeSeriesImpl(this, EUR, entry.getKey());
s.addRate(entry.getValue());
summary.addSeries(s);
}
data = summary;
}
monitor.worked(1);
// read all historic exchange rates
file = getStorageFile(FILE_STORAGE);
if (file.exists())
{
ECBData loaded = (ECBData) xstream().fromXML(file);
loaded.doPostLoadProcessing(this);
data = loaded;
}
monitor.worked(1);
}
@Override
public synchronized void update(IProgressMonitor monitor) throws IOException
{
ECBData copy = this.data.copy();
new ECBUpdater().update(this, copy);
this.data = copy;
}
@Override
public synchronized void save(IProgressMonitor monitor) throws IOException
{
if (!data.isDirty())
return;
// store latest exchange rate separately -> faster to load upon startup
// of the application
File file = getStorageFile(FILE_SUMMARY);
Map<String, ExchangeRate> summary = new HashMap<>();
for (ExchangeRateTimeSeriesImpl s : data.getSeries())
s.getLatest().ifPresent(rate -> summary.put(s.getTermCurrency(), rate));
write(summary, file);
// write the full history data
file = getStorageFile(FILE_STORAGE);
write(data, file);
}
@Override
public List<ExchangeRateTimeSeries> getAvailableTimeSeries()
{
return new ArrayList<>(data.getSeries());
}
private File getStorageFile(String name)
{
Bundle bundle = FrameworkUtil.getBundle(ECBExchangeRateProvider.class);
return bundle.getDataFile(name);
}
private void write(Object object, File file) throws IOException
{
try (FileOutputStream out = new FileOutputStream(file))
{
xstream().toXML(object, out);
}
}
/**
* Make sure we have at least one exchange rate available. The program might
* not have a connection to the internet (run behind a proxy) and is unable
* to load current exchange rate series from ECB.
*/
@SuppressWarnings("nls")
private void fillInDefaultData(ECBData summary)
{
ExchangeRateTimeSeriesImpl s = new ExchangeRateTimeSeriesImpl(this, EUR, "CHF");
s.addRate(new ExchangeRate(LocalDate.parse("2015-12-18"), BigDecimal.valueOf(1.0768)));
summary.addSeries(s);
s = new ExchangeRateTimeSeriesImpl(this, EUR, "HRK");
s.addRate(new ExchangeRate(LocalDate.parse("2015-12-18"), BigDecimal.valueOf(7.6495)));
summary.addSeries(s);
s = new ExchangeRateTimeSeriesImpl(this, EUR, "MXN");
s.addRate(new ExchangeRate(LocalDate.parse("2015-12-18"), BigDecimal.valueOf(18.4429)));
summary.addSeries(s);
s = new ExchangeRateTimeSeriesImpl(this, EUR, "LVL");
s.addRate(new ExchangeRate(LocalDate.parse("2013-12-31"), BigDecimal.valueOf(0.702804)));
summary.addSeries(s);
s = new ExchangeRateTimeSeriesImpl(this, EUR, "MTL");
s.addRate(new ExchangeRate(LocalDate.parse("2007-12-31"), BigDecimal.valueOf(0.4293)));
summary.addSeries(s);
s = new ExchangeRateTimeSeriesImpl(this, EUR, "LTL");
s.addRate(new ExchangeRate(LocalDate.parse("2014-12-31"), BigDecimal.valueOf(3.4528)));
summary.addSeries(s);
s = new ExchangeRateTimeSeriesImpl(this, EUR, "ZAR");
s.addRate(new ExchangeRate(LocalDate.parse("2015-12-18"), BigDecimal.valueOf(16.2998)));
summary.addSeries(s);
s = new ExchangeRateTimeSeriesImpl(this, EUR, "INR");
s.addRate(new ExchangeRate(LocalDate.parse("2015-12-18"), BigDecimal.valueOf(71.955)));
summary.addSeries(s);
s = new ExchangeRateTimeSeriesImpl(this, EUR, "CNY");
s.addRate(new ExchangeRate(LocalDate.parse("2015-12-18"), BigDecimal.valueOf(7.0274)));
summary.addSeries(s);
s = new ExchangeRateTimeSeriesImpl(this, EUR, "THB");
s.addRate(new ExchangeRate(LocalDate.parse("2015-12-18"), BigDecimal.valueOf(39.175)));
summary.addSeries(s);
s = new ExchangeRateTimeSeriesImpl(this, EUR, "TRL");
s.addRate(new ExchangeRate(LocalDate.parse("2004-12-31"), BigDecimal.valueOf(1836200)));
summary.addSeries(s);
s = new ExchangeRateTimeSeriesImpl(this, EUR, "AUD");
s.addRate(new ExchangeRate(LocalDate.parse("2015-12-18"), BigDecimal.valueOf(1.5206)));
summary.addSeries(s);
s = new ExchangeRateTimeSeriesImpl(this, EUR, "ILS");
s.addRate(new ExchangeRate(LocalDate.parse("2015-12-18"), BigDecimal.valueOf(4.221)));
summary.addSeries(s);
s = new ExchangeRateTimeSeriesImpl(this, EUR, "KRW");
s.addRate(new ExchangeRate(LocalDate.parse("2015-12-18"), BigDecimal.valueOf(1280.16)));
summary.addSeries(s);
s = new ExchangeRateTimeSeriesImpl(this, EUR, "JPY");
s.addRate(new ExchangeRate(LocalDate.parse("2015-12-18"), BigDecimal.valueOf(131.6)));
summary.addSeries(s);
s = new ExchangeRateTimeSeriesImpl(this, EUR, "PLN");
s.addRate(new ExchangeRate(LocalDate.parse("2015-12-18"), BigDecimal.valueOf(4.2806)));
summary.addSeries(s);
s = new ExchangeRateTimeSeriesImpl(this, EUR, "GBP");
s.addRate(new ExchangeRate(LocalDate.parse("2015-12-18"), BigDecimal.valueOf(0.72666)));
summary.addSeries(s);
s = new ExchangeRateTimeSeriesImpl(this, EUR, "IDR");
s.addRate(new ExchangeRate(LocalDate.parse("2015-12-18"), BigDecimal.valueOf(15096.1)));
summary.addSeries(s);
s = new ExchangeRateTimeSeriesImpl(this, EUR, "HUF");
s.addRate(new ExchangeRate(LocalDate.parse("2015-12-18"), BigDecimal.valueOf(314.25)));
summary.addSeries(s);
s = new ExchangeRateTimeSeriesImpl(this, EUR, "PHP");
s.addRate(new ExchangeRate(LocalDate.parse("2015-12-18"), BigDecimal.valueOf(51.253)));
summary.addSeries(s);
s = new ExchangeRateTimeSeriesImpl(this, EUR, "TRY");
s.addRate(new ExchangeRate(LocalDate.parse("2015-12-18"), BigDecimal.valueOf(3.1581)));
summary.addSeries(s);
s = new ExchangeRateTimeSeriesImpl(this, EUR, "CYP");
s.addRate(new ExchangeRate(LocalDate.parse("2007-12-31"), BigDecimal.valueOf(0.585274)));
summary.addSeries(s);
s = new ExchangeRateTimeSeriesImpl(this, EUR, "RUB");
s.addRate(new ExchangeRate(LocalDate.parse("2015-12-18"), BigDecimal.valueOf(77.1005)));
summary.addSeries(s);
s = new ExchangeRateTimeSeriesImpl(this, EUR, "HKD");
s.addRate(new ExchangeRate(LocalDate.parse("2015-12-18"), BigDecimal.valueOf(8.4005)));
summary.addSeries(s);
s = new ExchangeRateTimeSeriesImpl(this, EUR, "ISK");
s.addRate(new ExchangeRate(LocalDate.parse("2008-12-09"), BigDecimal.valueOf(290)));
summary.addSeries(s);
s = new ExchangeRateTimeSeriesImpl(this, EUR, "DKK");
s.addRate(new ExchangeRate(LocalDate.parse("2015-12-18"), BigDecimal.valueOf(7.4613)));
summary.addSeries(s);
s = new ExchangeRateTimeSeriesImpl(this, EUR, "USD");
s.addRate(new ExchangeRate(LocalDate.parse("2015-12-18"), BigDecimal.valueOf(1.0836)));
summary.addSeries(s);
s = new ExchangeRateTimeSeriesImpl(this, EUR, "CAD");
s.addRate(new ExchangeRate(LocalDate.parse("2015-12-18"), BigDecimal.valueOf(1.5123)));
summary.addSeries(s);
s = new ExchangeRateTimeSeriesImpl(this, EUR, "MYR");
s.addRate(new ExchangeRate(LocalDate.parse("2015-12-18"), BigDecimal.valueOf(4.644)));
summary.addSeries(s);
s = new ExchangeRateTimeSeriesImpl(this, EUR, "BGN");
s.addRate(new ExchangeRate(LocalDate.parse("2015-12-18"), BigDecimal.valueOf(1.9558)));
summary.addSeries(s);
s = new ExchangeRateTimeSeriesImpl(this, EUR, "EEK");
s.addRate(new ExchangeRate(LocalDate.parse("2010-12-31"), BigDecimal.valueOf(15.6466)));
summary.addSeries(s);
s = new ExchangeRateTimeSeriesImpl(this, EUR, "NOK");
s.addRate(new ExchangeRate(LocalDate.parse("2015-12-18"), BigDecimal.valueOf(9.5)));
summary.addSeries(s);
s = new ExchangeRateTimeSeriesImpl(this, EUR, "ROL");
s.addRate(new ExchangeRate(LocalDate.parse("2005-06-30"), BigDecimal.valueOf(36030)));
summary.addSeries(s);
s = new ExchangeRateTimeSeriesImpl(this, EUR, "RON");
s.addRate(new ExchangeRate(LocalDate.parse("2015-12-18"), BigDecimal.valueOf(4.516)));
summary.addSeries(s);
s = new ExchangeRateTimeSeriesImpl(this, EUR, "SGD");
s.addRate(new ExchangeRate(LocalDate.parse("2015-12-18"), BigDecimal.valueOf(1.53)));
summary.addSeries(s);
s = new ExchangeRateTimeSeriesImpl(this, EUR, "SKK");
s.addRate(new ExchangeRate(LocalDate.parse("2008-12-31"), BigDecimal.valueOf(30.126)));
summary.addSeries(s);
s = new ExchangeRateTimeSeriesImpl(this, EUR, "CZK");
s.addRate(new ExchangeRate(LocalDate.parse("2015-12-18"), BigDecimal.valueOf(27.03)));
summary.addSeries(s);
s = new ExchangeRateTimeSeriesImpl(this, EUR, "SEK");
s.addRate(new ExchangeRate(LocalDate.parse("2015-12-18"), BigDecimal.valueOf(9.266)));
summary.addSeries(s);
s = new ExchangeRateTimeSeriesImpl(this, EUR, "NZD");
s.addRate(new ExchangeRate(LocalDate.parse("2015-12-18"), BigDecimal.valueOf(1.616)));
summary.addSeries(s);
s = new ExchangeRateTimeSeriesImpl(this, EUR, "BRL");
s.addRate(new ExchangeRate(LocalDate.parse("2015-12-18"), BigDecimal.valueOf(4.2265)));
summary.addSeries(s);
s = new ExchangeRateTimeSeriesImpl(this, EUR, "SIT");
s.addRate(new ExchangeRate(LocalDate.parse("2006-12-29"), BigDecimal.valueOf(239.64)));
summary.addSeries(s);
}
@SuppressWarnings("nls")
private XStream xstream()
{
if (xstream == null)
{
synchronized (this)
{
if (xstream == null)
{
xstream = new XStream();
xstream.setClassLoader(ECBExchangeRateProvider.class.getClassLoader());
xstream.registerConverter(new XStreamLocalDateConverter());
xstream.alias("data", ECBData.class);
xstream.alias("series", ExchangeRateTimeSeriesImpl.class);
xstream.alias("rate", ExchangeRate.class);
xstream.useAttributeFor(ExchangeRate.class, "time");
xstream.aliasField("t", ExchangeRate.class, "time");
xstream.useAttributeFor(ExchangeRate.class, "value");
xstream.aliasField("v", ExchangeRate.class, "value");
}
}
}
return xstream;
}
}