Home | History | Annotate | Download | only in util
      1 /* GENERATED SOURCE. DO NOT MODIFY. */
      2 //  2016 and later: Unicode, Inc. and others.
      3 // License & terms of use: http://www.unicode.org/copyright.html#License
      4 /*
      5  *******************************************************************************
      6  * Copyright (C) 2011-2016, International Business Machines Corporation
      7  * All Rights Reserved.
      8  *******************************************************************************
      9  */
     10 package android.icu.util;
     11 
     12 import java.util.ArrayList;
     13 import java.util.Arrays;
     14 import java.util.Collections;
     15 import java.util.HashMap;
     16 import java.util.List;
     17 import java.util.Map;
     18 import java.util.Set;
     19 import java.util.TreeSet;
     20 
     21 import android.icu.impl.ICUData;
     22 import android.icu.impl.ICUResourceBundle;
     23 
     24 /**
     25  * <code>Region</code> is the class representing a Unicode Region Code, also known as a
     26  * Unicode Region Subtag, which is defined based upon the BCP 47 standard. We often think of
     27  * "regions" as "countries" when defining the characteristics of a locale.  Region codes There are different
     28  * types of region codes that are important to distinguish.
     29  * <p>
     30  *  Macroregion - A code for a "macro geographical (continental) region, geographical sub-region, or
     31  *  selected economic and other grouping" as defined in
     32  *  UN M.49 (http://unstats.un.org/unsd/methods/m49/m49regin.htm).
     33  *  These are typically 3-digit codes, but contain some 2-letter codes, such as the LDML code QO
     34  *  added for Outlying Oceania.  Not all UNM.49 codes are defined in LDML, but most of them are.
     35  *  Macroregions are represented in ICU by one of three region types: WORLD ( region code 001 ),
     36  *  CONTINENTS ( regions contained directly by WORLD ), and SUBCONTINENTS ( things contained directly
     37  *  by a continent ).
     38  *  <p>
     39  *  TERRITORY - A Region that is not a Macroregion. These are typically codes for countries, but also
     40  *  include areas that are not separate countries, such as the code "AQ" for Antarctica or the code
     41  *  "HK" for Hong Kong (SAR China). Overseas dependencies of countries may or may not have separate
     42  *  codes. The codes are typically 2-letter codes aligned with the ISO 3166 standard, but BCP47 allows
     43  *  for the use of 3-digit codes in the future.
     44  *  <p>
     45  *  UNKNOWN - The code ZZ is defined by Unicode LDML for use to indicate that the Region is unknown,
     46  *  or that the value supplied as a region was invalid.
     47  *  <p>
     48  *  DEPRECATED - Region codes that have been defined in the past but are no longer in modern usage,
     49  *  usually due to a country splitting into multiple territories or changing its name.
     50  *  <p>
     51  *  GROUPING - A widely understood grouping of territories that has a well defined membership such
     52  *  that a region code has been assigned for it.  Some of these are UNM.49 codes that do't fall into
     53  *  the world/continent/sub-continent hierarchy, while others are just well known groupings that have
     54  *  their own region code. Region "EU" (European Union) is one such region code that is a grouping.
     55  *  Groupings will never be returned by the getContainingRegion() API, since a different type of region
     56  *  ( WORLD, CONTINENT, or SUBCONTINENT ) will always be the containing region instead.
     57  *
     58  * @author       John Emmons
     59  * @hide Only a subset of ICU is exposed in Android
     60  */
     61 
     62 public class Region implements Comparable<Region> {
     63 
     64     /**
     65      * RegionType is an enumeration defining the different types of regions.  Current possible
     66      * values are WORLD, CONTINENT, SUBCONTINENT, TERRITORY, GROUPING, DEPRECATED, and UNKNOWN.
     67      */
     68 
     69     public enum RegionType {
     70         /**
     71          * Type representing the unknown region.
     72          */
     73         UNKNOWN,
     74 
     75         /**
     76          * Type representing a territory.
     77          */
     78         TERRITORY,
     79 
     80         /**
     81          * Type representing the whole world.
     82          */
     83         WORLD,
     84         /**
     85          * Type representing a continent.
     86          */
     87         CONTINENT,
     88         /**
     89          * Type representing a sub-continent.
     90          */
     91         SUBCONTINENT,
     92         /**
     93          * Type representing a grouping of territories that is not to be used in
     94          * the normal WORLD/CONTINENT/SUBCONTINENT/TERRITORY containment tree.
     95          */
     96         GROUPING,
     97         /**
     98          * Type representing a region whose code has been deprecated, usually
     99          * due to a country splitting into multiple territories or changing its name.
    100          */
    101         DEPRECATED,
    102     }
    103 
    104     private String id;
    105     private int code;
    106     private RegionType type;
    107     private Region containingRegion = null;
    108     private Set<Region> containedRegions = new TreeSet<Region>();
    109     private List<Region> preferredValues = null;
    110 
    111     private static boolean regionDataIsLoaded = false;
    112 
    113     private static Map<String,Region> regionIDMap = null;       // Map from ID the regions
    114     private static Map<Integer,Region> numericCodeMap = null;   // Map from numeric code to the regions
    115     private static Map<String,Region> regionAliases = null;     // Aliases
    116 
    117     private static ArrayList<Region> regions = null;            // This is the main data structure where the Regions are stored.
    118     private static ArrayList<Set<Region>> availableRegions = null;
    119 
    120     private static final String UNKNOWN_REGION_ID = "ZZ";
    121     private static final String OUTLYING_OCEANIA_REGION_ID = "QO";
    122     private static final String WORLD_ID = "001";
    123 
    124     /*
    125      * Private default constructor.  Use factory methods only.
    126      */
    127     private Region () {}
    128 
    129     /*
    130      * Initializes the region data from the ICU resource bundles.  The region data
    131      * contains the basic relationships such as which regions are known, what the numeric
    132      * codes are, any known aliases, and the territory containment data.
    133      *
    134      * If the region data has already loaded, then this method simply returns without doing
    135      * anything meaningful.
    136      *
    137      */
    138     private static synchronized void loadRegionData() {
    139 
    140         if ( regionDataIsLoaded ) {
    141             return;
    142         }
    143 
    144         regionAliases = new HashMap<String,Region>();
    145         regionIDMap = new HashMap<String,Region>();
    146         numericCodeMap = new HashMap<Integer,Region>();
    147 
    148         availableRegions = new ArrayList<Set<Region>>(RegionType.values().length);
    149 
    150 
    151         UResourceBundle metadataAlias = null;
    152         UResourceBundle territoryAlias = null;
    153         UResourceBundle codeMappings = null;
    154         UResourceBundle idValidity = null;
    155         UResourceBundle regionList = null;
    156         UResourceBundle regionRegular = null;
    157         UResourceBundle regionMacro = null;
    158         UResourceBundle regionUnknown = null;
    159         UResourceBundle worldContainment = null;
    160         UResourceBundle territoryContainment = null;
    161         UResourceBundle groupingContainment = null;
    162 
    163         UResourceBundle metadata = UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME,"metadata",ICUResourceBundle.ICU_DATA_CLASS_LOADER);
    164         metadataAlias = metadata.get("alias");
    165         territoryAlias = metadataAlias.get("territory");
    166 
    167         UResourceBundle supplementalData = UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME,"supplementalData", ICUResourceBundle.ICU_DATA_CLASS_LOADER);
    168         codeMappings = supplementalData.get("codeMappings");
    169         idValidity = supplementalData.get("idValidity");
    170         regionList = idValidity.get("region");
    171         regionRegular = regionList.get("regular");
    172         regionMacro = regionList.get("macroregion");
    173         regionUnknown = regionList.get("unknown");
    174 
    175         territoryContainment = supplementalData.get("territoryContainment");
    176         worldContainment = territoryContainment.get("001");
    177         groupingContainment = territoryContainment.get("grouping");
    178 
    179         String[] continentsArr = worldContainment.getStringArray();
    180         List<String> continents = Arrays.asList(continentsArr);
    181         String[] groupingArr = groupingContainment.getStringArray();
    182         List<String> groupings = Arrays.asList(groupingArr);
    183         List<String> regionCodes = new ArrayList<String>();
    184 
    185         List<String> allRegions = new ArrayList<String>();
    186         allRegions.addAll(Arrays.asList(regionRegular.getStringArray()));
    187         allRegions.addAll(Arrays.asList(regionMacro.getStringArray()));
    188         allRegions.add(regionUnknown.getString());
    189 
    190         for ( String r : allRegions ) {
    191             int rangeMarkerLocation = r.indexOf("~");
    192             if ( rangeMarkerLocation > 0 ) {
    193                 StringBuilder regionName = new StringBuilder(r);
    194                 char endRange = regionName.charAt(rangeMarkerLocation+1);
    195                 regionName.setLength(rangeMarkerLocation);
    196                 char lastChar = regionName.charAt(rangeMarkerLocation-1);
    197                 while ( lastChar <= endRange ) {
    198                     String newRegion = regionName.toString();
    199                     regionCodes.add(newRegion);
    200                     lastChar++;
    201                     regionName.setCharAt(rangeMarkerLocation-1,lastChar);
    202                 }
    203             } else {
    204                 regionCodes.add(r);
    205             }
    206         }
    207 
    208         regions = new ArrayList<Region>(regionCodes.size());
    209 
    210         // First process the region codes and create the master array of regions.
    211         for ( String id : regionCodes) {
    212             Region r = new Region();
    213             r.id = id;
    214             r.type = RegionType.TERRITORY; // Only temporary - figure out the real type later once the aliases are known.
    215             regionIDMap.put(id, r);
    216             if ( id.matches("[0-9]{3}")) {
    217                 r.code = Integer.valueOf(id).intValue();
    218                 numericCodeMap.put(r.code, r);
    219                 r.type = RegionType.SUBCONTINENT;
    220             } else {
    221                 r.code = -1;
    222             }
    223             regions.add(r);
    224         }
    225 
    226 
    227         // Process the territory aliases
    228         for ( int i = 0 ; i < territoryAlias.getSize(); i++ ) {
    229             UResourceBundle res = territoryAlias.get(i);
    230             String aliasFrom = res.getKey();
    231             String aliasTo = res.get("replacement").getString();
    232 
    233             if ( regionIDMap.containsKey(aliasTo) && !regionIDMap.containsKey(aliasFrom) ) { // This is just an alias from some string to a region
    234                 regionAliases.put(aliasFrom, regionIDMap.get(aliasTo));
    235             } else {
    236                 Region r;
    237                 if ( regionIDMap.containsKey(aliasFrom) ) {  // This is a deprecated region
    238                     r = regionIDMap.get(aliasFrom);
    239                 } else { // Deprecated region code not in the master codes list - so need to create a deprecated region for it.
    240                     r = new Region();
    241                     r.id = aliasFrom;
    242                     regionIDMap.put(aliasFrom, r);
    243                     if ( aliasFrom.matches("[0-9]{3}")) {
    244                         r.code = Integer.valueOf(aliasFrom).intValue();
    245                         numericCodeMap.put(r.code, r);
    246                     } else {
    247                         r.code = -1;
    248                     }
    249                     regions.add(r);
    250                 }
    251                 r.type = RegionType.DEPRECATED;
    252                 List<String> aliasToRegionStrings = Arrays.asList(aliasTo.split(" "));
    253                 r.preferredValues = new ArrayList<Region>();
    254                 for ( String s : aliasToRegionStrings ) {
    255                     if (regionIDMap.containsKey(s)) {
    256                         r.preferredValues.add(regionIDMap.get(s));
    257                     }
    258                 }
    259             }
    260         }
    261 
    262         // Process the code mappings - This will allow us to assign numeric codes to most of the territories.
    263         for ( int i = 0 ; i < codeMappings.getSize(); i++ ) {
    264             UResourceBundle mapping = codeMappings.get(i);
    265             if ( mapping.getType() == UResourceBundle.ARRAY ) {
    266                 String [] codeMappingStrings = mapping.getStringArray();
    267                 String codeMappingID = codeMappingStrings[0];
    268                 Integer codeMappingNumber = Integer.valueOf(codeMappingStrings[1]);
    269                 String codeMapping3Letter = codeMappingStrings[2];
    270 
    271                 if ( regionIDMap.containsKey(codeMappingID)) {
    272                     Region r = regionIDMap.get(codeMappingID);
    273                     r.code = codeMappingNumber.intValue();
    274                     numericCodeMap.put(r.code, r);
    275                     regionAliases.put(codeMapping3Letter, r);
    276                 }
    277             }
    278         }
    279 
    280         // Now fill in the special cases for WORLD, UNKNOWN, CONTINENTS, and GROUPINGS
    281         Region r;
    282         if ( regionIDMap.containsKey(WORLD_ID)) {
    283             r = regionIDMap.get(WORLD_ID);
    284             r.type = RegionType.WORLD;
    285         }
    286 
    287         if ( regionIDMap.containsKey(UNKNOWN_REGION_ID)) {
    288             r = regionIDMap.get(UNKNOWN_REGION_ID);
    289             r.type = RegionType.UNKNOWN;
    290         }
    291 
    292         for ( String continent : continents ) {
    293             if (regionIDMap.containsKey(continent)) {
    294                 r = regionIDMap.get(continent);
    295                 r.type = RegionType.CONTINENT;
    296             }
    297         }
    298 
    299         for ( String grouping : groupings ) {
    300             if (regionIDMap.containsKey(grouping)) {
    301                 r = regionIDMap.get(grouping);
    302                 r.type = RegionType.GROUPING;
    303             }
    304         }
    305 
    306         // Special case: The region code "QO" (Outlying Oceania) is a subcontinent code added by CLDR
    307         // even though it looks like a territory code.  Need to handle it here.
    308 
    309         if ( regionIDMap.containsKey(OUTLYING_OCEANIA_REGION_ID)) {
    310             r = regionIDMap.get(OUTLYING_OCEANIA_REGION_ID);
    311             r.type = RegionType.SUBCONTINENT;
    312         }
    313 
    314         // Load territory containment info from the supplemental data.
    315         for ( int i = 0 ; i < territoryContainment.getSize(); i++ ) {
    316             UResourceBundle mapping = territoryContainment.get(i);
    317             String parent = mapping.getKey();
    318             if (parent.equals("containedGroupings") || parent.equals("deprecated")) {
    319                 continue; // handle new pseudo-parent types added in ICU data per cldrbug 7808; for now just skip.
    320                 // #11232 is to do something useful with these.
    321             }
    322             Region parentRegion = regionIDMap.get(parent);
    323             for ( int j = 0 ; j < mapping.getSize(); j++ ) {
    324                 String child = mapping.getString(j);
    325                 Region childRegion = regionIDMap.get(child);
    326                 if ( parentRegion != null && childRegion != null ) {
    327 
    328                     // Add the child region to the set of regions contained by the parent
    329                     parentRegion.containedRegions.add(childRegion);
    330 
    331                     // Set the parent region to be the containing region of the child.
    332                     // Regions of type GROUPING can't be set as the parent, since another region
    333                     // such as a SUBCONTINENT, CONTINENT, or WORLD must always be the parent.
    334                     if ( parentRegion.getType() != RegionType.GROUPING) {
    335                         childRegion.containingRegion = parentRegion;
    336                     }
    337                 }
    338             }
    339         }
    340 
    341         // Create the availableRegions lists
    342 
    343         for (int i = 0 ; i < RegionType.values().length ; i++) {
    344             availableRegions.add(new TreeSet<Region>());
    345         }
    346 
    347         for ( Region ar : regions ) {
    348             Set<Region> currentSet = availableRegions.get(ar.type.ordinal());
    349             currentSet.add(ar);
    350             availableRegions.set(ar.type.ordinal(),currentSet);
    351         }
    352 
    353         regionDataIsLoaded = true;
    354     }
    355 
    356     /** Returns a Region using the given region ID.  The region ID can be either a 2-letter ISO code,
    357      * 3-letter ISO code,  UNM.49 numeric code, or other valid Unicode Region Code as defined by the CLDR.
    358      * @param id The id of the region to be retrieved.
    359      * @return The corresponding region.
    360      * @throws NullPointerException if the supplied id is null.
    361      * @throws IllegalArgumentException if the supplied ID cannot be canonicalized to a Region ID that is known by ICU.
    362      */
    363 
    364     public static Region getInstance(String id) {
    365 
    366         if ( id == null ) {
    367             throw new NullPointerException();
    368         }
    369 
    370         loadRegionData();
    371 
    372         Region r = regionIDMap.get(id);
    373 
    374         if ( r == null ) {
    375             r = regionAliases.get(id);
    376         }
    377 
    378         if ( r == null ) {
    379             throw new IllegalArgumentException("Unknown region id: " + id);
    380         }
    381 
    382         if ( r.type == RegionType.DEPRECATED && r.preferredValues.size() == 1) {
    383             r = r.preferredValues.get(0);
    384         }
    385 
    386         return r;
    387     }
    388 
    389 
    390     /** Returns a Region using the given numeric code as defined by UNM.49
    391      * @param code The numeric code of the region to be retrieved.
    392      * @return The corresponding region.
    393      * @throws IllegalArgumentException if the supplied numeric code is not recognized.
    394      */
    395 
    396     public static Region getInstance(int code) {
    397 
    398         loadRegionData();
    399 
    400         Region r = numericCodeMap.get(code);
    401 
    402         if ( r == null ) { // Just in case there's an alias that's numeric, try to find it.
    403             String pad = "";
    404             if ( code < 10 ) {
    405                 pad = "00";
    406             } else if ( code < 100 ) {
    407                 pad = "0";
    408             }
    409             String id = pad + Integer.toString(code);
    410             r = regionAliases.get(id);
    411         }
    412 
    413         if ( r == null ) {
    414             throw new IllegalArgumentException("Unknown region code: " + code);
    415         }
    416 
    417         if ( r.type == RegionType.DEPRECATED && r.preferredValues.size() == 1) {
    418             r = r.preferredValues.get(0);
    419         }
    420 
    421         return r;
    422     }
    423 
    424 
    425     /** Used to retrieve all available regions of a specific type.
    426      *
    427      * @param type The type of regions to be returned ( TERRITORY, MACROREGION, etc. )
    428      * @return An unmodifiable set of all known regions that match the given type.
    429      */
    430 
    431     public static Set<Region> getAvailable(RegionType type) {
    432 
    433         loadRegionData();
    434         return Collections.unmodifiableSet(availableRegions.get(type.ordinal()));
    435     }
    436 
    437 
    438     /** Used to determine the macroregion that geographically contains this region.
    439      *
    440      * @return The region that geographically contains this region.  Returns NULL if this region is
    441      *  code "001" (World) or "ZZ" (Unknown region).  For example, calling this method with region "IT" (Italy)
    442      *  returns the region "039" (Southern Europe).
    443      */
    444 
    445     public Region getContainingRegion() {
    446         loadRegionData();
    447         return containingRegion;
    448     }
    449 
    450     /** Used to determine the macroregion that geographically contains this region and that matches the given type.
    451      *
    452      * @return The region that geographically contains this region and matches the given type.  May return NULL if
    453      *  no containing region can be found that matches the given type.  For example, calling this method with region "IT" (Italy)
    454      *  and type CONTINENT returns the region "150" (Europe).
    455      */
    456 
    457     public Region getContainingRegion(RegionType type) {
    458         loadRegionData();
    459         if ( containingRegion == null ) {
    460             return null;
    461         }
    462         if ( containingRegion.type.equals(type)) {
    463             return containingRegion;
    464         } else {
    465             return containingRegion.getContainingRegion(type);
    466         }
    467     }
    468 
    469     /** Used to determine the sub-regions that are contained within this region.
    470      *
    471      * @return An unmodifiable set containing all the regions that are immediate children
    472      * of this region in the region hierarchy.  These returned regions could be either macro
    473      * regions, territories, or a mixture of the two, depending on the containment data as defined
    474      * in CLDR.  This API may return an empty set if this region doesn't have any sub-regions.
    475      * For example, calling this method with region "150" (Europe) returns a set containing
    476      * the various sub regions of Europe - "039" (Southern Europe) - "151" (Eastern Europe)
    477      * - "154" (Northern Europe) and "155" (Western Europe).
    478      */
    479 
    480     public Set<Region> getContainedRegions() {
    481         loadRegionData();
    482         return Collections.unmodifiableSet(containedRegions);
    483     }
    484 
    485     /** Used to determine all the regions that are contained within this region and that match the given type
    486      *
    487      * @return An unmodifiable set containing all the regions that are children of this region
    488      * anywhere in the region hierarchy and match the given type.  This API may return an empty set
    489      * if this region doesn't have any sub-regions that match the given type.
    490      * For example, calling this method with region "150" (Europe) and type "TERRITORY" returns a set
    491      *  containing all the territories in Europe ( "FR" (France) - "IT" (Italy) - "DE" (Germany) etc. )
    492      */
    493 
    494     public Set<Region> getContainedRegions(RegionType type) {
    495 
    496         loadRegionData();
    497 
    498         Set<Region> result = new TreeSet<Region>();
    499         Set<Region> cr = getContainedRegions();
    500 
    501         for ( Region r : cr ) {
    502             if ( r.getType() == type ) {
    503                 result.add(r);
    504             } else {
    505                 result.addAll(r.getContainedRegions(type));
    506             }
    507         }
    508         return Collections.unmodifiableSet(result);
    509     }
    510 
    511     /**
    512      * @return For deprecated regions, return an unmodifiable list of the regions that are the preferred replacement regions for this region.
    513      * Returns null for a non-deprecated region.  For example, calling this method with region "SU" (Soviet Union) would
    514      * return a list of the regions containing "RU" (Russia), "AM" (Armenia), "AZ" (Azerbaijan), etc...
    515      */
    516     public List<Region> getPreferredValues() {
    517 
    518         loadRegionData();
    519 
    520         if ( type == RegionType.DEPRECATED) {
    521             return Collections.unmodifiableList(preferredValues);
    522         } else {
    523             return null;
    524         }
    525     }
    526 
    527     /**
    528      * @return Returns true if this region contains the supplied other region anywhere in the region hierarchy.
    529      */
    530     public boolean contains(Region other) {
    531 
    532         loadRegionData();
    533 
    534         if (containedRegions.contains(other)) {
    535             return true;
    536         } else {
    537             for (Region cr : containedRegions) {
    538                 if (cr.contains(other)) {
    539                     return true;
    540                 }
    541             }
    542         }
    543 
    544         return false;
    545     }
    546 
    547     /** Returns the string representation of this region
    548      *
    549      * @return The string representation of this region, which is its ID.
    550      */
    551 
    552     public String toString() {
    553         return id;
    554     }
    555 
    556     /**
    557      * Returns the numeric code for this region
    558      *
    559      * @return The numeric code for this region. Returns a negative value if the given region does not have a numeric
    560      *         code assigned to it. This is a very rare case and only occurs for a few very small territories.
    561      */
    562 
    563     public int getNumericCode() {
    564         return code;
    565     }
    566 
    567     /** Returns this region's type.
    568      *
    569      * @return This region's type classification, such as MACROREGION or TERRITORY.
    570      */
    571 
    572     public RegionType getType() {
    573         return type;
    574     }
    575 
    576     /**
    577      * {@inheritDoc}
    578      */
    579     public int compareTo(Region other) {
    580         return id.compareTo(other.id);
    581     }
    582 }
    583