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