xref: /aosp_15_r20/external/cldr/tools/cldr-code/src/main/java/org/unicode/cldr/util/Containment.java (revision 912701f9769bb47905792267661f0baf2b85bed5)
1 package org.unicode.cldr.util;
2 
3 import com.google.common.collect.Multimap;
4 import com.ibm.icu.impl.Relation;
5 import com.ibm.icu.util.TimeZone;
6 import java.util.ArrayList;
7 import java.util.Collection;
8 import java.util.Collections;
9 import java.util.HashMap;
10 import java.util.HashSet;
11 import java.util.LinkedHashMap;
12 import java.util.LinkedHashSet;
13 import java.util.LinkedList;
14 import java.util.List;
15 import java.util.Map;
16 import java.util.Set;
17 
18 public class Containment {
19     private static final SupplementalDataInfo supplementalData = SupplementalDataInfo.getInstance();
20     static final Relation<String, String> containmentCore = supplementalData.getContainmentCore();
21     static final Set<String> continents = containmentCore.get("001");
22     static final Set<String> subcontinents;
23 
24     static {
25         LinkedHashSet<String> temp = new LinkedHashSet<>();
26         for (String continent : continents) {
containmentCore.get(continent)27             temp.addAll(containmentCore.get(continent));
28         }
29         subcontinents = Collections.unmodifiableSet(temp);
30     }
31 
32     static final Relation<String, String> containmentFull =
33             supplementalData.getTerritoryToContained();
34     static final Relation<String, String> containedToContainer =
35             Relation.of(new HashMap<String, Set<String>>(), HashSet.class)
36                     .addAllInverted(containmentFull)
37                     .freeze();
38 
39     static final Relation<String, String> leavesToContainers;
40 
41     static {
42         leavesToContainers = Relation.of(new HashMap<String, Set<String>>(), HashSet.class);
43         // for each container, get all of its leaf nodes
44         Set<String> containers = supplementalData.getContainers();
45         for (String s : containers) {
46             HashSet<String> leaves = new HashSet<>();
addLeaves(s, leaves, containers)47             addLeaves(s, leaves, containers);
leavesToContainers.putAll(leaves, s)48             leavesToContainers.putAll(leaves, s);
49         }
leavesToContainers.freeze()50         leavesToContainers.freeze();
51         //        for (Entry<String, Set<String>> e : leavesToContainers.keyValuesSet()) {
52         //            System.out.println(e.getKey() + " " + e.getValue());
53         //        }
54     }
55 
56     static final Relation<String, String> containedToContainerCore =
57             Relation.of(new HashMap<String, Set<String>>(), HashSet.class)
58                     .addAllInverted(containmentCore)
59                     .freeze();
60     static final Map<String, Integer> toOrder = new LinkedHashMap<>();
61     static int level = 0;
62     static int order;
63     /** track whether any errors, such as loops, were detected */
64     public static boolean hadErrors = false;
65 
66     static {
67         initOrder("001");
68         // Special cases. Cyprus is because it is in the wrong location because it gets picked up in
69         // the EU.
70         resetOrder("003", "021");
71         resetOrder("419", "005");
72         resetOrder("CY", "BH");
73     }
74 
75     // static Map<String, String> zone2country = StandardCodes.make().getZoneToCounty();
76 
getRegionFromZone(String tzid)77     public static String getRegionFromZone(String tzid) {
78         if ("Etc/Unknown".equals(tzid)) {
79             return "001";
80         }
81         try {
82             return TimeZone.getRegion(tzid);
83         } catch (IllegalArgumentException e) {
84             return "ZZ";
85         }
86         // return zone2country.get(source0);
87     }
88 
addLeaves(String s, Set<String> target, Set<String> nonLeaf)89     private static void addLeaves(String s, Set<String> target, Set<String> nonLeaf) {
90         Set<String> contained = supplementalData.getContained(s);
91         if (contained == null) {
92             return;
93         }
94         for (String child : contained) {
95             if (!nonLeaf.contains(child)) {
96                 target.add(child);
97             } else {
98                 addLeaves(child, target, nonLeaf);
99             }
100         }
101     }
102 
getContainer(String territory)103     public static String getContainer(String territory) {
104         Set<String> containers = containedToContainerCore.get(territory);
105         if (containers == null) {
106             containers = containedToContainer.get(territory);
107         }
108         String container =
109                 containers != null
110                         ? containers.iterator().next()
111                         : territory.equals("001") ? "001" : "ZZ";
112         return container;
113     }
114 
115     /**
116      * Return all the containers, including deprecated.
117      *
118      * @param territory
119      * @return
120      */
getContainers(String territory)121     public static Set<String> getContainers(String territory) {
122         return containedToContainer.get(territory);
123     }
124 
125     /**
126      * Return the Continent containing the territory, or 001 if the territory is 001, otherwise ZZ
127      *
128      * @param territory
129      */
getContinent(String territory)130     public static String getContinent(String territory) {
131         while (true) {
132             if (territory == null
133                     || territory.equals("001")
134                     || territory.equals("ZZ")
135                     || continents.contains(territory)) {
136                 return territory;
137             }
138             String newTerritory = getContainer(territory);
139             if (newTerritory == null) {
140                 return territory;
141             }
142             territory = newTerritory;
143         }
144     }
145 
146     /**
147      * Return the Subcontinent containing the territory, or the continent if it is a continent, or
148      * 001 if it is 001, otherwise ZZ.
149      *
150      * @param territory
151      */
getSubcontinent(String territory)152     public static String getSubcontinent(String territory) {
153         while (true) {
154             if (territory.equals("001")
155                     || territory.equals("ZZ")
156                     || continents.contains(territory)
157                     || subcontinents.contains(territory)) {
158                 return territory;
159             }
160             territory = getContainer(territory);
161         }
162     }
163 
getOrder(String territory)164     public static int getOrder(String territory) {
165         Integer temp = toOrder.get(territory);
166         return temp != null ? temp.intValue() : level;
167     }
168 
initOrder(String territory)169     private static void initOrder(String territory) {
170         if (!toOrder.containsKey(territory)) {
171             toOrder.put(territory, ++level);
172         }
173         Set<String> contained = containmentFull.get(territory);
174         if (contained == null) {
175             return;
176         }
177         for (String subitem : contained) {
178             if (!toOrder.containsKey(subitem)) {
179                 toOrder.put(subitem, ++level);
180             }
181         }
182         for (String subitem : contained) {
183             initOrder(subitem);
184         }
185     }
186 
resetOrder(String newTerritory, String oldTerritory)187     private static void resetOrder(String newTerritory, String oldTerritory) {
188         // final Integer newOrder = toOrder.get(newTerritory);
189         // if (newOrder != null) {
190         // throw new IllegalArgumentException(newTerritory + " already defined as " + newOrder);
191         // }
192         final Integer oldOrder = toOrder.get(oldTerritory);
193         if (oldOrder == null) {
194             hadErrors = true;
195             throw new IllegalArgumentException(oldTerritory + " not yet defined");
196         }
197         toOrder.put(newTerritory, oldOrder);
198     }
199 
getContinents()200     public Set<String> getContinents() {
201         return continents;
202     }
203 
getSubontinents()204     public Set<String> getSubontinents() {
205         return subcontinents;
206     }
207 
getAllDirected(Multimap<String, String> multimap, String lang)208     public static Set<List<String>> getAllDirected(Multimap<String, String> multimap, String lang) {
209         LinkedHashSet<List<String>> result = new LinkedHashSet<>();
210         getAllDirected(
211                 multimap,
212                 lang,
213                 new ArrayList<String>(),
214                 result,
215                 new LinkedList<String>(),
216                 new HashSet<String>());
217         return result;
218     }
219 
getAllDirected( Multimap<String, String> multimap, String lang, ArrayList<String> target, Set<List<String>> targets, List<String> depth, Set<String> loops)220     private static void getAllDirected(
221             Multimap<String, String> multimap,
222             String lang,
223             ArrayList<String> target,
224             Set<List<String>> targets,
225             List<String> depth,
226             Set<String> loops) {
227         int alreadySawThis = depth.indexOf(lang);
228         if (alreadySawThis != -1) {
229             hadErrors = true;
230             if (loops.add(lang)) {
231                 System.err.println(
232                         "WARNING: CLDR-15020 getAllDirected(): Loops Detected: "
233                                 + String.join(" -> ", depth)
234                                 + " -> "
235                                 + lang);
236             } // else: already reported it.
237             return;
238         } else {
239             depth.add(lang);
240         }
241         if (!target.add(lang)) {
242             hadErrors = true;
243             throw new StackOverflowError("Error: Saw " + lang + " multiple times in this target.");
244         } else if (depth.size() > 999) {
245             hadErrors = true;
246             throw new StackOverflowError("Error: Too deep getting " + lang);
247         }
248         Collection<String> parents = multimap.get(lang);
249         int size = parents.size();
250         if (size == 0) {
251             targets.add(target);
252         } else if (size == 1) {
253             for (String parent : parents) {
254                 if (parent.equals(lang)) {
255                     hadErrors = true;
256                     System.err.println("ERR: " + lang + " is its own parent");
257                 } else {
258                     getAllDirected(multimap, parent, target, targets, depth, loops);
259                 }
260             }
261         } else {
262             for (String parent : parents) {
263                 if (parent.equals(lang)) {
264                     hadErrors = true;
265                     System.err.println("ERR: " + lang + " is its own parent");
266                 } else {
267                     getAllDirected(
268                             multimap,
269                             parent,
270                             (ArrayList<String>) target.clone(),
271                             targets,
272                             depth,
273                             loops);
274                 }
275             }
276         }
277         depth.remove(lang); // pop stack
278     }
279 
280     /**
281      * For each leaf region (eg "CO"), return all containers [019, 419, 005, 001]
282      *
283      * @param leaf
284      * @return
285      */
leafToContainer(String leaf)286     public static Set<String> leafToContainer(String leaf) {
287         return leavesToContainers.get(leaf);
288     }
289 
isLeaf(String region)290     public static boolean isLeaf(String region) {
291         return leavesToContainers.containsKey(region);
292     }
293 }
294