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