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