pull parser style xml values decoder #18

This commit is contained in:
REAndroid 2023-05-01 20:27:16 +02:00
parent fea0583f61
commit c7c6863bbd
33 changed files with 1491 additions and 615 deletions

View File

@ -0,0 +1,110 @@
/*
* Copyright (C) 2022 github.com/REAndroid
*
* 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 com.reandroid.apk;
import com.reandroid.archive.InputSource;
import com.reandroid.archive2.block.ApkSignatureBlock;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
public abstract class ApkDecoder {
private final Set<String> mDecodedPaths;
private APKLogger apkLogger;
private boolean mLogErrors;
public ApkDecoder(){
mDecodedPaths = new HashSet<>();
}
public final void decodeTo(File outDir) throws IOException{
reset();
onDecodeTo(outDir);
}
abstract void onDecodeTo(File outDir) throws IOException;
boolean containsDecodedPath(String path){
return mDecodedPaths.contains(path);
}
void addDecodedPath(String path){
mDecodedPaths.add(path);
}
void writePathMap(File dir, Collection<? extends InputSource> sourceList) throws IOException {
PathMap pathMap = new PathMap();
pathMap.add(sourceList);
File file = new File(dir, PathMap.JSON_FILE);
pathMap.toJson().write(file);
}
void dumpSignatures(File outDir, ApkSignatureBlock signatureBlock) throws IOException {
if(signatureBlock == null){
return;
}
logMessage("Dumping signatures ...");
File dir = new File(outDir, ApkUtil.SIGNATURE_DIR_NAME);
signatureBlock.writeSplitRawToDirectory(dir);
}
void logOrThrow(String message, IOException exception) throws IOException{
if(isLogErrors()){
logError(message, exception);
return;
}
if(message == null && exception == null){
return;
}
if(exception == null){
exception = new IOException(message);
}
throw exception;
}
private void reset(){
mDecodedPaths.clear();
}
public boolean isLogErrors() {
return mLogErrors;
}
public void setLogErrors(boolean logErrors) {
this.mLogErrors = logErrors;
}
public void setApkLogger(APKLogger apkLogger) {
this.apkLogger = apkLogger;
}
APKLogger getApkLogger() {
return apkLogger;
}
void logMessage(String msg) {
APKLogger apkLogger = this.apkLogger;
if(apkLogger!=null){
apkLogger.logMessage(msg);
}
}
void logError(String msg, Throwable tr) {
APKLogger apkLogger = this.apkLogger;
if(apkLogger == null || (msg == null && tr == null)){
return;
}
apkLogger.logError(msg, tr);
}
void logVerbose(String msg) {
APKLogger apkLogger = this.apkLogger;
if(apkLogger!=null){
apkLogger.logVerbose(msg);
}
}
}

View File

@ -41,7 +41,7 @@ public class ApkJsonDecoder {
this(apkModule, false);
}
public void sanitizeFilePaths(){
PathSanitizer sanitizer = new PathSanitizer(apkModule);
PathSanitizer sanitizer = PathSanitizer.create(apkModule);
sanitizer.sanitize();
}
public File writeToDirectory(File dir) throws IOException {

View File

@ -120,14 +120,7 @@ public class ApkModule implements ApkFile {
}
return getAndroidManifestBlock().getSplit();
}
public FrameworkApk initializeAndroidFramework() throws IOException {
if(!hasTableBlock()){
return null;
}
Integer version = getAndroidFrameworkVersion();
return initializeAndroidFramework(getTableBlock(false), version);
}
private FrameworkApk initializeAndroidFramework(TableBlock tableBlock, Integer version) throws IOException {
public FrameworkApk initializeAndroidFramework(TableBlock tableBlock, Integer version) throws IOException {
if(tableBlock == null || isAndroid(tableBlock)){
return null;
}

View File

@ -1,115 +1,97 @@
/*
* Copyright (C) 2022 github.com/REAndroid
*
* 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.
*/
* Copyright (C) 2022 github.com/REAndroid
*
* 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 com.reandroid.apk;
import com.reandroid.apk.xmldecoder.ResXmlDocumentSerializer;
import com.reandroid.apk.xmldecoder.*;
import com.reandroid.archive.InputSource;
import com.reandroid.apk.xmldecoder.XMLBagDecoder;
import com.reandroid.apk.xmldecoder.XMLNamespaceValidator;
import com.reandroid.archive2.block.ApkSignatureBlock;
import com.reandroid.arsc.chunk.PackageBlock;
import com.reandroid.arsc.chunk.TableBlock;
import com.reandroid.arsc.chunk.TypeBlock;
import com.reandroid.arsc.chunk.xml.AndroidManifestBlock;
import com.reandroid.arsc.chunk.xml.ResXmlDocument;
import com.reandroid.arsc.container.SpecTypePair;
import com.reandroid.arsc.decoder.ValueDecoder;
import com.reandroid.arsc.value.*;
import com.reandroid.common.EntryStore;
import com.reandroid.json.JSONObject;
import com.reandroid.xml.XMLAttribute;
import com.reandroid.xml.XMLDocument;
import com.reandroid.xml.XMLElement;
import com.reandroid.xml.XMLException;
import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.function.Predicate;
public class ApkModuleXmlDecoder {
public class ApkModuleXmlDecoder extends ApkDecoder implements Predicate<Entry> {
private final ApkModule apkModule;
private final Map<Integer, Set<ResConfig>> decodedEntries;
private XMLBagDecoder xmlBagDecoder;
private final Set<String> mDecodedPaths;
private ResXmlDocumentSerializer documentSerializer;
private boolean useAndroidSerializer;
private XMLEntryDecoderSerializer entrySerializer;
public ApkModuleXmlDecoder(ApkModule apkModule){
this.apkModule=apkModule;
super();
this.apkModule = apkModule;
this.decodedEntries = new HashMap<>();
this.mDecodedPaths = new HashSet<>();
this.useAndroidSerializer = true;
}
public void setUseAndroidSerializer(boolean useAndroidSerializer) {
this.useAndroidSerializer = useAndroidSerializer;
super.setApkLogger(apkModule.getApkLogger());
}
public void sanitizeFilePaths(){
sanitizeFilePaths(false);
}
public void sanitizeFilePaths(boolean sanitizeResourceFiles){
PathSanitizer sanitizer = new PathSanitizer(apkModule, sanitizeResourceFiles);
PathSanitizer sanitizer = PathSanitizer.create(apkModule);
sanitizer.sanitize();
}
public void decodeTo(File outDir)
throws IOException, XMLException {
@Override
void onDecodeTo(File outDir) throws IOException{
this.decodedEntries.clear();
logMessage("Decoding ...");
if(!apkModule.hasTableBlock()){
logOrThrow(null, new IOException("Don't have resource table"));
return;
}
decodeUncompressedFiles(outDir);
apkModule.initializeAndroidFramework();
TableBlock tableBlock=apkModule.getTableBlock();
decodePackageInfo(outDir, tableBlock);
TableBlock tableBlock = apkModule.getTableBlock();
xmlBagDecoder=new XMLBagDecoder(tableBlock);
this.entrySerializer = new XMLEntryDecoderSerializer(tableBlock);
this.entrySerializer.setDecodedEntries(this);
decodePublicXml(tableBlock, outDir);
decodeAndroidManifest(outDir);
addDecodedPath(TableBlock.FILE_NAME);
decodeAndroidManifest(outDir, apkModule.getAndroidManifestBlock());
decodeTableBlock(outDir, tableBlock);
logMessage("Decoding resource files ...");
List<ResFile> resFileList=apkModule.listResFiles();
List<ResFile> resFileList = apkModule.listResFiles();
for(ResFile resFile:resFileList){
decodeResFile(outDir, resFile);
}
decodeValues(tableBlock, outDir, tableBlock);
decodeValues(outDir, tableBlock);
extractRootFiles(outDir);
writePathMap(outDir);
writePathMap(outDir, apkModule.getApkArchive().listInputSources());
dumpSignatures(outDir);
dumpSignatures(outDir, apkModule.getApkSignatureBlock());
}
private void dumpSignatures(File outDir) throws IOException {
ApkSignatureBlock signatureBlock = apkModule.getApkSignatureBlock();
if(signatureBlock == null){
return;
private void decodeTableBlock(File outDir, TableBlock tableBlock) throws IOException {
try{
decodePackageInfo(outDir, tableBlock);
decodePublicXml(tableBlock, outDir);
addDecodedPath(TableBlock.FILE_NAME);
}catch (IOException exception){
logOrThrow("Error decoding resource table", exception);
}
logMessage("Dumping signatures ...");
File dir = new File(outDir, ApkUtil.SIGNATURE_DIR_NAME);
signatureBlock.writeSplitRawToDirectory(dir);
}
private void writePathMap(File dir) throws IOException {
PathMap pathMap = new PathMap();
pathMap.add(apkModule.getApkArchive());
File file = new File(dir, PathMap.JSON_FILE);
pathMap.toJson().write(file);
}
private void decodePackageInfo(File outDir, TableBlock tableBlock) throws IOException {
for(PackageBlock packageBlock:tableBlock.listPackages()){
@ -123,7 +105,7 @@ import java.util.*;
jsonObject.write(packageJsonFile);
}
private void decodeUncompressedFiles(File outDir)
throws IOException {
throws IOException {
File file=new File(outDir, UncompressedFiles.JSON_FILE);
UncompressedFiles uncompressedFiles = apkModule.getUncompressedFiles();
uncompressedFiles.toJson().write(file);
@ -158,7 +140,7 @@ import java.util.*;
addDecodedEntry(entry);
}
private void decodeResXml(File outDir, ResFile resFile)
throws IOException{
throws IOException{
Entry entry = resFile.pickOne();
PackageBlock packageBlock = entry.getPackageBlock();
@ -181,11 +163,8 @@ import java.util.*;
}
return documentSerializer;
}
private TableBlock getTableBlock(){
return apkModule.getTableBlock();
}
private void decodePublicXml(TableBlock tableBlock, File outDir)
throws IOException{
throws IOException{
for(PackageBlock packageBlock:tableBlock.listPackages()){
decodePublicXml(packageBlock, outDir);
}
@ -207,7 +186,7 @@ import java.util.*;
xmlDocument.save(pubXml, false);
}
private void decodePublicXml(PackageBlock packageBlock, File outDir)
throws IOException {
throws IOException {
String packageDirName=getPackageDirName(packageBlock);
logMessage("Decoding public.xml: "+packageDirName);
File file=new File(outDir, packageDirName);
@ -218,15 +197,14 @@ import java.util.*;
resourceIds.loadPackageBlock(packageBlock);
resourceIds.writeXml(file);
}
private void decodeAndroidManifest(File outDir)
throws IOException {
private void decodeAndroidManifest(File outDir, AndroidManifestBlock manifestBlock)
throws IOException {
if(!apkModule.hasAndroidManifestBlock()){
logMessage("Don't have: "+ AndroidManifestBlock.FILE_NAME);
return;
}
File file=new File(outDir, AndroidManifestBlock.FILE_NAME);
logMessage("Decoding: "+file.getName());
AndroidManifestBlock manifestBlock=apkModule.getAndroidManifestBlock();
int currentPackageId = manifestBlock.guessCurrentPackageId();
serializeXml(currentPackageId, manifestBlock, file);
addDecodedPath(AndroidManifestBlock.FILE_NAME);
@ -235,50 +213,28 @@ import java.util.*;
throws IOException {
XMLNamespaceValidator namespaceValidator = new XMLNamespaceValidator(document);
namespaceValidator.validate();
if(useAndroidSerializer){
ResXmlDocumentSerializer serializer = getDocumentSerializer();
if(currentPackageId != 0){
serializer.getDecoder().setCurrentPackageId(currentPackageId);
}
try {
serializer.write(document, outFile);
} catch (XmlPullParserException ex) {
throw new IOException("Error: "+outFile.getName(), ex);
}
}else {
try {
XMLDocument xmlDocument = document.decodeToXml(getTableBlock(), currentPackageId);
xmlDocument.save(outFile, true);
} catch (XMLException ex) {
throw new IOException("Error: "+outFile.getName(), ex);
}
ResXmlDocumentSerializer serializer = getDocumentSerializer();
if(currentPackageId != 0){
serializer.getDecoder().setCurrentPackageId(currentPackageId);
}
try {
serializer.write(document, outFile);
} catch (XmlPullParserException ex) {
throw new IOException("Error: "+outFile.getName(), ex);
}
}
private void serializeXml(int currentPackageId, InputSource inputSource, File outFile)
throws IOException {
if(useAndroidSerializer){
ResXmlDocumentSerializer serializer = getDocumentSerializer();
if(currentPackageId != 0){
serializer.getDecoder().setCurrentPackageId(currentPackageId);
}
try {
serializer.write(inputSource, outFile);
} catch (XmlPullParserException ex) {
throw new IOException("Error: "+outFile.getName(), ex);
}
}else {
try {
ResXmlDocument document = apkModule.loadResXmlDocument(inputSource);
XMLNamespaceValidator namespaceValidator = new XMLNamespaceValidator(document);
namespaceValidator.validate();
XMLDocument xmlDocument = document.decodeToXml(getTableBlock(), currentPackageId);
xmlDocument.save(outFile, true);
} catch (XMLException ex) {
throw new IOException("Error: "+outFile.getName(), ex);
}
}
}
throws IOException {
ResXmlDocumentSerializer serializer = getDocumentSerializer();
if(currentPackageId != 0){
serializer.getDecoder().setCurrentPackageId(currentPackageId);
}
try {
serializer.write(inputSource, outFile);
} catch (XmlPullParserException ex) {
throw new IOException("Error: "+outFile.getName(), ex);
}
}
private void addDecodedEntry(Entry entry){
if(entry.isNull()){
return;
@ -298,71 +254,27 @@ import java.util.*;
}
return resConfigSet.contains(entry.getResConfig());
}
private void decodeValues(EntryStore entryStore, File outDir, TableBlock tableBlock) throws IOException {
private void decodeValues(File outDir, TableBlock tableBlock) throws IOException {
for(PackageBlock packageBlock:tableBlock.listPackages()){
decodeValues(entryStore, outDir, packageBlock);
decodeValues(outDir, packageBlock);
}
}
private void decodeValues(EntryStore entryStore, File outDir, PackageBlock packageBlock) throws IOException {
private void decodeValues(File outDir, PackageBlock packageBlock) throws IOException {
logMessage("Decoding values: "
+packageBlock.getIndex()
+"-"+packageBlock.getName());
packageBlock.sortTypes();
for(SpecTypePair specTypePair: packageBlock.listSpecTypePairs()){
for(TypeBlock typeBlock:specTypePair.listTypeBlocks()){
decodeValues(entryStore, outDir, typeBlock);
}
File pkgDir = new File(outDir, getPackageDirName(packageBlock));
File resDir = new File(pkgDir, ApkUtil.RES_DIR_NAME);
for(SpecTypePair specTypePair : packageBlock.listSpecTypePairs()){
decodeValues(resDir, specTypePair);
}
}
private void decodeValues(EntryStore entryStore, File outDir, TypeBlock typeBlock) throws IOException {
XMLDocument xmlDocument = new XMLDocument("resources");
XMLElement docElement = xmlDocument.getDocumentElement();
for(Entry entry :typeBlock.listEntries(true)){
if(containsDecodedEntry(entry)){
continue;
}
docElement.addChild(decodeValue(entryStore, entry));
}
if(docElement.getChildesCount()==0){
return;
}
File file=new File(outDir, getPackageDirName(typeBlock.getPackageBlock()));
file=new File(file, ApkUtil.RES_DIR_NAME);
file=new File(file, "values"+typeBlock.getQualifiers());
String type=typeBlock.getTypeName();
if(!type.endsWith("s")){
type=type+"s";
}
file=new File(file, type+".xml");
xmlDocument.save(file, false);
}
private XMLElement decodeValue(EntryStore entryStore, Entry entry){
XMLElement element=new XMLElement(XmlHelper.toXMLTagName(entry.getTypeName()));
int resourceId= entry.getResourceId();
XMLAttribute attribute=new XMLAttribute("name", entry.getName());
element.addAttribute(attribute);
attribute.setNameId(resourceId);
element.setResourceId(resourceId);
if(!entry.isComplex()){
ResValue resValue =(ResValue) entry.getTableEntry().getValue();
if(resValue.getValueType()== ValueType.STRING){
XmlHelper.setTextContent(element,
resValue.getDataAsPoolString());
}else {
String value = ValueDecoder.decodeEntryValue(entryStore,
entry.getPackageBlock(),
resValue.getValueType(),
resValue.getData());
element.setTextContent(value);
}
}else {
ResTableMapEntry mapEntry = (ResTableMapEntry) entry.getTableEntry();
xmlBagDecoder.decode(mapEntry, element);
return element;
}
return element;
private void decodeValues(File outDir, SpecTypePair specTypePair) throws IOException {
entrySerializer.decode(outDir, specTypePair);
}
private String getPackageDirName(PackageBlock packageBlock){
String name = ApkUtil.sanitizeForFileName(packageBlock.getName());
@ -394,29 +306,8 @@ import java.util.*;
inputSource.write(outputStream);
outputStream.close();
}
private boolean containsDecodedPath(String path){
return mDecodedPaths.contains(path);
}
private void addDecodedPath(String path){
mDecodedPaths.add(path);
}
private void logMessage(String msg) {
APKLogger apkLogger=apkModule.getApkLogger();
if(apkLogger!=null){
apkLogger.logMessage(msg);
}
}
private void logError(String msg, Throwable tr) {
APKLogger apkLogger=apkModule.getApkLogger();
if(apkLogger!=null){
apkLogger.logError(msg, tr);
}
}
private void logVerbose(String msg) {
APKLogger apkLogger=apkModule.getApkLogger();
if(apkLogger!=null){
apkLogger.logVerbose(msg);
}
@Override
public boolean test(Entry entry) {
return !containsDecodedEntry(entry);
}
}

View File

@ -118,7 +118,7 @@ public class PathMap implements JSONConvert<JSONArray> {
}
add(archive.listInputSources());
}
public void add(Collection<InputSource> sources){
public void add(Collection<? extends InputSource> sources){
if(sources==null){
return;
}

View File

@ -1,57 +1,62 @@
/*
* Copyright (C) 2022 github.com/REAndroid
*
* 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.
*/
/*
* Copyright (C) 2022 github.com/REAndroid
*
* 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 com.reandroid.apk;
import com.reandroid.archive.InputSource;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class PathSanitizer {
private final ApkModule apkModule;
private final Collection<? extends InputSource> sourceList;
private final boolean sanitizeResourceFiles;
private Collection<ResFile> resFileList;
private APKLogger apkLogger;
private final Set<String> mSanitizedPaths;
private final boolean sanitizeResourceFiles;
public PathSanitizer(ApkModule apkModule, boolean sanitizeResourceFiles){
this.apkModule = apkModule;
this.apkLogger = apkModule.getApkLogger();
public PathSanitizer(Collection<? extends InputSource> sourceList, boolean sanitizeResourceFiles){
this.sourceList = sourceList;
this.mSanitizedPaths = new HashSet<>();
this.sanitizeResourceFiles = sanitizeResourceFiles;
}
public PathSanitizer(ApkModule apkModule){
this(apkModule, false);
public PathSanitizer(Collection<? extends InputSource> sourceList){
this(sourceList, false);
}
public void sanitize(){
mSanitizedPaths.clear();
logMessage("Sanitizing paths ...");
sanitizeResFiles();
List<InputSource> sourceList = apkModule.getApkArchive().listInputSources();
for(InputSource inputSource:sourceList){
sanitize(inputSource, 1, false);
}
logMessage("DONE = "+mSanitizedPaths.size());
}
public void setResourceFileList(Collection<ResFile> resFileList){
this.resFileList = resFileList;
}
private void sanitizeResFiles(){
Collection<ResFile> resFileList = this.resFileList;
if(resFileList == null){
return;
}
boolean sanitizeRes = this.sanitizeResourceFiles;
Set<String> sanitizedPaths = this.mSanitizedPaths;
if(sanitizeRes){
logMessage("Sanitizing resource files ...");
}
List<ResFile> resFileList = apkModule.listResFiles();
for(ResFile resFile:resFileList){
if(sanitizeRes){
sanitize(resFile);
@ -122,13 +127,13 @@ public class PathSanitizer {
private String getLogTag(){
return "[SANITIZE]: ";
}
private void logMessage(String msg){
void logMessage(String msg){
APKLogger logger = this.apkLogger;
if(logger!=null){
logger.logMessage(getLogTag()+msg);
}
}
private void logVerbose(String msg){
void logVerbose(String msg){
APKLogger logger = this.apkLogger;
if(logger!=null){
logger.logVerbose(getLogTag()+msg);
@ -201,6 +206,14 @@ public class PathSanitizer {
|| (ch >= 'a' && ch <= 'z');
}
public static PathSanitizer create(ApkModule apkModule){
PathSanitizer pathSanitizer = new PathSanitizer(
apkModule.getApkArchive().listInputSources());
pathSanitizer.setApkLogger(apkModule.getApkLogger());
pathSanitizer.setResourceFileList(apkModule.listResFiles());
return pathSanitizer;
}
private static final int MAX_NAME_LENGTH = 75;
private static final int MAX_PATH_LENGTH = 100;
}

View File

@ -1,4 +1,4 @@
/*
/*
* Copyright (C) 2022 github.com/REAndroid
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -16,9 +16,10 @@
package com.reandroid.apk;
import com.reandroid.arsc.item.StringItem;
import com.reandroid.xml.XMLElement;
import com.reandroid.xml.*;
public class XmlHelper {
public static void setTextContent(XMLElement element, StringItem stringItem){
if(stringItem==null){
element.clearChildNodes();
@ -37,4 +38,6 @@ public class XmlHelper {
}
return typeName;
}
public static final String RESOURCES_TAG = "resources";
}

View File

@ -1,4 +1,4 @@
/*
/*
* Copyright (C) 2022 github.com/REAndroid
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -19,14 +19,9 @@ import com.reandroid.arsc.value.ResTableMapEntry;
import com.reandroid.common.EntryStore;
import com.reandroid.xml.XMLElement;
abstract class BagDecoder {
private final EntryStore entryStore;
abstract class BagDecoder<OUTPUT> extends DecoderTableEntry<ResTableMapEntry, OUTPUT> {
public BagDecoder(EntryStore entryStore){
this.entryStore=entryStore;
super(entryStore);
}
EntryStore getEntryStore(){
return entryStore;
}
public abstract void decode(ResTableMapEntry mapEntry, XMLElement parentElement);
public abstract boolean canDecode(ResTableMapEntry mapEntry);
}

View File

@ -0,0 +1,96 @@
/*
* Copyright (C) 2022 github.com/REAndroid
*
* 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 com.reandroid.apk.xmldecoder;
import com.reandroid.apk.ApkUtil;
import com.reandroid.apk.XmlHelper;
import com.reandroid.arsc.chunk.PackageBlock;
import com.reandroid.arsc.decoder.ValueDecoder;
import com.reandroid.arsc.value.*;
import com.reandroid.common.EntryStore;
import com.reandroid.xml.XMLElement;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
class BagDecoderArray<OUTPUT> extends BagDecoder<OUTPUT>{
public BagDecoderArray(EntryStore entryStore) {
super(entryStore);
}
@Override
public OUTPUT decode(ResTableMapEntry mapEntry, EntryWriter<OUTPUT> writer) throws IOException {
Entry entry = mapEntry.getParentEntry();
String tag = getTagName(mapEntry);
writer.startTag(tag);
writer.attribute("name", entry.getName());
PackageBlock packageBlock = entry.getPackageBlock();
ResValueMap[] resValueMaps = mapEntry.listResValueMap();
for(int i = 0; i < resValueMaps.length; i++){
ResValueMap valueMap = resValueMaps[i];
String childTag = "item";
writer.startTag(childTag);
writeText(writer, packageBlock, valueMap);
writer.endTag(childTag);
}
return writer.endTag(tag);
}
private String getTagName(ResTableMapEntry mapEntry){
ResValueMap[] resValueMaps = mapEntry.listResValueMap();
Set<ValueType> valueTypes = new HashSet<>();
for(int i = 0; i < resValueMaps.length; i++){
valueTypes.add(resValueMaps[i].getValueType());
}
if(valueTypes.contains(ValueType.STRING)){
return ApkUtil.TAG_STRING_ARRAY;
}
if(valueTypes.size() == 1 && valueTypes.contains(ValueType.INT_DEC)){
return ApkUtil.TAG_INTEGER_ARRAY;
}
return XmlHelper.toXMLTagName(mapEntry.getParentEntry().getTypeName());
}
@Override
public boolean canDecode(ResTableMapEntry mapEntry) {
return isArrayValue(mapEntry);
}
public static boolean isArrayValue(ResTableMapEntry mapEntry){
int parentId=mapEntry.getParentId();
if(parentId!=0){
return false;
}
ResValueMap[] bagItems = mapEntry.listResValueMap();
if(bagItems==null||bagItems.length==0){
return false;
}
int len=bagItems.length;
for(int i=0;i<len;i++){
ResValueMap item=bagItems[i];
int name = item.getName();
int high = (name >> 16) & 0xffff;
if(high!=0x0100 && high!=0x0200){
return false;
}
int low = name & 0xffff;
int id = low - 1;
if(id!=i){
return false;
}
}
return true;
}
}

View File

@ -0,0 +1,94 @@
/*
* Copyright (C) 2022 github.com/REAndroid
*
* 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 com.reandroid.apk.xmldecoder;
import com.reandroid.apk.XmlHelper;
import com.reandroid.arsc.value.Entry;
import com.reandroid.arsc.value.ResTableMapEntry;
import com.reandroid.arsc.value.attribute.AttributeBag;
import com.reandroid.arsc.value.attribute.AttributeBagItem;
import com.reandroid.common.EntryStore;
import com.reandroid.xml.XMLElement;
import java.io.IOException;
class BagDecoderAttr<OUTPUT> extends BagDecoder<OUTPUT>{
public BagDecoderAttr(EntryStore entryStore){
super(entryStore);
}
@Override
public OUTPUT decode(ResTableMapEntry mapEntry, EntryWriter<OUTPUT> writer) throws IOException {
Entry entry = mapEntry.getParentEntry();
String tag = XmlHelper.toXMLTagName(entry.getTypeName());
writer.startTag(tag);
writer.attribute("name", entry.getName());
AttributeBag attributeBag = AttributeBag.create(mapEntry.getValue());
writeParentAttributes(writer, attributeBag);
boolean is_flag = attributeBag.isFlag();
String childTag = is_flag ? "flag" : "enum";
AttributeBagItem[] bagItems = attributeBag.getBagItems();
EntryStore entryStore = getEntryStore();
for(int i=0;i< bagItems.length;i++){
AttributeBagItem item = bagItems[i];
if(item.isType()){
continue;
}
writer.startTag(childTag);
String name = item.getNameOrHex(entryStore);
writer.attribute("name", name);
int rawVal = item.getData();
String value;
if(is_flag){
value = String.format("0x%08x", rawVal);
}else {
value = String.valueOf(rawVal);
}
writer.text(value);
writer.endTag(childTag);
}
return writer.endTag(tag);
}
private void writeParentAttributes(EntryWriter<OUTPUT> writer, AttributeBag attributeBag) throws IOException {
String formats= attributeBag.decodeValueType();
if(formats!=null){
writer.attribute("formats", formats);
}
AttributeBagItem item = attributeBag.getMin();
if(item != null){
writer.attribute("min", item.getBound().toString());
}
item = attributeBag.getMax();
if(item!=null){
writer.attribute("max", item.getBound().toString());
}
item = attributeBag.getL10N();
if(item!=null){
writer.attribute("l10n", item.getBound().toString());
}
}
@Override
public boolean canDecode(ResTableMapEntry mapEntry) {
return AttributeBag.isAttribute(mapEntry);
}
}

View File

@ -0,0 +1,77 @@
/*
* Copyright (C) 2022 github.com/REAndroid
*
* 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 com.reandroid.apk.xmldecoder;
import com.reandroid.apk.XmlHelper;
import com.reandroid.arsc.chunk.PackageBlock;
import com.reandroid.arsc.decoder.ValueDecoder;
import com.reandroid.arsc.value.Entry;
import com.reandroid.arsc.value.ResTableMapEntry;
import com.reandroid.arsc.value.ResValueMap;
import com.reandroid.arsc.value.ValueType;
import com.reandroid.common.EntryStore;
import com.reandroid.xml.XMLElement;
import java.io.IOException;
class BagDecoderCommon<OUTPUT> extends BagDecoder<OUTPUT>{
public BagDecoderCommon(EntryStore entryStore) {
super(entryStore);
}
@Override
public OUTPUT decode(ResTableMapEntry mapEntry, EntryWriter<OUTPUT> writer) throws IOException {
Entry entry = mapEntry.getParentEntry();
String tag = XmlHelper.toXMLTagName(entry.getTypeName());
writer.startTag(tag);
writer.attribute("name", entry.getName());
PackageBlock packageBlock = entry.getPackageBlock();
int parentId = mapEntry.getParentId();
String parent;
if(parentId != 0){
parent = ValueDecoder.decodeEntryValue(getEntryStore(),
packageBlock, ValueType.REFERENCE, parentId);
}else {
parent = null;
}
if(parent != null){
writer.attribute("parent", parent);
}
EntryStore entryStore = getEntryStore();
ResValueMap[] resValueMaps = mapEntry.listResValueMap();
for(int i = 0; i < resValueMaps.length; i++){
ResValueMap valueMap = resValueMaps[i];
String childTag = "item";
writer.startTag(childTag);
String name = ValueDecoder.decodeAttributeName(
entryStore, packageBlock, valueMap.getName());
writer.attribute("name", name);
writeText(writer, packageBlock, valueMap);
writer.endTag(childTag);
}
return writer.endTag(tag);
}
@Override
public boolean canDecode(ResTableMapEntry mapEntry) {
return mapEntry !=null;
}
}

View File

@ -0,0 +1,91 @@
/*
* Copyright (C) 2022 github.com/REAndroid
*
* 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 com.reandroid.apk.xmldecoder;
import com.reandroid.apk.XmlHelper;
import com.reandroid.arsc.chunk.PackageBlock;
import com.reandroid.arsc.decoder.ValueDecoder;
import com.reandroid.arsc.value.*;
import com.reandroid.arsc.value.plurals.PluralsQuantity;
import com.reandroid.common.EntryStore;
import com.reandroid.xml.XMLElement;
import java.io.IOException;
class BagDecoderPlural<OUTPUT> extends BagDecoder<OUTPUT>{
public BagDecoderPlural(EntryStore entryStore) {
super(entryStore);
}
@Override
public OUTPUT decode(ResTableMapEntry mapEntry, EntryWriter<OUTPUT> writer) throws IOException {
Entry entry = mapEntry.getParentEntry();
String tag = XmlHelper.toXMLTagName(entry.getTypeName());
writer.startTag(tag);
writer.attribute("name", entry.getName());
ResValueMap[] resValueMaps = mapEntry.listResValueMap();
PackageBlock packageBlock = entry.getPackageBlock();
for(int i=0; i < resValueMaps.length; i++){
ResValueMap valueMap = resValueMaps[i];
String childTag = "item";
writer.startTag(childTag);
PluralsQuantity quantity =
PluralsQuantity.valueOf((short) (valueMap.getName() & 0xffff));
if(quantity == null){
throw new IOException("Unknown plural quantity: " + valueMap);
}
writer.attribute("quantity", quantity.toString());
writeText(writer, packageBlock, valueMap);
writer.endTag(childTag);
}
return writer.endTag(tag);
}
@Override
public boolean canDecode(ResTableMapEntry mapEntry) {
return isResBagPluralsValue(mapEntry);
}
public static boolean isResBagPluralsValue(ResTableMapEntry valueItem){
int parentId=valueItem.getParentId();
if(parentId!=0){
return false;
}
ResValueMap[] bagItems = valueItem.listResValueMap();
if(bagItems==null||bagItems.length==0){
return false;
}
int len=bagItems.length;
for(int i=0;i<len;i++){
ResValueMap item=bagItems[i];
int name = item.getName();
int high = (name >> 16) & 0xffff;
if(high!=0x0100){
return false;
}
int low = name & 0xffff;
PluralsQuantity pq=PluralsQuantity.valueOf((short) low);
if(pq==null){
return false;
}
}
return true;
}
}

View File

@ -0,0 +1,38 @@
/*
* Copyright (C) 2022 github.com/REAndroid
*
* 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 com.reandroid.apk.xmldecoder;
import com.reandroid.apk.XmlHelper;
import com.reandroid.arsc.value.Entry;
import com.reandroid.arsc.value.ResTableEntry;
import com.reandroid.common.EntryStore;
import java.io.IOException;
public class DecoderResTableEntry<OUTPUT> extends DecoderTableEntry<ResTableEntry, OUTPUT> {
public DecoderResTableEntry(EntryStore entryStore){
super(entryStore);
}
@Override
public OUTPUT decode(ResTableEntry tableEntry, EntryWriter<OUTPUT> writer) throws IOException{
Entry entry = tableEntry.getParentEntry();
String tag = XmlHelper.toXMLTagName(entry.getTypeName());
writer.startTag(tag);
writeText(writer, entry.getPackageBlock(), tableEntry.getValue());
return writer.endTag(tag);
}
}

View File

@ -0,0 +1,52 @@
/*
* Copyright (C) 2022 github.com/REAndroid
*
* 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 com.reandroid.apk.xmldecoder;
import com.reandroid.arsc.value.ResTableMapEntry;
import com.reandroid.common.EntryStore;
import java.io.IOException;
class DecoderResTableEntryMap<OUTPUT> extends DecoderTableEntry<ResTableMapEntry, OUTPUT> {
private final Object[] decoderList;
private final BagDecoderCommon<OUTPUT> bagDecoderCommon;
public DecoderResTableEntryMap(EntryStore entryStore) {
super(entryStore);
this.decoderList = new Object[] {
new BagDecoderAttr<>(entryStore),
new BagDecoderPlural<>(entryStore),
new BagDecoderArray<>(entryStore)
};
this.bagDecoderCommon = new BagDecoderCommon<>(entryStore);
}
@Override
public OUTPUT decode(ResTableMapEntry tableEntry, EntryWriter<OUTPUT> writer) throws IOException {
return getFor(tableEntry).decode(tableEntry, writer);
}
private BagDecoder<OUTPUT> getFor(ResTableMapEntry mapEntry){
Object[] decoderList = this.decoderList;
for(int i = 0; i < decoderList.length; i++){
BagDecoder<OUTPUT> bagDecoder = (BagDecoder<OUTPUT>) decoderList[i];
if(bagDecoder.canDecode(mapEntry)){
return bagDecoder;
}
}
return bagDecoderCommon;
}
}

View File

@ -0,0 +1,57 @@
/*
* Copyright (C) 2022 github.com/REAndroid
*
* 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 com.reandroid.apk.xmldecoder;
import com.reandroid.arsc.chunk.PackageBlock;
import com.reandroid.arsc.decoder.ValueDecoder;
import com.reandroid.arsc.value.TableEntry;
import com.reandroid.arsc.value.ValueItem;
import com.reandroid.arsc.value.ValueType;
import com.reandroid.common.EntryStore;
import java.io.IOException;
abstract class DecoderTableEntry<INPUT extends TableEntry<?, ?>, OUTPUT> {
private final EntryStore entryStore;
public DecoderTableEntry(EntryStore entryStore){
this.entryStore = entryStore;
}
public EntryStore getEntryStore() {
return entryStore;
}
public abstract OUTPUT decode(INPUT tableEntry, EntryWriter<OUTPUT> writer) throws IOException;
void writeText(EntryWriter<?> writer, PackageBlock packageBlock, ValueItem valueItem)
throws IOException {
if(valueItem.getValueType() == ValueType.STRING){
XMLDecodeHelper.writeTextContent(writer, valueItem.getDataAsPoolString());
}else {
String value = ValueDecoder.decodeEntryValue(
getEntryStore(),
packageBlock,
valueItem.getValueType(),
valueItem.getData());
value = XMLDecodeHelper.escapeXmlChars(value);
if(value == null){
System.err.println("\nNULL: " + valueItem);
}
writer.text(value);
}
}
}

View File

@ -0,0 +1,28 @@
/*
* Copyright (C) 2022 github.com/REAndroid
*
* 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 com.reandroid.apk.xmldecoder;
import java.io.IOException;
public interface EntryWriter<T>{
void setFeature(String name, Object value);
T startTag(String name) throws IOException;
T endTag(String name) throws IOException;
T attribute(String name, String value) throws IOException;
T text(String text) throws IOException;
void comment(String comment) throws IOException;
void flush() throws IOException;
}

View File

@ -0,0 +1,86 @@
/*
* Copyright (C) 2022 github.com/REAndroid
*
* 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 com.reandroid.apk.xmldecoder;
import com.reandroid.xml.XMLComment;
import com.reandroid.xml.XMLElement;
import java.io.IOException;
public class EntryWriterElement implements EntryWriter<XMLElement> {
private XMLElement mCurrentElement;
private XMLElement mResult;
public EntryWriterElement(){
}
public XMLElement getElement() {
return mResult;
}
@Override
public void setFeature(String name, Object value) {
}
@Override
public XMLElement startTag(String name) throws IOException {
XMLElement xmlElement = new XMLElement(name);
XMLElement current = mCurrentElement;
if(current != null){
current.addChild(xmlElement);
}else {
mResult = null;
}
mCurrentElement = xmlElement;
return xmlElement;
}
@Override
public XMLElement endTag(String name) throws IOException {
XMLElement current = mCurrentElement;
if(current == null){
throw new IOException("endTag called before startTag");
}
if(!name.equals(current.getTagName())){
throw new IOException("Mismatch endTag = "
+ name + ", expect = " + current.getTagName());
}
XMLElement parent = current.getParent();
if(parent == null){
mResult = current;
}else {
current = parent;
}
mCurrentElement = parent;
return current;
}
@Override
public XMLElement attribute(String name, String value) {
mCurrentElement.setAttribute(name, value);
return mCurrentElement;
}
@Override
public XMLElement text(String text) throws IOException {
mCurrentElement.setTextContent(text, false);
return mCurrentElement;
}
@Override
public void comment(String comment) throws IOException {
if(comment != null){
mCurrentElement.addComment(new XMLComment(comment));
}
}
@Override
public void flush() throws IOException {
}
}

View File

@ -0,0 +1,61 @@
/*
* Copyright (C) 2022 github.com/REAndroid
*
* 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 com.reandroid.apk.xmldecoder;
import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
public class EntryWriterSerializer implements EntryWriter<XmlSerializer> {
private final XmlSerializer xmlSerializer;
public EntryWriterSerializer(XmlSerializer xmlSerializer){
this.xmlSerializer = xmlSerializer;
}
public XmlSerializer getXmlSerializer() {
return xmlSerializer;
}
@Override
public void setFeature(String name, Object value) {
xmlSerializer.setFeature(name, (Boolean)value);
}
@Override
public XmlSerializer startTag(String name) throws IOException {
xmlSerializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
return xmlSerializer.startTag(null, name);
}
@Override
public XmlSerializer endTag(String name) throws IOException {
return xmlSerializer.endTag(null, name);
}
@Override
public XmlSerializer attribute(String name, String value) throws IOException {
return xmlSerializer.attribute(null, name, value);
}
@Override
public XmlSerializer text(String text) throws IOException {
return xmlSerializer.text(text);
}
@Override
public void comment(String comment) throws IOException {
xmlSerializer.comment(comment);
}
@Override
public void flush() throws IOException {
xmlSerializer.flush();
}
}

View File

@ -1,4 +1,4 @@
/*
/*
* Copyright (C) 2022 github.com/REAndroid
*
* Licensed under the Apache License, Version 2.0 (the "License");

View File

@ -1,88 +0,0 @@
/*
* Copyright (C) 2022 github.com/REAndroid
*
* 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 com.reandroid.apk.xmldecoder;
import com.reandroid.apk.ApkUtil;
import com.reandroid.apk.XmlHelper;
import com.reandroid.arsc.decoder.ValueDecoder;
import com.reandroid.arsc.value.ResTableMapEntry;
import com.reandroid.arsc.value.ResValueMap;
import com.reandroid.arsc.value.ValueType;
import com.reandroid.common.EntryStore;
import com.reandroid.xml.XMLElement;
import java.util.HashSet;
import java.util.Set;
class XMLArrayDecoder extends BagDecoder{
public XMLArrayDecoder(EntryStore entryStore) {
super(entryStore);
}
@Override
public void decode(ResTableMapEntry mapEntry, XMLElement parentElement) {
ResValueMap[] bagItems = mapEntry.listResValueMap();
EntryStore entryStore=getEntryStore();
Set<ValueType> valueTypes = new HashSet<>();
for(int i=0;i<bagItems.length;i++){
ResValueMap bagItem = bagItems[i];
ValueType valueType = bagItem.getValueType();
XMLElement child = new XMLElement("item");
if(valueType == ValueType.STRING){
XmlHelper.setTextContent(child, bagItem.getDataAsPoolString());
}else {
String value = ValueDecoder.decodeIntEntry(entryStore, bagItem);
child.setTextContent(value);
}
parentElement.addChild(child);
valueTypes.add(valueType);
}
if(valueTypes.contains(ValueType.STRING)){
parentElement.setTagName(ApkUtil.TAG_STRING_ARRAY);
}else if(valueTypes.size()==1 && valueTypes.contains(ValueType.INT_DEC)){
parentElement.setTagName(ApkUtil.TAG_INTEGER_ARRAY);
}
}
@Override
public boolean canDecode(ResTableMapEntry mapEntry) {
return isArrayValue(mapEntry);
}
public static boolean isArrayValue(ResTableMapEntry mapEntry){
int parentId=mapEntry.getParentId();
if(parentId!=0){
return false;
}
ResValueMap[] bagItems = mapEntry.listResValueMap();
if(bagItems==null||bagItems.length==0){
return false;
}
int len=bagItems.length;
for(int i=0;i<len;i++){
ResValueMap item=bagItems[i];
int name = item.getName();
int high = (name >> 16) & 0xffff;
if(high!=0x0100 && high!=0x0200){
return false;
}
int low = name & 0xffff;
int id = low - 1;
if(id!=i){
return false;
}
}
return true;
}
}

View File

@ -1,80 +0,0 @@
/*
* Copyright (C) 2022 github.com/REAndroid
*
* 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 com.reandroid.apk.xmldecoder;
import com.reandroid.arsc.value.ResTableMapEntry;
import com.reandroid.arsc.value.attribute.AttributeBag;
import com.reandroid.arsc.value.attribute.AttributeBagItem;
import com.reandroid.common.EntryStore;
import com.reandroid.xml.XMLElement;
class XMLAttrDecoder extends BagDecoder{
public XMLAttrDecoder(EntryStore entryStore){
super(entryStore);
}
@Override
public void decode(ResTableMapEntry mapEntry, XMLElement parentElement){
AttributeBag attributeBag=AttributeBag.create(mapEntry.getValue());
decodeParentAttributes(parentElement, attributeBag);
boolean is_flag=attributeBag.isFlag();
String tagName=is_flag?"flag":"enum";
AttributeBagItem[] bagItems = attributeBag.getBagItems();
EntryStore entryStore=getEntryStore();
for(int i=0;i< bagItems.length;i++){
AttributeBagItem item=bagItems[i];
if(item.isType()){
continue;
}
XMLElement child=new XMLElement(tagName);
String name = item.getNameOrHex(entryStore);
child.setAttribute("name", name);
int rawVal=item.getData();
String value;
if(is_flag){
value=String.format("0x%08x", rawVal);
}else {
value=String.valueOf(rawVal);
}
child.setTextContent(value);
parentElement.addChild(child);
}
}
@Override
public boolean canDecode(ResTableMapEntry mapEntry) {
return AttributeBag.isAttribute(mapEntry);
}
private void decodeParentAttributes(XMLElement element, AttributeBag attributeBag){
String formats= attributeBag.decodeValueType();
if(formats!=null){
element.setAttribute("formats", formats);
}
AttributeBagItem boundItem=attributeBag.getMin();
if(boundItem!=null){
element.setAttribute("min", boundItem.getBound().toString());
}
boundItem=attributeBag.getMax();
if(boundItem!=null){
element.setAttribute("max", boundItem.getBound().toString());
}
boundItem=attributeBag.getL10N();
if(boundItem!=null){
element.setAttribute("l10n", boundItem.getBound().toString());
}
}
}

View File

@ -1,4 +1,4 @@
/*
/*
* Copyright (C) 2022 github.com/REAndroid
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -15,36 +15,25 @@
*/
package com.reandroid.apk.xmldecoder;
import com.reandroid.arsc.array.ResValueMapArray;
import com.reandroid.arsc.value.ResTableMapEntry;
import com.reandroid.common.EntryStore;
import com.reandroid.xml.XMLElement;
import java.util.ArrayList;
import java.util.List;
import java.io.IOException;
@Deprecated
public class XMLBagDecoder {
private final EntryStore entryStore;
private final List<BagDecoder> decoderList;
private final XMLCommonBagDecoder commonBagDecoder;
private final DecoderResTableEntryMap<XMLElement> mDocumentDecoder;
private final EntryWriterElement mWriter;
public XMLBagDecoder(EntryStore entryStore){
this.entryStore=entryStore;
this.decoderList=new ArrayList<>();
this.decoderList.add(new XMLAttrDecoder(entryStore));
this.decoderList.add(new XMLPluralsDecoder(entryStore));
this.decoderList.add(new XMLArrayDecoder(entryStore));
this.commonBagDecoder = new XMLCommonBagDecoder(entryStore);
mDocumentDecoder = new DecoderResTableEntryMap<>(entryStore);
mWriter = new EntryWriterElement();
}
public void decode(ResTableMapEntry mapEntry, XMLElement parentElement){
BagDecoder bagDecoder=getFor(mapEntry);
bagDecoder.decode(mapEntry, parentElement);
}
private BagDecoder getFor(ResTableMapEntry mapEntry){
for(BagDecoder bagDecoder:decoderList){
if(bagDecoder.canDecode(mapEntry)){
return bagDecoder;
}
try {
XMLElement child = mDocumentDecoder.decode(mapEntry, mWriter);
parentElement.addChild(child);
} catch (IOException exception) {
}
return commonBagDecoder;
}
}

View File

@ -1,75 +0,0 @@
/*
* Copyright (C) 2022 github.com/REAndroid
*
* 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 com.reandroid.apk.xmldecoder;
import com.reandroid.apk.XmlHelper;
import com.reandroid.arsc.chunk.PackageBlock;
import com.reandroid.arsc.decoder.ValueDecoder;
import com.reandroid.arsc.value.ResTableMapEntry;
import com.reandroid.arsc.value.ResValueMap;
import com.reandroid.arsc.value.ValueType;
import com.reandroid.common.EntryStore;
import com.reandroid.xml.XMLElement;
class XMLCommonBagDecoder extends BagDecoder{
public XMLCommonBagDecoder(EntryStore entryStore) {
super(entryStore);
}
@Override
public void decode(ResTableMapEntry mapEntry, XMLElement parentElement) {
PackageBlock currentPackage= mapEntry
.getParentEntry().getPackageBlock();
int parentId = mapEntry.getParentId();
String parent;
if(parentId!=0){
parent = ValueDecoder.decodeEntryValue(getEntryStore(),
currentPackage, ValueType.REFERENCE, parentId);
}else {
parent=null;
}
if(parent!=null){
parentElement.setAttribute("parent", parent);
}
int currentPackageId=currentPackage.getId();
ResValueMap[] bagItems = mapEntry.listResValueMap();
EntryStore entryStore = getEntryStore();
for(int i=0;i< bagItems.length;i++){
ResValueMap item=bagItems[i];
XMLElement child=new XMLElement("item");
String name = ValueDecoder.decodeAttributeName(
entryStore, currentPackage, item.getName());
child.setAttribute("name", name);
ValueType valueType = item.getValueType();
if(valueType == ValueType.STRING){
XmlHelper.setTextContent(child, item.getDataAsPoolString());
}else {
String value = ValueDecoder.decode(entryStore, currentPackageId,
item);
child.setTextContent(value);
}
parentElement.addChild(child);
}
}
@Override
public boolean canDecode(ResTableMapEntry mapEntry) {
return mapEntry !=null;
}
}

View File

@ -0,0 +1,84 @@
/*
* Copyright (C) 2022 github.com/REAndroid
*
* 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 com.reandroid.apk.xmldecoder;
import com.reandroid.arsc.item.StringItem;
import com.reandroid.xml.*;
import com.reandroid.xml.parser.XMLSpanParser;
import java.io.IOException;
public class XMLDecodeHelper {
public static void writeTextContent(EntryWriter<?> writer, StringItem stringItem) throws IOException {
if(stringItem == null){
return;
}
if(!stringItem.hasStyle()){
writer.text(escapeXmlChars(stringItem.get()));
}else {
String xml = stringItem.getXml();
XMLElement element = parseSpanSafe(xml);
if(element != null){
writeElement(writer, element);
}else {
// TODO: throw or investigate the reason
writer.text(xml);
}
}
}
public static void writeElement(EntryWriter<?> writer, XMLElement element) throws IOException {
writer.startTag(element.getTagName());
for(XMLAttribute xmlAttribute : element.listAttributes()){
writer.attribute(xmlAttribute.getName(), xmlAttribute.getValue());
}
for(XMLNode xmlNode : element.getChildNodes()){
if(xmlNode instanceof XMLText){
writer.text(((XMLText)xmlNode).getText(false));
}else if(xmlNode instanceof XMLElement){
writeElement(writer, (XMLElement) xmlNode);
}
}
writer.endTag(element.getTagName());
}
private static XMLElement parseSpanSafe(String spanText){
if(spanText==null){
return null;
}
try {
XMLSpanParser spanParser = new XMLSpanParser();
return spanParser.parse(spanText);
} catch (XMLException ignored) {
return null;
}
}
public static String escapeXmlChars(String str){
if(str == null){
return null;
}
if(str.indexOf('&') < 0 && str.indexOf('<') < 0 && str.indexOf('>') < 0){
return str;
}
str=str.replaceAll("&amp;", "&");
str=str.replaceAll("&lt;", "<");
str=str.replaceAll("&gt;", ">");
str=str.replaceAll("&", "&amp;");
str=str.replaceAll("<", "&lt;");
str=str.replaceAll(">", "&gt;");
return str;
}
}

View File

@ -0,0 +1,135 @@
/*
* Copyright (C) 2022 github.com/REAndroid
*
* 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 com.reandroid.apk.xmldecoder;
import com.reandroid.arsc.chunk.TypeBlock;
import com.reandroid.arsc.group.EntryGroup;
import com.reandroid.arsc.value.*;
import com.reandroid.common.EntryStore;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.Iterator;
import java.util.function.Predicate;
public class XMLEntryDecoder<OUTPUT>{
private final Object mLock = new Object();
private final DecoderResTableEntry<OUTPUT> decoderEntry;
private final DecoderResTableEntryMap<OUTPUT> decoderEntryMap;
private Predicate<Entry> mDecodedEntries;
public XMLEntryDecoder(EntryStore entryStore){
this.decoderEntry = new DecoderResTableEntry<>(entryStore);
this.decoderEntryMap = new DecoderResTableEntryMap<>(entryStore);
}
public void setDecodedEntries(Predicate<Entry> decodedEntries) {
this.mDecodedEntries = decodedEntries;
}
private boolean shouldDecode(Entry entry){
if(entry == null || entry.isNull()){
return false;
}
if(this.mDecodedEntries != null){
return mDecodedEntries.test(entry);
}
return true;
}
public OUTPUT decode(EntryWriter<OUTPUT> writer, Entry entry) throws IOException{
if(!shouldDecode(entry)){
return null;
}
synchronized (mLock){
TableEntry<?, ?> tableEntry = entry.getTableEntry();
if(tableEntry instanceof ResTableMapEntry){
return decoderEntryMap.decode((ResTableMapEntry) tableEntry, writer);
}
return decoderEntry.decode((ResTableEntry) tableEntry, writer);
}
}
public int decode(EntryWriter<OUTPUT> writer, Collection<Entry> entryList) throws IOException {
int count = 0;
for(Entry entry : entryList){
OUTPUT output = decode(writer, entry);
if(output != null){
count ++;
}
}
return count;
}
public int decode(EntryWriter<OUTPUT> writer, ResConfig resConfig, Collection<EntryGroup> entryGroupList) throws IOException {
int count = 0;
for(EntryGroup entryGroup : entryGroupList){
OUTPUT output = decode(writer, entryGroup.getEntry(resConfig));
if(output != null){
count ++;
}
}
return count;
}
public int decode(EntryWriter<OUTPUT> writer, TypeBlock typeBlock) throws IOException {
Iterator<Entry> iterator = typeBlock.getEntryArray()
.iterator(true);
int count = 0;
while (iterator.hasNext()){
Entry entry = iterator.next();
OUTPUT output = decode(writer, entry);
if(output != null){
count++;
}
}
return count;
}
void deleteIfZero(int decodeCount, File file){
if(decodeCount > 0){
return;
}
file.delete();
File dir = file.getParentFile();
if(isEmptyDirectory(dir)){
dir.delete();
}
}
private boolean isEmptyDirectory(File dir){
if(dir == null || !dir.isDirectory()){
return false;
}
File[] files = dir.listFiles();
return files == null || files.length == 0;
}
File toOutXmlFile(File resDirectory, TypeBlock typeBlock){
String path = toValuesXml(typeBlock);
return new File(resDirectory, path);
}
String toValuesXml(TypeBlock typeBlock){
StringBuilder builder = new StringBuilder();
char sepChar = File.separatorChar;
builder.append("values");
builder.append(typeBlock.getQualifiers());
builder.append(sepChar);
String type = typeBlock.getTypeName();
builder.append(type);
if(!type.endsWith("s")){
builder.append('s');
}
builder.append(".xml");
return builder.toString();
}
}

View File

@ -0,0 +1,55 @@
/*
* Copyright (C) 2022 github.com/REAndroid
*
* 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 com.reandroid.apk.xmldecoder;
import com.reandroid.apk.XmlHelper;
import com.reandroid.arsc.value.Entry;
import com.reandroid.common.EntryStore;
import com.reandroid.xml.XMLDocument;
import com.reandroid.xml.XMLElement;
import java.io.IOException;
import java.util.Collection;
public class XMLEntryDecoderDocument extends XMLEntryDecoder<XMLElement>{
private final EntryWriterElement entryWriterElement;
public XMLEntryDecoderDocument(EntryStore entryStore) {
super(entryStore);
this.entryWriterElement = new EntryWriterElement();
}
public XMLElement decode(Entry entry) throws IOException {
return super.decode(this.entryWriterElement, entry);
}
public XMLDocument decode(XMLDocument xmlDocument, Collection<Entry> entryList)
throws IOException {
if(xmlDocument == null){
xmlDocument = new XMLDocument(XmlHelper.RESOURCES_TAG);
}
XMLElement docElement = xmlDocument.getDocumentElement();
if(docElement == null){
docElement = new XMLElement(XmlHelper.RESOURCES_TAG);
xmlDocument.setDocumentElement(docElement);
}
for(Entry entry : entryList){
docElement.addChild(decode(entry));
}
return xmlDocument;
}
}

View File

@ -0,0 +1,145 @@
/*
* Copyright (C) 2022 github.com/REAndroid
*
* 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 com.reandroid.apk.xmldecoder;
import com.android.org.kxml2.io.KXmlSerializer;
import com.reandroid.apk.XmlHelper;
import com.reandroid.arsc.chunk.TypeBlock;
import com.reandroid.arsc.container.SpecTypePair;
import com.reandroid.arsc.group.EntryGroup;
import com.reandroid.arsc.value.ResConfig;
import com.reandroid.common.EntryStore;
import org.xmlpull.v1.XmlSerializer;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
public class XMLEntryDecoderSerializer extends XMLEntryDecoder<XmlSerializer> implements Closeable {
private final EntryWriterSerializer entryWriterSerializer;
private Closeable mClosable;
private boolean mStart;
public XMLEntryDecoderSerializer(EntryStore entryStore, XmlSerializer serializer) {
super(entryStore);
this.entryWriterSerializer = new EntryWriterSerializer(serializer);
}
public XMLEntryDecoderSerializer(EntryStore entryStore) {
this(entryStore, new KXmlSerializer());
}
public int decode(File resDirectory, SpecTypePair specTypePair) throws IOException {
int count;
if(specTypePair.hasDuplicateResConfig(true)){
count = decodeDuplicateConfigs(resDirectory, specTypePair);
}else {
count = decodeUniqueConfigs(resDirectory, specTypePair);
}
return count;
}
private int decodeDuplicateConfigs(File resDirectory, SpecTypePair specTypePair) throws IOException {
List<ResConfig> resConfigList = specTypePair.listResConfig();
Collection<EntryGroup> entryGroupList = specTypePair
.createEntryGroups(true).values();
int total = 0;
for(ResConfig resConfig : resConfigList){
TypeBlock typeBlock = resConfig.getParentInstance(TypeBlock.class);
File outXml = toOutXmlFile(resDirectory, typeBlock);
total += decode(outXml, resConfig, entryGroupList);
}
return total;
}
private int decodeUniqueConfigs(File resDirectory, SpecTypePair specTypePair) throws IOException {
int total = 0;
Iterator<TypeBlock> itr = specTypePair.iteratorNonEmpty();
while (itr.hasNext()){
TypeBlock typeBlock = itr.next();
File outXml = toOutXmlFile(resDirectory, typeBlock);
total += decode(outXml, typeBlock);
}
return total;
}
public int decode(File outXmlFile, ResConfig resConfig, Collection<EntryGroup> entryGroupList) throws IOException {
setOutput(outXmlFile);
int count = decode(resConfig, entryGroupList);
close();
deleteIfZero(count, outXmlFile);
return count;
}
public int decode(File outXmlFile, TypeBlock typeBlock) throws IOException {
setOutput(outXmlFile);
int count = super.decode(entryWriterSerializer, typeBlock);
close();
deleteIfZero(count, outXmlFile);
return count;
}
public int decode(ResConfig resConfig, Collection<EntryGroup> entryGroupList) throws IOException {
return super.decode(entryWriterSerializer, resConfig, entryGroupList);
}
public void setOutput(File file) throws IOException {
File dir = file.getParentFile();
if(dir != null && !dir.exists()){
dir.mkdirs();
}
setOutput(new FileOutputStream(file));
}
public void setOutput(OutputStream outputStream) throws IOException {
close();
getXmlSerializer().setOutput(outputStream, StandardCharsets.UTF_8.name());
this.mClosable = outputStream;
start();
}
public void setOutput(Writer writer) throws IOException {
close();
getXmlSerializer().setOutput(writer);
this.mClosable = writer;
start();
}
private void start() throws IOException {
if(!mStart){
XmlSerializer xmlSerializer = getXmlSerializer();
xmlSerializer.startDocument("utf-8", null);
xmlSerializer.startTag(null, XmlHelper.RESOURCES_TAG);
mStart = true;
}
}
private void end() throws IOException {
if(mStart){
XmlSerializer xmlSerializer = getXmlSerializer();
xmlSerializer.endTag(null, XmlHelper.RESOURCES_TAG);
xmlSerializer.endDocument();
xmlSerializer.flush();
mStart = false;
}
}
private XmlSerializer getXmlSerializer(){
return entryWriterSerializer.getXmlSerializer();
}
@Override
public void close() throws IOException {
Closeable closeable = this.mClosable;
end();
if(closeable != null){
closeable.close();
}
this.mClosable = null;
}
}

View File

@ -1,82 +0,0 @@
/*
* Copyright (C) 2022 github.com/REAndroid
*
* 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 com.reandroid.apk.xmldecoder;
import com.reandroid.apk.XmlHelper;
import com.reandroid.arsc.decoder.ValueDecoder;
import com.reandroid.arsc.value.*;
import com.reandroid.arsc.value.plurals.PluralsQuantity;
import com.reandroid.common.EntryStore;
import com.reandroid.xml.XMLElement;
class XMLPluralsDecoder extends BagDecoder{
public XMLPluralsDecoder(EntryStore entryStore) {
super(entryStore);
}
@Override
public void decode(ResTableMapEntry mapEntry, XMLElement parentElement) {
ResValueMap[] bagItems = mapEntry.listResValueMap();
int len=bagItems.length;
EntryStore entryStore=getEntryStore();
for(int i=0;i<len;i++){
ResValueMap item = bagItems[i];
int low = item.getName() & 0xffff;
PluralsQuantity quantity = PluralsQuantity.valueOf((short) low);
XMLElement child=new XMLElement("item");
child.setAttribute("quantity", quantity.toString());
if(item.getValueType() == ValueType.STRING){
XmlHelper.setTextContent(child, item.getDataAsPoolString());
}else {
String value = ValueDecoder.decodeIntEntry(entryStore, item);
child.setTextContent(value);
}
parentElement.addChild(child);
}
}
@Override
public boolean canDecode(ResTableMapEntry mapEntry) {
return isResBagPluralsValue(mapEntry);
}
public static boolean isResBagPluralsValue(ResTableMapEntry valueItem){
int parentId=valueItem.getParentId();
if(parentId!=0){
return false;
}
ResValueMap[] bagItems = valueItem.listResValueMap();
if(bagItems==null||bagItems.length==0){
return false;
}
int len=bagItems.length;
for(int i=0;i<len;i++){
ResValueMap item=bagItems[i];
int name = item.getName();
int high = (name >> 16) & 0xffff;
if(high!=0x0100){
return false;
}
int low = name & 0xffff;
PluralsQuantity pq=PluralsQuantity.valueOf((short) low);
if(pq==null){
return false;
}
}
return true;
}
}

View File

@ -32,6 +32,7 @@ import com.reandroid.json.JSONObject;
import java.io.IOException;
import java.util.*;
import java.util.function.Predicate;
public class TypeBlockArray extends BlockArray<TypeBlock>
implements JSONConvert<JSONArray>, Comparator<TypeBlock> {
@ -236,6 +237,27 @@ public class TypeBlockArray extends BlockArray<TypeBlock>
}
};
}
public Iterator<TypeBlock> iteratorNonEmpty(){
return super.iterator(NON_EMPTY_TESTER);
}
public boolean hasDuplicateResConfig(boolean ignoreEmpty){
Set<Integer> uniqueHashSet = new HashSet<>();
Iterator<TypeBlock> itr;
if(ignoreEmpty){
itr = iteratorNonEmpty();
}else {
itr = iterator(true);
}
while (itr.hasNext()){
Integer hash = itr.next()
.getResConfig().hashCode();
if(uniqueHashSet.contains(hash)){
return true;
}
uniqueHashSet.add(hash);
}
return false;
}
private SpecBlock getSpecBlock(){
SpecTypePair parent = getParent(SpecTypePair.class);
if(parent != null){
@ -383,4 +405,14 @@ public class TypeBlockArray extends BlockArray<TypeBlock>
public int compare(TypeBlock typeBlock1, TypeBlock typeBlock2) {
return typeBlock1.compareTo(typeBlock2);
}
private static final Predicate<TypeBlock> NON_EMPTY_TESTER = new Predicate<TypeBlock>() {
@Override
public boolean test(TypeBlock typeBlock) {
if(typeBlock == null || typeBlock.isNull()){
return false;
}
return !typeBlock.isEmpty();
}
};
}

View File

@ -1,21 +1,22 @@
/*
* Copyright (C) 2022 github.com/REAndroid
*
* 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.
*/
/*
* Copyright (C) 2022 github.com/REAndroid
*
* 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 com.reandroid.arsc.base;
import java.util.*;
import java.util.function.Predicate;
public abstract class BlockArray<T extends Block> extends BlockContainer<T> implements BlockArrayCreator<T> {
@ -273,6 +274,9 @@ public abstract class BlockArray<T extends Block> extends BlockContainer<T> impl
public Iterator<T> iterator(boolean skipNullBlock) {
return new BlockIterator(skipNullBlock);
}
public Iterator<T> iterator(Predicate<T> tester) {
return new PredicateIterator(tester);
}
public boolean contains(Object block){
T[] items=elementData;
if(block==null || items==null){
@ -437,14 +441,62 @@ public abstract class BlockArray<T extends Block> extends BlockContainer<T> impl
if(!mSkipNullBlock || isFinished()){
return;
}
T item=BlockArray.this.get(mCursor);
while (item==null||item.isNull()){
T item = BlockArray.this.get(mCursor);
while (item == null || item.isNull()){
mCursor++;
item=BlockArray.this.get(mCursor);
item = BlockArray.this.get(mCursor);
if(mCursor>=mMaxSize){
break;
}
}
}
}
private class PredicateIterator implements Iterator<T> {
private int mCursor;
private final int mMaxSize;
private final Predicate<T> mTester;
PredicateIterator(Predicate<T> tester){
this.mTester = tester;
mCursor = 0;
mMaxSize = BlockArray.this.childesCount();
}
@Override
public boolean hasNext() {
checkCursor();
return hasItems();
}
@Override
public T next() {
if(hasItems()){
T item=BlockArray.this.get(mCursor);
mCursor++;
checkCursor();
return item;
}
return null;
}
private boolean hasItems(){
return mCursor < mMaxSize;
}
private void checkCursor(){
if(mTester == null){
return;
}
while (hasItems() && !test(BlockArray.this.get(getCursor()))){
mCursor++;
}
}
private int getCursor(){
return mCursor;
}
private boolean test(T item){
Predicate<T> tester = mTester;
if(tester != null){
return tester.test(item);
}
return true;
}
}
}

View File

@ -74,11 +74,11 @@ public class SpecTypePair extends BlockContainer<Block>
return createEntryGroups(false);
}
public Map<Integer, EntryGroup> createEntryGroups(boolean skipNullEntries){
Map<Integer, EntryGroup> map = new HashMap<>();
for(TypeBlock typeBlock:listTypeBlocks()){
Map<Integer, EntryGroup> map = new LinkedHashMap<>();
for(TypeBlock typeBlock : listTypeBlocks()){
EntryArray entryArray = typeBlock.getEntryArray();
for(Entry entry:entryArray.listItems(skipNullEntries)){
if(entry==null){
for(Entry entry : entryArray.listItems(skipNullEntries)){
if(entry == null){
continue;
}
int id = entry.getResourceId();
@ -177,6 +177,13 @@ public class SpecTypePair extends BlockContainer<Block>
return mTypeBlockArray.listResConfig();
}
public Iterator<TypeBlock> iteratorNonEmpty(){
return mTypeBlockArray.iteratorNonEmpty();
}
public boolean hasDuplicateResConfig(boolean ignoreEmpty){
return mTypeBlockArray.hasDuplicateResConfig(ignoreEmpty);
}
public byte getTypeId(){
return mSpecBlock.getTypeId();
}

View File

@ -1,4 +1,4 @@
/*
/*
* Copyright (C) 2022 github.com/REAndroid
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -32,6 +32,23 @@ public class EntryGroup extends ItemGroup<Entry> {
super(ARRAY_CREATOR, String.valueOf(resId));
this.resourceId=resId;
}
public Entry getEntry(ResConfig resConfig){
Entry[] items = getItems();
if(items == null || resConfig == null){
return null;
}
int length = items.length;
for(int i=0; i<length; i++){
Entry entry = items[i];
if(entry == null || entry.isNull()){
continue;
}
if(resConfig.equals(entry.getResConfig())){
return entry;
}
}
return null;
}
public int getResourceId(){
return resourceId;
}

View File

@ -160,7 +160,7 @@ public class XMLElement extends XMLNode{
}
this.mEndPrefix = pfx;
}
void setIndentScale(float scale){
public void setIndentScale(float scale){
mIndentScale=scale;
}
private float getIndentScale(){