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