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