• 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.restool;
17 
18 import ohos.BundleException;
19 import ohos.Log;
20 import ohos.ResourceIndexResult;
21 
22 import java.nio.ByteBuffer;
23 import java.nio.ByteOrder;
24 import java.nio.charset.StandardCharsets;
25 import java.util.ArrayList;
26 import java.util.HashMap;
27 import java.util.List;
28 import java.util.Optional;
29 
30 /**
31  * Resources Parser.
32  *
33  * @since 2021-03-11
34  */
35 public class ResourcesParserV1 implements ResourcesParser {
36     /**
37      * Parses resources default id.
38      */
39     public static final int RESOURCE_DEFAULT_ID = -1;
40 
41     private static final Log LOG = new Log(ResourcesParserV1.class.toString());
42     private static final int VERSION_BYTE_LENGTH = 128;
43     private static final int TAG_BYTE_LENGTH = 4;
44     private static final String LEFT_BRACKET = "<";
45     private static final String RIGHT_BRACKET = ">";
46     private static final String VERTICAL = "vertical";
47     private static final String HORIZONTAL = "horizontal";
48     private static final String DARK = "dark";
49     private static final String LIGHT = "light";
50     private static final String MCC = "mcc";
51     private static final String MNC = "mnc";
52     private static final String CONFIG_CONJUNCTION = "-";
53     private static final String MCC_CONJUNCTION = "_";
54     private static final String BASE = "base";
55     private static final int CHAR_LENGTH = 1;
56     private static final String EMPTY_STRING = "";
57 
58     private enum ResType {
59         VALUES(0, "Values"),
60         ANIMATOR(1, "Animator"),
61         DRAWABLE(2, "Drawable"),
62         LAYOUT(3, "Layout"),
63         MENU(4, "Menu"),
64         MIPMAP(5, "Mipmap"),
65         RAW(6, "Raw"),
66         XML(7, "Xml"),
67         INTEGER(8, "Integer"),
68         STRING(9, "String"),
69         STR_ARRAY(10, "StrArray"),
70         INT_ARRAY(11, "IntArray"),
71         BOOLEAN(12, "Boolean"),
72         DIMEN(13, "Dimen"),
73         COLOR(14, "Color"),
74         ID(15, "Id"),
75         THEME(16, "Theme"),
76         PLURALS(17, "Plurals"),
77         FLOAT(18, "Float"),
78         MEDIA(19, "Media"),
79         PROF(20, "Prof"),
80         SVG(21, "Svg"),
81         PATTERN(22, "Pattern");
82 
83         private final int index;
84         private final String type;
85 
ResType(int index, String type)86         private ResType(int index, String type) {
87             this.index = index;
88             this.type = type;
89         }
90 
getType(int index)91         public static String getType(int index) {
92             for (ResType resType : ResType.values()) {
93                 if (resType.getIndex() == index) {
94                     return resType.type;
95                 }
96             }
97             return "";
98         }
99 
getIndex()100         public int getIndex() {
101             return index;
102         }
getType()103         public String getType() {
104             return type;
105         }
106     }
107 
108     private enum DeviceType {
109         PHONE(0, "phone"),
110         TABLET(1, "tablet"),
111         CAR(2, "car"),
112         PC(3, "pc"),
113         TV(4, "tv"),
114         SPEAKER(5, "speaker"),
115         WEARABLE(6, "wearable"),
116         GLASSES(7, "glasses"),
117         HEADSET(8, "headset");
118 
119         private final int index;
120         private final String type;
DeviceType(int index, String type)121         private DeviceType(int index, String type) {
122             this.index = index;
123             this.type = type;
124         }
125 
getType(int index)126         public static String getType(int index) {
127             for (DeviceType deviceType : DeviceType.values()) {
128                 if (deviceType.getIndex() == index) {
129                     return deviceType.type;
130                 }
131             }
132             return "";
133         }
134 
getIndex()135         public int getIndex() {
136             return index;
137         }
getType()138         public String getType() {
139             return type;
140         }
141     }
142 
143     private enum Resolution {
144         NODPI(-2, "nodpi"),
145         ANYDPI(-1, "anydpi"),
146         SDPI(120, "sdpi"),
147         MDPI(160, "mdpi"),
148         TVDPI(213, "tvdpi"),
149         LDPI(240, "ldpi"),
150         XLDPI(320, "xldpi"),
151         XXLDPI(480, "xxldpi"),
152         XXXLDPI(640, "xxxldpi");
153 
154         private final int index;
155         private final String type;
Resolution(int index, String type)156         private Resolution(int index, String type) {
157             this.index = index;
158             this.type = type;
159         }
getType(int index)160         public static String getType(int index) {
161             for (Resolution resolution : Resolution.values()) {
162                 if (resolution.getIndex() == index) {
163                     return resolution.type;
164                 }
165             }
166             return "";
167         }
168 
getIndex()169         public int getIndex() {
170             return index;
171         }
getType()172         public String getType() {
173             return type;
174         }
175     }
176 
177     private enum ConfigType {
178         LANGUAGE,
179         REGION,
180         RESOLUTION,
181         DIRECTION,
182         DEVICE_TYPE,
183         SCRIPT,
184         LIGHT_MODE,
185         MCC,
186         MNC
187     }
188 
189     /**
190      * Key Param.
191      */
192     static class KeyParam {
193         int keyType;
194         int value;
195     }
196 
197     /**
198      * Config Index.
199      */
200     static class ConfigIndex {
201         String tag;
202         int offset;
203         int keyCount;
204         KeyParam[] params;
205     }
206 
207     /**
208      * Data Item.
209      */
210     static class DataItem {
211         int size;
212         int type;
213         int id;
214         String value;
215         String name;
216     }
217 
218     /**
219      * Get resource value by resource id.
220      *
221      * @param resourceId resource id
222      * @param data resource index data
223      * @return the resourceId value
224      * @throws BundleException IOException.
225      */
226     @Override
getResourceById(int resourceId, byte[] data)227     public String getResourceById(int resourceId, byte[] data) throws BundleException {
228         String resourceIdValue = "";
229         if (data == null || data.length <= 0 || resourceId == RESOURCE_DEFAULT_ID) {
230             LOG.error("ResourcesParser::getIconPath data byte or ResourceId is null");
231             return resourceIdValue;
232         }
233 
234         List<String> result = getResource(resourceId, data);
235         if (result != null && result.size() > 0 && result.get(0) != null && !EMPTY_STRING.equals(result.get(0))) {
236             resourceIdValue = result.get(0).substring(0, result.get(0).length() - 1);
237         }
238         return resourceIdValue;
239     }
240 
241     /**
242      * Get base resource value by resource id.
243      *
244      * @param resourceId resource id
245      * @param data resource index data
246      * @return the resource value
247      * @throws BundleException IOException.
248      */
249     @Override
getBaseResourceById(int resourceId, byte[] data)250     public String getBaseResourceById(int resourceId, byte[] data) throws BundleException {
251         String resourceIdValue = "";
252         if (data == null || data.length <= 0 || resourceId == RESOURCE_DEFAULT_ID) {
253             LOG.error("ResourcesParser::getBaseResourceById data byte or ResourceId is null");
254             return resourceIdValue;
255         }
256         resourceIdValue = getBaseResource(resourceId, data);
257         if (resourceIdValue != null && !EMPTY_STRING.equals(resourceIdValue)) {
258             resourceIdValue = resourceIdValue.substring(0, resourceIdValue.length() - 1);
259         }
260         return resourceIdValue;
261     }
262 
263     /**
264      * Get base resource.
265      *
266      * @param resId resource id
267      * @param data resource index data array
268      * @return the resource value
269      * @throws BundleException IOException.
270      */
getBaseResource(int resId, byte[] data)271     static String getBaseResource(int resId, byte[] data) {
272         ByteBuffer byteBuf = ByteBuffer.wrap(data);
273         byteBuf.order(ByteOrder.LITTLE_ENDIAN);
274         byte[] version = new byte[VERSION_BYTE_LENGTH];
275         byteBuf.get(version);
276         byteBuf.getInt();
277         int configCount = byteBuf.getInt();
278         Optional<ConfigIndex> optionalConfigIndex = loadBaseConfig(byteBuf, configCount);
279         if (!optionalConfigIndex.isPresent()) {
280             LOG.error("ResourcesParser::getBaseResource configIndex is null");
281             return "";
282         }
283         return readBaseItem(resId, optionalConfigIndex.get(), byteBuf);
284     }
285 
286     /**
287      * Load index config.
288      *
289      * @param bufBuf config byte buffer
290      * @param count config count
291      * @return the base config index
292      * @throws BundleException IOException.
293      */
loadBaseConfig(ByteBuffer bufBuf, int count)294     static Optional<ConfigIndex> loadBaseConfig(ByteBuffer bufBuf, int count) {
295         for (int i = 0; i < count; i++) {
296             ConfigIndex cfg = new ConfigIndex();
297             byte[] tag = new byte[TAG_BYTE_LENGTH];
298             bufBuf.get(tag);
299             cfg.tag = new String(tag, StandardCharsets.UTF_8);
300             cfg.offset = bufBuf.getInt();
301             cfg.keyCount = bufBuf.getInt();
302             cfg.params = new KeyParam[cfg.keyCount];
303             for (int j = 0; j < cfg.keyCount; j++) {
304                 cfg.params[j] = new KeyParam();
305                 cfg.params[j].keyType = bufBuf.getInt();
306                 cfg.params[j].value = bufBuf.getInt();
307             }
308             if (cfg.keyCount == 0) {
309                 return Optional.of(cfg);
310             }
311         }
312         return Optional.empty();
313     }
314 
315     /**
316      * Read base config item.
317      *
318      * @param resId resource id
319      * @param configIndex the config index
320      * @param buf config byte buffer
321      * @return the base item
322      * @throws BundleException IOException.
323      */
readBaseItem(int resId, ConfigIndex configIndex, ByteBuffer buf)324     static String readBaseItem(int resId, ConfigIndex configIndex, ByteBuffer buf) {
325         buf.rewind();
326         buf.position(configIndex.offset);
327         byte[] tag = new byte[TAG_BYTE_LENGTH];
328         buf.get(tag);
329         int count = buf.getInt();
330         for (int i = 0; i < count; i++) {
331             int id = buf.getInt();
332             int offset = buf.getInt();
333             if (id == resId) {
334                 buf.rewind();
335                 buf.position(offset);
336                 DataItem item = readItem(buf);
337                 return item.value;
338             }
339         }
340         return "";
341     }
342 
343     /**
344      * Get Icon resource.
345      *
346      * @param resId resource id
347      * @param data resource index data array
348      * @return the result
349      * @throws BundleException IOException.
350      */
getResource(int resId, byte[] data)351     static List<String> getResource(int resId, byte[] data) {
352         ByteBuffer byteBuf = ByteBuffer.wrap(data);
353         byteBuf.order(ByteOrder.LITTLE_ENDIAN);
354         byte[] version = new byte[VERSION_BYTE_LENGTH];
355         byteBuf.get(version);
356         byteBuf.getInt();
357         int configCount = byteBuf.getInt();
358         List<ConfigIndex> cfg = loadConfig(byteBuf, configCount);
359         return readAllItem(resId, cfg, byteBuf);
360     }
361 
362     /**
363      * Load index config.
364      *
365      * @param bufBuf config byte buffer
366      * @param count config count
367      * @return the config list
368      * @throws BundleException IOException.
369      */
loadConfig(ByteBuffer bufBuf, int count)370     static List<ConfigIndex> loadConfig(ByteBuffer bufBuf, int count) {
371         List<ConfigIndex> configList = new ArrayList<>(count);
372         for (int i = 0; i < count; i++) {
373             ConfigIndex cfg = new ConfigIndex();
374             byte[] tag = new byte[TAG_BYTE_LENGTH];
375             bufBuf.get(tag);
376             cfg.tag = new String(tag, StandardCharsets.UTF_8);
377             cfg.offset = bufBuf.getInt();
378             cfg.keyCount = bufBuf.getInt();
379             cfg.params = new KeyParam[cfg.keyCount];
380             for (int j = 0; j < cfg.keyCount; j++) {
381                 cfg.params[j] = new KeyParam();
382                 cfg.params[j].keyType = bufBuf.getInt();
383                 cfg.params[j].value = bufBuf.getInt();
384             }
385 
386             configList.add(cfg);
387         }
388         return configList;
389     }
390 
391     /**
392      * Read all config item.
393      *
394      * @param resId resource id
395      * @param configs the config list
396      * @param buf config byte buffer
397      * @return the item list
398      * @throws BundleException IOException.
399      */
readAllItem(int resId, List<ConfigIndex> configs, ByteBuffer buf)400     static List<String> readAllItem(int resId, List<ConfigIndex> configs, ByteBuffer buf) {
401         List<String> result = new ArrayList<>();
402         for (ConfigIndex index : configs) {
403             buf.rewind();
404             buf.position(index.offset);
405             byte[] tag = new byte[TAG_BYTE_LENGTH];
406             buf.get(tag);
407             int count = buf.getInt();
408             for (int i = 0; i < count; i++) {
409                 int id = buf.getInt();
410                 int offset = buf.getInt();
411                 if (id == resId) {
412                     buf.rewind();
413                     buf.position(offset);
414                     DataItem item = readItem(buf);
415                     result.add(item.value);
416                     break;
417                 }
418             }
419         }
420         return result;
421     }
422 
423     /**
424      * Read the config item.
425      *
426      * @param buf config byte buffer
427      * @return the item info
428      */
readItem(ByteBuffer buf)429     static DataItem readItem(ByteBuffer buf) {
430         DataItem item = new DataItem();
431         item.size = buf.getInt();
432         item.type = buf.getInt();
433         item.id = buf.getInt();
434         int len = buf.getShort() & 0xFFFF;
435         byte[] value = new byte[len];
436         buf.get(value);
437         item.value = new String(value, StandardCharsets.UTF_8);
438         len = buf.getShort() & 0xFFFF;
439         byte[] name = new byte[len];
440         buf.get(name);
441         item.name = new String(name, StandardCharsets.UTF_8);
442         return item;
443     }
444 
445     /**
446      * Read all config item.
447      *
448      * @param data config byte buffer
449      * @return the item info.
450      */
451     @Override
getAllDataItem(byte[] data)452     public List<ResourceIndexResult> getAllDataItem(byte[] data) {
453         ByteBuffer byteBuf = ByteBuffer.wrap(data);
454         byteBuf.order(ByteOrder.LITTLE_ENDIAN);
455         byte[] version = new byte[VERSION_BYTE_LENGTH];
456         byteBuf.get(version);
457         byteBuf.getInt();
458         int configCount = byteBuf.getInt();
459         List<ConfigIndex> cfg = loadConfig(byteBuf, configCount);
460         return readDataAllItem(cfg, byteBuf);
461     }
462 
463     /**
464      * Read resource map by id.
465      *
466      * @param resId resource id
467      * @param data config byte buffer
468      * @return the resource map of id.
469      */
470     @Override
getResourceMapById(int resId, byte[] data)471     public HashMap<String, String> getResourceMapById(int resId, byte[] data) {
472         List<ResourceIndexResult> resources = getAllDataItem(data);
473         HashMap<String, String> resourceMap = new HashMap<>();
474         for (ResourceIndexResult indexResult : resources) {
475             if (indexResult.id == resId) {
476                 resourceMap.put(indexResult.configClass, indexResult.value);
477             }
478         }
479         return resourceMap;
480     }
481 
482     /**
483      * Read resource by id.
484      *
485      * @param resId resource id
486      * @param data config byte buffer
487      * @return the resource.
488      */
489     @Override
getResourceStringById(int resId, byte[] data)490     public String getResourceStringById(int resId, byte[] data) {
491         List<ResourceIndexResult> resources = getAllDataItem(data);
492         for (ResourceIndexResult indexResult : resources) {
493             if (indexResult.id == resId) {
494                 return indexResult.value;
495             }
496         }
497         return "";
498     }
499 
500     /**
501      * Read all config item.
502      *
503      * @param configs the config list
504      * @param buf config byte buffer
505      * @return the item list
506      */
readDataAllItem(List<ConfigIndex> configs, ByteBuffer buf)507     static List<ResourceIndexResult> readDataAllItem(List<ConfigIndex> configs, ByteBuffer buf) {
508         List<ResourceIndexResult> resourceIndexResults = new ArrayList<>();
509         for (ConfigIndex index : configs) {
510             String configClass = convertConfigIndexToString(index);
511             buf.rewind();
512             buf.position(index.offset);
513             byte[] tag = new byte[TAG_BYTE_LENGTH];
514             buf.get(tag);
515             int count = buf.getInt();
516             int position = buf.position();
517             for (int i = 0; i < count; ++i) {
518                 buf.position(position);
519                 buf.getInt();
520                 int offset = buf.getInt();
521                 position = buf.position();
522                 buf.rewind();
523                 buf.position(offset);
524                 DataItem item = readItem(buf);
525                 resourceIndexResults.add(parseDataItems(item, configClass));
526             }
527         }
528         return resourceIndexResults;
529     }
530 
531     /**
532      * convert DataItems to ResourceIndexResult.
533      *
534      *  @param item Indicates the DataItem.
535      *  @param configClass config info.
536      *  @return the final ResourceIndexResult
537      */
parseDataItems(DataItem item, String configClass)538     static ResourceIndexResult parseDataItems(DataItem item, String configClass) {
539         ResourceIndexResult resourceIndexResult = new ResourceIndexResult();
540         resourceIndexResult.configClass = configClass;
541         if (item != null) {
542             resourceIndexResult.type = ResType.getType(item.type);
543             resourceIndexResult.id = item.id;
544             resourceIndexResult.name = item.name.substring(0, item.name.length() - 1);
545             if (requireBytesConversion(item.type)) {
546                 byte[] bytes =
547                         item.value.substring(0, item.value.length() - 1).getBytes(StandardCharsets.UTF_8);
548                 resourceIndexResult.value = convertBytesToString(bytes);
549             } else {
550                 resourceIndexResult.value = item.value.substring(0, item.value.length() - 1);
551             }
552         }
553         return resourceIndexResult;
554     }
555 
requireBytesConversion(int resType)556     private static boolean requireBytesConversion(int resType) {
557         return resType == ResType.STR_ARRAY.getIndex()
558                 || resType == ResType.INT_ARRAY.getIndex()
559                 || resType == ResType.THEME.getIndex()
560                 || resType == ResType.PLURALS.getIndex()
561                 || resType == ResType.PATTERN.getIndex();
562     }
563 
564     /**
565      * convert bytes to string.
566      *
567      * @param data Indicates the bytes of data.
568      * @return the final string
569      */
convertBytesToString(byte[] data)570     static String convertBytesToString(byte[] data) {
571         StringBuilder result = new StringBuilder();
572         ByteBuffer byteBuf = ByteBuffer.wrap(data);
573         byteBuf.order(ByteOrder.LITTLE_ENDIAN);
574         while (byteBuf.hasRemaining()) {
575             result.append(LEFT_BRACKET);
576             int len = byteBuf.getShort();
577             if (len <= 0) {
578                 LOG.info("len less than 0, dismiss");
579                 result.append(RIGHT_BRACKET);
580                 break;
581             }
582             byte[] value = new byte[len + CHAR_LENGTH];
583             byteBuf.get(value);
584             String item = new String(value, StandardCharsets.UTF_8);
585             result.append(item, 0, item.length() - 1);
586             result.append(RIGHT_BRACKET);
587         }
588         return result.toString();
589     }
590 
591     /**
592      * convert config to string.
593      *
594      * @param configIndex Indicates the configIndex.
595      *  @return the final string
596      */
convertConfigIndexToString(ConfigIndex configIndex)597     static String convertConfigIndexToString(ConfigIndex configIndex) {
598         StringBuilder configClass = new StringBuilder();
599         int lastKeyType = -1;
600         for (int i = 0; i < configIndex.keyCount; ++i) {
601             KeyParam param = configIndex.params[i];
602             if (param.keyType == ConfigType.LANGUAGE.ordinal()
603                     || param.keyType == ConfigType.REGION.ordinal()
604                     || param.keyType == ConfigType.SCRIPT.ordinal()) {
605                 if (EMPTY_STRING.equals(configClass.toString())) {
606                     configClass.append(parseAscii(param.value));
607                 } else {
608                     if (lastKeyType == ConfigType.LANGUAGE.ordinal() ||
609                             lastKeyType == ConfigType.REGION.ordinal() || lastKeyType == ConfigType.SCRIPT.ordinal()) {
610                         configClass.append(MCC_CONJUNCTION).append(parseAscii(param.value));
611                     } else {
612                         configClass.append(CONFIG_CONJUNCTION).append(parseAscii(param.value));
613                     }
614                 }
615                 lastKeyType = param.keyType;
616             } else if (param.keyType == ConfigType.RESOLUTION.ordinal()) {
617                 if (EMPTY_STRING.equals(configClass.toString())) {
618                     configClass.append(Resolution.getType(param.value));
619                 } else {
620                     configClass.append(CONFIG_CONJUNCTION).append(Resolution.getType(param.value));
621                 }
622             } else if (param.keyType == ConfigType.DIRECTION.ordinal()) {
623                 if (EMPTY_STRING.equals(configClass.toString())) {
624                     configClass.append(param.value == 0 ? VERTICAL : HORIZONTAL);
625                 } else {
626                     configClass.append(CONFIG_CONJUNCTION).append(param.value == 0 ? VERTICAL : HORIZONTAL);
627                 }
628             } else if (param.keyType == ConfigType.DEVICE_TYPE.ordinal()) {
629                 if (EMPTY_STRING.equals(configClass.toString())) {
630                     configClass.append(DeviceType.getType(param.value));
631                 } else {
632                     configClass.append(CONFIG_CONJUNCTION).append(DeviceType.getType(param.value));
633                 }
634             } else if (param.keyType == ConfigType.LIGHT_MODE.ordinal()) {
635                 if (EMPTY_STRING.equals(configClass.toString())) {
636                     configClass.append(param.value == 0 ? DARK : LIGHT);
637                 } else {
638                     configClass.append(CONFIG_CONJUNCTION).append(param.value == 0 ? DARK : LIGHT);
639                 }
640             } else if (param.keyType == ConfigType.MCC.ordinal()) {
641                 if (EMPTY_STRING.equals(configClass.toString())) {
642                     configClass.append(MCC).append(String.valueOf(param.value));
643                 } else {
644                     configClass.append(MCC_CONJUNCTION).append(MCC).append(String.valueOf(param.value));
645                 }
646             } else if (param.keyType == ConfigType.MNC.ordinal()) {
647                 if (EMPTY_STRING.equals(configClass.toString())) {
648                     configClass.append(MNC).append(fillUpZero(String.valueOf(param.value), 3));
649                 } else {
650                     configClass.append(MCC_CONJUNCTION).append(MNC).append(fillUpZero(String.valueOf(param.value), 3));
651                 }
652             }
653         }
654         if (EMPTY_STRING.equals(configClass.toString())) {
655             configClass = new StringBuilder(BASE);
656         }
657         return configClass.toString();
658     }
659 
660     /**
661      * convert integer to string.
662      *
663      * @param value Indicates the Integer.
664      *  @return the final string
665      */
parseAscii(Integer value)666     private static String parseAscii(Integer value) {
667         StringBuilder result = new StringBuilder();
668         while (value > 0) {
669             result.insert(0, (char) (value & 0xFF));
670             value = value >> 8;
671         }
672         return result.toString();
673     }
674 
675     /**
676      * fillup zero to string.
677      * @param inputString Indicates the string should to be filled.
678      * @param length Indicates the final length of String.
679      * @return the final string
680      */
fillUpZero(String inputString, int length)681     private static String fillUpZero(String inputString, int length) {
682         if (inputString.length() >= length) {
683             return inputString;
684         }
685         StringBuilder result = new StringBuilder();
686         while (result.length() < length - inputString.length()) {
687             result.append('0');
688         }
689         result.append(inputString);
690         return result.toString();
691     }
692 }
693 
694