• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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