/*
* CREDIT SUISSE IS WILLING TO LICENSE THIS SPECIFICATION TO YOU ONLY UPON THE
* CONDITION THAT YOU ACCEPT ALL OF THE TERMS CONTAINED IN THIS AGREEMENT.
* PLEASE READ THE TERMS AND CONDITIONS OF THIS AGREEMENT CAREFULLY. BY
* DOWNLOADING THIS SPECIFICATION, YOU ACCEPT THE TERMS AND CONDITIONS OF THE
* AGREEMENT. IF YOU ARE NOT WILLING TO BE BOUND BY IT, SELECT THE "DECLINE"
* BUTTON AT THE BOTTOM OF THIS PAGE. Specification: JSR-354 Money and Currency
* API ("Specification") Copyright (c) 2012-2013, Credit Suisse All rights
* reserved.
*/
package org.javamoney.regions.internal.data;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.GregorianCalendar;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TimeZone;
import javax.money.CurrencyUnit;
import javax.money.MonetaryCurrencies;
import org.javamoney.data.icu4j.CLDRCurrencyRegionData;
import org.javamoney.data.icu4j.CLDRCurrencyRegionData.Currency4Region;
import org.javamoney.data.icu4j.CLDRCurrencyRegionData.CurrencyRegionRecord;
import org.javamoney.regions.internal.ICURegionData;
import org.javamoney.regions.Region;
import org.javamoney.regions.RegionType;
import org.javamoney.regions.Regions;
import org.javamoney.validity.RelatedValidityInfo;
import org.javamoney.validity.RelatedValidityQuery;
import org.javamoney.validity.ValidityType;
import org.javamoney.validity.spi.RelatedValidityProviderSpi;
import org.slf4j.LoggerFactory;
/**
* This class provides {@link RelatedValidityInfo} based on the CLDC
* SupplementalData. It covers the relationship between ISO currencies and
* territories (countries).
*
* @author Anatole Tresch
*/
public class CLDRCurrencyValidity implements RelatedValidityProviderSpi {
/**
* The validity types supported, current
* {@code ValidityType.EXISTENCE, ValidityType.of("legal")}.
*/
private static final Set<ValidityType> FLAVORS = Collections
.unmodifiableSet(new HashSet<ValidityType>(
Arrays.asList(new ValidityType[] { ValidityType.EXISTENCE,
ValidityType.of("legal") })));
/**
* The item types supported, currently {@link javax.money.CurrencyUnit}.
*/
@SuppressWarnings("rawtypes")
private static final Set<Class> ITEM_TYPES = Collections
.unmodifiableSet(new HashSet<Class>(
Arrays.asList(new Class[] { CurrencyUnit.class })));
/**
* The related types supported, currently {@link Region}.
*/
@SuppressWarnings("rawtypes")
private static final Set<Class> RELATED_ITEM_TYPES = Collections
.unmodifiableSet(new HashSet<Class>(
Arrays.asList(new Class[] { Region.class })));
/*
* (non-Javadoc)
*
* @see javax.money.ext.spi.RelatedValidityProviderSpi#getProviderId()
*/
@Override
public String getProviderId() {
return "CLDR";
}
/*
* (non-Javadoc)
*
* @see javax.money.ext.spi.RelatedValidityProviderSpi#getValidityTypes()
*/
@Override
public Set<ValidityType> getValidityTypes(Class itemType, Class relatedType) {
if (itemType.equals(CurrencyUnit.class)
&& relatedType.equals(Region.class)) {
return FLAVORS;
}
return Collections.emptySet();
}
/*
* (non-Javadoc)
*
* @see javax.money.ext.spi.RelatedValidityProviderSpi#getItemTypes()
*/
@SuppressWarnings("rawtypes")
@Override
public Set<Class> getItemTypes() {
return ITEM_TYPES;
}
/*
* (non-Javadoc)
*
* @see
* javax.money.ext.spi.RelatedValidityProviderSpi#getRelatedItemTypes(java
* .lang.Class)
*/
@Override
public Set<Class> getRelatedItemTypes(Class itemType) {
if (itemType == null) {
throw new IllegalArgumentException("itemType required.");
}
if (!ITEM_TYPES.contains(itemType)) {
throw new IllegalArgumentException("Invalid item type for " + this
+ ": " + itemType);
}
// Only one item type (CurrencyUnit) is supported, so just return our
// static list...
return RELATED_ITEM_TYPES;
}
/*
* (non-Javadoc)
*
* @see
* javax.money.ext.spi.RelatedValidityProviderSpi#getRelatedValidityInfo
* (javax.money.ext.RelatedValidityQuery)
*/
@Override
public <T, R> Collection<RelatedValidityInfo<T, R>> getRelatedValidityInfo(
RelatedValidityQuery<T, R> query) {
if (!query.getItemType().equals(CurrencyUnit.class)
|| !(query.getRelatedToType().equals((Region.class)))) {
return Collections.emptySet();
}
if (!this.FLAVORS.contains(query.getValidityType())) {
return Collections.emptySet();
}
return getRelatedValidityInfoInternal(
(CurrencyUnit) query.getItem(),
query);
}
/**
* Method that finally collects, filters and evaluates the
* {@link Currency4Region} instances returned by
* {@link org.javamoney.data.icu4j.CLDRCurrencyRegionData} to return the appropriate result for
* {@code query}.
*
* @param currency
* The acquired currency
* @param query
* The {@link RelatedValidityQuery} instance
* @return the {@link RelatedValidityInfo} instances matching the
* {@code query}.
*/
@SuppressWarnings("unchecked")
protected <T, R> Collection<RelatedValidityInfo<T, R>> getRelatedValidityInfoInternal(
CurrencyUnit currency, RelatedValidityQuery<T, R> query) {
Collection<Currency4Region> regionData = CLDRCurrencyRegionData
.getInstance()
.getAllCurrencyData();
regionData = filterRequestedRegions(regionData, query);
List<RelatedValidityInfo<T, R>> result = new ArrayList<RelatedValidityInfo<T, R>>();
for (Currency4Region currencyData : regionData) {
for (CurrencyRegionRecord data : currencyData.getEntries()) {
if (query.getValidityType().equals(ValidityType.of("legal")) &&
!data.isLegalTender()) {
continue;
}
if (currency != null
&& !(data.getCurrencyCode().equals(
currency.getCurrencyCode()))) {
continue;
}
for (String tzName : ICURegionData.get()
.getRegion(data.getRegionCode()).getTimezoneIds()) {
TimeZone tz = TimeZone.getTimeZone(tzName);
if (tz.getID().equals("GMT")) {
// the timezone was not recognized by the JDK, ignore
// it!
// TODO think about a more intelligent variant here...
continue;
}
int[] from = data.getFromYMD();
Calendar fromCal = null;
Calendar toCal = null;
if (from != null) {
fromCal = new GregorianCalendar(tz);
fromCal.clear();
fromCal.setTimeZone(tz);
fromCal.set(from[0], from[1], from[2]);
}
int[] to = data.getToYMD();
if (to != null) {
toCal = new GregorianCalendar(tz);
toCal.clear();
toCal.setTimeZone(tz);
toCal.set(to[0], to[1], to[2]);
}
try {
RegionType regionType = RegionType.of(ICURegionData
.get()
.getRegion(data.getRegionCode())
.getRegionType()
.name());
RelatedValidityInfo<T, R> vi = new RelatedValidityInfo<T, R>(
(T) MonetaryCurrencies.getCurrency(data.getCurrencyCode()),
(R) Regions.getRegion(regionType,
data.getRegionCode()),
query.getValidityType(),
"CLDR", fromCal, toCal, tzName, data);
result.add(vi);
} catch (Exception e) {
LoggerFactory.getLogger(getClass()).error(
"Error assembling RelatedValidityInfo for Currency->Region", e);
}
}
}
}
result = filterRequestedTimeRange(result, query);
return result;
}
/**
* Filter out the items which do not match the query's RelatedTo predicate
* (if any).
*
* @param regionData
* the CLDR data items.
* @param query
* the query.
* @return the filered collection for further processing.
*/
private <T, R> Collection<Currency4Region> filterRequestedRegions(
Collection<Currency4Region> regionData,
RelatedValidityQuery<T, R> query) {
if (query.getRelatedToPredicate() == null) {
return regionData;
}
List<Currency4Region> filtered = new ArrayList<Currency4Region>();
for (Currency4Region currency4Region : regionData) {
try {
RegionType regionType = RegionType.of(ICURegionData.get()
.getRegion(currency4Region.getRegionCode())
.getRegionType()
.name());
Region region = Regions.getRegion(regionType,
currency4Region.getRegionCode());
if (region == null) {
continue;
}
if (query.getRelatedToPredicate().test(
(R) region)) {
filtered.add(currency4Region);
}
} catch (Exception e) {
LoggerFactory.getLogger(getClass()).error(
"Error assembling ValidityInfo for Region", e);
}
}
return filtered;
}
/**
* Filter out the items which do not match the query's RelatedTo predicate
* (if any).
*
* @param validities
* the validities prepared so far.
* @param query
* the query.
* @return the filtered collection matching the required time range.
*/
private <T, R> List<RelatedValidityInfo<T, R>> filterRequestedTimeRange(
List<RelatedValidityInfo<T, R>> validities,
RelatedValidityQuery<T, R> query) {
if (query.isTimeUnbounded()) {
return validities;
}
List<RelatedValidityInfo<T, R>> filtered = new ArrayList<RelatedValidityInfo<T, R>>();
for (RelatedValidityInfo<T, R> info : validities) {
Long from = query.getFrom();
if (from != null && info.getFromTimeInMillis() != null
&& from > info.getFromTimeInMillis()) {
continue;
}
Long to = query.getTo();
if (to != null && info.getToTimeInMillis() != null
&& to < info.getToTimeInMillis()) {
continue;
}
filtered.add(info);
}
return filtered;
}
}