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