xref: /aosp_15_r20/external/cldr/tools/cldr-code/src/main/java/org/unicode/cldr/util/SpecialLocales.java (revision 912701f9769bb47905792267661f0baf2b85bed5)
1 package org.unicode.cldr.util;
2 
3 import com.ibm.icu.util.ICUUncheckedIOException;
4 import java.io.BufferedReader;
5 import java.io.IOException;
6 import java.util.ArrayList;
7 import java.util.Collections;
8 import java.util.HashMap;
9 import java.util.HashSet;
10 import java.util.List;
11 import java.util.Locale;
12 import java.util.Map;
13 import java.util.Set;
14 import java.util.TreeSet;
15 import org.unicode.cldr.tool.CLDRFileTransformer;
16 import org.unicode.cldr.tool.CLDRFileTransformer.LocaleTransform;
17 
18 /**
19  * List of locale IDs which are somehow 'special'. Parses SpecialLocales.txt
20  *
21  * @author srl
22  */
23 public class SpecialLocales {
24     private static final String INCLUDE_SUBLOCALES = "*";
25 
26     public enum Type {
27         /** Locale may not be modified by user. */
28         readonly,
29         /** Locale is algorithmically generated and may not be modified by user. */
30         algorithmic,
31         /** Locale may be modified by user. Contents aren't part of CLDR release and may change. */
32         scratch;
33 
34         /**
35          * Is this type read-only (includes algorithmic)?
36          *
37          * @param type the type, or null
38          * @return true if type is readonly or algorithmic
39          *     <p>Ordinary locales may have type == null, which implies NOT read-only
40          */
isReadOnly(Type type)41         public static boolean isReadOnly(Type type) {
42             return type == Type.readonly || type == Type.algorithmic;
43         }
44     }
45 
46     /**
47      * Get the type of this locale
48      *
49      * @param l
50      * @return a Type or null
51      */
getType(CLDRLocale l)52     public static Type getType(CLDRLocale l) {
53         return getInstance().getTypeInternal(l);
54     }
55 
56     /**
57      * Get all CLDRLocales matching this type. Does not include wildcard (*) sublocales.
58      *
59      * @param t
60      * @return a set, or null if none found
61      */
getByType(Type t)62     public static Set<CLDRLocale> getByType(Type t) {
63         return getInstance().getByTypeInternal(t);
64     }
65 
66     /**
67      * Get the comment on this locale. Strip out @ text.
68      *
69      * @param l
70      * @return string or null
71      */
getComment(CLDRLocale l)72     public static String getComment(CLDRLocale l) {
73         return getCommentRaw(l).replaceAll("@", "");
74     }
75 
76     /**
77      * Get the comment on this locale. Include "@locale" markers.
78      *
79      * @param l
80      * @return string or null
81      */
getCommentRaw(CLDRLocale l)82     public static String getCommentRaw(CLDRLocale l) {
83         return getInstance().getCommentInternal(l).replaceAll("@@", "@" + l.getBaseName());
84     }
85 
86     private static final class SpecialLocalesHelper {
87         static final SpecialLocales SINGLETON = new SpecialLocales();
88     }
89 
90     /**
91      * Internal accessor. All access is via the static functions.
92      *
93      * @return
94      */
getInstance()95     private static synchronized SpecialLocales getInstance() {
96         return SpecialLocalesHelper.SINGLETON;
97     }
98 
99     private Map<CLDRLocale, Type> specials = new HashMap<>();
100     private Map<Type, Set<CLDRLocale>> types = new HashMap<>();
101     private Map<CLDRLocale, String> comments = new HashMap<>();
102     private Set<CLDRLocale> specialsWildcards = new HashSet<>();
103 
getByTypeInternal(Type t)104     public Set<CLDRLocale> getByTypeInternal(Type t) {
105         return types.get(t);
106     }
107 
getTypeInternal(CLDRLocale l)108     public Type getTypeInternal(CLDRLocale l) {
109         l = findLocale(l, l);
110         return specials.get(l);
111     }
112 
getCommentInternal(CLDRLocale l)113     public String getCommentInternal(CLDRLocale l) {
114         l = findLocale(l, l);
115         return comments.get(l);
116     }
117 
findLocale(CLDRLocale fromLocale, CLDRLocale origLocale)118     public CLDRLocale findLocale(CLDRLocale fromLocale, CLDRLocale origLocale) {
119         if (origLocale == fromLocale && specials.containsKey(origLocale)) {
120             return origLocale; // explicit locale - no search.
121         }
122         if (fromLocale == null) {
123             return origLocale;
124         }
125         if (specialsWildcards.contains(fromLocale)) {
126             return fromLocale;
127         }
128         return findLocale(fromLocale.getParent(), origLocale);
129     }
130 
131     private static boolean DEBUG = false;
132 
133     /** Internal constructor */
SpecialLocales()134     private SpecialLocales() {
135         // First, read the algorithmic locales.
136         for (final LocaleTransform lt : CLDRFileTransformer.LocaleTransform.values()) {
137             if (lt.getPolicyIfExisting() != CLDRFileTransformer.PolicyIfExisting.DISCARD) {
138                 continue;
139             }
140             // Add each of these almost as if they were in SpecialLocales.txt
141             CLDRLocale inputLocale = CLDRLocale.getInstance(lt.getInputLocale());
142             CLDRLocale outputLocale = CLDRLocale.getInstance(lt.getOutputLocale());
143 
144             addToType(Type.algorithmic, outputLocale);
145 
146             // add a comment similar to the comments in SpecialLocales.txt
147             comments.put(
148                     outputLocale,
149                     "@"
150                             + outputLocale.getBaseName()
151                             + " is generated from @"
152                             + inputLocale.getBaseName()
153                             + " via transliteration, and so @@ may not be edited directly. Edit @"
154                             + inputLocale.getBaseName()
155                             + " to make changes.");
156         }
157 
158         for (final DataFileRow r : DataFileRow.ROWS) {
159             // verify that the locale is valid
160             CLDRLocale l = null;
161             try {
162                 l = CLDRLocale.getInstance(r.id);
163             } catch (Exception e) {
164                 throw new IllegalArgumentException(
165                         "Invalid CLDRLocale in SpecialLocales.txt:" + r.id);
166             }
167 
168             addToType(r.type, l);
169             if (r.includeSubLocs) {
170                 if (r.type == Type.scratch) {
171                     throw new IllegalArgumentException(
172                             "Scratch locales cannot include sublocales: " + l);
173                 }
174                 specialsWildcards.add(l);
175             }
176             if (!r.comment.isEmpty()) {
177                 comments.put(l, r.comment);
178             }
179             if (DEBUG) {
180                 System.out.println(
181                         SpecialLocales.class.getSimpleName()
182                                 + ": locale "
183                                 + l
184                                 + ", includejSublocs="
185                                 + r.includeSubLocs
186                                 + ", type="
187                                 + r.type
188                                 + ", comment: "
189                                 + r.comment);
190             }
191         }
192         specials = Collections.unmodifiableMap(specials);
193         specialsWildcards = Collections.unmodifiableSet(specialsWildcards);
194         comments = Collections.unmodifiableMap(comments);
195         types = Collections.unmodifiableMap(types);
196     }
197 
198     private static class DataFileRow {
199         public boolean includeSubLocs;
200 
DataFileRow(String id, Type type, String comment, boolean includeSubLocs)201         public DataFileRow(String id, Type type, String comment, boolean includeSubLocs) {
202             this.id = id;
203             this.type = type;
204             this.comment = comment;
205             this.includeSubLocs = includeSubLocs;
206         }
207 
208         public String id;
209         public Type type;
210         public String comment;
211 
212         public static List<DataFileRow> ROWS = readDataFile();
213 
readDataFile()214         private static List<DataFileRow> readDataFile() {
215             List<DataFileRow> rows = new ArrayList<>();
216             // from StandardCodes.java
217             String line;
218             int ln = 0;
219             try {
220                 BufferedReader lstreg = CldrUtility.getUTF8Data("SpecialLocales.txt");
221                 while (true) {
222                     line = lstreg.readLine();
223                     ln++;
224                     if (line == null) break;
225                     int commentPos = line.indexOf('#');
226                     if (commentPos >= 0) {
227                         line = line.substring(0, commentPos);
228                     }
229                     line = line.trim();
230                     if (line.length() == 0) continue;
231                     List<String> stuff = CldrUtility.splitList(line, ';', true);
232                     String id = stuff.get(0);
233                     boolean includeSublocs = (id.endsWith(INCLUDE_SUBLOCALES));
234                     if (includeSublocs) {
235                         id = id.substring(0, id.length() - INCLUDE_SUBLOCALES.length());
236                     }
237                     String type = stuff.get(1);
238                     String comment = stuff.get(2);
239                     Type t = null;
240 
241                     // verify that the type is valid
242                     try {
243                         t = Type.valueOf(type.toLowerCase(Locale.ENGLISH));
244                     } catch (Exception e) {
245                         throw new IllegalArgumentException(
246                                 "Invalid SpecialLocales.Type in SpecialLocales.txt:"
247                                         + ln
248                                         + ": "
249                                         + line);
250                     }
251 
252                     rows.add(new DataFileRow(id, t, comment, includeSublocs));
253                 }
254             } catch (IOException e) {
255                 throw new ICUUncheckedIOException("Internal Error", e);
256             }
257             return rows;
258         }
259     }
260 
addToType(Type t, CLDRLocale l)261     private Set<CLDRLocale> addToType(Type t, CLDRLocale l) {
262         Set<CLDRLocale> s = types.get(t);
263         if (s == null) {
264             s = new TreeSet<>();
265             types.put(t, s);
266         }
267         s.add(l);
268         specials.put(l, t);
269         return s;
270     }
271 
272     /**
273      * @param locale
274      * @return true if the locale type is scratch
275      * @deprecated use the CLDRLocale variant
276      */
277     @Deprecated
isScratchLocale(String locale)278     public static boolean isScratchLocale(String locale) {
279         return isScratchLocale(CLDRLocale.getInstance(locale));
280     }
281 
282     /**
283      * Check if this is a scratch (sandbox) locale
284      *
285      * @param loc
286      * @return true if it is a sandbox locale
287      */
isScratchLocale(CLDRLocale loc)288     public static boolean isScratchLocale(CLDRLocale loc) {
289         return getType(loc) == Type.scratch;
290     }
291 
292     /**
293      * Low level function to list scratch locales. Used for fetching the list prior to CLDRLocale
294      * startup.
295      *
296      * @return
297      */
getScratchLocaleIds()298     public static List<String> getScratchLocaleIds() {
299         List<String> ids = new ArrayList<>();
300         for (final DataFileRow r : DataFileRow.ROWS) {
301             if (r.type == Type.scratch) {
302                 ids.add(r.id);
303             }
304         }
305         return ids;
306     }
307 }
308