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