• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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