• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.unicode.cldr.util;
2 
3 import java.util.Collection;
4 import java.util.Collections;
5 import java.util.Map;
6 import java.util.Set;
7 import java.util.TreeMap;
8 import java.util.stream.Collectors;
9 
10 /**
11  * Normalize and validate sets of locales. This class was split off from UserRegistry.java with
12  * the goal of encapsulation to support refactoring and implementation of new features such as
13  * warning a Manager who tries to assign to a Vetter unknown locales or locales that are not
14  * covered by their organization.
15  *
16  * A single locale may be represented by a string like "fr_CA" for Canadian French, or by
17  * a CLDRLocale object.
18  *
19  * A set of locales related to a particular Survey Tool user is compactly represented by a single string
20  * like "am fr_CA zh" (meaning "Amharic, Canadian French, and Chinese"). Survey Tool uses this compact
21  * representation for storage in the user database, and for browser inputting/editing forms, etc.
22  *
23  * Otherwise the preferred representation is a LocaleSet, which encapsulates a Set<CLDRLocale> along
24  * with special handling for isAllLocales.
25  */
26 public class LocaleNormalizer {
27     public enum LocaleRejection {
28         outside_org_coverage("Outside org. coverage"),
29         unknown("Unknown");
30 
LocaleRejection(String message)31         LocaleRejection(String message) {
32             this.message = message;
33         }
34         final String message;
35 
36         @Override
37         public
toString()38         String toString() {
39             return message;
40         }
41     }
42 
43     /**
44      * Special constant for specifying access to no locales. Used with intlocs (not with locale access)
45      */
46     public static final String NO_LOCALES = "none";
47 
48     /**
49      * Special String constant for specifying access to all locales.
50      */
51     public static final String ALL_LOCALES = StandardCodes.ALL_LOCALES;
52 
isAllLocales(String localeList)53     public static boolean isAllLocales(String localeList) {
54         return (localeList != null) && (localeList.contains(ALL_LOCALES) || localeList.trim().equals("all"));
55     }
56 
57     /**
58      * Special LocaleSet constant for specifying access to all locales.
59      */
60     public static final LocaleSet ALL_LOCALES_SET = new LocaleSet(true);
61 
62     /**
63      * The actual set of locales used by CLDR. For Survey Tool, this may be set by SurveyMain during initialization.
64      * It is used for validation so it should not simply be ALL_LOCALES_SET.
65      */
66     private static LocaleSet knownLocales = null;
67 
setKnownLocales(Set<CLDRLocale> localeListSet)68     public static void setKnownLocales(Set<CLDRLocale> localeListSet) {
69         knownLocales = new LocaleSet();
70         knownLocales.addAll(localeListSet);
71     }
72 
73     /**
74      * Normalize the given locale-list string, removing invalid/duplicate locale names,
75      * and saving error/warning messages in this LocaleNormalizer object
76      *
77      * @param list the String like "zh  aa test123"
78      * @return the normalized string like "aa zh"
79      */
normalize(String list)80     public String normalize(String list) {
81         return norm(this, list, null);
82     }
83 
84     /**
85      * Normalize the given locale-list string, removing invalid/duplicate locale names
86      *
87      * Do not report any errors or warnings
88      *
89      * @param list the String like "zh  aa test123"
90      * @return the normalized string like "aa zh"
91      */
normalizeQuietly(String list)92     public static String normalizeQuietly(String list) {
93         return norm(null, list, null);
94     }
95 
96     /**
97      * Normalize the given locale-list string, removing invalid/duplicate locale names,
98      * and saving error/warning messages in this LocaleNormalizer object
99      *
100      * @param list the String like "zh  aa test123"
101      * @param orgLocaleSet the locales covered by a particular organization,
102      *        used as a filter unless null or ALL_LOCALES_SET
103      * @return the normalized string like "aa zh"
104      */
normalizeForSubset(String list, LocaleSet orgLocaleSet)105     public String normalizeForSubset(String list, LocaleSet orgLocaleSet) {
106         return norm(this, list, orgLocaleSet);
107     }
108 
109     /**
110      * Normalize the given locale-list string, removing invalid/duplicate locale names
111      *
112      * Always filter out unknown locales.
113      * If orgLocaleSet isn't null, filter out locales missing from it.
114      *
115      * This is static and has an optional LocaleNormalizer parameter that enables saving
116      * warning/error messages that can be shown to the user.
117      *
118      * @param locNorm the object to be filled in with warning/error messages, if not null
119      * @param list the String like "zh  aa test123"
120      * @param orgLocaleSet the locales covered by a particular organization,
121      *        used as a filter unless null or ALL_LOCALES_SET
122      * @return the normalized string like "aa zh"
123      */
norm(LocaleNormalizer locNorm, String list, LocaleSet orgLocaleSet)124     private static String norm(LocaleNormalizer locNorm, String list, LocaleSet orgLocaleSet) {
125         if (list == null) {
126             return "";
127         }
128         list = list.trim();
129         if (list.isEmpty() || NO_LOCALES.equals(list)) {
130             return "";
131         }
132         if (isAllLocales(list)) {
133             return ALL_LOCALES;
134         }
135         final LocaleSet locSet = setFromString(locNorm, list, orgLocaleSet);
136         return locSet.toString();
137     }
138 
139     private Map<String, LocaleRejection> messages = null;
140 
addMessage(String locale, LocaleRejection rejection)141     private void addMessage(String locale, LocaleRejection rejection) {
142         if (messages == null) {
143             messages = new TreeMap<>();
144         }
145         messages.put(locale, rejection);
146     }
147 
hasMessage()148     public boolean hasMessage() {
149         return messages != null && !messages.isEmpty();
150     }
151 
getMessagePlain()152     public String getMessagePlain() {
153         return String.join("\n", getMessageArrayPlain());
154     }
155 
getMessageHtml()156     public String getMessageHtml() {
157         return String.join("<br />\n", getMessageArrayPlain());
158     }
159 
getMessageArrayPlain()160     public String[] getMessageArrayPlain() {
161         return getMessagesPlain().toArray(new String[0]);
162     }
163 
getMessagesPlain()164     public Collection<String> getMessagesPlain() {
165         return getMessages().entrySet().stream()
166             .map(e -> (e.getValue() + ": " + e.getKey()))
167             .collect(Collectors.toList());
168     }
169 
getMessages()170     public Map<String, LocaleRejection> getMessages() {
171         if (messages == null) return Collections.emptyMap();
172         return Collections.unmodifiableMap(messages);
173     }
174 
setFromStringQuietly(String locales, LocaleSet orgLocaleSet)175     public static LocaleSet setFromStringQuietly(String locales, LocaleSet orgLocaleSet) {
176         return setFromString(null, locales, orgLocaleSet);
177     }
178 
setFromString(LocaleNormalizer locNorm, String localeList, LocaleSet orgLocaleSet)179     private static LocaleSet setFromString(LocaleNormalizer locNorm, String localeList, LocaleSet orgLocaleSet) {
180         if (isAllLocales(localeList)) {
181             if (orgLocaleSet == null || orgLocaleSet.isAllLocales()) {
182                 return ALL_LOCALES_SET;
183             }
184             return intersectKnownWithOrgLocales(orgLocaleSet);
185         }
186         final LocaleSet newSet = new LocaleSet();
187         if (localeList == null || (localeList = localeList.trim()).length() == 0) {
188             return newSet;
189         }
190         final String[] array = localeList.split("[, \t\u00a0\\s]+"); // whitespace
191         for (String s : array) {
192             CLDRLocale locale = CLDRLocale.getInstance(s);
193             if (knownLocales == null || knownLocales.contains(locale)) {
194                 if (orgLocaleSet == null || orgLocaleSet.containsLocaleOrParent(locale)) {
195                     newSet.add(locale);
196                 } else if (locNorm != null) {
197                     locNorm.addMessage(locale.getBaseName(), LocaleRejection.outside_org_coverage);
198                 }
199             } else if (locNorm != null) {
200                 locNorm.addMessage(locale.getBaseName(), LocaleRejection.unknown);
201             }
202         }
203         return newSet;
204     }
205 
intersectKnownWithOrgLocales(LocaleSet orgLocaleSet)206     private static LocaleSet intersectKnownWithOrgLocales(LocaleSet orgLocaleSet) {
207         if (knownLocales == null) {
208             final LocaleSet orgSetCopy = new LocaleSet();
209             orgSetCopy.addAll(orgLocaleSet.getSet());
210             return orgSetCopy;
211         }
212         final LocaleSet intersection = new LocaleSet();
213         for (CLDRLocale locale : knownLocales.getSet()) {
214             if (orgLocaleSet.containsLocaleOrParent(locale)) {
215                 intersection.add(locale);
216             }
217         }
218         return intersection;
219     }
220 }
221