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 */ 10package android.icu.util; 11 12import java.util.ArrayList; 13import java.util.Arrays; 14import java.util.Collections; 15import java.util.HashMap; 16import java.util.List; 17import java.util.Map; 18import java.util.Set; 19import java.util.TreeSet; 20 21import android.icu.impl.ICUData; 22import 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 62public 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