1 /* 2 * Copyright (c) 2021 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16 package ohos.global.i18n; 17 18 import com.ibm.icu.text.DateFormatSymbols; 19 import com.ibm.icu.text.DateTimePatternGenerator; 20 import com.ibm.icu.text.DecimalFormatSymbols; 21 import com.ibm.icu.text.NumberFormat; 22 import com.ibm.icu.text.NumberingSystem; 23 import com.ibm.icu.util.ULocale; 24 import com.ibm.icu.text.DateFormat; 25 import com.ibm.icu.text.SimpleDateFormat; 26 import com.ibm.icu.util.Calendar; 27 28 import java.util.ArrayList; 29 import java.util.HashMap; 30 import java.util.Objects; 31 import java.util.concurrent.locks.ReentrantLock; 32 import java.util.Map; 33 import java.util.logging.Level; 34 import java.util.logging.Logger; 35 import java.lang.reflect.Field; 36 import java.lang.reflect.InvocationTargetException; 37 import java.lang.reflect.Method; 38 39 import ohos.global.i18n.ResourceConfiguration.ConfigItem; 40 import ohos.global.i18n.ResourceConfiguration.Element; 41 42 /** 43 * Fetcher is used to fetche a locale's specified data 44 */ 45 public class Fetcher implements Runnable, Comparable<Fetcher> { 46 /** configuration extracted from resourec_items.json */ 47 private static ArrayList<ConfigItem> configItems = null; 48 private static final Logger LOG = Logger.getLogger("Fetcher"); 49 private static int resourceCount = 0; 50 private static HashMap<Integer, String> int2Str = new HashMap<>(); 51 private static HashMap<String, Integer> str2Int = new HashMap<>(); 52 private static boolean sStatusOk = true; 53 54 static { 55 configItems = ResourceConfiguration.parse(); 56 configItems.sort((ConfigItem first, ConfigItem second) -> first.getIndex() - second.getIndex()); 57 resourceCount = configItems.size(); 58 } 59 60 /** Used to store data related to a locale */ 61 public ArrayList<String> datas = new ArrayList<>(); 62 63 /** All non-repeated strings will be put into idMap */ 64 public Map<String, Integer> idMap; 65 66 /** Indicate whether this Fetcher is included in the final generation process of i18n.dat file */ 67 public boolean included = true; 68 69 /** LanguageTag related to the locale */ 70 public String languageTag; 71 72 private String lan; // language 73 private ReentrantLock lock; // Lock used to synchronize dump operation 74 private ULocale locale; 75 private DateFormatSymbols formatSymbols; 76 private DateTimePatternGenerator patternGenerator; 77 private int status = 0; 78 private String defaultHourString; 79 private ArrayList<Integer> reserved = new ArrayList<>(); 80 81 /** 82 * show whether resouce_items is loaded successfully 83 * 84 * @return true if status is right, otherwise false 85 */ isFetcherStatusOk()86 public static boolean isFetcherStatusOk() { 87 return sStatusOk; 88 } 89 90 /** 91 * return the total resource number 92 * 93 * @return resourceCount 94 */ getResourceCount()95 public static int getResourceCount() { 96 return resourceCount; 97 } 98 99 /** 100 * Methods to get int2Str 101 * 102 * @return Return int2Str 103 */ getInt2Str()104 public static HashMap<Integer, String> getInt2Str() { 105 return int2Str; 106 } 107 108 /** 109 * Methods to get str2Int 110 * 111 * @return Return str2Int 112 */ getStr2Int()113 public static HashMap<String, Integer> getStr2Int() { 114 return str2Int; 115 } 116 Fetcher(String tag, ReentrantLock lock, Map<String, Integer> idMap)117 public Fetcher(String tag, ReentrantLock lock, Map<String, Integer> idMap) { 118 if (!Utils.isValidLanguageTag(tag)) { 119 LOG.log(Level.SEVERE, String.format("wrong languageTag %s", tag)); 120 status = 1; 121 } 122 this.languageTag = tag; 123 Objects.requireNonNull(lock); 124 this.lock = lock; 125 Objects.requireNonNull(idMap); 126 this.idMap = idMap; 127 this.lan = this.languageTag.split("-")[0]; 128 this.locale = ULocale.forLanguageTag(this.languageTag); 129 formatSymbols = DateFormatSymbols.getInstance(locale); 130 patternGenerator = DateTimePatternGenerator.getInstance(locale); 131 defaultHourString = defaultHour(); 132 } 133 134 /** 135 * Check the status of the fetcher, normally a wrong language tag 136 * can make the status wrong. 137 * 138 * @return the status 139 */ checkStatus()140 public boolean checkStatus() { 141 return status == 0; 142 } 143 144 /** 145 * Get all meta data defined in resource_items.json 146 */ getData()147 public void getData() { 148 int current = 0; 149 Method method = null; 150 for (ConfigItem item : configItems) { 151 int index = item.getIndex(); 152 if (current != index) { 153 throw new IllegalStateException(); 154 } 155 String methodString = item.getMethod(); 156 try { 157 method = Fetcher.class.getDeclaredMethod(methodString, ConfigItem.class); 158 method.setAccessible(true); 159 method.invoke(this, item); 160 } catch(IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { 161 LOG.severe("get data failed for index " + current); 162 } 163 ++current; 164 } 165 } 166 167 /** 168 * Dump all datas in this locale to idMap 169 */ dump()170 public void dump() { 171 try { 172 lock.lock(); 173 int size = this.idMap.size(); 174 for (int i = 0; i < datas.size(); i++) { 175 String data = datas.get(i); 176 if (!idMap.containsKey(data)) { 177 idMap.put(data, size); 178 size++; 179 } 180 } 181 } finally { 182 lock.unlock(); 183 } 184 } 185 186 /** 187 * Equals function to determine whether two objs are equal 188 * 189 * @param obj Object to be compared 190 * @return Return true if obj is equals to this Fetcher object, otherwise false 191 */ equals(Object obj)192 public boolean equals(Object obj) { 193 if (!(obj instanceof Fetcher)) { 194 return false; 195 } 196 Fetcher fetcher = (Fetcher) obj; 197 if (datas.size() != fetcher.datas.size()) { 198 return false; 199 } 200 for (int i = 0; i < datas.size(); i++) { 201 if (!datas.get(i).equals(fetcher.datas.get(i))) { 202 return false; 203 } 204 } 205 return true; 206 } 207 208 /** 209 * Returns hashcode 210 * 211 * @return HashCode of Fetcher object 212 */ 213 @Override hashCode()214 public int hashCode() { 215 return datas.hashCode() + languageTag.hashCode(); 216 } 217 218 /** 219 * Override methods in Runnable 220 */ run()221 public void run() { 222 getData(); 223 dump(); 224 } 225 226 /** 227 * the i'th value represent whether i'th resource should reserved for this locale 228 * 229 * @param index resource id 230 * @return 1 represents i'th resource reserved. 231 */ reservedGet(int index)232 public int reservedGet(int index) { 233 return reserved.get(index); 234 } 235 236 /** 237 * the i'th value represent whether i'th resource should reserved for this locale 238 * 239 * @param val whether reserves current resource 240 */ reservedAdd(int val)241 public void reservedAdd(int val) { 242 reserved.add(val); 243 } 244 convertNoAscii(String str)245 private String convertNoAscii(String str) { 246 return str; 247 } 248 249 // Get month names getMonthNames(int formatType, int lengthType)250 private void getMonthNames(int formatType, int lengthType) { 251 StringBuilder sb = new StringBuilder(); 252 String[] months = formatSymbols.getMonths(formatType, lengthType); 253 for (int i = 0; i < months.length; i++) { 254 sb.append(months[i]); 255 if (i != months.length - 1) { 256 sb.append(FileConfig.SEP); 257 } 258 } 259 datas.add(sb.toString()); 260 } 261 262 // Get weekday names getWeekDayNames(int formatType, int lengthType)263 private void getWeekDayNames(int formatType, int lengthType) { 264 StringBuilder sb = new StringBuilder(); 265 String[] weekdays = formatSymbols.getWeekdays(formatType, lengthType); 266 String[] adjustWeekdays = new String[(weekdays.length - 1)]; 267 for (int i = 0; i < adjustWeekdays.length; i++) { 268 adjustWeekdays[i] = weekdays[i + 1]; 269 } 270 for (int i = 0; i < adjustWeekdays.length; i++) { 271 sb.append(adjustWeekdays[i]); 272 if (i != adjustWeekdays.length - 1) { 273 sb.append(FileConfig.SEP); 274 } 275 } 276 this.datas.add(sb.toString()); 277 } 278 getPatterns(ConfigItem config)279 private void getPatterns(ConfigItem config) { 280 if (config.elements == null) { 281 throw new IllegalArgumentException("no patterns defined in resource_items.json for index: " + config.index); 282 } 283 Element[] elements = config.elements; 284 int current = 0; 285 ArrayList<String> skeletons = new ArrayList<String>(); 286 for (Element ele : elements) { 287 int index = ele.index; 288 if (current != index) { 289 throw new IllegalStateException("wrong index order in patterns for index: " + config.index); 290 } 291 ++current; 292 skeletons.add(ele.skeleton); 293 } 294 StringBuilder sb = new StringBuilder(); 295 String[] outPatterns = new String[skeletons.size()]; 296 processPatterns(outPatterns, skeletons); 297 for (int i = 0; i < skeletons.size(); i++) { 298 sb.append(outPatterns[i]); 299 if (i != outPatterns.length - 1) { 300 sb.append(FileConfig.SEP); 301 } 302 } 303 datas.add(sb.toString()); 304 } 305 processPatterns(String[] outPatterns, ArrayList<String> skeletons)306 private void processPatterns(String[] outPatterns, ArrayList<String> skeletons) { 307 for (int i = 0; i < skeletons.size(); ++i) { 308 switch (skeletons.get(i)) { 309 case "FULL": 310 case "MEDIUM": 311 case "SHORT": { 312 outPatterns[i] = getFMSPattern(skeletons.get(i)); 313 break; 314 } 315 default: { 316 processSpecialPattern(outPatterns, skeletons, i); 317 } 318 } 319 } 320 } 321 processSpecialPattern(String[] outPatterns, ArrayList<String> skeletons, int i)322 private void processSpecialPattern(String[] outPatterns, ArrayList<String> skeletons, int i) { 323 if ("en-US".equals(languageTag) && ("Ed".equals(skeletons.get(i)))) { 324 outPatterns[i] = "EEE d"; 325 return; 326 } 327 if ("jm".equals(skeletons.get(i))) { 328 if ("h".equals(defaultHourString)) { 329 outPatterns[i] = patternGenerator.getBestPattern("hm"); 330 } else { 331 outPatterns[i] = patternGenerator.getBestPattern("Hm"); 332 } 333 return; 334 } 335 if ("jms".equals(skeletons.get(i))) { 336 if ("h".equals(defaultHourString)) { 337 outPatterns[i] = patternGenerator.getBestPattern("hms"); 338 } else { 339 outPatterns[i] = patternGenerator.getBestPattern("Hms"); 340 } 341 return; 342 } 343 outPatterns[i] = patternGenerator.getBestPattern(skeletons.get(i)); 344 } 345 346 // Get FULL-MEDIUM_SHORT pattern getFMSPattern(String skeleton)347 private String getFMSPattern(String skeleton) { 348 DateFormat formatter = null; 349 try { 350 Field patternField = DateFormat.class.getField(skeleton); 351 int patternIndex = patternField.getInt(null); 352 formatter = DateFormat.getDateInstance(patternIndex, locale); 353 } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException e ) { 354 LOG.log(Level.SEVERE, "cannot get field " + skeleton); 355 } 356 if (formatter instanceof SimpleDateFormat) { 357 return ((SimpleDateFormat)formatter).toPattern(); 358 } else { 359 LOG.log(Level.SEVERE, "wrong type in getFMSPattern"); 360 return ""; 361 } 362 } 363 364 // 0. get format abbreviated month names getFormatAbbrMonthNames(ConfigItem config)365 private void getFormatAbbrMonthNames(ConfigItem config) { 366 getMonthNames(DateFormatSymbols.FORMAT, DateFormatSymbols.ABBREVIATED); 367 } 368 369 // 1. get format abbreviated day names getFormatAbbrDayNames(ConfigItem config)370 private void getFormatAbbrDayNames(ConfigItem config) { 371 getWeekDayNames(DateFormatSymbols.FORMAT, DateFormatSymbols.ABBREVIATED); 372 } 373 374 // 4. get am pm markser getAmPmMarkers(ConfigItem config)375 private void getAmPmMarkers(ConfigItem config) { 376 StringBuilder sb = new StringBuilder(); 377 String[] amPmStrings = formatSymbols.getAmPmStrings(); 378 for (int i = 0; i < amPmStrings.length; ++i) { 379 sb.append(amPmStrings[i]); 380 if (i != amPmStrings.length - 1) { 381 sb.append(FileConfig.SEP); 382 } 383 } 384 this.datas.add(sb.toString()); 385 } 386 387 // 5. get plural data getPluralRules(ConfigItem config)388 private void getPluralRules(ConfigItem config) { 389 String str = PluralFetcher.getInstance().get(this.lan); 390 if (str == null) { 391 str = ""; 392 } 393 this.datas.add(str); 394 } 395 getDecimalPluralRules(ConfigItem config)396 private void getDecimalPluralRules(ConfigItem config) { 397 String str = PluralFetcher.getInstance().getDecimal(this.lan); 398 if (str == null) { 399 str = ""; 400 } 401 this.datas.add(str); 402 } 403 404 // 6. get number format data 405 @SuppressWarnings("Deprecation") getNumberFormat(ConfigItem config)406 private void getNumberFormat(ConfigItem config) { 407 String pattern = NumberFormat.getPatternForStyle(locale, NumberFormat.NUMBERSTYLE); 408 String percentPattern = NumberFormat.getPatternForStyle(locale, NumberFormat.PERCENTSTYLE); 409 DecimalFormatSymbols decimalFormatSymbols = new DecimalFormatSymbols(locale); 410 String percent = decimalFormatSymbols.getPercentString(); 411 String groupingSeparator = decimalFormatSymbols.getGroupingSeparatorString(); 412 String decimalSeparator = decimalFormatSymbols.getDecimalSeparatorString(); 413 StringBuilder sb = new StringBuilder(); 414 sb.append(pattern); 415 sb.append(FileConfig.SEP); 416 sb.append(percentPattern); 417 sb.append(FileConfig.SEP); 418 sb.append(convertNoAscii(decimalSeparator)); 419 sb.append(FileConfig.SEP); 420 sb.append(convertNoAscii(groupingSeparator)); 421 sb.append(FileConfig.SEP); 422 sb.append(convertNoAscii(percent)); 423 datas.add(sb.toString()); 424 } 425 426 // 7. get number digits getNumberDigits(ConfigItem config)427 private void getNumberDigits(ConfigItem config) { 428 NumberingSystem numberSystem = NumberingSystem.getInstance(locale); 429 String description = numberSystem.getDescription(); 430 StringBuilder sb = new StringBuilder(); 431 for (int i = 0; i < description.length(); i++) { 432 sb.append(String.valueOf(description.charAt(i))); 433 if (i != description.length() - 1) { 434 sb.append(FileConfig.NUMBER_SEP); 435 } 436 } 437 datas.add(sb.toString()); 438 } 439 440 // 8. get time separtor 441 @SuppressWarnings("Deprecation") getTimeSeparator(ConfigItem config)442 private void getTimeSeparator(ConfigItem config) { 443 datas.add(formatSymbols.getTimeSeparatorString()); 444 } 445 446 // 9. get default hour getDefaultHour(ConfigItem config)447 private void getDefaultHour(ConfigItem config) { 448 datas.add(defaultHourString); 449 } 450 451 // 10.get standalone abbreviated month getStandAloneAbbrMonthNames(ConfigItem config)452 private void getStandAloneAbbrMonthNames(ConfigItem config) { 453 getMonthNames(DateFormatSymbols.STANDALONE, DateFormatSymbols.ABBREVIATED); 454 } 455 456 // 11. get standalone abbreviated weekday getStandAloneAbbrWeekDayNames(ConfigItem config)457 private void getStandAloneAbbrWeekDayNames(ConfigItem config) { 458 getWeekDayNames(DateFormatSymbols.STANDALONE, DateFormatSymbols.ABBREVIATED); 459 } 460 461 // 12. get format wide month getFormatWideMonthNames(ConfigItem config)462 private void getFormatWideMonthNames(ConfigItem config) { 463 getMonthNames(DateFormatSymbols.FORMAT, DateFormatSymbols.WIDE); 464 } 465 466 // 13. get format wide days getFormatWideWeekDayNames(ConfigItem config)467 private void getFormatWideWeekDayNames(ConfigItem config) { 468 getWeekDayNames(DateFormatSymbols.FORMAT, DateFormatSymbols.WIDE); 469 } 470 471 // 14. get standalone wide days getStandAloneWideWeekDayNames(ConfigItem config)472 private void getStandAloneWideWeekDayNames(ConfigItem config) { 473 getWeekDayNames(DateFormatSymbols.STANDALONE, DateFormatSymbols.WIDE); 474 } 475 476 // 15. get standalone wide month getStandAloneWideMonthNames(ConfigItem config)477 private void getStandAloneWideMonthNames(ConfigItem config) { 478 getMonthNames(DateFormatSymbols.STANDALONE, DateFormatSymbols.WIDE); 479 } 480 481 // 16. get measure format pattern getMeasureFormatPatterns(ConfigItem config)482 private void getMeasureFormatPatterns(ConfigItem config) { 483 String str = MeasureFormatPatternFetcher.getInstance().get(this.languageTag); 484 if (str == null) { 485 str = ""; 486 } 487 this.datas.add(str); 488 } 489 defaultHour()490 private String defaultHour() { 491 DateFormat tempFormat = DateFormat 492 .getTimeInstance(DateFormat.SHORT, ULocale.forLanguageTag(languageTag)); 493 SimpleDateFormat timeInstance = null; 494 if (tempFormat instanceof SimpleDateFormat) { 495 timeInstance = (SimpleDateFormat) tempFormat; 496 } 497 String shortDateTimePattern = (timeInstance == null) ? "" : timeInstance.toPattern(); 498 if (shortDateTimePattern.contains("H")) { 499 return "H"; 500 } else { 501 return "h"; 502 } 503 } 504 getWeekdata(ConfigItem config)505 private void getWeekdata(ConfigItem config) { 506 Calendar cal = Calendar.getInstance(ULocale.forLanguageTag(languageTag)); 507 Calendar.WeekData weekdata = cal.getWeekData(); 508 StringBuilder sb = new StringBuilder(); 509 sb.append(weekdata.firstDayOfWeek); 510 sb.append(FileConfig.NUMBER_SEP); 511 sb.append(weekdata.minimalDaysInFirstWeek); 512 sb.append(FileConfig.NUMBER_SEP); 513 sb.append(weekdata.weekendOnset); 514 sb.append(FileConfig.NUMBER_SEP); 515 sb.append(weekdata.weekendCease); 516 datas.add(sb.toString()); 517 } 518 getMinusSign(ConfigItem config)519 private void getMinusSign(ConfigItem config) { 520 NumberFormat formatter = NumberFormat.getNumberInstance(locale); 521 String formatValue = formatter.format(-1); 522 NumberingSystem numberSystem = NumberingSystem.getInstance(locale); 523 String description = numberSystem.getDescription(); 524 if (formatValue.length() > 0) { 525 String temp = formatValue.substring(0, formatValue.indexOf(description.charAt(1))); 526 datas.add(temp); 527 } 528 } 529 compareTo(Fetcher other)530 public @Override int compareTo(Fetcher other) { 531 if (languageTag == null && other.languageTag == null) { 532 return 0; 533 } 534 if (languageTag == null) { 535 return -1; 536 } else if (other.languageTag == null) { 537 return 1; 538 } else { 539 return languageTag.compareTo(other.languageTag); 540 } 541 } 542 } 543