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