public final class CLDRCurrencyRegionData extends AbstractXmlResourceLoaderListener {

    private static final Logger LOG = Logger.getLogger(CLDRCurrencyRegionData.class.getName());
    private static final CLDRCurrencyRegionData INSTANCE = createInstance();

    /*
     * <region iso3166="AD"> <currency iso4217="EUR" from="1999-01-01"/>
     * <currency iso4217="ESP" from="1873-01-01" to="2002-02-28"/> <currency
     * iso4217="FRF" from="1960-01-01" to="2002-02-17"/> <currency iso4217="ADP"
     * from="1936-01-01" to="2001-12-31"/> </region>
     */
    private Map<String, Currency4Region> currencyRegionData;

    private CLDRCurrencyRegionData() throws MalformedURLException {
        Bootstrap.getService(LoaderService.class).addLoaderListener(this, "CLDR-SupplementalData");
        try {
            Bootstrap.getService(LoaderService.class).loadData("CLDR-SupplementalData");
        } catch (IOException e) {
            LOG.log(Level.SEVERE, "Error loading CLDR supplemental data.", e);
        }
    }

    public static CLDRCurrencyRegionData getInstance() {
        return INSTANCE;
    }

    private static CLDRCurrencyRegionData createInstance() {
        try {
            return new CLDRCurrencyRegionData();
        } catch (MalformedURLException e) {
            throw new IllegalStateException(
                    "Error creating CLDR SupplementalData.", e);
        }
    }

    @Override
    protected void loadDocument(Document document) {
        // load currencies
        Map<String, Currency4Region> data = new HashMap<>();
        try {
            NodeList nl = document.getDocumentElement()
                    .getElementsByTagName("region");
            for (int i = 0; i < nl.getLength(); i++) {
                Node childNode = nl.item(i);
                String regionCode = childNode.getAttributes()
                        .getNamedItem("iso3166").getNodeValue();
                Currency4Region currencyEntry = new Currency4Region(
                        regionCode, childNode);
                data.put(regionCode, currencyEntry);
            }
            this.currencyRegionData = data;
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public Currency4Region getCurrencyData(String countryCode) {
        if (this.currencyRegionData == null) {
            return null;
        }
        return this.currencyRegionData.get(countryCode);
    }

    public Collection<Currency4Region> getAllCurrencyData() {
        if (this.currencyRegionData == null) {
            return Collections.emptySet();
        }
        return this.currencyRegionData.values();
    }

    public boolean isCurrencyDataAvailable(String countryCode) {
        if (this.currencyRegionData == null) {
            return false;
        }
        return this.currencyRegionData.containsKey(countryCode);
    }

    public String getSource() {
        return "http://unicode.org/repos/cldr/trunk/common/supplemental/supplementalData.xml";
    }


    public static final class Currency4Region implements Serializable {

        private String regionCode;
        private List<CurrencyRegionRecord> entries = new ArrayList<>();

        public Currency4Region(String regionCode, Node item) {
            this.regionCode = regionCode;
            NodeList nl = item.getChildNodes();
            for (int i = 0; i < nl.getLength(); i++) {
                Node childNode = nl.item(i);
                if (childNode.getNodeType() == Node.ELEMENT_NODE) {
                    entries.add(new CurrencyRegionRecord(regionCode, childNode));
                }
            }
            // <region iso3166="AF">
            // <currency iso4217="AFN" from="2002-10-07"/>
            // <currency iso4217="AFA" from="1927-03-14" to="2002-12-31"/>
        }

        public String getRegionCode() {
            return regionCode;
        }


        public List<CurrencyRegionRecord> getEntries() {
            return Collections.unmodifiableList(this.entries);
        }

        /*
         * (non-Javadoc)
         *
         * @see java.lang.Object#toString()
         */
        @Override
        public String toString() {
            return "Currency4Region [regionCode=" + regionCode + ", entries="
                    + entries + "]";
        }

    }

    public static final class CurrencyRegionRecord implements Serializable {
        private String regionCode;
        private String currencyCode;
        private int[] fromYMD;
        private int[] toYMD;
        private boolean legalTender = true;

        public CurrencyRegionRecord(String regionCode, Node item) {
            // <region iso3166="AF">
            // <currency iso4217="AFN" from="2002-10-07"/>
            // <currency iso4217="AFA" from="1927-03-14" to="2002-12-31"/>
            this.regionCode = regionCode;
            NamedNodeMap attrs = item.getAttributes();
            this.currencyCode = attrs.getNamedItem("iso4217").getNodeValue();
            if (attrs.getNamedItem("from") != null) {
                String from = attrs.getNamedItem("from").getNodeValue();
                String[] dtParts = from.split("-");
                this.fromYMD = new int[3];
                this.fromYMD[0] = Integer.parseInt(dtParts[0]);
                this.fromYMD[1] = Integer.parseInt(dtParts[1]);
                this.fromYMD[2] = Integer.parseInt(dtParts[2]);
            }
            if (attrs.getNamedItem("to") != null) {
                String to = attrs.getNamedItem("to").getNodeValue();
                String[] dtParts = to.split("-");
                this.toYMD = new int[3];
                this.toYMD[0] = Integer.parseInt(dtParts[0]);
                this.toYMD[1] = Integer.parseInt(dtParts[1]);
                this.toYMD[2] = Integer.parseInt(dtParts[2]);
            }
            if (attrs.getNamedItem("tender") != null) {
                this.legalTender = Boolean.parseBoolean(attrs.getNamedItem(
                        "tender").getNodeValue());
            }
        }

        public String getRegionCode() {
            return regionCode;
        }

        /**
         * @return the currencyCode
         */
        public final String getCurrencyCode() {
            return currencyCode;
        }

        /**
         * @return the fromYMD
         */
        public final int[] getFromYMD() {
            return fromYMD;
        }

        /**
         * @return the toYMD
         */
        public final int[] getToYMD() {
            return toYMD;
        }

        public boolean isLegalTender() {
            return legalTender;
        }

        /*
         * (non-Javadoc)
         *
         * @see java.lang.Object#toString()
         */
        @Override
        public String toString() {
            return "CurrencyRegionRecord [regionCode=" + regionCode
                    + ", currencyCode=" + currencyCode + ", fromYMD="
                    + Arrays.toString(fromYMD) + ", toYMD="
                    + Arrays.toString(toYMD) + "]";
        }

    }

} public static final class Currency4Region implements Serializable {

    private String regionCode;
    private List<CurrencyRegionRecord> entries = new ArrayList<>();

    public Currency4Region(String regionCode, Node item) {
        this.regionCode = regionCode;
        NodeList nl = item.getChildNodes();
        for (int i = 0; i < nl.getLength(); i++) {
            Node childNode = nl.item(i);
            if (childNode.getNodeType() == Node.ELEMENT_NODE) {
                entries.add(new CurrencyRegionRecord(regionCode, childNode));
            }
        }
        // <region iso3166="AF">
        // <currency iso4217="AFN" from="2002-10-07"/>
        // <currency iso4217="AFA" from="1927-03-14" to="2002-12-31"/>
    }

    public String getRegionCode() {
        return regionCode;
    }


    public List<CurrencyRegionRecord> getEntries() {
        return Collections.unmodifiableList(this.entries);
    }

    /*
     * (non-Javadoc)
     *
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        return "Currency4Region [regionCode=" + regionCode + ", entries="
                + entries + "]";
    }

} public static final class CurrencyRegionRecord implements Serializable {
    private String regionCode;
    private String currencyCode;
    private int[] fromYMD;
    private int[] toYMD;
    private boolean legalTender = true;

    public CurrencyRegionRecord(String regionCode, Node item) {
        // <region iso3166="AF">
        // <currency iso4217="AFN" from="2002-10-07"/>
        // <currency iso4217="AFA" from="1927-03-14" to="2002-12-31"/>
        this.regionCode = regionCode;
        NamedNodeMap attrs = item.getAttributes();
        this.currencyCode = attrs.getNamedItem("iso4217").getNodeValue();
        if (attrs.getNamedItem("from") != null) {
            String from = attrs.getNamedItem("from").getNodeValue();
            String[] dtParts = from.split("-");
            this.fromYMD = new int[3];
            this.fromYMD[0] = Integer.parseInt(dtParts[0]);
            this.fromYMD[1] = Integer.parseInt(dtParts[1]);
            this.fromYMD[2] = Integer.parseInt(dtParts[2]);
        }
        if (attrs.getNamedItem("to") != null) {
            String to = attrs.getNamedItem("to").getNodeValue();
            String[] dtParts = to.split("-");
            this.toYMD = new int[3];
            this.toYMD[0] = Integer.parseInt(dtParts[0]);
            this.toYMD[1] = Integer.parseInt(dtParts[1]);
            this.toYMD[2] = Integer.parseInt(dtParts[2]);
        }
        if (attrs.getNamedItem("tender") != null) {
            this.legalTender = Boolean.parseBoolean(attrs.getNamedItem(
                    "tender").getNodeValue());
        }
    }

    public String getRegionCode() {
        return regionCode;
    }

    /**
     * @return the currencyCode
     */
    public final String getCurrencyCode() {
        return currencyCode;
    }

    /**
     * @return the fromYMD
     */
    public final int[] getFromYMD() {
        return fromYMD;
    }

    /**
     * @return the toYMD
     */
    public final int[] getToYMD() {
        return toYMD;
    }

    public boolean isLegalTender() {
        return legalTender;
    }

    /*
     * (non-Javadoc)
     *
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        return "CurrencyRegionRecord [regionCode=" + regionCode
                + ", currencyCode=" + currencyCode + ", fromYMD="
                + Arrays.toString(fromYMD) + ", toYMD="
                + Arrays.toString(toYMD) + "]";
    }

} public class ICURegionData {

	private static final ICURegionData INSTANCE = createInstance();
	
	private Set<RegionType> regionTypes = new HashSet<RegionType>();

	private Map<String, ICURegion> regions = new HashMap<String, ICURegion>();

	// CLDR/world/continents/territories

	private ICURegionData() {
		reload();
	}
	
	public static ICURegionData get() {
		return INSTANCE;
	}
	
	private static ICURegionData createInstance() {
		try {
			return new ICURegionData();
		} catch (Exception e) {
			throw new IllegalStateException(
					"Error creating CLDR ICURegionProvider.", e);
		}
	}

	private void reload() {
		regionTypes.clear();
		ICURegion world = null;
		for (RegionType rt : RegionType
				.values()) {
			this.regionTypes.add(rt);
			Set<Region> icuRegions = Region
					.getAvailable(RegionType
							.valueOf(rt.name()));
			for (Region icuRegion : icuRegions) {
				RegionType type = icuRegion.getType();
				regionTypes.add(type);
				ICURegion region = new ICURegion(icuRegion);
				regions.put(region.getRegionCode(), region);
				if (region.getRegionType().equals(RegionType.WORLD)) {
					world = region;
				}
			}
		}
		Set<Region> icuRegions = Region
				.getAvailable(RegionType.WORLD);
	}

	public Collection<RegionType> getRegionTypes() {
		return Collections.unmodifiableSet(regionTypes);
	}

	public ICURegion getRegion(RegionType type, String code) {
		Region icuRegion = Region
				.getInstance(code);
		if (icuRegion != null) {
			ICURegion region = this.regions.get(code);
			if (region.getRegionType().equals(type)) {
				return region;
			}
		}
		return null;
	}
	
	public ICURegion getRegion(String code) {
		Region icuRegion = Region
				.getInstance(code);
		return this.regions.get(code);
	}

	public Collection<ICURegion> getRegions(RegionType type) {
		Set<ICURegion> result = new HashSet<ICURegion>();
		for (ICURegion region : regions.values()) {
			if (region.getRegionType().equals(type)) {
				result.add(region);
			}
		}
		return result;
	}

	public ICURegion getRegion(RegionType type, int numericId) {
		for (ICURegion region : regions.values()) {
			if (region.getRegionType().equals(type)
					&& region.getNumericCode() == numericId) {
				return region;
			}
		}
		return null;
	}

	public ICURegion getRegion(Locale locale) {
		return regions.get(locale.getCountry());
	}

} public interface Region {

	/**
	 * Get the region's type.
	 * 
	 * @return the region's type, never {@code null}.
	 */
	public RegionType getRegionType();

	/**
	 * Access the region's code. The code is unique in combination with the
	 * region type.
	 * 
	 * @return the region's type, never {@code null}.
	 */
	public String getRegionCode();

	/**
	 * Get the region's numeric code. If not defined -1 is returned.
	 * 
	 * @return the numeric region code, or {@code -1}.
	 */
	public int getNumericRegionCode();

	/**
	 * Return the time zones valid for this region (in the long form, e.g.
	 * Europe/Berlin). If the region has subregions, by default, the timezones
	 * returned should be the transitive closure of all timezones of all child
	 * regions. Nevertheless there might be use cases were the child regions
	 * must not transitively define the parents timezones, so transitivity is
	 * not enforced by this JSR.<br/>
	 * Additionally all ids returned should be known by {@link java.util.TimeZone}.
	 * 
	 * @return the timezone ids of this region, never {@code null}.
	 */
	public Collection<String> getTimezoneIds();

	/**
	 * Return according {@link java.util.Locale}, if possible.
	 * 
	 * @return the corresponding {@link java.util.Locale} for that {@link org.javamoney.regions.Region}, may
	 *         also return {@code null}.
	 */
	public Locale getLocale();

} public final class RegionType implements Serializable, Comparable<RegionType> {

	/**
	 * serialVersionUID.
	 */
	private static final long serialVersionUID = -921476180849747654L;

	/** Shared cache of types instantiated with the #of(String) method. */
	private static final Map<String, RegionType> TYPE_CACHE = new ConcurrentHashMap<String, RegionType>();

	/** Type representing a continent. */
	public static final RegionType CONTINENT = of("CONTINENT");

	/**
	 * Type representing a region whose code has been deprecated, usually due to
	 * a country splitting into multiple territories or changing its name.
	 */
	public static final RegionType DEPRECATED = of("DEPRECATED");
	/**
	 * Type representing a grouping of territories that is not to be used in the
	 * normal WORLD/CONTINENT/SUBCONTINENT/TERRITORY containment tree.
	 */
	public static final RegionType GROUPING = of("GROUPING");
	/** Type representing a sub-continent. */
	public static final RegionType SUBCONTINENT = of("SUBCONTINENT");
	/** Type representing a territory. */
	public static final RegionType TERRITORY = of("TERRITORY");
	/** Type representing the unknown region. */
	public static final RegionType UNKNOWN = of("UNKNOWN");
	/** Type representing the whole world. */
	public static final RegionType WORLD = of("WORLD");

	/** The type's id. */
	private String id;

	/**
	 * Constructor for the instance.
	 * 
	 * @param id
	 *            The type's identifier, not {@code null}
	 */
	public RegionType(String id) {
		if (id == null || id.isEmpty()) {
			throw new IllegalArgumentException("id null or empty.");
		}
		this.id = id;
	}

	/**
	 * Access an {@link org.javamoney.regions.RegionType} by id. Instances with the same id will be
	 * shared.
	 * 
	 * @param id
	 *            The rate's identifier.
	 * @return the corresponding type, never {@code null}.
	 */
	public static RegionType of(String id) {
		RegionType type = TYPE_CACHE.get(id);
		if (type != null) {
			return type;
		}
		type = new RegionType(id);
		TYPE_CACHE.put(id, type);
		return type;
	}

	/**
	 * Access all region types.
	 * 
	 * @return
	 */
	public static Collection<RegionType> getTypes() {
		return TYPE_CACHE.values();
	}

	/**
	 * Get the (non localized) identifier of the {@link ExchangeRateType}.
	 * 
	 * @return The identifier, never null.
	 */
	public String getId() {
		return id;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((id == null) ? 0 : id.hashCode());
		return result;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		RegionType other = (RegionType) obj;
		if (id == null) {
			if (other.id != null)
				return false;
		} else if (!id.equals(other.id))
			return false;
		return true;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return "RegionType [id=" + id + "]";
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Comparable#compareTo(java.lang.Object)
	 */
	@Override
	public int compareTo(RegionType o) {
		if (o == null) {
			return -1;
		}
		return this.id.compareTo(o.id);
	}

} public final class Regions {

	/**
	 * The spi currently active, use {@link java.util.ServiceLoader} to register an
	 * implementation.
	 */
	private static final RegionsSingletonSpi REGION_SPI = Bootstrap.getService(RegionsSingletonSpi.class);

	/**
	 * Private singleton constructor.
	 */
	private Regions() {
	}

	/**
	 * Access the currently available {@link RegionType} instances.
	 * 
	 * @return the currently available {@link RegionType} instances, never
	 *         {@code null}.
	 */
	public static Set<RegionType> getRegionTypes() {
		return REGION_SPI.getRegionTypes();
	}

	/**
	 * Access a region by region type and the region code.
	 * 
	 * @param type
	 *            the region type, not null.
	 * @param code
	 *            the region code, not null.
	 * @return the region found.
	 * @throws IllegalArgumentException
	 *             if the required {@link Region} is not available.
	 */
	public static Region getRegion(RegionType type, String code) {
		return REGION_SPI.getRegion(type, code);
	}

	/**
	 * Access a region by region type and the numeric region code.
	 * 
	 * @param type
	 *            the region type, not null.
	 * @param code
	 *            the numeric region code, not null.
	 * @return the region found.
	 * @throws IllegalArgumentException
	 *             if the required {@link Region} is not available, or
	 *             ambiguous.
	 */
	public static Region getRegion(RegionType type, int code) {
		return REGION_SPI.getRegion(type, code);
	}

	/**
	 * Access a region using a {@link java.util.Locale}.
	 * 
	 * @param locale
	 *            The required locale.
	 * @return the corresponding region, or {@code null}.
	 */
	public static Region getRegion(Locale locale) {
		return REGION_SPI.getRegion(locale);
	}

	/**
	 * Access all regions of a given region type.
	 * 
	 * @param type
	 *            the region type, not {@code null}.
	 * @return the regions found, never {@code null}.
	 */
	public static Collection<Region> getRegions(RegionType type) {
		return REGION_SPI.getRegions(type);
	}

	/**
	 * Get the extended data types, that can be accessed from this
	 * {@link Region} by calling {@link #getRegionData(Class)}.
	 * 
	 * @return the collection of supported region data, may be {@code empty},
	 *         but never {@code null}.
	 */
	public static <T> Collection<Class> getExtendedRegionDataTypes(Region region) {
		return REGION_SPI.getExtendedRegionDataTypes(region);
	}

	/**
	 * Access the additional region data, using its type.
	 * 
	 * @param type
	 *            The region data type, not {@code null}.
	 * @return the corresponding data item.
	 * @throws IllegalArgumentException
	 *             if the type passed is not supported. See
	 *             {@link #getRegionDataTypes()}.
	 */
	public static <T> T getExtendedRegionData(Region region, Class<T> type) {
		return REGION_SPI.getExtendedRegionData(region, type);
	}

	/**
	 * Access the defined region trees.
	 * 
	 * @see #getRegionTree(String)
	 * @return the set of defined region trees, accessible calling
	 *         {@link #getRegionTree(String)}, never {@code null}.
	 */
	public static Set<String> getRegionTreeIds() {
		return REGION_SPI.getRegionTreeIds();
	}

	/**
	 * Get the given region tree, for a list call {@link #getRegionTreeIds()}
	 * beforehand.
	 * 
	 * @see #getRegionTreeIds()
	 * @param id
	 *            The tree name
	 * @return the region tree.
	 * @throws IllegalArgumentException
	 *             if no such region tree is available.
	 */
	public static RegionTreeNode getRegionTree(String treeId) {
		return REGION_SPI.getRegionTree(treeId);
	}


	/**
	 * This class represents the default implementation of
	 * {@link MonetaryConversionSpi} used always when no alternative is
	 * registered within the {@link java.util.ServiceLoader}.
	 * 
	 * @author Anatole Tresch
	 * 
	 */
	private final static class DefaultMonetaryRegionsSpi implements
			RegionsSingletonSpi {

		@Override
		public Region getRegion(RegionType type, int numericId) {
			return null;
		}

		@Override
		public Region getRegion(RegionType type, String code) {
			return null;
		}

		@Override
		public Set<RegionType> getRegionTypes() {
			return Collections.emptySet();
		}

		@Override
		public Set<String> getRegionTreeIds() {
			return Collections.emptySet();
		}

		@Override
		public RegionTreeNode getRegionTree(String treeId) {
			throw new IllegalArgumentException(
					"DefaultProvider: no such region tree: " + treeId);
		}

		@Override
		public Region getRegion(Locale locale) {
			return null;
		}

		@Override
		public Collection<Region> getRegions(RegionType type) {
			return Collections.emptySet();
		}

		@Override
		public Collection<Class> getExtendedRegionDataTypes(Region region) {
			return Collections.emptySet();
		}

		@Override
		public <T> T getExtendedRegionData(Region region, Class<T> type) {
			throw new IllegalArgumentException("Unsupported data type for "
					+ this
					+ ", use one of " + getExtendedRegionDataTypes(region));
		}
	}
} public final class RelatedValidityInfo<T, R> extends ValidityInfo<T> {

	/**
	 * Serial version UID.
	 */
	private static final long serialVersionUID = 1258686258819748871L;

	/**
	 * THe reference type to which this validity is related.
	 */
	private final R relatedItem;

	/**
	 * Creates an instance of ValidityInfo.
	 * 
	 * @param item
	 *            the item, not null.
	 * @param referenceItem
	 *            , the reference, not null.
	 * @param validitySource
	 *            the validity source.
	 * @param from
	 *            the calendar instance, defining the start of the validity
	 *            range.
	 * @param to
	 *            the calendar instance, defining the end of the validity range.
	 */
	public RelatedValidityInfo(T item, R referenceItem,
			ValidityType validityType, String validitySource,
			Calendar from, Calendar to, String targetTimezoneId, Object userData) {
		super(item, validityType, validitySource, from, to, targetTimezoneId,
				userData);
		if (referenceItem == null) {
			throw new IllegalArgumentException("Reference Item required.");
		}
		this.relatedItem = referenceItem;
	}

	/**
	 * Creates an instance of ValidityInfo.
	 * 
	 * @param item
	 *            the item, not null.
	 * @param referenceItem
	 *            , the reference, not null.
	 * @param validitySource
	 *            the validity source.
	 * @param from
	 *            the UTC timestamp, defining the start of the validity range.
	 * @param to
	 *            the UTC timestamp, defining the end of the validity range.
	 */
	public RelatedValidityInfo(T item, R referenceItem,
			ValidityType validityType, String validitySource,
			Long from, Long to, String targetTimezoneId, Object userData) {
		super(item, validityType, validitySource, from, to, targetTimezoneId,
				userData);
		if (referenceItem == null) {
			throw new IllegalArgumentException("Reference Item required.");
		}
		this.relatedItem = referenceItem;
	}

	/**
	 * Access the type or range for which the item T is valid.
	 * 
	 * @return the ference instance, never null.
	 */
	public R getRelatedtem() {
		return relatedItem;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = super.hashCode();
		result = prime * result
				+ ((relatedItem == null) ? 0 : relatedItem.hashCode());
		return result;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (getClass() != obj.getClass()) {
			return false;
		}
		Calendar cal;
		@SuppressWarnings("rawtypes")
		RelatedValidityInfo other = (RelatedValidityInfo) obj;
		if (!super.equals(other)) {
			return false;
		}
		if (relatedItem == null) {
			if (other.relatedItem != null) {
				return false;
			}
		} else if (!relatedItem.equals(other.relatedItem)) {
			return false;
		}
		return true;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Comparable#compareTo(java.lang.Object)
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	@Override
	public int compareTo(ValidityInfo other) {
		if (this == other) {
			return 0;
		}
		if (other == null) {
			return -1;
		}
		int compare = super.compareTo(other);
		if (compare != 0) {
			return compare;
		}
		if (!(other instanceof RelatedValidityInfo)) {
			return -1;
		}
		RelatedValidityInfo otherRef = (RelatedValidityInfo) other;

		if (compare == 0 && relatedItem instanceof Comparable) {
			if (relatedItem == null) {
				if (otherRef.relatedItem != null) {
					compare = 1;
				}
			} else {
				if (otherRef.relatedItem == null) {
					compare = -1;
				} else {
					compare = ((Comparable) relatedItem)
							.compareTo(otherRef.relatedItem);
				}
			}
		}
		return compare;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return "RelatedValidityInfo [item="
				+ getItem()
				+ ", ref="
				+ relatedItem
				+ ", from="
				+ formatTime(getFromTimeInMillis())
				+ ", to="
				+ formatTime(getToTimeInMillis())
				+ ", userData="
				+ (getUserData() == null ? "-" : getUserData().getClass()
						.getName()) + "]";
	}

	/**
	 * Format the given time as {@code "YYYY-MM-dd timezoneID"}.
	 * 
	 * @param time
	 *            the time
	 * @return the formatted time, or {@code '-'}, when time is {@code null}.
	 */
	private String formatTime(Long time) {
		if (time == null) {
			return "-";
		}
		StringBuilder b = new StringBuilder();
		GregorianCalendar cal = new GregorianCalendar(
				TimeZone.getTimeZone("GMT"));
		cal.setTimeInMillis(time);
		b.append(cal.get(Calendar.YEAR)).append("-")
				.append(cal.get(Calendar.MONTH)).append("-")
				.append(cal.get(Calendar.DAY_OF_MONTH));
		b.append(" ").append(cal.getTimeZone().getID());
		return b.toString();
	}

} public class RelatedValidityQuery<T, R> extends ValidityQuery<T>{

    /**
     * The base item.
     */
    private Class<R> relatedToType;
    /**
     * Get the predicate to filter the validity items to be returned.
     */
    private MonetaryPredicate<R> relatedToPredicate;

    /**
     * Constructor.
     *
     * @param validityType     the validity type, not {@code null}.
     * @param itemType         the item type, not {@code null}.
     * @param item             the item constraint.
     * @param from             the starting UTC timestamp.
     * @param to               the ending UTC timestamp.
     * @param targetTimezoneId the target timezone ID.
     */
    protected RelatedValidityQuery(ValidityType validityType, Class<T> itemType, T item, String validityProviderId,
                                   Long from, Long to, String targetTimezoneId, Class<R> relatedToType,
                                   MonetaryPredicate<R> relatedToPredicate){
        super(validityType, itemType, item, validityProviderId, from, from, targetTimezoneId);
        if(relatedToType == null){
            throw new IllegalArgumentException("relatedToType required");
        }
        this.relatedToType = relatedToType;
        this.relatedToPredicate = relatedToPredicate;
    }

    /**
     * Access the related items for which the validities are queried.
     *
     * @return the item The item for which the validity information is queried.
     * @see #getRelatedToType()
     */
    public final MonetaryPredicate<R> getRelatedToPredicate(){
        return relatedToPredicate;
    }

    /**
     * Access the item class for which the validities are queried. It is also
     * possible query only for a related {@link Class} instance, which will
     * select all validities of all items of that type.<br/>
     * If {@link #relatedToInstance} was set, this method returns the class of
     * {@link #relatedToInstance}, so implementors of
     * {@link ValidityProviderSpi} must additionally explicitly check, if
     * {@link #getRelatedToInstance()} is returning any non {@code null} value.
     *
     * @return the item The item for which the validity information is queried.
     * @see #getRelatedToInstance()
     */
    public final Class<R> getRelatedToType(){
        return relatedToType;
    }

    @Override
    public String toString(){
        return "RelatedValidityQuery [itemType=" + getItemType() + ", item=" + getItem() + "relatedType=" +
                getItemType() + ", relatedPredicate=" + getRelatedToPredicate() + ", from=" + getFrom() + ", to=" +
                getTo() + ", targetTimezoneId=" + getTargetTimezoneId() + ", validitySource=" + getValiditySource() +
                ", validityType=" + getValidityType() + "]";
    }

    /**
     * For accessing {@link ValidityInfo} instances from the {@link Validities}
     * singleton, instances of this class must be created an configured.
     * <p>
     * This class is immutable, thread-safe and {@link Serializable}.
     *
     * @param <T> the item type, on which validities are defined.
     * @author Anatole Tresch
     */
    public static class Builder<T, R> extends ValidityQuery.Builder<T>{

        /**
         * The base item.
         */
        protected Class<R> relatedToType;
        /**
         * Get the predicate to filter the validity items to be returned.
         */
        protected MonetaryPredicate<R> relatedToPredicate;

        /**
         * Constructor.
         *
         * @param validityType the validity type, not {@code null}.
         * @param itemType     the item type, not {@code null}.
         */
        public Builder(ValidityType validityType, Class<T> itemType, Class<R> relatedToType){
            super();
            withItemType(itemType);
            withValidityType(validityType);
            withRelatedToType(relatedToType);
        }

        /**
         * Constructor.
         */
        public Builder(){
            super();
        }

        /**
         * Sets the related item class for which the related validities are
         * queried. It is also possible query only for a related {@link Class}
         * instance, which will select all validities of all items of that type.<br/>
         * If {@link #relatedToInstance} was set, this method returns the class
         * of {@link #relatedToInstance}, so implementors of
         * {@link ValidityProviderSpi} must additionally explicitly check, if
         * {@link #getRelatedToInstance()} is returning any non {@code null}
         * value.
         *
         * @return the item The item for which the validity information is
         * queried.
         * @see #getRelatedToInstance()
         */
        public Builder<T,R> withRelatedToType(Class<R> relatedToType){
            if(relatedToType == null){
                throw new IllegalArgumentException("relatedToType required");
            }
            this.relatedToType = relatedToType;
            return this;
        }

        /**
         * Sets a predicate to constraint the relatedTo items returned.
         *
         * @param relatedToPredicate
         * @return the Builder, for chaining.
         */
        public Builder<T,R> withRelatedToPredicate(MonetaryPredicate<R> relatedToPredicate){
            this.relatedToPredicate = relatedToPredicate;
            return this;
        }

        /**
         * Builds a new instance of {@link RelatedValidityQuery}. throws
         * IllegalArgumentException if the data provided is not sufficient zo
         * of the instance.
         */
        @Override
        public RelatedValidityQuery<T,R> build(){
            return new RelatedValidityQuery<T,R>(validityType, itemType, item, validitySource, from, to,
                                                 targetTimezoneId, relatedToType, relatedToPredicate);
        }

        /*
         * (non-Javadoc)
         *
         * @see javax.money.ext.ValidityQuery.Builder#withFrom(long)
         */
        @Override
        public RelatedValidityQuery.Builder<T,R> withFrom(long timestamp){
            super.withFrom(timestamp);
            return this;
        }

        /*
         * (non-Javadoc)
         *
         * @see javax.money.ext.ValidityQuery.Builder#withItem(java.lang.Object)
         */
        @Override
        public RelatedValidityQuery.Builder<T,R> withItem(T item){
            super.withItem(item);
            return this;
        }

        /*
         * (non-Javadoc)
         *
         * @see
         * javax.money.ext.ValidityQuery.Builder#withItemType(java.lang.Class)
         */
        @Override
        public RelatedValidityQuery.Builder<T,R> withItemType(Class<T> itemType){
            super.withItemType(itemType);
            return this;
        }

        /*
         * (non-Javadoc)
         *
         * @see
         * javax.money.ext.ValidityQuery.Builder#withTargetTimezone(java.util
         * .TimeZone)
         */
        @Override
        public RelatedValidityQuery.Builder<T,R> withTargetTimezone(TimeZone timezone){
            super.withTargetTimezone(timezone);
            return this;
        }

        /*
         * (non-Javadoc)
         *
         * @see
         * javax.money.ext.ValidityQuery.Builder#withTargetTimezoneID(java.lang
         * .String)
         */
        @Override
        public RelatedValidityQuery.Builder<T,R> withTargetTimezoneID(String timezoneÎD){
            super.withTargetTimezoneID(timezoneÎD);
            return this;
        }

        /*
         * (non-Javadoc)
         *
         * @see javax.money.ext.ValidityQuery.Builder#withTo(long)
         */
        @Override
        public RelatedValidityQuery.Builder<T,R> withTo(long timestamp){
            super.withTo(timestamp);
            return this;
        }

        /*
         * (non-Javadoc)
         *
         * @see
         * javax.money.ext.ValidityQuery.Builder#withValiditySource(java.lang
         * .String)
         */
        @Override
        public RelatedValidityQuery.Builder<T,R> withValiditySource(String source){
            super.withValiditySource(source);
            return this;
        }

        /*
         * (non-Javadoc)
         *
         * @see
         * javax.money.ext.ValidityQuery.Builder#withValidityType(javax.money
         * .ext.ValidityType)
         */
        @Override
        public RelatedValidityQuery.Builder<T,R> withValidityType(ValidityType validityType){
            super.withValidityType(validityType);
            return this;
        }

        /*
         * (non-Javadoc)
         *
         * @see javax.money.ext.ValidityQuery.Builder#toString()
         */
        @Override
        public String toString(){
            return "RelatedValidityQuery.Builder [itemType=" + itemType + ", item=" + item + "relatedType=" +
                    relatedToType + ", relatedPredicate=" + relatedToPredicate + ", from=" + from + ", to=" + to +
                    ", targetTimezoneId=" + targetTimezoneId + ", validitySource=" + validitySource +
                    ", validityType=" + validityType + "]";
        }
    }

} public final class ValidityType implements Serializable,
		Comparable<ValidityType> {
	/**
	 * serialVersionUID.
	 */
	private static final long serialVersionUID = -7505497771490888058L;
	/** The id of this type. */
	private final String id;
	/** The cache of types. */
	private static final Map<String, ValidityType> CACHED_INSTANCES = new ConcurrentHashMap<String, ValidityType>();
	/** Default validity modelling the temporal existance of an item. */
	public static final ValidityType EXISTENCE = new ValidityType("EXISTENCE");
	/** Used for quering validities of any type. */
	public static final ValidityType ANY = new ValidityType("ANY");

	/**
	 * Creates a new instance.
	 * 
	 * @param id
	 *            The validity type identifier.
	 * @return The new validity type.
	 */
	public static ValidityType of(String id) {
		if (id == null) {
			throw new IllegalArgumentException("id required.");
		}
		ValidityType instance = CACHED_INSTANCES.get(id);
		if (instance == null) {
			instance = new ValidityType(id);
			CACHED_INSTANCES.put(id, instance);
		}
		return instance;
	}

	/**
	 * Constructs a new instance of an {@link ValidityType}.
	 * 
	 * @param id
	 *            The id of this type instance, never null.
	 */
	public ValidityType(String id) {
		if (id == null) {
			throw new IllegalArgumentException("Id must not be null.");
		}
		this.id = id;
	}

	/**
	 * Get the identifier of this instance.
	 * 
	 * @return The identifier, never null.
	 */
	public String getId() {
		return this.id;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((id == null) ? 0 : id.hashCode());
		return result;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		ValidityType other = (ValidityType) obj;
		if (id == null) {
			if (other.id != null)
				return false;
		} else if (!id.equals(other.id))
			return false;
		return true;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return "ValidityType [id=" + id + "]";
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Comparable#compareTo(java.lang.Object)
	 */
	@Override
	public int compareTo(ValidityType o) {
		if (o == null) {
			return -1;
		}
		int compare = id.compareTo(o.id);
		return compare;
	}

} public interface RelatedValidityProviderSpi {

	/**
	 * Access the (unique) provider id.
	 * 
	 * @see {@link RelatedValidityQuery#getValiditySource()}
	 * @see {@link RelatedValidityQuery#withValiditySource(String)}
	 * @return the validity provider id, never {@code null}, not empty. The
	 *         identifier must be unique, since it may also be used to
	 *         explicitly select validity information from a certain provider.
	 */
	public String getProviderId();

	/**
	 * Return the {@link ValidityType} instances that this instance is
	 * supporting, this is used for determining which providers have to be
	 * called for evaluating a given {@link RelatedValidityQuery} query.
	 * 
	 * @see {@link RelatedValidityQuery#getValidityType()}
	 * @see {@link RelatedValidityQuery#withValidityType(ValidityType)}
	 * @param itemType
	 *            the item type, not {@code null}.
	 * @param relatedType
	 *            the type of the related item, not {@code null}.
	 * @return the set of supported {@link ValidityType}s, never {@code null}.
	 */
	public Set<ValidityType> getValidityTypes(Class itemType, Class relatedType);

	/**
	 * Return the item types that this provider instance is supporting, this is
	 * used for determining, which providers must be called for a given
	 * {@link RelatedValidityQuery} query.
	 * 
	 * @see {@link RelatedValidityQuery#getItemType()}
	 * @return the set of supported item types, never {@code null}.
	 */
	public Set<Class> getItemTypes();

	/**
	 * Return the related item types that this instance is supporting, given the
	 * base item type. This information is used for determining which providers
	 * must be called for a given {@link RelatedValidityQuery} query.
	 * 
	 * @see {@link RelatedValidityQuery#getRelatedToType()}
	 * 
	 * @param itemType
	 *            the item's type, which can be related to the types returned.
	 * @return the set of supported related item types, never {@code null}.
	 * @throws IllegalArgumentException
	 *             if the itemType is not one of the types returned by
	 *             {@link #getItemTypes()}
	 */
	public Set<Class> getRelatedItemTypes(Class<?> itemType);

	/**
	 * Access all {@link RelatedValidityInfo} for the given query.
	 * 
	 * @param query
	 *            the related validity query.
	 * @return the {@link RelatedValidityInfo} instances found, never
	 *         {@code null}.
	 */
	public <T, R> Collection<RelatedValidityInfo<T, R>> getRelatedValidityInfo(
			RelatedValidityQuery<T, R> query);

}
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;
/*
 * 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;




/**
 * 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,