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