xref: /aosp_15_r20/external/cldr/tools/cldr-code/src/main/java/org/unicode/cldr/util/LocalesTxtReader.java (revision 912701f9769bb47905792267661f0baf2b85bed5)
1 package org.unicode.cldr.util;
2 
3 import com.google.common.collect.ImmutableSet;
4 import com.ibm.icu.impl.Relation;
5 import com.ibm.icu.util.ICUUncheckedIOException;
6 import java.io.BufferedReader;
7 import java.io.IOException;
8 import java.util.EnumMap;
9 import java.util.HashSet;
10 import java.util.List;
11 import java.util.Map;
12 import java.util.Set;
13 import java.util.TreeMap;
14 import java.util.TreeSet;
15 
16 class LocalesTxtReader {
17     Map<Organization, Map<String, Level>> platform_locale_level = null;
18     Map<Organization, Relation<Level, String>> platform_level_locale = null;
19     Map<String, Map<String, String>> platform_locale_levelString = null;
20     Map<Organization, Map<String, Integer>> organization_locale_weight = null;
21     Map<Organization, Map<String, Set<String>>> organization_locale_match = null;
22 
23     public static final String DEFAULT_NAME = "Locales.txt";
24 
LocalesTxtReader()25     public LocalesTxtReader() {}
26 
27     /**
28      * Read from Locales.txt, from the default location
29      *
30      * @param lstreg stream to read from
31      */
read(StandardCodes sc)32     public LocalesTxtReader read(StandardCodes sc) {
33         try (BufferedReader lstreg = CldrUtility.getUTF8Data(DEFAULT_NAME); ) {
34             return read(sc, lstreg);
35         } catch (IOException e) {
36             throw new ICUUncheckedIOException("Internal Error reading Locales.txt", e);
37         }
38     }
39 
40     /**
41      * Parse a Locales.txt file
42      *
43      * @param sc StandardCodes used for validation
44      * @param lstreg stream to read from
45      */
read(StandardCodes sc, BufferedReader lstreg)46     public LocalesTxtReader read(StandardCodes sc, BufferedReader lstreg) {
47         LocaleIDParser parser = new LocaleIDParser();
48         platform_locale_level = new EnumMap<>(Organization.class);
49         organization_locale_weight = new EnumMap<>(Organization.class);
50         organization_locale_match = new EnumMap<>(Organization.class);
51         SupplementalDataInfo sd = SupplementalDataInfo.getInstance();
52         Set<String> defaultContentLocales = sd.getDefaultContentLocales();
53         String line;
54         try {
55             while (true) {
56                 Integer weight = null; // @weight
57                 String pathMatch = null; // @pathMatch
58                 line = lstreg.readLine();
59                 if (line == null) break;
60                 int commentPos = line.indexOf('#');
61                 if (commentPos >= 0) {
62                     line = line.substring(0, commentPos);
63                 }
64                 line = line.trim();
65                 if (line.length() == 0) continue;
66                 List<String> stuff = CldrUtility.splitList(line, ';', true);
67                 Organization organization;
68 
69                 // verify that the organization is valid
70                 try {
71                     organization = Organization.fromString(stuff.get(0));
72                 } catch (Exception e) {
73                     throw new IllegalArgumentException(
74                             "Invalid organization in Locales.txt: " + line);
75                 }
76 
77                 // verify that the locale is valid BCP47
78                 String localePart = stuff.get(1).trim();
79                 List<String> localeStuff = CldrUtility.splitList(localePart, ' ', true);
80                 Set<String> locales = new TreeSet<>();
81 
82                 for (final String entry : localeStuff) {
83                     if (entry.startsWith("@")) {
84                         List<String> kwStuff = CldrUtility.splitList(entry, '=', true);
85                         if (kwStuff.size() > 2 || kwStuff.size() < 1) {
86                             throw new IllegalArgumentException(
87                                     "Invalid @-command " + entry + " in Locales.txt: " + line);
88                         }
89                         final String atCommand = kwStuff.get(0);
90                         switch (atCommand) {
91                             case "@weight":
92                                 weight = Integer.parseInt(kwStuff.get(1));
93                                 break;
94 
95                             case "@pathMatch":
96                                 pathMatch = kwStuff.get(1);
97                                 break;
98                             default:
99                                 throw new IllegalArgumentException(
100                                         "Unknown @-command "
101                                                 + atCommand
102                                                 + " in Locales.txt: "
103                                                 + line);
104                         }
105                     } else {
106                         locales.add(entry);
107                     }
108                 }
109 
110                 if (locales.size() != 1) {
111                     // require there to be exactly one locale.
112                     // This would allow collapsing into fewer lines.
113                     throw new IllegalArgumentException(
114                             "Expected one locale entry in Locales.txt but got "
115                                     + locales.size()
116                                     + ": "
117                                     + line);
118                 }
119 
120                 // extract the single locale, process as before
121                 String locale = locales.iterator().next();
122 
123                 if (!locale.equals(StandardCodes.ALL_LOCALES)) {
124                     parser.set(locale);
125                     String valid = sc.validate(parser);
126                     if (valid.length() != 0) {
127                         throw new IllegalArgumentException(
128                                 "Invalid locale in Locales.txt: " + line);
129                     }
130                     locale = parser.toString(); // normalize
131 
132                     // verify that the locale is not a default content locale
133                     if (defaultContentLocales.contains(locale)) {
134                         throw new IllegalArgumentException(
135                                 "Cannot have default content locale in Locales.txt: " + line);
136                     }
137                 }
138 
139                 Level status = Level.get(stuff.get(2));
140                 if (status == Level.UNDETERMINED) {
141                     System.out.println("Warning: Level unknown on: " + line);
142                 }
143                 Map<String, Level> locale_status = platform_locale_level.get(organization);
144                 if (locale_status == null) {
145                     platform_locale_level.put(organization, locale_status = new TreeMap<>());
146                 }
147                 locale_status.put(locale, status);
148 
149                 if (weight != null) {
150                     organization_locale_weight
151                             .computeIfAbsent(organization, ignored -> new TreeMap<>())
152                             .put(locale, weight);
153                 }
154                 if (pathMatch != null) {
155                     organization_locale_match
156                             .computeIfAbsent(organization, ignored -> new TreeMap<>())
157                             .put(locale, ImmutableSet.copyOf(pathMatch.split(",")));
158                 }
159             }
160         } catch (IOException e) {
161             throw new ICUUncheckedIOException("Internal Error", e);
162         }
163 
164         // backwards compat hack
165         platform_locale_levelString = new TreeMap<>();
166         platform_level_locale = new EnumMap<>(Organization.class);
167         for (Organization platform : platform_locale_level.keySet()) {
168             Map<String, String> locale_levelString = new TreeMap<>();
169             platform_locale_levelString.put(platform.toString(), locale_levelString);
170             Map<String, Level> locale_level = platform_locale_level.get(platform);
171             for (String locale : locale_level.keySet()) {
172                 locale_levelString.put(locale, locale_level.get(locale).toString());
173             }
174             Relation<Level, String> level_locale =
175                     Relation.of(new EnumMap(Level.class), HashSet.class);
176             level_locale.addAllInverted(locale_level).freeze();
177             platform_level_locale.put(platform, level_locale);
178         }
179         CldrUtility.protectCollection(platform_level_locale);
180         platform_locale_level = CldrUtility.protectCollection(platform_locale_level);
181         platform_locale_levelString = CldrUtility.protectCollection(platform_locale_levelString);
182         return this;
183     }
184 }
185