package org.javamoney.moneta.internal.convert.frb; import java.math.BigDecimal; import java.time.LocalDate; import java.time.OffsetDateTime; import java.util.Collections; import java.util.Currency; import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; import javax.money.CurrencyUnit; import javax.money.Monetary; import javax.money.MonetaryException; 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; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; /** * SAX Event Handler that reads the quotes. * <p> * RDF format: <item rdf:about="http://www.federalreserve.gov/releases/H10#16"> <title>US: H10 0.7100 2015-08-31 FRB * Australia Dollar (USD per AUD)</title> <link>http://www.federalreserve.gov/releases/H10#16</link> * <description>Australia Dollar (USD per AUD)</description> <dc:date>2015-08-31T12:00:00-05:00</dc:date> * <dc:language>en</dc:language> <dc:creator>FRB</dc:creator> <cb:statistics> <cb:country>US</cb:country> * <cb:institutionAbbrev>FRB</cb:institutionAbbrev> <cb:otherStatistic> <cb:value decimals="4" unit_mult="1" * units="Currency:_Per_AUD">0.7100</cb:value> <cb:topic>H10</cb:topic> <cb:coverage>Australia Dollar (USD per * AUD)</cb:coverage> <cb:observationPeriod frequency="business">2015-08-31</cb:observationPeriod> <cb:dataType/> * </cb:otherStatistic> </cb:statistics> </item> */ class USFederalReserveRateReadingHandler extends DefaultHandler { private LocalDate localDate; private String currencyCode; private String description; private boolean dcDateNode = false; private boolean cbValueNode = false; private boolean descriptionNode = false; private final Map<LocalDate, Map<String, ExchangeRate>> historicRates; private final ProviderContext context; private static final Pattern unitsPattern = Pattern.compile("^Currency:_Per_(\\w{3})$"); private static final Map<String, CurrencyUnit> CURRENCIES_BY_NAME; static { Map<String, CurrencyUnit> currenciesByName = new HashMap<>(); for (Currency currency : Currency.getAvailableCurrencies()) { currenciesByName.put(currency.getDisplayName(Locale.ENGLISH), Monetary.getCurrency(currency.getCurrencyCode())); } currenciesByName.put("Brazil Real", Monetary.getCurrency("BRL")); currenciesByName.put("Canada Dollar", Monetary.getCurrency("CAD")); currenciesByName.put("China, P.R. Yuan", Monetary.getCurrency("CNY")); currenciesByName.put("Denmark Krone", Monetary.getCurrency("DKK")); currenciesByName.put("EMU member countries Euro", Monetary.getCurrency("EUR")); currenciesByName.put("India Rupee", Monetary.getCurrency("INR")); currenciesByName.put("Japan Yen", Monetary.getCurrency("JPY")); currenciesByName.put("Malaysia Ringgit", Monetary.getCurrency("MYR")); currenciesByName.put("Mexico Peso", Monetary.getCurrency("MXN")); currenciesByName.put("Norway Krone", Monetary.getCurrency("NOK")); currenciesByName.put("South Africa Rand", Monetary.getCurrency("ZAR")); currenciesByName.put("South Korea Won", Monetary.getCurrency("KRW")); currenciesByName.put("Sri Lanka Rupee", Monetary.getCurrency("LKR")); currenciesByName.put("Sweden Krona", Monetary.getCurrency("SEK")); currenciesByName.put("Switzerland Franc", Monetary.getCurrency("CHF")); currenciesByName.put("Thailand Baht", Monetary.getCurrency("THB")); currenciesByName.put("Taiwan Dollar", Monetary.getCurrency("TWD")); currenciesByName.put("United Kingdom Pound", Monetary.getCurrency("GBP")); currenciesByName.put("Venezuela Bolivar", Monetary.getCurrency("VEF")); CURRENCIES_BY_NAME = Collections.unmodifiableMap(currenciesByName); } /** * Creates a new handler. * * @param historicRates * the rates, not null. * @param context * the context, not null. */ USFederalReserveRateReadingHandler(Map<LocalDate, Map<String, ExchangeRate>> historicRates, ProviderContext context) { this.historicRates = historicRates; this.context = context; } @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { if ("description".equals(qName)) { descriptionNode = true; } else if ("dc:date".equals(qName)) { dcDateNode = true; } else if ("cb:value".equals(qName)) { cbValueNode = true; String units = attributes.getValue("units"); if (attributes.getValue("units") != null) { Matcher matcher = unitsPattern.matcher(units); if (matcher.find()) { try { this.currencyCode = matcher.group(1); } catch (MonetaryException me) { // ignore...currency index not an actual currency } } } } super.startElement(uri, localName, qName, attributes); } @Override public void endElement(String uri, String localName, String qName) throws SAXException { dcDateNode = false; cbValueNode = false; descriptionNode = false; super.endElement(uri, localName, qName); } @Override public void characters(char ch[], int start, int length) throws SAXException { if (this.descriptionNode) { this.description = new String(ch, start, length); } else if (this.dcDateNode) { this.localDate = OffsetDateTime.parse(new String(ch, start, length)).toLocalDate(); } else if (this.cbValueNode && this.currencyCode != null) { String rateStr = new String(ch, start, length); CurrencyUnit currencyUnit = null; boolean inverse = false; if (USFederalReserveRateProvider.BASE_CURRENCY_CODE.equals(this.currencyCode)) { currencyUnit = CURRENCIES_BY_NAME.get(description); } else { currencyUnit = Monetary.getCurrency(this.currencyCode); inverse = true; } if (currencyUnit != null) { if(!"ND".equals(rateStr)) { addRate(currencyUnit, this.localDate, BigDecimal.valueOf(Double.parseDouble(rateStr)), inverse); } } this.currencyCode = null; this.localDate = null; this.description = null; } super.characters(ch, start, length); } private void addRate(CurrencyUnit term, LocalDate localDate, Number rate, boolean inverse) { RateType rateType = RateType.HISTORIC; ExchangeRateBuilder builder = new ExchangeRateBuilder(ConversionContextBuilder.create(context, rateType).set(localDate).build()); builder.setBase(inverse ? term : USFederalReserveRateProvider.BASE_CURRENCY); builder.setTerm(inverse ? USFederalReserveRateProvider.BASE_CURRENCY : term); builder.setFactor(DefaultNumberValue.of(rate)); ExchangeRate exchangeRate = builder.build(); if(inverse) { exchangeRate = USFederalReserveRateProvider.reverse(exchangeRate); } Map<String, ExchangeRate> rateMap = this.historicRates.get(localDate); if (Objects.isNull(rateMap)) { synchronized (this.historicRates) { rateMap = Optional.ofNullable(this.historicRates.get(localDate)).orElse(new ConcurrentHashMap<>()); this.historicRates.putIfAbsent(localDate, rateMap); } } rateMap.put(term.getCurrencyCode(), exchangeRate); } }