• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2025 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.Map;
29 import java.util.Optional;
30 
31 /**
32  * Resources Parser V2.
33  *
34  * @since 2025-06-06
35  */
36 public class ResourcesParserV2 implements ResourcesParser {
37     /**
38      * Parses resources default id.
39      */
40     public static final int RESOURCE_DEFAULT_ID = -1;
41 
42     private static final Log LOG = new Log(ResourcesParserV2.class.toString());
43     private static final int VERSION_BYTE_LENGTH = 128;
44     private static final int TAG_BYTE_LENGTH = 4;
45     private static final int TYPE_BYTE_LENGTH = 4;
46     private static final int VALUE_BYTE_LENGTH = 4;
47     private static final int IDSS_TYPE_BYTE_LENGTH = 4;
48     private static final int DATA_OFFSET_LENGTH = 4;
49     private static final String LEFT_BRACKET = "<";
50     private static final String RIGHT_BRACKET = ">";
51     private static final String VERTICAL = "vertical";
52     private static final String HORIZONTAL = "horizontal";
53     private static final String DARK = "dark";
54     private static final String LIGHT = "light";
55     private static final String MCC = "mcc";
56     private static final String MNC = "mnc";
57     private static final String CONFIG_CONJUNCTION = "-";
58     private static final String MCC_CONJUNCTION = "_";
59     private static final String BASE = "base";
60     private static final int CHAR_LENGTH = 1;
61     private static final String EMPTY_STRING = "";
62 
63     private static int idssOffset;
64     private static Map<String, Map<String, KeysItemV2>> keysMap = new HashMap<>();
65     private static Map<Integer, Map<Integer, IdssItemV2>> idssMap = new HashMap<>();
66     private static Map<Integer, Map<Integer, DataItemV2>> dataMap = new HashMap<>();
67 
68     /**
69      * ResourcesParserV2
70      *
71      * @param data resource index data
72      */
ResourcesParserV2(byte[] data)73     public ResourcesParserV2(byte[] data) {
74         parseZone(data);
75     }
76 
77     private enum ResType {
78         VALUES(0, "Values"),
79         ANIMATOR(1, "Animator"),
80         DRAWABLE(2, "Drawable"),
81         LAYOUT(3, "Layout"),
82         MENU(4, "Menu"),
83         MIPMAP(5, "Mipmap"),
84         RAW(6, "Raw"),
85         XML(7, "Xml"),
86         INTEGER(8, "Integer"),
87         STRING(9, "String"),
88         STR_ARRAY(10, "StrArray"),
89         INT_ARRAY(11, "IntArray"),
90         BOOLEAN_TYPE(12, "Boolean"),
91         DIMEN(13, "Dimen"),
92         COLOR(14, "Color"),
93         ID(15, "Id"),
94         THEME(16, "Theme"),
95         PLURALS(17, "Plurals"),
96         FLOAT_TYPE(18, "Float"),
97         MEDIA(19, "Media"),
98         PROF(20, "Prof"),
99         SVG(21, "Svg"),
100         PATTERN(22, "Pattern");
101 
102         private final int index;
103         private final String type;
104 
ResType(int index, String type)105         private ResType(int index, String type) {
106             this.index = index;
107             this.type = type;
108         }
109 
getType(int index)110         public static String getType(int index) {
111             for (ResType resType : ResType.values()) {
112                 if (resType.getIndex() == index) {
113                     return resType.type;
114                 }
115             }
116             return "";
117         }
118 
getIndex()119         public int getIndex() {
120             return index;
121         }
getType()122         public String getType() {
123             return type;
124         }
125     }
126 
127     private enum DeviceType {
128         PHONE(0, "phone"),
129         TABLET(1, "tablet"),
130         CAR(2, "car"),
131         PC(3, "pc"),
132         TV(4, "tv"),
133         SPEAKER(5, "speaker"),
134         WEARABLE(6, "wearable"),
135         GLASSES(7, "glasses"),
136         HEADSET(8, "headset");
137 
138         private final int index;
139         private final String type;
140         private static final Map<Integer, DeviceType> INDEX_MAP;
141 
142         static {
143             INDEX_MAP = new HashMap<>();
144             for (DeviceType deviceType : values()) {
INDEX_MAP.put(deviceType.index, deviceType)145                 INDEX_MAP.put(deviceType.index, deviceType);
146             }
147         }
148 
DeviceType(int index, String type)149         private DeviceType(int index, String type) {
150             this.index = index;
151             this.type = type;
152         }
153 
154         /**
155          * get index
156          *
157          * @return index of DeviceType
158          */
getIndex()159         public int getIndex() {
160             return index;
161         }
162 
163         /**
164          * get type
165          *
166          * @return type of DeviceType
167          */
getType()168         public String getType() {
169             return type;
170         }
171 
172         /**
173          * get type by index
174          *
175          * @param index index of DeviceType
176          * @return type of DeviceType
177          */
getTypeByIndex(int index)178         public static String getTypeByIndex(int index) {
179             DeviceType deviceType = INDEX_MAP.get(index);
180             return deviceType != null ? deviceType.type : "";
181         }
182     }
183 
184     private enum Resolution {
185         NODPI(-2, "nodpi"),
186         ANYDPI(-1, "anydpi"),
187         SDPI(120, "sdpi"),
188         MDPI(160, "mdpi"),
189         TVDPI(213, "tvdpi"),
190         LDPI(240, "ldpi"),
191         XLDPI(320, "xldpi"),
192         XXLDPI(480, "xxldpi"),
193         XXXLDPI(640, "xxxldpi");
194 
195         private final int index;
196         private final String type;
197         private static final Map<Integer, Resolution> INDEX_MAP;
198 
199         static {
200             INDEX_MAP = new HashMap<>();
201             for (Resolution resolution : values()) {
INDEX_MAP.put(resolution.index, resolution)202                 INDEX_MAP.put(resolution.index, resolution);
203             }
204         }
205 
Resolution(int index, String type)206         private Resolution(int index, String type) {
207             this.index = index;
208             this.type = type;
209         }
210 
211         /**
212          * get index
213          *
214          * @return index of Resolution
215          */
getIndex()216         public int getIndex() {
217             return index;
218         }
219 
220         /**
221          * get type
222          *
223          * @return type of Resolution
224          */
getType()225         public String getType() {
226             return type;
227         }
228 
229         /**
230          * get type by index
231          *
232          * @param index index of Resolution
233          * @return type of Resolution
234          */
getTypeByIndex(int index)235         public static String getTypeByIndex(int index) {
236             Resolution resolution = INDEX_MAP.get(index);
237             return resolution != null ? resolution.type : "";
238         }
239     }
240 
241     private enum ConfigType {
242         LANGUAGE,
243         REGION,
244         RESOLUTION,
245         DIRECTION,
246         DEVICE_TYPE,
247         SCRIPT,
248         LIGHT_MODE,
249         MCC,
250         MNC
251     }
252 
253     /**
254      * Key Param.
255      */
256     static class KeyParamV2 {
257         int keyType;
258         int value;
259     }
260 
261     /**
262      * Config Index.
263      */
264     static class ConfigIndexV2 {
265         String tag;
266         int resCfgId;
267         int keyCount;
268         KeyParamV2[] params;
269     }
270 
271     /**
272      * KEYS item.
273      */
274     static class KeysItemV2 {
275         String tag;
276         int resCfgId;
277         String type;
278         String value;
279     }
280 
281     /**
282      * IDSS item.
283      */
284     static class IdssItemV2 {
285         int type;
286         int resId;
287         int offset;
288         int nameLen;
289         String name;
290     }
291 
292     /**
293      * DATA item.
294      */
295     static class DataItemV2 {
296         int type;
297         int resId;
298         int resCfgId;
299         String name;
300         String value;
301     }
302 
303     /**
304      * Get resource value by resource id.
305      *
306      * @param resourceId resource id
307      * @param data resource index data
308      * @return the resourceId value
309      * @throws BundleException IOException.
310      */
311     @Override
getResourceById(int resourceId, byte[] data)312     public String getResourceById(int resourceId, byte[] data) throws BundleException {
313         String resourceIdValue = "";
314         if (data == null || data.length <= 0 || resourceId == RESOURCE_DEFAULT_ID) {
315             LOG.error("ResourcesParserV2::getIconPath data byte or ResourceId is null");
316             return resourceIdValue;
317         }
318 
319         List<String> result = getResource(resourceId);
320         if (result != null && result.size() > 0 && result.get(0) != null && !EMPTY_STRING.equals(result.get(0))) {
321             resourceIdValue = result.get(0);
322         }
323         return resourceIdValue;
324     }
325 
326     /**
327      * Get base resource value by resource id.
328      *
329      * @param resourceId resource id
330      * @param data resource index data
331      * @return the resource value
332      * @throws BundleException IOException.
333      */
334     @Override
getBaseResourceById(int resourceId, byte[] data)335     public String getBaseResourceById(int resourceId, byte[] data) throws BundleException {
336         String resourceIdValue = "";
337         if (data == null || data.length <= 0 || resourceId == RESOURCE_DEFAULT_ID) {
338             LOG.error("ResourcesParserV2::getBaseResourceById data byte or ResourceId is null");
339             return resourceIdValue;
340         }
341         resourceIdValue = getBaseResource(resourceId, data);
342         return resourceIdValue;
343     }
344 
345     /**
346      * Get base resource.
347      *
348      * @param resId resource id
349      * @param data resource index data array
350      * @return the resource value
351      * @throws BundleException IOException.
352      */
getBaseResource(int resId, byte[] data)353     static String getBaseResource(int resId, byte[] data) {
354         ByteBuffer byteBuf = ByteBuffer.wrap(data);
355         byteBuf.order(ByteOrder.LITTLE_ENDIAN);
356         byte[] version = new byte[VERSION_BYTE_LENGTH];
357         byteBuf.get(version);
358         byteBuf.getInt(); // length
359         int configCount = byteBuf.getInt();
360         byteBuf.getInt(); // dataBrokeOffset
361         Optional<ConfigIndexV2> optionalConfigIndex = loadBaseConfig(byteBuf, configCount);
362         if (!optionalConfigIndex.isPresent()) {
363             LOG.error("ResourcesParserV2::getBaseResource configIndex is null");
364             return "";
365         }
366         return readBaseItem(resId, optionalConfigIndex.get());
367     }
368 
369     /**
370      * Load index config.
371      *
372      * @param bufBuf config byte buffer
373      * @param count config count
374      * @return the base config index
375      * @throws BundleException IOException.
376      */
loadBaseConfig(ByteBuffer bufBuf, int count)377     static Optional<ConfigIndexV2> loadBaseConfig(ByteBuffer bufBuf, int count) {
378         for (int i = 0; i < count; i++) {
379             ConfigIndexV2 cfg = new ConfigIndexV2();
380             byte[] tag = new byte[TAG_BYTE_LENGTH];
381             bufBuf.get(tag);
382             cfg.tag = new String(tag, StandardCharsets.UTF_8);
383             cfg.resCfgId = bufBuf.getInt();
384             cfg.keyCount = bufBuf.getInt();
385             cfg.params = new KeyParamV2[cfg.keyCount];
386             for (int j = 0; j < cfg.keyCount; j++) {
387                 cfg.params[j] = new KeyParamV2();
388                 cfg.params[j].keyType = bufBuf.getInt();
389                 cfg.params[j].value = bufBuf.getInt();
390             }
391             if (cfg.keyCount == 0) {
392                 return Optional.of(cfg);
393             }
394         }
395         return Optional.empty();
396     }
397 
398     /**
399      * Read base config item.
400      *
401      * @param resId resource id
402      * @param configIndex the config index
403      * @return the base item
404      * @throws BundleException IOException.
405      */
readBaseItem(int resId, ConfigIndexV2 configIndex)406     static String readBaseItem(int resId, ConfigIndexV2 configIndex) {
407         Map<Integer, DataItemV2> map = dataMap.get(resId);
408         return map.get(configIndex.resCfgId).value;
409     }
410 
411     /**
412      * Get Icon resource.
413      *
414      * @param resId resource id
415      * @return the result
416      * @throws BundleException IOException.
417      */
getResource(int resId)418     static List<String> getResource(int resId) {
419         return readAllItem(resId);
420     }
421 
422     /**
423      * Load index config.
424      *
425      * @param bufBuf config byte buffer
426      * @param count config count
427      * @return the config list
428      * @throws BundleException IOException.
429      */
loadConfig(ByteBuffer bufBuf, int count)430     static List<ConfigIndexV2> loadConfig(ByteBuffer bufBuf, int count) {
431         List<ConfigIndexV2> configList = new ArrayList<>(count);
432         for (int i = 0; i < count; i++) {
433             ConfigIndexV2 cfg = new ConfigIndexV2();
434             byte[] tag = new byte[TAG_BYTE_LENGTH];
435             bufBuf.get(tag);
436             cfg.tag = new String(tag, StandardCharsets.UTF_8);
437             cfg.resCfgId = bufBuf.getInt();
438             cfg.keyCount = bufBuf.getInt();
439             cfg.params = new KeyParamV2[cfg.keyCount];
440             for (int j = 0; j < cfg.keyCount; j++) {
441                 cfg.params[j] = new KeyParamV2();
442                 cfg.params[j].keyType = bufBuf.getInt();
443                 cfg.params[j].value = bufBuf.getInt();
444             }
445             configList.add(cfg);
446         }
447         return configList;
448     }
449 
450     /**
451      * Read all config item.
452      *
453      * @param resId resource id
454      * @return the item list
455      * @throws BundleException IOException.
456      */
readAllItem(int resId)457     static List<String> readAllItem(int resId) {
458         List<String> result = new ArrayList<>();
459         Map<Integer, DataItemV2> map = dataMap.get(resId);
460         for (DataItemV2 dataItemV2 : map.values()) {
461             result.add(dataItemV2.value);
462         }
463         return result;
464     }
465 
466     /**
467      * Read all config item.
468      *
469      * @param data config byte buffer
470      * @return the item info.
471      */
472     @Override
getAllDataItem(byte[] data)473     public List<ResourceIndexResult> getAllDataItem(byte[] data) {
474         ByteBuffer byteBuf = ByteBuffer.wrap(data);
475         byteBuf.order(ByteOrder.LITTLE_ENDIAN);
476         byte[] version = new byte[VERSION_BYTE_LENGTH];
477         byteBuf.get(version);
478         byteBuf.getInt();
479         int keyCount = byteBuf.getInt();
480         byteBuf.getInt(); // dataBrokeOffset
481         List<ConfigIndexV2> cfg = loadConfig(byteBuf, keyCount);
482         return readDataAllItem(cfg, byteBuf);
483     }
484 
485     /**
486      * Read resource map by id.
487      *
488      * @param resId resource id
489      * @param data config byte buffer
490      * @return the resource map of id.
491      */
492     @Override
getResourceMapById(int resId, byte[] data)493     public HashMap<String, String> getResourceMapById(int resId, byte[] data) {
494         List<ResourceIndexResult> resources = getAllDataItem(data);
495         HashMap<String, String> resourceMap = new HashMap<>();
496         for (ResourceIndexResult indexResult : resources) {
497             if (indexResult.id == resId) {
498                 resourceMap.put(indexResult.configClass, indexResult.value);
499             }
500         }
501         return resourceMap;
502     }
503 
504     /**
505      * Read resource by id.
506      *
507      * @param resId resource id
508      * @param data config byte buffer
509      * @return resource
510      */
511     @Override
getResourceStringById(int resId, byte[] data)512     public String getResourceStringById(int resId, byte[] data) {
513         List<ResourceIndexResult> resources = getAllDataItem(data);
514         for (ResourceIndexResult indexResult : resources) {
515             if (indexResult.id == resId) {
516                 return indexResult.value;
517             }
518         }
519         return "";
520     }
521 
522     /**
523      * Read all config item.
524      *
525      * @param configs the config list
526      * @param buf config byte buffer
527      * @return the item list
528      */
readDataAllItem(List<ConfigIndexV2> configs, ByteBuffer buf)529     static List<ResourceIndexResult> readDataAllItem(List<ConfigIndexV2> configs, ByteBuffer buf) {
530         List<ResourceIndexResult> resourceIndexResults = new ArrayList<>();
531         for (ConfigIndexV2 index : configs) {
532             String configClass = convertConfigIndexToString(index);
533             dataMap.values()
534                     .forEach(
535                             integerDataItemV2Map ->
536                                     integerDataItemV2Map
537                                             .values()
538                                             .forEach(
539                                                     dataItemV2 -> {
540                                                         if (dataItemV2.resCfgId == index.resCfgId) {
541                                                             resourceIndexResults.add(
542                                                                     parseDataItems(dataItemV2, configClass));
543                                                         }
544                                                     }));
545         }
546         return resourceIndexResults;
547     }
548 
549     /**
550      * convert DataItems to ResourceIndexResult.
551      *
552      * @param item Indicates the DataItem.
553      * @param configClass config info
554      * @return the final ResourceIndexResult
555      */
parseDataItems(DataItemV2 item, String configClass)556     static ResourceIndexResult parseDataItems(DataItemV2 item, String configClass) {
557         ResourceIndexResult resourceIndexResult = new ResourceIndexResult();
558         resourceIndexResult.configClass = configClass;
559         if (item != null) {
560             resourceIndexResult.type = ResType.getType(item.type);
561             resourceIndexResult.id = item.resId;
562             resourceIndexResult.name = item.name;
563             if (requireBytesConversion(item.type)) {
564                 byte[] bytes = item.value.getBytes(StandardCharsets.UTF_8);
565                 resourceIndexResult.value = convertBytesToString(bytes);
566             } else {
567                 resourceIndexResult.value = item.value;
568             }
569         }
570         return resourceIndexResult;
571     }
572 
requireBytesConversion(int resType)573     private static boolean requireBytesConversion(int resType) {
574         return resType == ResType.STR_ARRAY.getIndex()
575                 || resType == ResType.INT_ARRAY.getIndex()
576                 || resType == ResType.THEME.getIndex()
577                 || resType == ResType.PLURALS.getIndex()
578                 || resType == ResType.PATTERN.getIndex();
579     }
580 
581     /**
582      * convert bytes to string.
583      *
584      * @param data Indicates the bytes of data.
585      * @return the final string
586      */
convertBytesToString(byte[] data)587     static String convertBytesToString(byte[] data) {
588         StringBuilder result = new StringBuilder();
589         ByteBuffer byteBuf = ByteBuffer.wrap(data);
590         byteBuf.order(ByteOrder.LITTLE_ENDIAN);
591         while (byteBuf.hasRemaining()) {
592             result.append(LEFT_BRACKET);
593             int len = byteBuf.getShort();
594             if (len <= 0) {
595                 LOG.info("len less than 0, dismiss");
596                 result.append(RIGHT_BRACKET);
597                 break;
598             }
599             byte[] value = new byte[len + CHAR_LENGTH];
600             byteBuf.get(value);
601             String item = new String(value, StandardCharsets.UTF_8);
602             result.append(item, 0, item.length());
603             result.append(RIGHT_BRACKET);
604         }
605         return result.toString();
606     }
607 
608     /**
609      * convert config to string.
610      *
611      * @param configIndex Indicates the configIndex.
612      *  @return the final string
613      */
convertConfigIndexToString(ConfigIndexV2 configIndex)614     static String convertConfigIndexToString(ConfigIndexV2 configIndex) {
615         if (configIndex.keyCount == 0) {
616             return BASE;
617         }
618 
619         StringBuilder configClass = new StringBuilder();
620         ConfigType lastKeyType = null;
621 
622         for (int i = 0; i < configIndex.keyCount; ++i) {
623             KeyParamV2 param = configIndex.params[i];
624             ConfigType currentType = ConfigType.values()[param.keyType];
625             Optional<String> valuePart = processParamValue(param, currentType);
626 
627             if (valuePart.isPresent()) {
628                 appendValue(configClass, lastKeyType, currentType, valuePart.get());
629                 lastKeyType = currentType;
630             }
631         }
632 
633         return configClass.length() == 0 ? BASE : configClass.toString();
634     }
635 
processParamValue(KeyParamV2 param, ConfigType type)636     private static Optional<String> processParamValue(KeyParamV2 param, ConfigType type) {
637         switch (type) {
638             case LANGUAGE:
639             case REGION:
640             case SCRIPT:
641                 return Optional.ofNullable(parseAscii(param.value));
642             case RESOLUTION:
643                 return Optional.ofNullable(Resolution.getTypeByIndex(param.value));
644             case DIRECTION:
645                 return Optional.ofNullable(param.value == 0 ? VERTICAL : HORIZONTAL);
646             case DEVICE_TYPE:
647                 return Optional.ofNullable(DeviceType.getTypeByIndex(param.value));
648             case LIGHT_MODE:
649                 return Optional.ofNullable(param.value == 0 ? DARK : LIGHT);
650             case MCC:
651                 return Optional.ofNullable(MCC + param.value);
652             case MNC:
653                 return Optional.ofNullable(MNC + fillUpZero(String.valueOf(param.value), 3));
654             default:
655                 return Optional.empty();
656         }
657     }
658 
appendValue( StringBuilder builder, ConfigType lastKeyType, ConfigType currentKeyType, String value)659     private static void appendValue(
660             StringBuilder builder, ConfigType lastKeyType, ConfigType currentKeyType, String value) {
661         if (builder.length() == 0) {
662             builder.append(value);
663             return;
664         }
665 
666         String conjunction = shouldUseMccConjunction(lastKeyType, currentKeyType)
667                 ? MCC_CONJUNCTION
668                 : CONFIG_CONJUNCTION;
669         builder.append(conjunction).append(value);
670     }
671 
shouldUseMccConjunction(ConfigType lastKeyType, ConfigType currentKeyType)672     private static boolean shouldUseMccConjunction(ConfigType lastKeyType, ConfigType currentKeyType) {
673         boolean isCurrentMccMnc = currentKeyType == ConfigType.MCC || currentKeyType == ConfigType.MNC;
674         boolean isLastLanguageRegionScript =
675                 lastKeyType != null
676                         && (lastKeyType == ConfigType.LANGUAGE
677                         || lastKeyType == ConfigType.REGION
678                         || lastKeyType == ConfigType.SCRIPT);
679 
680         return isCurrentMccMnc || isLastLanguageRegionScript;
681     }
682 
683     /**
684      * convert integer to string.
685      *
686      * @param value Indicates the Integer.
687      *  @return the final string
688      */
parseAscii(Integer value)689     private static String parseAscii(Integer value) {
690         if (value == null) {
691             return "";
692         }
693         StringBuilder result = new StringBuilder();
694         int tempValue = value;
695         while (tempValue > 0) {
696             result.insert(0, (char) (tempValue & 0xFF));
697             tempValue = tempValue >> 8;
698         }
699         return result.toString();
700     }
701 
702     /**
703      * fillup zero to string.
704      *
705      * @param inputString Indicates the string should to be filled.
706      * @param length Indicates the final length of String.
707      * @return the final string
708      */
fillUpZero(String inputString, int length)709     private static String fillUpZero(String inputString, int length) {
710         if (inputString.length() >= length) {
711             return inputString;
712         }
713         StringBuilder result = new StringBuilder();
714         while (result.length() < length - inputString.length()) {
715             result.append('0');
716         }
717         result.append(inputString);
718         return result.toString();
719     }
720 
721     /**
722      * Parse KEYS,IDSS,DATA zone.
723      *
724      * @param data resource byte
725      */
parseZone(byte[] data)726     static void parseZone(byte[] data) {
727         if (!parseKEYSZone(data)) {
728             LOG.error("ResourcesParserV2 parseKEYSZone() failed");
729         }
730         if (!parseDATAZone(data)) {
731             LOG.error("ResourcesParserV2 parseDATAZone() failed");
732         }
733         if (!parseIDSSZone(data)) {
734             LOG.error("ResourcesParserV2 parseIDSSZone() failed");
735         }
736     }
737 
738     /**
739      * Parse KEYS zone.
740      *
741      * @param data resource byte
742      * @return parse result
743      */
parseKEYSZone(byte[] data)744     static boolean parseKEYSZone(byte[] data) {
745         if (data == null || data.length == 0) {
746             LOG.error("ResourcesParserV2::parseKEYSZone data byte is null");
747             return false;
748         }
749         ByteBuffer buf = ByteBuffer.wrap(data);
750         buf.order(ByteOrder.LITTLE_ENDIAN);
751         byte[] version = new byte[VERSION_BYTE_LENGTH];
752         buf.get(version);
753         buf.getInt(); // length
754         int keyCount = buf.getInt();
755         buf.getInt(); // dataBrokeOffset
756         // KEYS zone
757         for (int i = 0; i < keyCount; i++) {
758             Map<String, KeysItemV2> map = new HashMap<>();
759             byte[] tag = new byte[TAG_BYTE_LENGTH];
760             buf.get(tag);
761             String tagStr = new String(tag, StandardCharsets.UTF_8);
762             int resCfgId = buf.getInt();
763             int keyParamCount = buf.getInt();
764             for (int j = 0; j < keyParamCount; j++) {
765                 KeysItemV2 keysItemV2 = new KeysItemV2();
766                 keysItemV2.tag = tagStr;
767                 keysItemV2.resCfgId = resCfgId;
768                 byte[] type = new byte[TYPE_BYTE_LENGTH];
769                 buf.get(type);
770                 keysItemV2.type = new String(type, StandardCharsets.UTF_8);
771                 byte[] value = new byte[VALUE_BYTE_LENGTH];
772                 buf.get(value);
773                 keysItemV2.value = new String(value, StandardCharsets.UTF_8);
774                 map.put(keysItemV2.type, keysItemV2);
775             }
776             keysMap.put(tagStr, map);
777         }
778         idssOffset = buf.position();
779         return true;
780     }
781 
782     /**
783      * Parse IDSS zone.
784      *
785      * @param data resource byte
786      * @return parse result
787      */
parseIDSSZone(byte[] data)788     static boolean parseIDSSZone(byte[] data) {
789         if (data == null || data.length == 0) {
790             LOG.error("ResourcesParserV2::parseIDSSZone data byte is null");
791             return false;
792         }
793         if (idssOffset == 0) {
794             LOG.error("ResourcesParserV2::parseIDSSZone idssOffset has not parse");
795             return false;
796         }
797         ByteBuffer buf = ByteBuffer.wrap(data);
798         buf.order(ByteOrder.LITTLE_ENDIAN);
799         buf.position(idssOffset);
800         buf.getInt(); // tag
801         buf.getInt(); // len
802         int typeCount = buf.getInt();
803         buf.getInt(); // idCount
804         // IDSS zone
805         for (int i = 0; i < typeCount; i++) {
806             Map<Integer, IdssItemV2> map = new HashMap<>();
807             int type = buf.getInt();
808             buf.getInt(); // length
809             int count = buf.getInt();
810             for (int j = 0; j < count; j++) {
811                 IdssItemV2 idssItemV2 = new IdssItemV2();
812                 idssItemV2.type = type;
813                 idssItemV2.resId = buf.getInt();
814                 idssItemV2.offset = buf.getInt();
815                 int nameLen = buf.getInt();
816                 byte[] name = new byte[nameLen];
817                 buf.get(name);
818                 idssItemV2.nameLen = nameLen;
819                 idssItemV2.name = new String(name, StandardCharsets.UTF_8);
820                 dataMap.get(idssItemV2.resId)
821                         .values()
822                         .forEach(
823                                 dataItemV2 -> {
824                                     dataItemV2.type = idssItemV2.type;
825                                     dataItemV2.name = idssItemV2.name;
826                                 });
827                 map.put(idssItemV2.resId, idssItemV2);
828             }
829             idssMap.put(type, map);
830         }
831         return true;
832     }
833 
834     /**
835      * Parse DATA zone.
836      *
837      * @param data resource byte
838      * @return data zone
839      */
parseDATAZone(byte[] data)840     static boolean parseDATAZone(byte[] data) {
841         if (data == null || data.length == 0) {
842             LOG.error("ResourcesParserV2::parseDATAZone data byte is null");
843             return false;
844         }
845         ByteBuffer buf = ByteBuffer.wrap(data);
846         buf.order(ByteOrder.LITTLE_ENDIAN);
847         byte[] version = new byte[VERSION_BYTE_LENGTH];
848         buf.get(version);
849         buf.getInt(); // length
850         buf.getInt();
851         int dataZoneOffset = buf.getInt(); // dataBrokeOffset
852         buf.position(dataZoneOffset);
853         buf.getInt(); // DATA tag
854         buf.getInt(); // DATA length
855         int resIdCount = buf.getInt();
856         for (int i = 0; i < resIdCount; i++) {
857             Map<Integer, DataItemV2> resCfgIdMap = new HashMap<>();
858             int resId = buf.getInt();
859             buf.getInt(); // length
860             int resCfgIdCount = buf.getInt();
861             for (int j = 0; j < resCfgIdCount; j++) {
862                 DataItemV2 dataItemV2 = new DataItemV2();
863                 dataItemV2.resId = resId;
864                 dataItemV2.resCfgId = buf.getInt();
865                 int dataOffset = buf.getInt();
866                 int position = buf.position();
867                 buf.position(dataOffset);
868                 short dataLen = buf.getShort();
869                 byte[] tag = new byte[dataLen];
870                 buf.get(tag);
871                 dataItemV2.value = new String(tag, StandardCharsets.UTF_8);
872                 buf.position(position);
873                 resCfgIdMap.put(dataItemV2.resCfgId, dataItemV2);
874             }
875             dataMap.put(resId, resCfgIdMap);
876         }
877         return true;
878     }
879 }
880 
881