From 8e5154679642ee1026e6615f04fb2b1300187158 Mon Sep 17 00:00:00 2001 From: z30034863 Date: Sat, 7 Jun 2025 23:38:03 +0800 Subject: [PATCH] feature new res protocol Signed-off-by: z30034863 --- adapter/ohos/BundleException.java | 2 +- adapter/ohos/JsonUtil.java | 22 +- adapter/ohos/Uncompress.java | 5 +- adapter/ohos/restool/ResourcesParser.java | 76 ++ .../ohos/restool/ResourcesParserFactory.java | 45 + .../ResourcesParserV1.java} | 20 +- adapter/ohos/restool/ResourcesParserV2.java | 821 ++++++++++++++++++ 7 files changed, 971 insertions(+), 20 deletions(-) create mode 100644 adapter/ohos/restool/ResourcesParser.java create mode 100644 adapter/ohos/restool/ResourcesParserFactory.java rename adapter/ohos/{ResourcesParser.java => restool/ResourcesParserV1.java} (97%) create mode 100644 adapter/ohos/restool/ResourcesParserV2.java diff --git a/adapter/ohos/BundleException.java b/adapter/ohos/BundleException.java index ea391185..cc332c43 100644 --- a/adapter/ohos/BundleException.java +++ b/adapter/ohos/BundleException.java @@ -19,7 +19,7 @@ package ohos; * bundle tool exception class. * */ -class BundleException extends Exception { +public class BundleException extends Exception { private static final long serialVersionUID = 1813070042705457755L; /** diff --git a/adapter/ohos/JsonUtil.java b/adapter/ohos/JsonUtil.java index 74ba0f65..f786da9b 100644 --- a/adapter/ohos/JsonUtil.java +++ b/adapter/ohos/JsonUtil.java @@ -25,6 +25,8 @@ import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONException; +import ohos.restool.ResourcesParserFactory; +import ohos.restool.ResourcesParserV2; /** * Json Util. @@ -392,7 +394,7 @@ public class JsonUtil { String labelRes = ""; if (appJson.containsKey("labelId")) { int labelId = appJson.getIntValue("labelId"); - labelRes = ResourcesParser.getBaseResourceById(labelId, data); + labelRes = ResourcesParserFactory.createParser(data).getBaseResourceById(labelId, data); } if (labelRes != null && !labelRes.isEmpty()) { appInfo.appName = labelRes; @@ -818,7 +820,7 @@ public class JsonUtil { ability.name = getJsonString(abilityJson, "name"); if (abilityJson.containsKey("iconId")) { int iconId = abilityJson.getIntValue("iconId"); - String iconPath = ResourcesParser.getResourceById(iconId, data); + String iconPath = ResourcesParserFactory.createParser(data).getResourceById(iconId, data); if (iconPath != null && !iconPath.isEmpty()) { ability.iconPath = ASSETS_DIR_NAME + iconPath; } @@ -828,7 +830,7 @@ public class JsonUtil { if (abilityJson.containsKey("descriptionId")) { int descriptionId = abilityJson.getIntValue("descriptionId"); - ability.descriptionRes = ResourcesParser.getBaseResourceById(descriptionId, data); + ability.descriptionRes = ResourcesParserFactory.createParser(data).getBaseResourceById(descriptionId, data); } ability.description = ability.descriptionRes != null && !ability.descriptionRes.isEmpty() ? ability.descriptionRes : getJsonString(abilityJson, "description"); @@ -836,7 +838,7 @@ public class JsonUtil { if (abilityJson.containsKey("labelId")) { int labelId = abilityJson.getIntValue("labelId"); - ability.labelRes = ResourcesParser.getBaseResourceById(labelId, data); + ability.labelRes = ResourcesParserFactory.createParser(data).getBaseResourceById(labelId, data); } if (ability.labelRes != null && !ability.labelRes.isEmpty()) { ability.label = ability.labelRes; @@ -1782,7 +1784,7 @@ public class JsonUtil { String descriptionId = descriptionStr.substring(len); try { int id = Integer.parseInt(descriptionId); - descriptions = ResourcesParser.getResourceMapById(id, data); + descriptions = ResourcesParserFactory.createParser(data).getResourceMapById(id, data); } catch (NumberFormatException e) { LOG.error("parseFormDescriptions failed: invalid descriptionId: " + descriptionId); } @@ -1935,7 +1937,7 @@ public class JsonUtil { } try { int finalId = Integer.parseInt(id.substring(index)); - res = ResourcesParser.getResourceStringById(finalId, data); + res = ResourcesParserFactory.createParser(data).getResourceStringById(finalId, data); } catch (NumberFormatException e) { LOG.error("parseResourceByStringID failed: input invalid of " + id + "."); } @@ -1955,7 +1957,7 @@ public class JsonUtil { String res = ""; if (jsonObject.containsKey(keyId)) { int resId = jsonObject.getIntValue(keyId); - res = ResourcesParser.getResourceStringById(resId, data); + res = ResourcesParserFactory.createParser(data).getResourceStringById(resId, data); } if (res != null && !res.isEmpty()) { return res; @@ -1970,7 +1972,7 @@ public class JsonUtil { HashMap map = new HashMap<>(); if (jsonObject.containsKey(keyId)) { int resId = jsonObject.getIntValue(keyId); - map = ResourcesParser.getResourceMapById(resId, data); + map = ResourcesParserFactory.createParser(data).getResourceMapById(resId, data); } return map; } @@ -1987,9 +1989,9 @@ public class JsonUtil { String iconPath = ""; if (jsonObject.containsKey("iconId")) { int resId = jsonObject.getIntValue("iconId"); - iconPath = ResourcesParser.getBaseResourceById(resId, data); + iconPath = ResourcesParserFactory.createParser(data).getBaseResourceById(resId, data); if (iconPath.isEmpty()) { - iconPath = ResourcesParser.getResourceById(resId, data); + iconPath = ResourcesParserFactory.createParser(data).getResourceById(resId, data); } if (iconPath.contains("resources")) { iconPath = iconPath.substring(iconPath.lastIndexOf("resources")); diff --git a/adapter/ohos/Uncompress.java b/adapter/ohos/Uncompress.java index 129d343b..5dd352a0 100644 --- a/adapter/ohos/Uncompress.java +++ b/adapter/ohos/Uncompress.java @@ -15,6 +15,9 @@ package ohos; +import ohos.restool.ResourcesParserFactory; +import ohos.restool.ResourcesParserV2; + import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; @@ -1803,7 +1806,7 @@ public class Uncompress { File srcFile = new File(srcPath); zipFile = new ZipFile(srcFile); byte[] data = getResourceDataFromHap(zipFile); - return ResourcesParser.getAllDataItem(data); + return ResourcesParserFactory.createParser(data).getAllDataItem(data); } finally { Utility.closeStream(zipFile); } diff --git a/adapter/ohos/restool/ResourcesParser.java b/adapter/ohos/restool/ResourcesParser.java new file mode 100644 index 00000000..948eb42a --- /dev/null +++ b/adapter/ohos/restool/ResourcesParser.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ohos.restool; + +import ohos.BundleException; +import ohos.ResourceIndexResult; + +import java.util.HashMap; +import java.util.List; + +/** + * Resources Parser Interface. + * + * @since 2025-06-06 + */ +public interface ResourcesParser { + /** + * Get resource value by resource id. + * + * @param resourceId resource id + * @param data resource index data + * @return the resource value + * @throws BundleException IOException. + */ + String getResourceById(int resourceId, byte[] data) throws BundleException; + + /** + * Get base resource value by resource id. + * + * @param resourceId resource id + * @param data resource index data + * @return the resource value + * @throws BundleException IOException. + */ + String getBaseResourceById(int resourceId, byte[] data) throws BundleException; + + /** + * Read all config item. + * + * @param data config byte buffer + * @return the item info. + */ + List getAllDataItem(byte[] data); + + /** + * Read resource map by id. + * + * @param resId The resource ID to query + * @param data config byte buffer + * @return the resource map of id. + */ + HashMap getResourceMapById(int resId, byte[] data); + + /** + * Gets the resource string value by ID + * + * @param resId The resource ID to query + * @param data The resource data buffer + * @return The string value of the resource + * @throws BundleException When the resource cannot be accessed or parsed + */ + String getResourceStringById(int resId, byte[] data); +} diff --git a/adapter/ohos/restool/ResourcesParserFactory.java b/adapter/ohos/restool/ResourcesParserFactory.java new file mode 100644 index 00000000..a309e385 --- /dev/null +++ b/adapter/ohos/restool/ResourcesParserFactory.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ohos.restool; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; + +/** + * ResourcesParserFactory + * + */ +public class ResourcesParserFactory { + private static final int VERSION_BYTE_LENGTH = 128; + private static final String RESOURCE_PROTOCOL_VERSION_TAG = "RestoolV2"; + + public static ResourcesParser createParser(byte[] data) { + if (isV2Protocol(data)) { + return new ResourcesParserV2(data); + } + return new ResourcesParserV1(); + } + + private static boolean isV2Protocol(byte[] data) { + ByteBuffer byteBuf = ByteBuffer.wrap(data); + byteBuf.order(ByteOrder.LITTLE_ENDIAN); + byte[] version = new byte[VERSION_BYTE_LENGTH]; + byteBuf.get(version); + String versionStr = new String(version, StandardCharsets.UTF_8); + return versionStr.contains(RESOURCE_PROTOCOL_VERSION_TAG); + } +} diff --git a/adapter/ohos/ResourcesParser.java b/adapter/ohos/restool/ResourcesParserV1.java similarity index 97% rename from adapter/ohos/ResourcesParser.java rename to adapter/ohos/restool/ResourcesParserV1.java index 82545856..00ef584e 100644 --- a/adapter/ohos/ResourcesParser.java +++ b/adapter/ohos/restool/ResourcesParserV1.java @@ -13,7 +13,11 @@ * limitations under the License. */ -package ohos; +package ohos.restool; + +import ohos.BundleException; +import ohos.Log; +import ohos.ResourceIndexResult; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -27,7 +31,7 @@ import java.util.Optional; * Resources Parser. * */ -public class ResourcesParser { +public class ResourcesParserV1 implements ResourcesParser{ /** * Parses resources default id. */ @@ -171,7 +175,7 @@ public class ResourcesParser { Language, Region, Resolution, Direction, DeviceType, Script, LightMode, MCC, MNC } - private static final Log LOG = new Log(ResourcesParser.class.toString()); + private static final Log LOG = new Log(ResourcesParserV1.class.toString()); /** * Key Param. @@ -210,7 +214,7 @@ public class ResourcesParser { * @return the resourceId value * @throws BundleException IOException. */ - static String getResourceById(int resourceId, byte[] data) throws BundleException { + public String getResourceById(int resourceId, byte[] data) throws BundleException { String resourceIdValue = ""; if (data == null || data.length <= 0 || resourceId == RESOURCE_DEFAULT_ID) { LOG.error("ResourcesParser::getIconPath data byte or ResourceId is null"); @@ -232,7 +236,7 @@ public class ResourcesParser { * @return the resource value * @throws BundleException IOException. */ - static String getBaseResourceById(int resourceId, byte[] data) throws BundleException { + public String getBaseResourceById(int resourceId, byte[] data) throws BundleException { String resourceIdValue = ""; if (data == null || data.length <= 0 || resourceId == RESOURCE_DEFAULT_ID) { LOG.error("ResourcesParser::getBaseResourceById data byte or ResourceId is null"); @@ -433,7 +437,7 @@ public class ResourcesParser { * @param data config byte buffer * @return the item info. */ - static List getAllDataItem(byte[] data) { + public List getAllDataItem(byte[] data) { ByteBuffer byteBuf = ByteBuffer.wrap(data); byteBuf.order(ByteOrder.LITTLE_ENDIAN); byte[] version = new byte[VERSION_BYTE_LENGTH]; @@ -450,7 +454,7 @@ public class ResourcesParser { * @param data config byte buffer * @return the resource map of id. */ - static HashMap getResourceMapById(int resId, byte[] data) { + public HashMap getResourceMapById(int resId, byte[] data) { List resources = getAllDataItem(data); HashMap resourceMap = new HashMap<>(); for (ResourceIndexResult indexResult : resources) { @@ -461,7 +465,7 @@ public class ResourcesParser { return resourceMap; } - static String getResourceStringById(int resId, byte[] data) { + public String getResourceStringById(int resId, byte[] data) { List resources = getAllDataItem(data); for (ResourceIndexResult indexResult : resources) { if (indexResult.id == resId) { diff --git a/adapter/ohos/restool/ResourcesParserV2.java b/adapter/ohos/restool/ResourcesParserV2.java new file mode 100644 index 00000000..f14fc711 --- /dev/null +++ b/adapter/ohos/restool/ResourcesParserV2.java @@ -0,0 +1,821 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ohos.restool; + +import ohos.BundleException; +import ohos.Log; +import ohos.ResourceIndexResult; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.function.Consumer; + +/** + * Resources Parser V2. + * + */ +public class ResourcesParserV2 implements ResourcesParser{ + /** + * Parses resources default id. + */ + public static final int RESOURCE_DEFAULT_ID = -1; + + private static final int VERSION_BYTE_LENGTH = 128; + private static final int TAG_BYTE_LENGTH = 4; + private static final int TYPE_BYTE_LENGTH = 4; + private static final int VALUE_BYTE_LENGTH = 4; + private static final int IDSS_TYPE_BYTE_LENGTH = 4; + private static final int DATA_OFFSET_LENGTH = 4; + private static final String LEFT_BRACKET = "<"; + private static final String RIGHT_BRACKET = ">"; + private static final String VERTICAL = "vertical"; + private static final String HORIZONTAL = "horizontal"; + private static final String DARK = "dark"; + private static final String LIGHT = "light"; + private static final String MCC = "mcc"; + private static final String MNC = "mnc"; + private static final String CONFIG_CONJUNCTION = "-"; + private static final String MCC_CONJUNCTION = "_"; + private static final String BASE = "base"; + private static final int CHAR_LENGTH = 1; + private static final String EMPTY_STRING = ""; + + private static int idssOffset; + private static Map> keysMap; + private static Map> idssMap; + private static Map> dataMap; + + private enum ResType { + Values(0, "Values"), + Animator(1, "Animator"), + Drawable(2, "Drawable"), + Layout(3, "Layout"), + Menu(4, "Menu"), + Mipmap(5, "Mipmap"), + Raw(6, "Raw"), + Xml(7, "Xml"), + Integer(8, "Integer"), + String(9, "String"), + StrArray(10, "StrArray"), + IntArray(11, "IntArray"), + Boolean(12, "Boolean"), + Dimen(13, "Dimen"), + Color(14, "Color"), + Id(15, "Id"), + Theme(16, "Theme"), + Plurals(17, "Plurals"), + Float(18, "Float"), + Media(19, "Media"), + Prof(20, "Prof"), + Svg(21, "Svg"), + Pattern(22, "Pattern"); + + private final int index; + private final String type; + + private ResType(int index, String type) { + this.index = index; + this.type = type; + } + + public static String getType(int index) { + for (ResType resType : ResType.values()) { + if (resType.getIndex() == index) { + return resType.type; + } + } + return ""; + } + + public int getIndex() { + return index; + } + public String getType() { + return type; + } + } + + private enum DeviceType { + Phone(0, "phone"), + Tablet(1, "tablet"), + Car(2, "car"), + Pc(3, "pc"), + Tv(4, "tv"), + Speaker(5, "speaker"), + Wearable(6, "wearable"), + Glasses(7, "glasses"), + Headset(8, "headset"); + + private final int index; + private final String type; + private DeviceType(int index, String type) { + this.index = index; + this.type = type; + } + + public static String getType(int index) { + for (DeviceType deviceType : DeviceType.values()) { + if (deviceType.getIndex() == index) { + return deviceType.type; + } + } + return ""; + } + + public int getIndex() { + return index; + } + public String getType() { + return type; + } + } + + private enum Resolution { + Nodpi(-2, "nodpi"), + Anydpi(-1, "anydpi"), + Sdpi(120, "sdpi"), + Mdpi(160, "mdpi"), + Tvdpi(213, "tvdpi"), + Ldpi(240, "ldpi"), + Xldpi(320, "xldpi"), + Xxldpi(480, "xxldpi"), + Xxxldpi(640, "xxxldpi"); + + private final int index; + private final String type; + private Resolution(int index, String type) { + this.index = index; + this.type = type; + } + public static String getType(int index) { + for (Resolution resolution : Resolution.values()) { + if (resolution.getIndex() == index) { + return resolution.type; + } + } + return ""; + } + + public int getIndex() { + return index; + } + public String getType() { + return type; + } + } + + private enum ConfigType { + Language, Region, Resolution, Direction, DeviceType, Script, LightMode, MCC, MNC + } + + private static final Log LOG = new Log(ResourcesParserV2.class.toString()); + + /** + * Key Param. + */ + static class KeyParamV2 { + int keyType; + int value; + } + + /** + * Config Index. + */ + static class ConfigIndex { + String tag; + int offset; + int keyCount; + KeyParamV2[] params; + } + + /** + * Config Index. + */ + static class ConfigIndexV2 { + String tag; + int resCfgId; + int keyCount; + KeyParamV2[] params; + } + + /** + * KEYS item. + */ + static class KEYSItemV2 { + String tag; + int resCfgId; + String type; + String value; + } + + /** + * IDSS item. + */ + static class IDSSItemV2 { + int type; + int resId; + int offset; + int nameLen; + String name; + } + + /** + * DATA item. + */ + static class DataItemV2 { + int type; + int resId; + int resCfgId; + String name; + String value; + } + + /** + * Data Item. + */ + static class DataItem { + int size; + int type; + int id; + String value; + String name; + } + + public ResourcesParserV2(byte[] data) { + parseZone(data); + } + + /** + * Get resource value by resource id. + * + * @param resourceId resource id + * @param data resource index data + * @return the resourceId value + * @throws BundleException IOException. + */ + public String getResourceById(int resourceId, byte[] data) throws BundleException { + String resourceIdValue = ""; + if (data == null || data.length <= 0 || resourceId == RESOURCE_DEFAULT_ID) { + LOG.error("ResourcesParserV2::getIconPath data byte or ResourceId is null"); + return resourceIdValue; + } + + List result = getResource(resourceId); + if (result != null && result.size() > 0 && result.get(0) != null && !EMPTY_STRING.equals(result.get(0))) { + resourceIdValue = result.get(0); + } + return resourceIdValue; + } + + /** + * Get base resource value by resource id. + * + * @param resourceId resource id + * @param data resource index data + * @return the resource value + * @throws BundleException IOException. + */ + public String getBaseResourceById(int resourceId, byte[] data) throws BundleException { + String resourceIdValue = ""; + if (data == null || data.length <= 0 || resourceId == RESOURCE_DEFAULT_ID) { + LOG.error("ResourcesParserV2::getBaseResourceById data byte or ResourceId is null"); + return resourceIdValue; + } + resourceIdValue = getBaseResource(resourceId, data); + return resourceIdValue; + } + + /** + * Get base resource. + * + * @param resId resource id + * @param data resource index data array + * @return the resource value + * @throws BundleException IOException. + */ + static String getBaseResource(int resId, byte[] data) { + ByteBuffer byteBuf = ByteBuffer.wrap(data); + byteBuf.order(ByteOrder.LITTLE_ENDIAN); + byte[] version = new byte[VERSION_BYTE_LENGTH]; + byteBuf.get(version); + byteBuf.getInt(); // length + int configCount = byteBuf.getInt(); + byteBuf.getInt(); // dataBrokeOffset + Optional optionalConfigIndex = loadBaseConfig(byteBuf, configCount); + if (!optionalConfigIndex.isPresent()) { + LOG.error("ResourcesParserV2::getBaseResource configIndex is null"); + return ""; + } + return readBaseItem(resId, optionalConfigIndex.get()); + } + + /** + * Load index config. + * + * @param bufBuf config byte buffer + * @param count config count + * @return the base config index + * @throws BundleException IOException. + */ + static Optional loadBaseConfig(ByteBuffer bufBuf, int count) { + for (int i = 0; i < count; i++) { + ConfigIndexV2 cfg = new ConfigIndexV2(); + byte[] tag = new byte[TAG_BYTE_LENGTH]; + bufBuf.get(tag); + cfg.tag = new String(tag, StandardCharsets.UTF_8); + cfg.resCfgId = bufBuf.getInt(); + cfg.keyCount = bufBuf.getInt(); + cfg.params = new KeyParamV2[cfg.keyCount]; + for (int j = 0; j < cfg.keyCount; j++) { + cfg.params[j] = new KeyParamV2(); + cfg.params[j].keyType = bufBuf.getInt(); + cfg.params[j].value = bufBuf.getInt(); + } + if (cfg.keyCount == 0) { + return Optional.of(cfg); + } + } + return Optional.empty(); + } + + /** + * Read base config item. + * + * @param resId resource id + * @param configIndex the config index + * @return the base item + * @throws BundleException IOException. + */ + static String readBaseItem(int resId, ConfigIndexV2 configIndex) { + Map map = dataMap.get(resId); + return map.get(configIndex.resCfgId).value; + } + + /** + * Get Icon resource. + * + * @param resId resource id + * @return the result + * @throws BundleException IOException. + */ + static List getResource(int resId) { + return readAllItem(resId); + } + + /** + * Load index config. + * + * @param bufBuf config byte buffer + * @param count config count + * @return the config list + * @throws BundleException IOException. + */ + static List loadConfig(ByteBuffer bufBuf, int count) { + List configList = new ArrayList<>(count); + for (int i = 0; i < count; i++) { + ConfigIndexV2 cfg = new ConfigIndexV2(); + byte[] tag = new byte[TAG_BYTE_LENGTH]; + bufBuf.get(tag); + cfg.tag = new String(tag, StandardCharsets.UTF_8); + cfg.resCfgId = bufBuf.getInt(); + cfg.keyCount = bufBuf.getInt(); + cfg.params = new KeyParamV2[cfg.keyCount]; + for (int j = 0; j < cfg.keyCount; j++) { + cfg.params[j] = new KeyParamV2(); + cfg.params[j].keyType = bufBuf.getInt(); + cfg.params[j].value = bufBuf.getInt(); + } + configList.add(cfg); + } + return configList; + } + + /** + * Read all config item. + * + * @param resId resource id + * @return the item list + * @throws BundleException IOException. + */ + static List readAllItem(int resId) { + List result = new ArrayList<>(); + Map map = dataMap.get(resId); + for (DataItemV2 dataItemV2 : map.values()) { + result.add(dataItemV2.value); + } + return result; + } + + /** + * Read the config item. + * + * @param buf config byte buffer + * @return the item info + */ + static DataItem readItem(ByteBuffer buf) { + DataItem item = new DataItem(); + item.size = buf.getInt(); + item.type = buf.getInt(); + item.id = buf.getInt(); + int len = buf.getShort() & 0xFFFF; + byte[] value = new byte[len]; + buf.get(value); + item.value = new String(value, StandardCharsets.UTF_8); + len = buf.getShort() & 0xFFFF; + byte[] name = new byte[len]; + buf.get(name); + item.name = new String(name, StandardCharsets.UTF_8); + return item; + } + + /** + * Read all config item. + * + * @param data config byte buffer + * @return the item info. + */ + public List getAllDataItem(byte[] data) { + ByteBuffer byteBuf = ByteBuffer.wrap(data); + byteBuf.order(ByteOrder.LITTLE_ENDIAN); + byte[] version = new byte[VERSION_BYTE_LENGTH]; + byteBuf.get(version); + byteBuf.getInt(); + int keyCount = byteBuf.getInt(); + byteBuf.getInt();//dataBrokeOffset + List cfg = loadConfig(byteBuf, keyCount); + return readDataAllItem(cfg, byteBuf); + } + + /** + * Read resource map by id. + * + * @param data config byte buffer + * @return the resource map of id. + */ + public HashMap getResourceMapById(int resId, byte[] data) { + List resources = getAllDataItem(data); + HashMap resourceMap = new HashMap<>(); + for (ResourceIndexResult indexResult : resources) { + if (indexResult.id == resId) { + resourceMap.put(indexResult.configClass, indexResult.value); + } + } + return resourceMap; + } + + public String getResourceStringById(int resId, byte[] data) { + List resources = getAllDataItem(data); + for (ResourceIndexResult indexResult : resources) { + if (indexResult.id == resId) { + return indexResult.value; + } + } + return ""; + } + + /** + * Read all config item. + * + * @param configs the config list + * @param buf config byte buffer + * @return the item list + */ + static List readDataAllItem(List configs, ByteBuffer buf) { + List resourceIndexResults = new ArrayList<>(); + for (ConfigIndexV2 index : configs) { + String configClass = convertConfigIndexToString(index); + dataMap.values().forEach(integerDataItemV2Map -> integerDataItemV2Map.values().forEach(dataItemV2 -> { + if (dataItemV2.resCfgId == index.resCfgId) { + resourceIndexResults.add(parseDataItems(dataItemV2, configClass)); + } + })); + } + return resourceIndexResults; + } + + /** + * convert DataItems to ResourceIndexResult. + * + * @param item Indicates the DataItem. + * @return the final ResourceIndexResult + */ + static ResourceIndexResult parseDataItems(DataItemV2 item, String configClass) { + ResourceIndexResult resourceIndexResult = new ResourceIndexResult(); + resourceIndexResult.configClass = configClass; + if (item != null) { + resourceIndexResult.type = ResType.getType(item.type); + resourceIndexResult.id = item.resId; + resourceIndexResult.name = item.name; + if (item.type == ResType.StrArray.getIndex() || item.type == ResType.IntArray.getIndex() + || item.type == ResType.Theme.getIndex() || item.type == ResType.Plurals.getIndex() + || item.type == ResType.Pattern.getIndex()) { + byte[] bytes = + item.value.getBytes(StandardCharsets.UTF_8); + resourceIndexResult.value = convertBytesToString(bytes); + } else { + resourceIndexResult.value = item.value; + } + } + return resourceIndexResult; + } + + /** + * convert bytes to string. + * + * @param data Indicates the bytes of data. + * @return the final string + */ + static String convertBytesToString(byte[] data) { + StringBuilder result = new StringBuilder(); + ByteBuffer byteBuf = ByteBuffer.wrap(data); + byteBuf.order(ByteOrder.LITTLE_ENDIAN); + while(byteBuf.hasRemaining()) { + result.append(LEFT_BRACKET); + int len = byteBuf.getShort(); + if (len <= 0) { + LOG.info("len less than 0, dismiss"); + result.append(RIGHT_BRACKET); + break; + } + byte[] value = new byte[len + CHAR_LENGTH]; + byteBuf.get(value); + String item = new String(value, StandardCharsets.UTF_8); + result.append(item, 0, item.length()); + result.append(RIGHT_BRACKET); + } + return result.toString(); + } + + /** + * convert config to string. + * + * @param configIndex Indicates the configIndex. + * @return the final string + */ + static String convertConfigIndexToString(ConfigIndexV2 configIndex) { + StringBuilder configClass = new StringBuilder(); + int lastKeyType = -1; + for (int i = 0; i < configIndex.keyCount; ++i) { + KeyParamV2 param = configIndex.params[i]; + if (param.keyType == ConfigType.Language.ordinal() || param.keyType == ConfigType.Region.ordinal() + || param.keyType == ConfigType.Script.ordinal()) { + if (EMPTY_STRING.equals(configClass.toString())) { + configClass.append(parseAscii(param.value)); + } else { + if (lastKeyType == ConfigType.Language.ordinal() || + lastKeyType == ConfigType.Region.ordinal() || lastKeyType == ConfigType.Script.ordinal()) { + configClass.append(MCC_CONJUNCTION).append(parseAscii(param.value)); + } else { + configClass.append(CONFIG_CONJUNCTION).append(parseAscii(param.value)); + } + } + lastKeyType = param.keyType; + } else if (param.keyType == ConfigType.Resolution.ordinal()) { + if (EMPTY_STRING.equals(configClass.toString())) { + configClass.append(Resolution.getType(param.value)); + } else { + configClass.append(CONFIG_CONJUNCTION).append(Resolution.getType(param.value)); + } + } else if (param.keyType == ConfigType.Direction.ordinal()) { + if (EMPTY_STRING.equals(configClass.toString())) { + configClass.append(param.value == 0 ? VERTICAL : HORIZONTAL); + } else { + configClass.append(CONFIG_CONJUNCTION).append(param.value == 0 ? VERTICAL : HORIZONTAL); + } + } else if (param.keyType == ConfigType.DeviceType.ordinal()) { + if (EMPTY_STRING.equals(configClass.toString())) { + configClass.append(DeviceType.getType(param.value)); + } else { + configClass.append(CONFIG_CONJUNCTION).append(DeviceType.getType(param.value)); + } + } else if (param.keyType == ConfigType.LightMode.ordinal()) { + if (EMPTY_STRING.equals(configClass.toString())) { + configClass.append(param.value == 0 ? DARK : LIGHT); + } else { + configClass.append(CONFIG_CONJUNCTION).append(param.value == 0 ? DARK : LIGHT); + } + } else if (param.keyType == ConfigType.MCC.ordinal()) { + if (EMPTY_STRING.equals(configClass.toString())) { + configClass.append(MCC).append(String.valueOf(param.value)); + } else { + configClass.append(MCC_CONJUNCTION).append(MCC).append(String.valueOf(param.value)); + } + } else if (param.keyType == ConfigType.MNC.ordinal()) { + if (EMPTY_STRING.equals(configClass.toString())) { + configClass.append(MNC).append(fillUpZero(String.valueOf(param.value), 3)); + } else { + configClass.append(MCC_CONJUNCTION).append(MNC).append(fillUpZero(String.valueOf(param.value), 3)); + } + } + } + if (EMPTY_STRING.equals(configClass.toString())) { + configClass = new StringBuilder(BASE); + } + return configClass.toString(); + } + + /** + * convert integer to string. + * + * @param value Indicates the Integer. + * @return the final string + */ + private static String parseAscii(Integer value) { + StringBuilder result = new StringBuilder(); + while(value > 0) { + result.insert(0, (char) (value & 0xFF)); + value = value >> 8; + } + return result.toString(); + } + + /** + * fillup zero to string. + * @param inputString Indicates the string should to be filled. + * @param length Indicates the final length of String. + * @return the final string + */ + private static String fillUpZero(String inputString, int length) { + if (inputString.length() >= length) { + return inputString; + } + StringBuilder result = new StringBuilder(); + while(result.length() < length - inputString.length()) { + result.append('0'); + } + result.append(inputString); + return result.toString(); + } + + /** + * Parse KEYS,IDSS,DATA zone. + * + * @param data resource byte + */ + static void parseZone(byte[] data) { + if (!parseKEYSZone(data)) { + LOG.error("ResourcesParserV2 parseKEYSZone() failed"); + } + if (!parseDATAZone(data)) { + LOG.error("ResourcesParserV2 parseDATAZone() failed"); + } + if (!parseIDSSZone(data)) { + LOG.error("ResourcesParserV2 parseIDSSZone() failed"); + } + } + + /** + * Parse KEYS zone. + * + * @param data resource byte + * @return parse result + */ + static boolean parseKEYSZone(byte[] data) { + if (data == null || data.length == 0) { + LOG.error("ResourcesParserV2::parseKEYSZone data byte is null"); + return false; + } + ByteBuffer buf = ByteBuffer.wrap(data); + buf.order(ByteOrder.LITTLE_ENDIAN); + byte[] version = new byte[VERSION_BYTE_LENGTH]; + buf.get(version); + buf.getInt(); // length + int keyCount = buf.getInt(); + buf.getInt(); // dataBrokeOffset + //KEYS zone + for (int i = 0; i < keyCount; i++) { + Map map = new HashMap<>(); + byte[] tag = new byte[TAG_BYTE_LENGTH]; + buf.get(tag); + String tagStr = new String(tag, StandardCharsets.UTF_8); + int resCfgId = buf.getInt(); + int keyParamCount = buf.getInt(); + for (int j = 0; j < keyParamCount; j++) { + KEYSItemV2 keysItemV2 = new KEYSItemV2(); + keysItemV2.tag = tagStr; + keysItemV2.resCfgId = resCfgId; + byte[] type = new byte[TYPE_BYTE_LENGTH]; + buf.get(type); + keysItemV2.type = new String(type, StandardCharsets.UTF_8); + byte[] value = new byte[VALUE_BYTE_LENGTH]; + buf.get(value); + keysItemV2.value = new String(value, StandardCharsets.UTF_8); + map.put(keysItemV2.type,keysItemV2); + } + keysMap.put(tagStr,map); + } + idssOffset = buf.position(); + return true; + } + + /** + * Parse IDSS zone. + * + * @param data resource byte + * @return parse result + */ + static boolean parseIDSSZone(byte[] data) { + if (data == null || data.length == 0) { + LOG.error("ResourcesParserV2::parseIDSSZone data byte is null"); + return false; + } + if (idssOffset == 0) { + LOG.error("ResourcesParserV2::parseIDSSZone idssOffset has not parse"); + return false; + } + ByteBuffer buf = ByteBuffer.wrap(data); + buf.order(ByteOrder.LITTLE_ENDIAN); + buf.position(idssOffset); + buf.getInt();//tag + buf.getInt();//len + int typeCount = buf.getInt(); + buf.getInt();//idCount + //IDSS zone + for (int i = 0; i < typeCount; i++) { + Map map = new HashMap<>(); + int type = buf.getInt(); + buf.getInt();//length + int count = buf.getInt(); + for (int j = 0; j < count; j++) { + IDSSItemV2 idssItemV2 = new IDSSItemV2(); + idssItemV2.type = type; + idssItemV2.resId = buf.getInt(); + idssItemV2.offset = buf.getInt(); + int nameLen = buf.getInt(); + byte[] name = new byte[nameLen]; + buf.get(name); + idssItemV2.nameLen = nameLen; + idssItemV2.name = new String(name, StandardCharsets.UTF_8); + dataMap.get(idssItemV2.resId).values().forEach(dataItemV2 -> { + dataItemV2.type = idssItemV2.type; + dataItemV2.name = idssItemV2.name; + }); + map.put(idssItemV2.resId,idssItemV2); + } + idssMap.put(type,map); + } + return true; + } + + /** + * Parse DATA zone. + * + * @param data resource byte + * @return data zone + */ + static boolean parseDATAZone(byte[] data) { + if (data == null || data.length == 0) { + LOG.error("ResourcesParserV2::parseDATAZone data byte is null"); + return false; + } + ByteBuffer buf = ByteBuffer.wrap(data); + buf.order(ByteOrder.LITTLE_ENDIAN); + byte[] version = new byte[VERSION_BYTE_LENGTH]; + buf.get(version); + buf.getInt(); // length + buf.getInt(); + int dataZoneOffset = buf.getInt(); // dataBrokeOffset + buf.position(dataZoneOffset); + buf.getInt(); // DATA tag + buf.getInt(); // DATA length + int resIdCount = buf.getInt(); + for (int i = 0; i < resIdCount; i++) { + Map resCfgIdMap = new HashMap<>(); + int resId = buf.getInt(); + buf.getInt();// length + int resCfgIdCount = buf.getInt(); + for (int j = 0; j < resCfgIdCount; j++) { + DataItemV2 dataItemV2 = new DataItemV2(); + dataItemV2.resId = resId; + dataItemV2.resCfgId = buf.getInt(); + int dataOffset = buf.getInt(); + int position = buf.position(); + buf.position(dataOffset); + short dataLen = buf.getShort(); + byte[] tag = new byte[dataLen]; + buf.get(tag); + dataItemV2.value = new String(tag, StandardCharsets.UTF_8); + buf.position(position); + resCfgIdMap.put(dataItemV2.resCfgId,dataItemV2); + } + dataMap.put(resId,resCfgIdMap); + } + return true; + } +} + -- Gitee