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