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.util.ULocale; 19 20 import java.io.BufferedReader; 21 import java.io.BufferedWriter; 22 import java.io.DataOutputStream; 23 import java.io.IOException; 24 import java.io.InputStreamReader; 25 import java.io.OutputStreamWriter; 26 import java.io.UnsupportedEncodingException; 27 import java.nio.charset.StandardCharsets; 28 import java.util.ArrayList; 29 import java.util.Comparator; 30 import java.util.Iterator; 31 import java.io.File; 32 import java.io.FileInputStream; 33 import java.io.FileOutputStream; 34 import java.util.logging.Level; 35 import java.util.logging.Logger; 36 37 import ohos.global.i18n.ResourceConfiguration.ConfigItem; 38 import ohos.global.i18n.ResourceConfiguration.Element; 39 40 /** 41 * utils class. 42 * 43 * @since 2022-8-22 44 */ 45 public class Utils { 46 private static final String AVAILABLE_LINE = "enum AvailableDateTimeFormatPattern {"; 47 private static final String AVAILABLE_END_LINE = "};"; 48 private static final int TYPE_SHIFT = 16; 49 private static final String PATTERN_INDEX_MASK = "#define PATTERN_INDEX_MASK = 0x0000ffff"; 50 private static final String I18N_MACROS_BEGIN = "// this file should only be included by date_time_format_impl.cpp"; 51 private static final int MAX_CASE_NUMBER = 14; 52 private static final Logger LOG = Logger.getLogger("Utils"); 53 Utils()54 private Utils() {} 55 56 /** 57 * Get a locale's fallback, locale is specified with languageTag 58 * 59 * @param languageTag Use this languageTag to compute the fallback 60 * @return Fallback languageTag 61 */ getFallback(String languageTag)62 public static String getFallback(String languageTag) { 63 if ("".equals(languageTag)) { 64 return "en-US"; 65 } 66 String[] split = languageTag.split("-"); 67 if ("en-US".equals(languageTag) || split.length == 1) { 68 return "en-US"; 69 } 70 if (split.length != 2) { 71 return split[0] + "-" + split[1]; 72 } 73 if ((split[1].length() != 4) && (!"en".equals(split[0]))) { 74 return split[0]; 75 } 76 return "en-US"; 77 } 78 79 /** 80 * Determines whether a languageTag is valid. 81 * 82 * @param languageTag tag to be checked 83 * @return returns true if languageTag is valid, otherwise false. 84 */ isValidLanguageTag(String languageTag)85 public static boolean isValidLanguageTag(String languageTag) { 86 if (languageTag == null) { 87 return false; 88 } 89 String[] items = languageTag.split("-"); 90 switch (items.length) { 91 case 1: { 92 return checkLanguage(items[0]); 93 } 94 case 2: { 95 if (!checkLanguage(items[0])) { 96 return false; 97 } 98 // script 99 if (items[1].length() == 4) { 100 if (checkScript(items[1])) { 101 return true; 102 } 103 } else if (items[1].length() == 2) { 104 if (checkRegion(items[1])) { 105 return true; 106 } 107 } else { 108 return false; 109 } 110 return false; 111 } 112 case 3: { 113 return checkLanguage(items[0]) && checkScript(items[1]) && checkRegion(items[2]); 114 } 115 default: { 116 return false; 117 } 118 } 119 } 120 checkLanguage(String lan)121 private static boolean checkLanguage(String lan) { 122 if (lan == null) { 123 return false; 124 } 125 int length = lan.length(); 126 if (length > 3 || length < 2) { 127 return false; 128 } 129 for (int i = 0; i < length; ++i) { 130 if ((int) lan.charAt(i) > 255) { 131 return false; 132 } 133 } 134 return true; 135 } 136 137 // script is a 4 character string, started with a uppercase letter checkScript(String script)138 private static boolean checkScript(String script) { 139 int length = script.length(); 140 if (length != 4) { 141 return false; 142 } 143 for (int i = 0; i < length; ++i) { 144 if (i == 0) { 145 if (!Character.isUpperCase(script.charAt(0))) { 146 return false; 147 } 148 } else { 149 char cur = script.charAt(i); 150 if ('a' > cur || 'z' < cur) { 151 return false; 152 } 153 } 154 } 155 return true; 156 } 157 checkRegion(String region)158 private static boolean checkRegion(String region) { 159 int length = region.length(); 160 if (length != 2) { 161 return false; 162 } 163 for (int i = 0; i < length; ++i) { 164 char cur = region.charAt(i); 165 if ('A' > cur || 'Z' < cur) { 166 return false; 167 } 168 } 169 return true; 170 } 171 172 /** 173 * Write i18n.dat's Header to DataOutputStream 174 * 175 * @param out data will be written into the stream 176 * @param hashCode reserved for future use 177 * @param localesCount valid locales in total 178 * @param metaCount all metaData in total 179 * @throws IOException throw IOException if write error happen 180 */ writeHeader(DataOutputStream out, int hashCode, int localesCount, int metaCount)181 public static void writeHeader(DataOutputStream out, int hashCode, int localesCount, 182 int metaCount) throws IOException { 183 out.writeInt(hashCode); // reserved hashcode 184 out.writeByte(FileConfig.FILE_VERSION); 185 out.writeByte(0); // reserved 186 out.writeChar(0); 187 out.writeChar(0); // reserved 188 out.writeChar(FileConfig.HEADER_SIZE + 8 * localesCount); 189 out.writeChar(localesCount); 190 out.writeChar(FileConfig.HEADER_SIZE + 8 * localesCount + metaCount * 6); 191 out.flush(); 192 } 193 194 /** 195 * Get mask of a locale 196 * 197 * @param locale Indicates the specified locale related to the output mask 198 * @param maskOut The value of mask will be stored in the first element of maskOut 199 * @return The text representation of mask in hex format 200 * @throws UnsupportedEncodingException if getBytes function failed 201 */ getMask(ULocale locale, long[] maskOut)202 public static String getMask(ULocale locale, long[] maskOut) throws UnsupportedEncodingException { 203 long mask = 0L; 204 byte[] langs; 205 // Deal with "fil" and "mai" these 3-leters language 206 if ("fil".equals(locale.getLanguage())) { 207 langs = "tl".getBytes("utf-8"); 208 } else if ("mai".equals(locale.getLanguage())) { 209 langs = "md".getBytes("utf-8"); 210 } else { 211 langs = locale.getLanguage().getBytes("utf-8"); 212 } 213 mask = mask | ((long) (langs[0] - 48)) << 25 | ((long) (langs[1] - 48)) << 18; 214 int temp = 0; 215 if ("Latn".equals(locale.getScript())) { 216 temp = 1; 217 } else if ("Hans".equals(locale.getScript())) { 218 temp = 2; 219 } else if ("Hant".equals(locale.getScript())) { 220 temp = 3; 221 } else if ("Qaag".equals(locale.getScript())) { 222 temp = 4; 223 } else if ("Cyrl".equals(locale.getScript())) { 224 temp = 5; 225 } else if ("Deva".equals(locale.getScript())) { 226 temp = 6; 227 } else { 228 temp = "Guru".equals(locale.getScript()) ? 7 : 0; 229 } 230 mask = mask | ((long) temp << 14); 231 if (locale.getCountry() != null && locale.getCountry().length() == 2) { 232 byte[] ret = locale.getCountry().getBytes("utf-8"); 233 mask = mask | ((long) (ret[0] - 48) << 7) | ((long) (ret[1] - 48)); 234 } 235 maskOut[0] = mask; 236 return "0x" + Long.toHexString(mask); 237 } 238 239 /** 240 * Generate the types.h in interfaces 241 * 242 * @param src the original types.h file 243 * @param dst the generated types.h file 244 * @param configItems ConfigItems extracted from resource_items.json 245 */ generateTypesFile(File src, File dst, ArrayList<ConfigItem> configItems)246 public static void generateTypesFile(File src, File dst, ArrayList<ConfigItem> configItems) { 247 try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(src), 248 StandardCharsets.UTF_8)); 249 BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(dst), 250 StandardCharsets.UTF_8))) { 251 String line = null; 252 boolean found = false; 253 while ((line = reader.readLine()) != null) { 254 if (!found) { 255 writer.write(line + System.lineSeparator()); 256 } 257 if (AVAILABLE_LINE.equals(line)) { 258 found = true; 259 writer.write(generateAvailableDateTimeFormatPattern(configItems)); 260 continue; 261 } 262 if (found && AVAILABLE_END_LINE.equals(line)) { 263 writer.write(line + System.lineSeparator()); 264 found = false; 265 } 266 } 267 } catch (IOException e) { 268 LOG.log(Level.SEVERE, "Generated errors when processed file."); 269 } 270 } 271 generateAvailableDateTimeFormatPattern(ArrayList<ConfigItem> configItems)272 private static String generateAvailableDateTimeFormatPattern(ArrayList<ConfigItem> configItems) { 273 ArrayList<Element> adjust = new ArrayList<>(); 274 for (ConfigItem item : configItems) { 275 if ("true".equals(item.pub) && item.elements != null) { 276 for (Element ele : item.elements) { 277 adjust.add(ele); 278 } 279 } 280 } 281 adjust.sort(new Comparator<Element>() { 282 @Override 283 public int compare(Element first, Element second) { 284 if (first.enumIndex < second.enumIndex) { 285 return -1; 286 } else if (first.enumIndex > second.enumIndex) { 287 return 1; 288 } else { 289 return 0; 290 } 291 } 292 }); 293 StringBuilder sb = new StringBuilder(); 294 for (int i = 0; i < adjust.size(); ++i) { 295 sb.append("\t"); 296 sb.append(adjust.get(i).getAvailableFormat()); 297 if (i != adjust.size() - 1) { 298 sb.append(","); 299 } 300 sb.append(System.lineSeparator()); 301 } 302 return sb.toString(); 303 } 304 generateI18nPatternMacros(ArrayList<ConfigItem> configItems)305 private static String generateI18nPatternMacros(ArrayList<ConfigItem> configItems) { 306 ArrayList<ConfigItem> adjust = new ArrayList<>(); 307 for (ConfigItem item : configItems) { 308 if (item.elements != null) { 309 adjust.add(item); 310 } 311 } 312 adjust.sort(new Comparator<ConfigItem>() { 313 @Override 314 public int compare(ConfigItem first, ConfigItem second) { 315 if (first.index < second.index) { 316 return -1; 317 } else if (first.index > second.index) { 318 return 1; 319 } else { 320 return 0; 321 } 322 } 323 }); 324 int current = 0; 325 StringBuilder sb = new StringBuilder(); 326 for (ConfigItem item : adjust) { 327 int type = current++; 328 int innerIndex = 0; 329 for (Element ele : item.elements) { 330 if (innerIndex++ != ele.index) { 331 throw new IllegalStateException("not consecutive index in resourceItem " + item.index); 332 } 333 sb.append("#define " + ele.getAvailableFormat() + "_INDEX " + getHexIndexString(type, ele.index) + 334 System.lineSeparator()); 335 } 336 } 337 sb.append(PATTERN_INDEX_MASK + System.lineSeparator()); 338 sb.append("#define TYPE_SHIFT " + TYPE_SHIFT + System.lineSeparator()); 339 return sb.toString(); 340 } 341 342 /** 343 * Generate the i18n_pattern.h in frameworks 344 * 345 * @param src the original i18n_pattern.h file path 346 * @param dst the generated i18n_pattern.h file path 347 * @param items ConfigItems extracted from resource_items.json 348 */ generateI18nPatternFile(File src, File dst, ArrayList<ConfigItem> items)349 public static void generateI18nPatternFile(File src, File dst, ArrayList<ConfigItem> items) { 350 try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(src), "utf-8")); 351 BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(dst), "utf-8"))) { 352 String line = null; 353 boolean found = false; 354 while ((line = reader.readLine()) != null) { 355 if (found && ("} // I18N".equals(line))) { 356 found = false; 357 } 358 if (!found) { 359 writer.write(line + System.lineSeparator()); 360 } 361 if (I18N_MACROS_BEGIN.equals(line)) { 362 found = true; 363 writer.write(generateI18nPatternMacros(items)); 364 writer.write(System.lineSeparator()); 365 writer.write("namespace OHOS{" + System.lineSeparator()); 366 writer.write("namespace I18N{" + System.lineSeparator()); 367 writer.write(getPatternTypeEnum(items)); 368 writer.write(System.lineSeparator()); 369 writer.write(getGetPatternFromIndexCode(items)); 370 writer.write(System.lineSeparator()); 371 writer.write(getGetStringFromPattern(items)); 372 continue; 373 } 374 } 375 } catch (IOException e) { 376 LOG.log(Level.SEVERE, "Generated error when processed file."); 377 } 378 } 379 getGetStringFromPattern(ArrayList<ConfigItem> configItems)380 private static String getGetStringFromPattern(ArrayList<ConfigItem> configItems) { 381 ArrayList<Element> eles = new ArrayList<>(); 382 for (ConfigItem item : configItems) { 383 if (item.elements == null) { 384 continue; 385 } 386 for (Element ele : item.elements) { 387 if (ele.enumIndex >= 0) { 388 eles.add(ele); 389 } 390 } 391 } 392 int size = eles.size(); 393 // every GetStringFromPattern function can only have 14 cases; 394 int functionSize = 1; 395 if (size >= (MAX_CASE_NUMBER + 1)) { 396 if (1 == size % MAX_CASE_NUMBER) { 397 functionSize = size / MAX_CASE_NUMBER; 398 } else { 399 functionSize = size / MAX_CASE_NUMBER + 1; 400 } 401 } 402 int currentFunction = 1; 403 String[] temp = new String[functionSize]; 404 StringBuilder sb = new StringBuilder(); 405 while (currentFunction <= functionSize) { 406 temp[currentFunction - 1] = getGetStringFromPattern(currentFunction, eles); 407 ++currentFunction; 408 } 409 for (int i = functionSize - 1; i >= 0; --i) { 410 sb.append(temp[i]); 411 if (i != 0) { 412 sb.append(System.lineSeparator()); 413 } 414 } 415 return sb.toString(); 416 } 417 getGetStringFromPattern(int functionIndex, ArrayList<Element> left)418 private static String getGetStringFromPattern(int functionIndex, ArrayList<Element> left) { 419 StringBuilder sb = new StringBuilder(); 420 if (functionIndex == 1) { 421 sb.append("std::string GetStringFromPattern(const AvailableDateTimeFormatPattern &requestPattern," + 422 "const DateTimeData* const data)"); 423 } else { 424 sb.append("std::string GetStringFromPattern" + functionIndex + "(const AvailableDateTimeFormatPattern" + 425 "&requestPattern, const DateTimeData* const data)"); 426 } 427 sb.append(System.lineSeparator() + "{" + System.lineSeparator()); 428 sb.append(" switch (requestPattern) {" + System.lineSeparator()); 429 int totalLength = 0; 430 boolean hasRemainingFunction = true; 431 if (left.size() <= (MAX_CASE_NUMBER + 1)) { 432 totalLength = left.size(); 433 hasRemainingFunction = false; 434 } else { 435 totalLength = MAX_CASE_NUMBER; 436 } 437 Iterator<Element> iter = left.iterator(); 438 while (iter.hasNext() && (totalLength-- > 0)) { 439 Element ele = iter.next(); 440 if (totalLength == 0 && !hasRemainingFunction) { 441 sb.append(" default: {" + System.lineSeparator()); 442 } else { 443 sb.append(" case " + ele.getAvailableFormat() + ": {" + System.lineSeparator()); 444 } 445 sb.append(" return GetPatternFromIndex(" + ele.getAvailableFormat() + "_INDEX, data);" + 446 System.lineSeparator()); 447 sb.append(" }" + System.lineSeparator()); 448 iter.remove(); 449 } 450 if (hasRemainingFunction) { 451 sb.append(" default: {" + System.lineSeparator()); 452 sb.append(" return GetPatternFromIndex" + (functionIndex + 1) + "(requestPattern, data);" + 453 System.lineSeparator()); 454 sb.append(" }" + System.lineSeparator()); 455 } 456 sb.append(" }" + System.lineSeparator()); 457 sb.append("}" + System.lineSeparator()); 458 return sb.toString(); 459 } 460 getGetPatternFromIndexCode(ArrayList<ConfigItem> configItems)461 private static String getGetPatternFromIndexCode(ArrayList<ConfigItem> configItems) { 462 StringBuilder sb = new StringBuilder(); 463 sb.append("std::string GetPatternFromIndex(uint32_t index, const DateTimeData * const data)" + 464 System.lineSeparator()); 465 sb.append("{" + System.lineSeparator()); 466 sb.append(" uint32_t type = index >> PATTERN_TYPE_SHIFT;" + System.lineSeparator()); 467 sb.append(" if (type > PatternType::PATTERN_TYPE_END) {" + System.lineSeparator()); 468 sb.append(" return \"\";" + System.lineSeparator()); 469 sb.append(" }" + System.lineSeparator()); 470 sb.append(" uint32_t ind = index & PATTERN_INDEX_MASK;" + System.lineSeparator()); 471 sb.append(" PatternType patternType = static_cast<PatternType>(type);" + System.lineSeparator()); 472 sb.append(" switch (patternType) {" + System.lineSeparator()); 473 ArrayList<ConfigItem> adjust = new ArrayList<>(); 474 for (ConfigItem item : configItems) { 475 if (item.type != null) { 476 adjust.add(item); 477 } 478 } 479 for (int i = 0; i < adjust.size(); ++i) { 480 if (i != adjust.size() - 1) { 481 sb.append(" case " + adjust.get(i).type + ": {" + System.lineSeparator()); 482 } else { 483 sb.append(" default: {" + System.lineSeparator()); 484 } 485 sb.append(" return Parse(data->" + adjust.get(i).pointer + " , ind);" + System.lineSeparator()); 486 sb.append(" }" + System.lineSeparator()); 487 } 488 sb.append(" }" + System.lineSeparator()); 489 sb.append("}" + System.lineSeparator()); 490 return sb.toString(); 491 } 492 getPatternTypeEnum(ArrayList<ConfigItem> configItems)493 private static String getPatternTypeEnum(ArrayList<ConfigItem> configItems) { 494 StringBuilder sb = new StringBuilder(); 495 sb.append("enum PatternType {" + System.lineSeparator()); 496 sb.append(" PATTERN_TYPE_BEGIN = 0," + System.lineSeparator()); 497 for (int i = 0; i < configItems.size(); ++i) { 498 if (configItems.get(i).type == null) { 499 continue; 500 } 501 if (i == 0) { 502 sb.append(" " + configItems.get(i).type + " = PATTERN_TYPE_BEGIN," + System.lineSeparator()); 503 } else { 504 sb.append(" " + configItems.get(i).type + "," + System.lineSeparator()); 505 } 506 } 507 sb.append(" PATTERN_TYPE_END" + System.lineSeparator()); 508 sb.append("};" + System.lineSeparator()); 509 return sb.toString(); 510 } 511 getHexIndexString(int type, int index)512 private static String getHexIndexString(int type, int index) { 513 if (type < 0 || index < 0) { 514 return ""; 515 } 516 return "0x" + Integer.toHexString((type << TYPE_SHIFT) + index); 517 } 518 } 519