| /* |
| ******************************************************************************* |
| * Copyright (C) 2011, International Business Machines Corporation * |
| * All Rights Reserved. * |
| ******************************************************************************* |
| */ |
| package com.ibm.icu.util; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.BitSet; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.TreeSet; |
| |
| import com.ibm.icu.impl.ICUResourceBundle; |
| |
| /** |
| * <code>Region</code> is the class representing a Unicode Region Code, also known as a |
| * Unicode Region Subtag, which is defined based upon the BCP 47 standard. We often think of |
| * "regions" as "countries" when defining the characteristics of a locale. Region codes There are different |
| * types of region codes that are important to distinguish. |
| * <p> |
| * Macroregion - A code for a "macro geographical (continental) region, geographical sub-region, or |
| * selected economic and other grouping" as defined in |
| * UN M.49 (http://unstats.un.org/unsd/methods/m49/m49regin.htm). |
| * These are typically 3-digit codes, but contain some 2-letter codes, such as the LDML code QO |
| * added for Outlying Oceania. Not all UNM.49 codes are defined in LDML, but most of them are. |
| * Macroregions are represented in ICU by one of three region types: WORLD ( region code 001 ), |
| * CONTINENTS ( regions contained directly by WORLD ), and SUBCONTINENTS ( things contained directly |
| * by a continent ). |
| * <p> |
| * TERRITORY - A Region that is not a Macroregion. These are typically codes for countries, but also |
| * include areas that are not separate countries, such as the code "AQ" for Antarctica or the code |
| * "HK" for Hong Kong (SAR China). Overseas dependencies of countries may or may not have separate |
| * codes. The codes are typically 2-letter codes aligned with the ISO 3166 standard, but BCP47 allows |
| * for the use of 3-digit codes in the future. |
| * <p> |
| * UNKNOWN - The code ZZ is defined by Unicode LDML for use to indicate that the Region is unknown, |
| * or that the value supplied as a region was invalid. |
| * <p> |
| * DEPRECATED - Region codes that have been defined in the past but are no longer in modern usage, |
| * usually due to a country splitting into multiple territories or changing its name. |
| * <p> |
| * GROUPING - A widely understood grouping of territories that has a well defined membership such |
| * that a region code has been assigned for it. Some of these are UNM.49 codes that do't fall into |
| * the world/continent/sub-continent hierarchy, while others are just well known groupings that have |
| * their own region code. Region "EU" (European Union) is one such region code that is a grouping. |
| * Groupings will never be returned by the getContainingRegion() API, since a different type of region |
| * ( WORLD, CONTINENT, or SUBCONTINENT ) will always be the containing region instead. |
| * |
| * @author John Emmons |
| * @internal ICU 4.8 technology preview |
| * @deprecated This API might change or be removed in a future release. |
| */ |
| |
| public class Region implements Comparable<Region> { |
| |
| /** |
| * RegionType is an enumeration defining the different types of regions. Current possible |
| * values are WORLD, CONTINENT, SUBCONTINENT, TERRITORY, GROUPING, DEPRECATED, and UNKNOWN. |
| * |
| * @internal ICU 4.8 technology preview |
| * @deprecated This API might change or be removed in a future release. |
| */ |
| |
| public enum RegionType { |
| /** |
| * Type representing the unknown region. |
| * @internal ICU 4.8 technology preview |
| * @deprecated This API might change or be removed in a future release. |
| */ |
| UNKNOWN, |
| |
| /** |
| * Type representing a territory. |
| * @internal ICU 4.8 technology preview |
| * @deprecated This API might change or be removed in a future release. |
| */ |
| TERRITORY, |
| |
| /** |
| * Type representing the whole world. |
| * @internal ICU 4.8 technology preview |
| * @deprecated This API might change or be removed in a future release. |
| */ |
| WORLD, |
| /** |
| * Type representing a continent. |
| * @internal ICU 4.8 technology preview |
| * @deprecated This API might change or be removed in a future release. |
| */ |
| CONTINENT, |
| /** |
| * Type representing a sub-continent. |
| * @internal ICU 4.8 technology preview |
| * @deprecated This API might change or be removed in a future release. |
| */ |
| SUBCONTINENT, |
| /** |
| * Type representing a grouping of territories that is not to be used in |
| * the normal WORLD/CONTINENT/SUBCONTINENT/TERRITORY containment tree. |
| * @internal ICU 4.8 technology preview |
| * @deprecated This API might change or be removed in a future release. |
| */ |
| GROUPING, |
| /** |
| * Type representing a region whose code has been deprecated, usually |
| * due to a country splitting into multiple territories or changing its name. |
| * @internal ICU 4.8 technology preview |
| * @deprecated This API might change or be removed in a future release. |
| */ |
| DEPRECATED, |
| } |
| |
| /** |
| * A constant used for unknown numeric region code. |
| * @see #getNumericCode() |
| * @internal ICU 4.8 technology preview |
| * @deprecated This API might change or be removed in a future release. |
| */ |
| public static final int UNDEFINED_NUMERIC_CODE = -1; |
| |
| private String id; |
| private int code; |
| private RegionType type; |
| |
| private static boolean hasData = false; |
| private static boolean hasContainmentData = false; |
| |
| private static Map<String,Integer> regionIndexMap = null; // Map from ID to position in the table |
| private static Map<Integer,Integer> numericIndexMap = null; // Map from numeric code to position in the table |
| private static Map<String,String> territoryAliasMap = null; // Aliases |
| private static Map<String,Integer> numericCodeMap = null; // Map of all possible IDs to numeric codes |
| private static Region[] regions = null; |
| private static BitSet[] subRegionData = null; |
| private static Integer[] containingRegionData = null; |
| private static ArrayList<Set<Region>> availableRegions = null; |
| |
| private static final String UNKNOWN_REGION_ID = "ZZ"; |
| private static final String WORLD_ID = "001"; |
| |
| /* |
| * Private default constructor. Use factory methods only. |
| */ |
| private Region () {} |
| |
| /* |
| * Initializes the region data from the ICU resource bundles. The region data |
| * contains the basic relationships such as which regions are known, what the numeric |
| * codes are, and any known aliases. It does not contain the territory containment data. |
| * Territory containment data only gets loaded if someone calls an API that is actually |
| * going to use that data. |
| * |
| * If the region data has already loaded, then this method simply returns without doing |
| * anything meaningful. |
| * |
| */ |
| private static synchronized void initRegionData() { |
| |
| if ( hasData ) { |
| return; |
| } |
| |
| territoryAliasMap = new HashMap<String,String>(); |
| numericCodeMap = new HashMap<String,Integer>(); |
| regionIndexMap = new HashMap<String,Integer>(); |
| numericIndexMap = new HashMap<Integer,Integer>(); |
| availableRegions = new ArrayList<Set<Region>>(RegionType.values().length); |
| |
| for (int i = 0 ; i < RegionType.values().length ; i++) { |
| availableRegions.add(null); |
| } |
| UResourceBundle regionCodes = null; |
| UResourceBundle territoryAlias = null; |
| UResourceBundle codeMappings = null; |
| UResourceBundle worldContainment = null; |
| UResourceBundle territoryContainment = null; |
| UResourceBundle groupingContainment = null; |
| UResourceBundle rb = UResourceBundle.getBundleInstance( |
| ICUResourceBundle.ICU_BASE_NAME, |
| "metadata", |
| ICUResourceBundle.ICU_DATA_CLASS_LOADER); |
| regionCodes = rb.get("regionCodes"); |
| territoryAlias = rb.get("territoryAlias"); |
| |
| UResourceBundle rb2 = UResourceBundle.getBundleInstance( |
| ICUResourceBundle.ICU_BASE_NAME, |
| "supplementalData", |
| ICUResourceBundle.ICU_DATA_CLASS_LOADER); |
| codeMappings = rb2.get("codeMappings"); |
| |
| // Right now only fetch as much territory containment as we need in order to determine |
| // types. Only fetch the rest if we have to. |
| // |
| territoryContainment = rb2.get("territoryContainment"); |
| worldContainment = territoryContainment.get("001"); |
| groupingContainment = territoryContainment.get("grouping"); |
| |
| String[] continentsArr = worldContainment.getStringArray(); |
| List<String> continents = Arrays.asList(continentsArr); |
| String[] groupingArr = groupingContainment.getStringArray(); |
| List<String> groupings = Arrays.asList(groupingArr); |
| |
| |
| // First put alias mappings for iso3 and numeric code mappings |
| for ( int i = 0 ; i < codeMappings.getSize(); i++ ) { |
| UResourceBundle mapping = codeMappings.get(i); |
| if ( mapping.getType() == UResourceBundle.ARRAY ) { |
| String [] codeStrings = mapping.getStringArray(); |
| if ( !territoryAliasMap.containsKey(codeStrings[1])) { |
| territoryAliasMap.put(codeStrings[1],codeStrings[0]); // Put alias from the numeric to the iso2 code |
| } |
| territoryAliasMap.put(codeStrings[2],codeStrings[0]); // Put alias from the iso3 to the iso2 code. |
| numericCodeMap.put(codeStrings[0], Integer.valueOf(codeStrings[1])); // Create the mapping from the iso2 code to its numeric value |
| } |
| } |
| |
| for ( int i = 0 ; i < territoryAlias.getSize(); i++ ) { |
| UResourceBundle res = territoryAlias.get(i); |
| String key = res.getKey(); |
| String value = res.getString(); |
| if ( !territoryAliasMap.containsKey(key)) { |
| territoryAliasMap.put(key, value); |
| } |
| } |
| |
| |
| regions = new Region[regionCodes.getSize()]; |
| for ( int i = 0 ; i < regions.length ; i++ ) { |
| regions[i] = new Region(); |
| String id = regionCodes.getString(i); |
| regions[i].id = id; |
| regionIndexMap.put(id, Integer.valueOf(i)); |
| |
| if ( id.matches("[0-9]{3}")) { |
| regions[i].code = Integer.valueOf(id).intValue(); |
| numericIndexMap.put(regions[i].code, Integer.valueOf(i)); |
| } else if (numericCodeMap.containsKey(id)) { |
| regions[i].code = numericCodeMap.get(id).intValue(); |
| if ( !numericIndexMap.containsKey(regions[i].code)) { |
| numericIndexMap.put(regions[i].code, Integer.valueOf(i)); |
| } |
| } else { |
| regions[i].code = UNDEFINED_NUMERIC_CODE; |
| } |
| |
| if ( territoryAliasMap.containsKey(id)){ |
| regions[i].type = RegionType.DEPRECATED; |
| } else if ( id.equals(WORLD_ID) ) { |
| regions[i].type = RegionType.WORLD; |
| } else if ( id.equals(UNKNOWN_REGION_ID) ) { |
| regions[i].type = RegionType.UNKNOWN; |
| } else if ( continents.contains(id) ) { |
| regions[i].type = RegionType.CONTINENT; |
| } else if ( groupings.contains(id) ) { |
| regions[i].type = RegionType.GROUPING; |
| } else if ( id.matches("[0-9]{3}|QO") ) { |
| regions[i].type = RegionType.SUBCONTINENT; |
| } else { |
| regions[i].type = RegionType.TERRITORY; |
| } |
| } |
| |
| hasData = true; |
| } |
| |
| /* |
| * Initializes the containment data from the ICU resource bundles. The containment data |
| * defines the relationships between different regions, such as which regions are contained |
| * within other regions. |
| * |
| * Territory containment data only gets loaded if someone calls an API that is actually |
| * going to use that data. Since you have to have the basic region data as well, this |
| * method will attempt to load the basic region data if it hasn't been loaded already. |
| * |
| * If the containment data has already loaded, then this method simply returns without doing |
| * anything meaningful. |
| * |
| */ |
| |
| private static synchronized void initContainmentData() { |
| if ( hasContainmentData ) { |
| return; |
| } |
| |
| initRegionData(); |
| subRegionData = new BitSet[regions.length]; |
| containingRegionData = new Integer[regions.length]; |
| for ( int i = 0 ; i < regions.length ; i++ ) { |
| subRegionData[i] = new BitSet(regions.length); |
| containingRegionData[i] = null; |
| } |
| UResourceBundle territoryContainment = null; |
| |
| UResourceBundle rb = UResourceBundle.getBundleInstance( |
| ICUResourceBundle.ICU_BASE_NAME, |
| "supplementalData", |
| ICUResourceBundle.ICU_DATA_CLASS_LOADER); |
| territoryContainment = rb.get("territoryContainment"); |
| |
| |
| // Get territory containment info from the supplemental data. |
| for ( int i = 0 ; i < territoryContainment.getSize(); i++ ) { |
| UResourceBundle mapping = territoryContainment.get(i); |
| String parent = mapping.getKey(); |
| Integer parentRegionIndex = regionIndexMap.get(parent); |
| for ( int j = 0 ; j < mapping.getSize(); j++ ) { |
| String child = mapping.getString(j); |
| Integer childRegionIndex = regionIndexMap.get(child); |
| if ( parentRegionIndex != null && childRegionIndex != null ) { |
| subRegionData[parentRegionIndex.intValue()].set(childRegionIndex.intValue()); // Set the containment bit for this pair |
| // Regions of type GROUPING can't be set as the parent, since another region |
| // such as a SUBCONTINENT, CONTINENT, or WORLD must always be the parent. |
| if ( !regions[parentRegionIndex].isOfType(RegionType.GROUPING)) { |
| containingRegionData[childRegionIndex] = parentRegionIndex; |
| } |
| } |
| } |
| } |
| hasContainmentData = true; |
| } |
| |
| |
| /** Returns a Region using the given region ID. The region ID can be either a 2-letter ISO code, |
| * 3-letter ISO code, UNM.49 numeric code, or other valid Unicode Region Code as defined by the CLDR. |
| * @param id The id of the region to be retrieved. |
| * @return The corresponding region. |
| * @throws NullPointerException if the supplied id is null. |
| * @throws IllegalArgumentException if the supplied ID cannot be canonicalized to a Region ID that is known by ICU. |
| * @internal ICU 4.8 technology preview |
| * @deprecated This API might change or be removed in a future release. |
| */ |
| |
| public static Region get(String id) { |
| if ( id == null ) { |
| throw new NullPointerException(); |
| } |
| String canonicalID = canonicalize(id); |
| if (canonicalID.equals(UNKNOWN_REGION_ID) && !id.equals(UNKNOWN_REGION_ID)) { |
| throw new IllegalArgumentException("Unknown region id: " + id); |
| } |
| |
| return regions[regionIndexMap.get(canonicalID)]; |
| } |
| |
| |
| /** Returns a Region using the given numeric code as defined by UNM.49 |
| * @param code The numeric code of the region to be retrieved. |
| * @return The corresponding region. |
| * @throws IllegalArgumentException if the supplied numeric code is not recognized. |
| * @internal ICU 4.8 technology preview |
| * @deprecated This API might change or be removed in a future release. |
| */ |
| |
| public static Region get(int code) { |
| Integer index = numericIndexMap.get(Integer.valueOf(code)); |
| if ( index != null ) { |
| Region r = regions[index]; |
| // Since a deprecated region will have the same numeric code as its new region code |
| // we get by id which will make sure we get the canonicalized one. |
| return Region.get(r.id); |
| } else { |
| throw new IllegalArgumentException("Unknown region code: " + code); |
| } |
| } |
| |
| /** Returns the canonicalized (preferred) form of the Region code. For territories, it will |
| * convert the string to the 2-letter ISO 3166 code if at all possible, and will convert any |
| * known aliases to their modern counterparts. |
| * |
| * @param id The string representing the region code to be canonicalized. |
| * @return The canonicalized (preferred) form of the region code. If the supplied region |
| * code is not recognized, the unknown region ( code "ZZ" ) is returned. |
| * @internal ICU 4.8 technology preview |
| * @deprecated This API might change or be removed in a future release. |
| */ |
| |
| public static String canonicalize(String id) { |
| initRegionData(); |
| String result = territoryAliasMap.get(id); |
| if ( result != null && regionIndexMap.containsKey(result)) { |
| return result; |
| } |
| |
| if ( regionIndexMap.containsKey(id)) { |
| return id; |
| } |
| return UNKNOWN_REGION_ID; |
| } |
| /** Returns true if the supplied region code is already in its canonical ( preferred ) form. |
| * |
| * @param id The string representing the region code to be checked. |
| * @return TRUE if the supplied region code is canonical, FALSE otherwise. |
| * @internal ICU 4.8 technology preview |
| * @deprecated This API might change or be removed in a future release. |
| */ |
| |
| public static boolean isCanonical(String id) { |
| return ( canonicalize(id).equals(id)); |
| } |
| |
| |
| /** Used to retrieve all available regions of a specific type. |
| * |
| * @param type The type of regions to be returned ( TERRITORY, MACROREGION, etc. ) |
| * @return An unmodifiable set of all known regions that match the given type. |
| * @internal ICU 4.8 technology preview |
| * @deprecated This API might change or be removed in a future release. |
| */ |
| |
| public static Set<Region> getAvailable(RegionType type) { |
| initRegionData(); |
| if ( availableRegions.get(type.ordinal()) == null) { |
| Set<Region> result = new TreeSet<Region>(); |
| for ( Region r : regions ) { |
| if ( r.type == type ) { |
| result.add(r); |
| } |
| } |
| availableRegions.set(type.ordinal(), Collections.unmodifiableSet(result)); |
| } |
| return availableRegions.get(type.ordinal()); |
| } |
| |
| |
| /** Used to determine the macroregion that geographically contains this region. |
| * |
| * @return The region that geographically contains this region. Returns NULL if this region is |
| * code "001" (World) or "ZZ" (Unknown region). For example, calling this method with region "IT" (Italy) |
| * returns the region "039" (Southern Europe). |
| * @internal ICU 4.8 technology preview |
| * @deprecated This API might change or be removed in a future release. |
| */ |
| |
| public Region getContainingRegion() { |
| initContainmentData(); |
| Integer index = regionIndexMap.get(id); |
| assert(index!=null); |
| if ( containingRegionData[index] == null ) { |
| return null; |
| } else { |
| return regions[containingRegionData[index]]; |
| } |
| } |
| |
| /** Used to determine the sub-regions that are contained within this region. |
| * |
| * @return An unmodifiable set containing all the regions that are immediate children |
| * of this region in the region hierarchy. These returned regions could be either macro |
| * regions, territories, or a mixture of the two, depending on the containment data as defined |
| * in CLDR. This API may return an empty set if this region doesn't have any sub-regions. |
| * For example, calling this method with region "150" (Europe) returns a set containing |
| * the various sub regions of Europe - "039" (Southern Europe) - "151" (Eastern Europe) |
| * - "154" (Northern Europe) and "155" (Western Europe). |
| * |
| * @internal ICU 4.8 technology preview |
| * @deprecated This API might change or be removed in a future release. |
| */ |
| |
| public Set<Region> getSubRegions() { |
| initContainmentData(); |
| |
| Set<Region> result = new TreeSet<Region>(); |
| Integer index = regionIndexMap.get(id); |
| BitSet contains = subRegionData[index]; |
| for( int i = contains.nextSetBit(0); i>=0; i=contains.nextSetBit(i+1)) { |
| result.add(regions[i]); |
| } |
| return Collections.unmodifiableSet(result); |
| } |
| |
| /** Used to determine all the territories that are contained within this region. |
| * |
| * @return An unmodifiable set containing all the territories that are children of this |
| * region anywhere in the region hierarchy. If this region is already a territory, |
| * the empty set is returned, since territories by definition do not contain other regions. |
| * For example, calling this method with region "150" (Europe) returns a set containing all |
| * the territories in Europe ( "FR" (France) - "IT" (Italy) - "DE" (Germany) etc. ) |
| * |
| * @internal ICU 4.8 technology preview |
| * @deprecated This API might change or be removed in a future release. |
| */ |
| |
| public Set<Region> getContainedTerritories() { |
| initContainmentData(); |
| Set<Region> result = new TreeSet<Region>(); |
| Set<Region> subRegions = getSubRegions(); |
| Iterator<Region> it = subRegions.iterator(); |
| while ( it.hasNext() ) { |
| Region r = it.next(); |
| if ( r.isOfType(RegionType.TERRITORY) ) { |
| result.add(r); |
| } else if ( r.isOfType(RegionType.CONTINENT) || r.isOfType(RegionType.SUBCONTINENT)) { |
| result.addAll(r.getContainedTerritories()); // Recursion!!! |
| } |
| } |
| return Collections.unmodifiableSet(result); |
| } |
| |
| /** Returns the string representation of this region |
| * |
| * @return The string representation of this region, which is its canonical ID. |
| * |
| * @internal ICU 4.8 technology preview |
| * @deprecated This API might change or be removed in a future release. |
| */ |
| |
| public String toString() { |
| return id; |
| } |
| |
| /** Returns the numeric code for this region |
| * |
| * @return The numeric code for this region. Returns UNDEFINED_NUMERIC_CODE (-1) if the |
| * given region does not have a numeric code assigned to it. This is a very rare case and |
| * only occurs for a few very small territories. |
| * |
| * @internal ICU 4.8 technology preview |
| * @deprecated This API might change or be removed in a future release. |
| */ |
| |
| public int getNumericCode() { |
| return code; |
| } |
| |
| /** Returns this region's type. |
| * |
| * @return This region's type classification, such as MACROREGION or TERRITORY. |
| * |
| * @internal ICU 4.8 technology preview |
| * @deprecated This API might change or be removed in a future release. |
| */ |
| |
| public RegionType getType() { |
| return type; |
| } |
| |
| /** Checks to see if this region is of a specific type. |
| * |
| * @return Returns TRUE if this region matches the supplied type. |
| * |
| * @internal ICU 4.8 technology preview |
| * @deprecated This API might change or be removed in a future release. |
| */ |
| |
| public boolean isOfType(RegionType type) { |
| return this.type.equals(type); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * @internal ICU 4.8 technology preview |
| * @deprecated This API might change or be removed in a future release. |
| */ |
| public int compareTo(Region other) { |
| return id.compareTo(other.id); |
| } |
| } |