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