This commit is contained in:
REAndroid 2022-12-07 11:27:26 -05:00
parent 30bfe4b763
commit 6656786ecf
53 changed files with 2107 additions and 365 deletions

View File

@ -6,58 +6,26 @@
import com.reandroid.lib.arsc.io.BlockReader;
public static void example() throws IOException{
File inFile=new File("resources.arsc");
TableBlock tableBlock=new TableBlock();
tableBlock.readBytes(inFile);
//edit tableBlock as desired, for example to change the package:
PackageBlock packageBlock=tableBlock.getPackageArray().get(0);
packageBlock.setPackageName("com.new.package.name");
//refresh to recalculate offsets
tableBlock.refresh();
//convert to json object
JSONObject jsonObject=tableBlock.toJson();
System.out.println(jsonObject.toString(4));
//save the edited table
File outFile=new File("resources_out.arsc");
tableBlock.writeBytes(outFile);
}
public static void exampleManifest() throws IOException {
File inFile=new File("AndroidManifest.xml");
AndroidManifestBlock manifestBlock=new AndroidManifestBlock();
manifestBlock.readBytes(file);
List<String> permissionNames = manifestBlock.getUsesPermissions();
for(String perm:permissionNames){
System.out.println(perm);
}
//edit AndroidManifest as desired, for example to change the package:
manifestBlock.setPackageName("com.new.package.name");
// add some permission
manifestBlock.addUsesPermission("android.permission.WRITE_EXTERNAL_STORAGE");
//refresh to recalculate offsets
manifestBlock.refresh();
//save the edited xml
File outFile=new File("AndroidManifest_out.xml");
manifestBlock.writeBytes(outFile);
}
public static void convertToJson() throws IOException{
File inFile=new File("test.apk");
File outDir=new File("test_out");
ApkModule apkModule=ApkModule.loadApkFile(inFile);
apkModule.convertToJson(outDir);
ApkJsonDecoder decoder=new ApkJsonDecoder(apkModule);
outDir=decoder.writeToDirectory(outDir);
System.out.println("Decoded to: "+outDir);
// You can do any logical modification on any json files
// To convert back json to apk
ApkJsonEncoder encoder=new ApkJsonEncoder();
ApkModule encodedModule=encoder.scanDirectory(outDir);
File outApk=new File("test_out_re-encoded.apk");
encodedModule.writeApk(outApk);
System.out.println("Created apk: "+outApk);
}
```

Binary file not shown.

View File

@ -1,10 +0,0 @@
package com.reandroid.lib.apk;
import com.reandroid.archive.InputSource;
public class ApkEntry {
private InputSource mInputSource;
public ApkEntry(InputSource inputSource){
this.mInputSource=inputSource;
}
}

View File

@ -0,0 +1,171 @@
package com.reandroid.lib.apk;
import com.reandroid.archive.InputSource;
import com.reandroid.lib.arsc.chunk.TableBlock;
import com.reandroid.lib.arsc.chunk.xml.AndroidManifestBlock;
import com.reandroid.lib.arsc.chunk.xml.ResXmlBlock;
import com.reandroid.lib.json.JSONObject;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
public class ApkJsonDecoder {
private final ApkModule apkModule;
private final Set<String> decodedPaths;
private final boolean splitTypes;
public ApkJsonDecoder(ApkModule apkModule, boolean splitTypes){
this.apkModule = apkModule;
this.splitTypes = splitTypes;
this.decodedPaths = new HashSet<>();
}
public ApkJsonDecoder(ApkModule apkModule){
this(apkModule, false);
}
public File writeToDirectory(File dir) throws IOException {
this.decodedPaths.clear();
writeUncompressed(dir);
writeManifest(dir);
writeTable(dir);
writeResourceIds(dir);
writeResources(dir);
writeRootFiles(dir);
return new File(dir, apkModule.getModuleName());
}
private void writeUncompressed(File dir) throws IOException {
File file=toUncompressedJsonFile(dir);
UncompressedFiles uncompressedFiles=new UncompressedFiles();
uncompressedFiles.add(apkModule.getApkArchive());
uncompressedFiles.toJson().write(file);
}
private void writeResources(File dir) throws IOException {
for(ResFile resFile:apkModule.listResFiles()){
writeResource(dir, resFile);
}
}
private void writeResource(File dir, ResFile resFile) throws IOException {
if(resFile.isBinaryXml()){
writeResourceJson(dir, resFile);
}
}
private void writeResourceJson(File dir, ResFile resFile) throws IOException {
InputSource inputSource= resFile.getInputSource();
String path=inputSource.getAlias();
File file=toResJson(dir, path);
ResXmlBlock resXmlBlock=new ResXmlBlock();
resXmlBlock.readBytes(inputSource.openStream());
JSONObject jsonObject=resXmlBlock.toJson();
jsonObject.write(file);
addDecoded(path);
}
private void writeRootFiles(File dir) throws IOException {
for(InputSource inputSource:apkModule.getApkArchive().listInputSources()){
writeRootFile(dir, inputSource);
}
}
private void writeRootFile(File dir, InputSource inputSource) throws IOException {
String path=inputSource.getAlias();
if(hasDecoded(path)){
return;
}
File file=toRootFile(dir, path);
File parent=file.getParentFile();
if(parent!=null && !parent.exists()){
parent.mkdirs();
}
FileOutputStream outputStream=new FileOutputStream(file);
inputSource.write(outputStream);
outputStream.close();
addDecoded(path);
}
private void writeTable(File dir) throws IOException {
if(!splitTypes){
writeTableSingle(dir);
return;
}
writeTableSplit(dir);
}
private void writeTableSplit(File dir) throws IOException {
if(!apkModule.hasTableBlock()){
return;
}
TableBlock tableBlock = apkModule.getTableBlock();
File splitDir= toJsonTableSplitDir(dir);
TableBlockJson tableBlockJson=new TableBlockJson(tableBlock);
tableBlockJson.writeJsonFiles(splitDir);
addDecoded(TableBlock.FILE_NAME);
}
private void writeTableSingle(File dir) throws IOException {
if(!apkModule.hasTableBlock()){
return;
}
TableBlock tableBlock = apkModule.getTableBlock();
File file= toJsonTableFile(dir);
tableBlock.toJson().write(file);
addDecoded(TableBlock.FILE_NAME);
}
private void writeResourceIds(File dir) throws IOException {
if(!apkModule.hasTableBlock()){
return;
}
TableBlock tableBlock = apkModule.getTableBlock();
ResourceIds resourceIds=new ResourceIds();
resourceIds.loadTableBlock(tableBlock);
JSONObject jsonObject= resourceIds.toJson();
File file=toResourceIds(dir);
jsonObject.write(file);
}
private void writeManifest(File dir) throws IOException {
if(!apkModule.hasAndroidManifestBlock()){
return;
}
AndroidManifestBlock manifestBlock = apkModule.getAndroidManifestBlock();
File file = toJsonManifestFile(dir);
manifestBlock.toJson().write(file);
addDecoded(AndroidManifestBlock.FILE_NAME);
}
private boolean hasDecoded(String path){
return decodedPaths.contains(path);
}
private void addDecoded(String path){
this.decodedPaths.add(path);
}
private File toJsonTableFile(File dir){
File file=new File(dir, apkModule.getModuleName());
String name = TableBlock.FILE_NAME + ApkUtil.JSON_FILE_EXTENSION;
return new File(file, name);
}
private File toJsonTableSplitDir(File dir){
File file=new File(dir, apkModule.getModuleName());
return new File(file, ApkUtil.SPLIT_JSON_DIRECTORY);
}
private File toResourceIds(File dir){
File file=new File(dir, apkModule.getModuleName());
String name = ResourceIds.JSON_FILE_NAME;
return new File(file, name);
}
private File toUncompressedJsonFile(File dir){
File file = new File(dir, apkModule.getModuleName());
return new File(file, UncompressedFiles.JSON_FILE);
}
private File toJsonManifestFile(File dir){
File file=new File(dir, apkModule.getModuleName());
String name = AndroidManifestBlock.FILE_NAME + ApkUtil.JSON_FILE_EXTENSION;
return new File(file, name);
}
private File toResJson(File dir, String path){
File file=new File(dir, apkModule.getModuleName());
file=new File(file, ApkUtil.RES_JSON_NAME);
path=path + ApkUtil.JSON_FILE_EXTENSION;
path=path.replace('/', File.separatorChar);
return new File(file, path);
}
private File toRootFile(File dir, String path){
File file=new File(dir, apkModule.getModuleName());
file=new File(file, ApkUtil.ROOT_NAME);
path=path.replace('/', File.separatorChar);
return new File(file, path);
}
}

View File

@ -0,0 +1,110 @@
package com.reandroid.lib.apk;
import com.reandroid.archive.APKArchive;
import com.reandroid.archive.FileInputSource;
import com.reandroid.lib.arsc.chunk.TableBlock;
import com.reandroid.lib.arsc.chunk.xml.AndroidManifestBlock;
import java.io.File;
import java.io.IOException;
import java.util.List;
public class ApkJsonEncoder {
private APKArchive apkArchive;
public ApkJsonEncoder(){
}
public ApkModule scanDirectory(File moduleDir){
this.apkArchive=new APKArchive();
String moduleName=moduleDir.getName();
scanManifest(moduleDir);
scanTable(moduleDir);
scanResJsonDirs(moduleDir);
scanRootDirs(moduleDir);
ApkModule module=new ApkModule(moduleName, apkArchive);
loadUncompressed(module, moduleDir);
return module;
}
private void loadUncompressed(ApkModule module, File moduleDir){
File jsonFile=toUncompressedJsonFile(moduleDir);
UncompressedFiles uf= module.getUncompressedFiles();
try {
uf.fromJson(jsonFile);
} catch (IOException ignored) {
}
}
private void scanRootDirs(File moduleDir){
File rootDir=toRootDir(moduleDir);
List<File> jsonFileList=ApkUtil.recursiveFiles(rootDir);
for(File file:jsonFileList){
scanRootFile(rootDir, file);
}
}
private void scanRootFile(File rootDir, File file){
String path=ApkUtil.toArchivePath(rootDir, file);
FileInputSource inputSource=new FileInputSource(file, path);
apkArchive.add(inputSource);
}
private void scanResJsonDirs(File moduleDir){
File resJsonDir=toResJsonDir(moduleDir);
List<File> jsonFileList=ApkUtil.recursiveFiles(resJsonDir);
for(File file:jsonFileList){
scanResJsonFile(resJsonDir, file);
}
}
private void scanResJsonFile(File resJsonDir, File file){
JsonXmlInputSource inputSource=JsonXmlInputSource.fromFile(resJsonDir, file);
apkArchive.add(inputSource);
}
private void scanManifest(File moduleDir){
File file=toJsonManifestFile(moduleDir);
if(!file.isFile()){
return;
}
JsonManifestInputSource inputSource=JsonManifestInputSource.fromFile(moduleDir, file);
apkArchive.add(inputSource);
}
private void scanTable(File moduleDir) {
boolean splitFound=scanTableSplitJson(moduleDir);
if(splitFound){
return;
}
scanTableSingleJson(moduleDir);
}
private boolean scanTableSplitJson(File moduleDir) {
File dir=toJsonTableSplitDir(moduleDir);
if(!dir.isDirectory()){
return false;
}
SplitJsonTableInputSource inputSource=new SplitJsonTableInputSource(dir);
apkArchive.add(inputSource);
return true;
}
private void scanTableSingleJson(File moduleDir) {
File file=toJsonTableFile(moduleDir);
if(!file.isFile()){
return;
}
SingleJsonTableInputSource inputSource= SingleJsonTableInputSource.fromFile(moduleDir, file);
apkArchive.add(inputSource);
}
private File toJsonTableFile(File dir){
String name = TableBlock.FILE_NAME + ApkUtil.JSON_FILE_EXTENSION;
return new File(dir, name);
}
private File toJsonManifestFile(File dir){
String name = AndroidManifestBlock.FILE_NAME + ApkUtil.JSON_FILE_EXTENSION;
return new File(dir, name);
}
private File toUncompressedJsonFile(File dir){
return new File(dir, UncompressedFiles.JSON_FILE);
}
private File toJsonTableSplitDir(File dir){
return new File(dir, ApkUtil.SPLIT_JSON_DIRECTORY);
}
private File toResJsonDir(File dir){
return new File(dir, ApkUtil.RES_JSON_NAME);
}
private File toRootDir(File dir){
return new File(dir, ApkUtil.ROOT_NAME);
}
}

View File

@ -2,6 +2,8 @@ package com.reandroid.lib.apk;
import com.reandroid.archive.APKArchive;
import com.reandroid.archive.InputSource;
import com.reandroid.archive.ZipArchive;
import com.reandroid.archive.ZipSerializer;
import com.reandroid.lib.arsc.array.PackageArray;
import com.reandroid.lib.arsc.chunk.PackageBlock;
import com.reandroid.lib.arsc.chunk.TableBlock;
@ -12,22 +14,36 @@ import com.reandroid.lib.arsc.pool.TableStringPool;
import com.reandroid.lib.arsc.value.EntryBlock;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
public class ApkModule {
private final String moduleName;
private final APKArchive apkArchive;
private boolean loadDefaultFramework = true;
private TableBlock mTableBlock;
private AndroidManifestBlock mManifestBlock;
private ApkModule(APKArchive apkArchive){
private final UncompressedFiles mUncompressedFiles;
ApkModule(String moduleName, APKArchive apkArchive){
this.moduleName=moduleName;
this.apkArchive=apkArchive;
this.mUncompressedFiles=new UncompressedFiles();
this.mUncompressedFiles.add(apkArchive);
}
public void writeTo(File file) throws IOException {
APKArchive archive=getApkArchive();
archive.writeApk(file);
public String getModuleName(){
return moduleName;
}
public void writeApk(File file) throws IOException {
ZipArchive archive=new ZipArchive();
archive.addAll(getApkArchive().listInputSources());
UncompressedFiles uf=getUncompressedFiles();
uf.apply(archive);
ZipSerializer serializer=new ZipSerializer(archive.listInputSources(), false);
serializer.writeZip(file);
}
public UncompressedFiles getUncompressedFiles(){
return mUncompressedFiles;
}
public void removeDir(String dirName){
getApkArchive().removeDir(dirName);
@ -164,48 +180,12 @@ public class ApkModule {
public void setLoadDefaultFramework(boolean loadDefaultFramework) {
this.loadDefaultFramework = loadDefaultFramework;
}
public void convertToJson(File outDir) throws IOException {
Set<String> convertedFiles=new HashSet<>();
if(hasAndroidManifestBlock()){
AndroidManifestBlock manifestBlock=getAndroidManifestBlock();
String fileName=AndroidManifestBlock.FILE_NAME+ApkUtil.JSON_FILE_EXTENSION;
File file=new File(outDir, fileName);
manifestBlock.toJson().write(file);
convertedFiles.add(AndroidManifestBlock.FILE_NAME);
}
if(hasTableBlock()){
TableBlock tableBlock=getTableBlock();
String fileName=TableBlock.FILE_NAME+ApkUtil.JSON_FILE_EXTENSION;
File file=new File(outDir, fileName);
tableBlock.toJson().write(file);
convertedFiles.add(TableBlock.FILE_NAME);
}
List<ResFile> resFileList=listResFiles();
for(ResFile resFile:resFileList){
boolean convertOk=resFile.dumpToJson(outDir);
if(convertOk){
convertedFiles.add(resFile.getFilePath());
}
}
List<InputSource> allSources = getApkArchive().listInputSources();
for(InputSource inputSource:allSources){
String path=inputSource.getAlias();
if(convertedFiles.contains(path)){
continue;
}
path=path.replace('/', File.separatorChar);
File file=new File(outDir, path);
File dir=file.getParentFile();
if(dir!=null && !dir.exists()){
dir.mkdirs();
}
FileOutputStream outputStream=new FileOutputStream(file);
inputSource.write(outputStream);
outputStream.close();
}
}
public static ApkModule loadApkFile(File apkFile) throws IOException {
return loadApkFile(apkFile, ApkUtil.DEF_MODULE_NAME);
}
public static ApkModule loadApkFile(File apkFile, String moduleName) throws IOException {
APKArchive archive=APKArchive.loadZippedApk(apkFile);
return new ApkModule(archive);
return new ApkModule(moduleName, archive);
}
}

View File

@ -1,5 +1,9 @@
package com.reandroid.lib.apk;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class ApkUtil {
public static String replaceRootDir(String path, String dirName){
int i=path.indexOf('/')+1;
@ -12,5 +16,100 @@ public class ApkUtil {
}
return path;
}
public static final String JSON_FILE_EXTENSION=".a.json";
public static String toArchiveResourcePath(File dir, File file){
String path = toArchivePath(dir, file);
if(path.endsWith(ApkUtil.JSON_FILE_EXTENSION)){
int i2=path.length()-ApkUtil.JSON_FILE_EXTENSION.length();
path=path.substring(0, i2);
}
return path;
}
public static String toArchivePath(File dir, File file){
String dirPath = dir.getAbsolutePath()+File.separator;
String path = file.getAbsolutePath().substring(dirPath.length());
path=path.replace(File.separatorChar, '/');
return path;
}
public static List<File> recursiveFiles(File dir, String ext){
List<File> results=new ArrayList<>();
if(dir.isFile()){
results.add(dir);
return results;
}
if(!dir.isDirectory()){
return results;
}
File[] files=dir.listFiles();
if(files==null){
return results;
}
for(File file:files){
if(file.isFile()){
if(ext!=null && !file.getName().endsWith(ext)){
continue;
}
results.add(file);
continue;
}
results.addAll(recursiveFiles(file));
}
return results;
}
public static List<File> recursiveFiles(File dir){
List<File> results=new ArrayList<>();
if(dir.isFile()){
results.add(dir);
return results;
}
if(!dir.isDirectory()){
return results;
}
File[] files=dir.listFiles();
if(files==null){
return results;
}
for(File file:files){
if(file.isFile()){
results.add(file);
continue;
}
results.addAll(recursiveFiles(file));
}
return results;
}
public static List<File> listDirectories(File dir){
List<File> results=new ArrayList<>();
File[] files=dir.listFiles();
if(files==null){
return results;
}
for(File file:files){
if(file.isDirectory()){
results.add(file);
}
}
return results;
}
public static List<File> listFiles(File dir, String ext){
List<File> results=new ArrayList<>();
File[] files=dir.listFiles();
if(files==null){
return results;
}
for(File file:files){
if(file.isFile()){
if(ext!=null && !file.getName().endsWith(ext)){
continue;
}
results.add(file);
}
}
return results;
}
public static final String JSON_FILE_EXTENSION=".json";
public static final String RES_JSON_NAME="res-json";
public static final String ROOT_NAME="root";
public static final String PACKAGE_JSON_FILE="package.json";
public static final String SPLIT_JSON_DIRECTORY="resources";
public static final String DEF_MODULE_NAME="base";
}

View File

@ -1,24 +1,37 @@
package com.reandroid.lib.apk;
import com.reandroid.archive.ByteInputSource;
import com.reandroid.archive.InputSource;
import com.reandroid.lib.arsc.base.Block;
import com.reandroid.lib.arsc.chunk.BaseChunk;
import com.reandroid.lib.arsc.chunk.TableBlock;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class BlockInputSource<T extends BaseChunk> extends InputSource {
public class BlockInputSource<T extends BaseChunk> extends ByteInputSource{
private final T mBlock;
public BlockInputSource(String name, T block) {
super(name);
super(new byte[0], name);
this.mBlock=block;
}
public T getBlock() {
mBlock.refresh();
return mBlock;
}
@Override
public InputStream openStream(){
T block=getBlock();
block.refresh();
return new ByteArrayInputStream(block.getBytes());
public long getLength() throws IOException{
Block block = getBlock();
return block.countBytes();
}
@Override
public long write(OutputStream outputStream) throws IOException {
return getBlock().writeBytes(outputStream);
}
@Override
public byte[] getBytes() {
return getBlock().getBytes();
}
}

View File

@ -0,0 +1,21 @@
package com.reandroid.lib.apk;
import com.reandroid.archive.FileInputSource;
import com.reandroid.archive.InputSource;
import com.reandroid.lib.arsc.chunk.xml.AndroidManifestBlock;
import java.io.File;
public class JsonManifestInputSource extends JsonXmlInputSource {
public JsonManifestInputSource(InputSource inputSource) {
super(inputSource);
}
AndroidManifestBlock newInstance(){
return new AndroidManifestBlock();
}
public static JsonManifestInputSource fromFile(File rootDir, File jsonFile){
String path=ApkUtil.toArchiveResourcePath(rootDir, jsonFile);
FileInputSource fileInputSource=new FileInputSource(jsonFile, path);
return new JsonManifestInputSource(fileInputSource);
}
}

View File

@ -0,0 +1,47 @@
package com.reandroid.lib.apk;
import com.reandroid.archive.FileInputSource;
import com.reandroid.archive.InputSource;
import com.reandroid.lib.arsc.base.Block;
import com.reandroid.lib.arsc.chunk.xml.ResXmlBlock;
import com.reandroid.lib.json.JSONObject;
import java.io.*;
public class JsonXmlInputSource extends InputSource {
private final InputSource inputSource;
public JsonXmlInputSource(InputSource inputSource) {
super(inputSource.getAlias());
this.inputSource=inputSource;
}
@Override
public long write(OutputStream outputStream) throws IOException {
return getResXmlBlock().writeBytes(outputStream);
}
@Override
public InputStream openStream() throws IOException {
ResXmlBlock resXmlBlock= getResXmlBlock();
return new ByteArrayInputStream(resXmlBlock.getBytes());
}
@Override
public long getLength() throws IOException{
ResXmlBlock resXmlBlock = getResXmlBlock();
return resXmlBlock.countBytes();
}
private ResXmlBlock getResXmlBlock() throws IOException{
ResXmlBlock resXmlBlock=newInstance();
InputStream inputStream=inputSource.openStream();
JSONObject jsonObject=new JSONObject(inputStream);
resXmlBlock.fromJson(jsonObject);
inputStream.close();
return resXmlBlock;
}
ResXmlBlock newInstance(){
return new ResXmlBlock();
}
public static JsonXmlInputSource fromFile(File rootDir, File jsonFile){
String path=ApkUtil.toArchiveResourcePath(rootDir, jsonFile);
FileInputSource fileInputSource=new FileInputSource(jsonFile, path);
return new JsonXmlInputSource(fileInputSource);
}
}

View File

@ -0,0 +1,472 @@
package com.reandroid.lib.apk;
import com.reandroid.lib.arsc.chunk.PackageBlock;
import com.reandroid.lib.arsc.chunk.TableBlock;
import com.reandroid.lib.arsc.group.EntryGroup;
import com.reandroid.lib.json.JSONArray;
import com.reandroid.lib.json.JSONObject;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ResourceIds {
private final Table mTable;
public ResourceIds(Table table){
this.mTable=table;
}
public ResourceIds(){
this(new Table());
}
public JSONObject toJson(){
return mTable.toJson();
}
public void loadTableBlock(TableBlock tableBlock){
for(PackageBlock packageBlock:tableBlock.listPackages()){
loadPackageBlock(packageBlock);
}
}
public void loadPackageBlock(PackageBlock packageBlock){
Collection<EntryGroup> entryGroupList = packageBlock.listEntryGroup();
String name= packageBlock.getName();
for(EntryGroup entryGroup:entryGroupList){
Table.Package.Type.Entry entry= Table.Package.Type.Entry.fromEntryGroup(entryGroup);
mTable.add(entry);
if(name==null){
continue;
}
Table.Package.Type type=entry.type;
if(type!=null && type.mPackage!=null){
type.mPackage.name=name;
name=null;
}
}
}
public static class Table{
public final Map<Byte, Package> packageMap;
public Table(){
this.packageMap = new HashMap<>();
}
public void add(Package pkg){
Package exist=this.packageMap.get(pkg.id);
if(exist!=null){
exist.merge(pkg);
return;
}
this.packageMap.put(pkg.id, pkg);
}
public void add(Package.Type.Entry entry){
if(entry==null){
return;
}
byte pkgId=entry.getPackageId();
Package pkg = packageMap.get(pkgId);
if(pkg==null){
pkg=new Package(pkgId);
packageMap.put(pkgId, pkg);
}
pkg.add(entry);
}
public Package.Type.Entry getEntry(int resourceId){
byte packageId = (byte) ((resourceId>>24) & 0xff);
byte typeId = (byte) ((resourceId>>16) & 0xff);
short entryId = (short) (resourceId & 0xff);
Package pkg = getPackage(packageId);
if(pkg == null){
return null;
}
return getEntry(packageId, typeId, entryId);
}
public Package getPackage(byte packageId){
return packageMap.get(packageId);
}
public Package.Type getType(byte packageId, byte typeId){
Package pkg=getPackage(packageId);
if(pkg==null){
return null;
}
return pkg.getType(typeId);
}
public Package.Type.Entry getEntry(byte packageId, byte typeId, short entryId){
Package pkg=getPackage(packageId);
if(pkg==null){
return null;
}
return pkg.getEntry(typeId, entryId);
}
public JSONObject toJson(){
JSONObject jsonObject=new JSONObject();
JSONArray jsonArray=new JSONArray();
for(Package pkg: packageMap.values()){
jsonArray.put(pkg.toJson());
}
jsonObject.put("packages", jsonArray);
return jsonObject;
}
public static Table fromJson(JSONObject jsonObject){
Table table=new Table();
JSONArray jsonArray= jsonObject.optJSONArray("packages");
if(jsonArray!=null){
int length= jsonArray.length();
for(int i=0;i<length;i++){
table.add(Package.fromJson(jsonArray.getJSONObject(i)));
}
}
return table;
}
public static class Package {
public final byte id;
public String name;
public final Map<Byte, Type> typeMap;
public Package(byte id){
this.id = id;
this.typeMap = new HashMap<>();
}
public void merge(Package pkg){
if(pkg==this||pkg==null){
return;
}
if(pkg.id!=this.id){
throw new IllegalArgumentException("Different package id: "+this.id+"!="+pkg.id);
}
if(pkg.name!=null){
this.name = pkg.name;
}
for(Type type:pkg.typeMap.values()){
add(type);
}
}
public Type getType(byte typeId){
return typeMap.get(typeId);
}
public void add(Type type){
Byte typeId= type.id;;
Type exist=this.typeMap.get(typeId);
if(exist!=null){
exist.merge(type);
return;
}
type.mPackage=this;
this.typeMap.put(typeId, type);
}
public Package.Type.Entry getEntry(byte typeId, short entryId){
Package.Type type=getType(typeId);
if(type==null){
return null;
}
return type.getEntry(entryId);
}
public void add(Type.Entry entry){
if(entry==null){
return;
}
if(entry.getPackageId()!=this.id){
throw new IllegalArgumentException("Different package id: "+entry);
}
byte typeId=entry.getTypeId();
Type type=typeMap.get(typeId);
if(type==null){
type=new Type(typeId);
type.mPackage=this;
typeMap.put(typeId, type);
}
type.add(entry);
}
public String getHexId(){
return String.format("0x%02x", id);
}
public JSONObject toJson(){
JSONObject jsonObject=new JSONObject();
jsonObject.put("id", this.id);
if(this.name!=null){
jsonObject.put("name", this.name);
}
JSONArray jsonArray=new JSONArray();
for(Type type:typeMap.values()){
jsonArray.put(type.toJson());
}
jsonObject.put("types", jsonArray);
return jsonObject;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Package aPackage = (Package) o;
return id == aPackage.id;
}
@Override
public int hashCode() {
return Objects.hash(id);
}
@Override
public String toString(){
return getHexId() + ", types=" + typeMap.size();
}
public static Package fromJson(JSONObject jsonObject){
Package pkg=new Package((byte) jsonObject.getInt("id"));
pkg.name = jsonObject.optString("name", null);
JSONArray jsonArray = jsonObject.optJSONArray("types");
int length = jsonArray.length();
for(int i=0;i<length;i++){
Type type=Type.fromJson(jsonArray.getJSONObject(i));
pkg.add(type);
}
return pkg;
}
public static class Type{
public final byte id;
public String name;
public Package mPackage;
public final Map<Short, Entry> entryMap;
public Type(byte id){
this.id = id;
this.entryMap = new HashMap<>();
}
public byte getPackageId(){
if(mPackage!=null){
return mPackage.id;
}
return 0;
}
public void merge(Type type){
if(type==this||type==null){
return;
}
if(this.id!= type.id){
throw new IllegalArgumentException("Different type ids: "+id+"!="+type.id);
}
if(type.name!=null){
this.name=type.name;
}
for(Entry entry:type.entryMap.values()){
Short entryId=entry.getEntryId();
Entry existEntry=this.entryMap.get(entryId);
if(existEntry != null && Objects.equals(existEntry.name, entry.name)){
continue;
}
this.entryMap.remove(entryId);
entry.type=this;
this.entryMap.put(entryId, entry);
}
}
public Entry getEntry(short entryId){
return entryMap.get(entryId);
}
public String getHexId(){
return String.format("0x%02x", id);
}
public void add(Entry entry){
if(entry==null){
return;
}
if(entry.getTypeId()!=this.id){
throw new IllegalArgumentException("Different type id: "+entry);
}
short key=entry.getEntryId();
Entry exist=entryMap.get(key);
if(exist!=null){
if(Objects.equals(exist.name, entry.name)){
return;
}
throw new IllegalArgumentException("Duplicate entry exist: "+exist+", entry: "+entry);
}
if(name == null){
this.name = entry.typeName;
}
entry.type=this;
entryMap.put(key, entry);
}
public JSONObject toJson(){
JSONObject jsonObject=new JSONObject();
jsonObject.put("id", id);
jsonObject.put("name", name);
JSONArray jsonArray=new JSONArray();
for(Entry entry: entryMap.values()){
jsonArray.put(entry.toJson());
}
jsonObject.put("entries", jsonArray);
return jsonObject;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Type that = (Type) o;
return id == that.id;
}
@Override
public int hashCode() {
return Objects.hash(id);
}
@Override
public String toString(){
StringBuilder builder=new StringBuilder();
builder.append(getHexId());
if(name !=null){
builder.append(" ").append(name);
}
builder.append(", entries=").append(entryMap.size());
return builder.toString();
}
public static Type fromJson(JSONObject jsonObject){
Type type = new Type((byte) jsonObject.getInt("id"));
type.name = jsonObject.optString("name", null);
JSONArray jsonArray = jsonObject.optJSONArray("entries");
if(jsonArray!=null){
int length=jsonArray.length();
for(int i=0;i<length;i++){
Entry entry=Entry.fromJson(jsonArray.getJSONObject(i));
type.add(entry);
}
}
return type;
}
public static class Entry implements Comparable<Entry>{
public int resourceId;
public String typeName;
public String name;
public Type type;
public Entry(int resourceId, String typeName, String name){
this.resourceId = resourceId;
this.typeName = typeName;
this.name = name;
}
public Entry(int resourceId, String name){
this(resourceId, null, name);
}
public String getTypeName(){
if(this.type!=null){
return this.type.name;
}
return this.typeName;
}
public byte getPackageId(){
if(this.type!=null){
Package pkg=this.type.mPackage;
if(pkg!=null){
return pkg.id;
}
}
return (byte) ((resourceId>>24) & 0xff);
}
public byte getTypeId(){
if(this.type!=null){
return this.type.id;
}
return (byte) ((resourceId>>16) & 0xff);
}
public short getEntryId(){
return (short) (resourceId & 0xffff);
}
public int getResourceId(){
return ((getPackageId() & 0xff)<<24)
| ((getTypeId() & 0xff)<<16)
| (getEntryId() & 0xffff);
}
public String getHexId(){
return String.format("0x%08x", getResourceId());
}
@Override
public int compareTo(Entry entry) {
return Integer.compare(getResourceId(), entry.getResourceId());
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Entry that = (Entry) o;
return getResourceId() == that.getResourceId();
}
@Override
public int hashCode() {
return Objects.hash(getResourceId());
}
public JSONObject toJson(){
JSONObject jsonObject=new JSONObject();
jsonObject.put("id", getResourceId());
jsonObject.put("name", name);
return jsonObject;
}
public String toXml(){
StringBuilder builder=new StringBuilder();
builder.append("<public");
builder.append(" id=\"").append(getHexId()).append("\"");
String tn=getTypeName();
if(tn !=null){
builder.append(" type=\"").append(tn).append("\"");
}
if(name!=null){
builder.append(" name=\"").append(name).append("\"");
}
builder.append("/>");
return builder.toString();
}
@Override
public String toString(){
return toJson().toString();
}
public static Entry fromEntryGroup(EntryGroup entryGroup){
return new Entry(entryGroup.getResourceId(),
entryGroup.getTypeName(),
entryGroup.getSpecName());
}
public static Entry fromJson(JSONObject jsonObject){
return new Entry(jsonObject.getInt("id"),
jsonObject.optString("type", null),
jsonObject.getString("name"));
}
public static Entry fromXml(String xmlElement){
String element=xmlElement;
element=element.replaceAll("[\n\r\t]+", " ");
element=element.trim();
String start="<public ";
if(!element.startsWith(start) || !element.endsWith(">")){
return null;
}
element=element.substring(start.length()).trim();
Pattern pattern=PATTERN;
int id=0;
String type=null;
String name=null;
Matcher matcher=pattern.matcher(element);
while (matcher.find()){
String attr=matcher.group("Attr").toLowerCase();
String value=matcher.group("Value");
element=matcher.group("Next");
if(attr.equals("id")){
id=Integer.decode(value);
}else if(attr.equals("name")){
name=value;
}else if(attr.equals("type")){
type=value;
}
matcher= pattern.matcher(element);
}
if(id==0){
throw new IllegalArgumentException("Missing id: "+xmlElement);
}
if(name==null){
throw new IllegalArgumentException("Missing name: "+xmlElement);
}
return new Entry(id, type, name);
}
private static final Pattern PATTERN=Pattern.compile("^\\s*(?<Attr>[^\\s=\"]+)\\s*=\\s*\"(?<Value>[^\"]+)\"(?<Next>.*)$");
}
}
}
}
public static final String JSON_FILE_NAME ="resource-ids.json";
}

View File

@ -0,0 +1,53 @@
package com.reandroid.lib.apk;
import com.reandroid.archive.FileInputSource;
import com.reandroid.archive.InputSource;
import com.reandroid.lib.arsc.chunk.TableBlock;
import com.reandroid.lib.json.JSONObject;
import java.io.*;
public class SingleJsonTableInputSource extends InputSource {
private final InputSource inputSource;
private TableBlock mCache;
public SingleJsonTableInputSource(InputSource inputSource) {
super(inputSource.getAlias());
this.inputSource=inputSource;
}
@Override
public long write(OutputStream outputStream) throws IOException {
return getTableBlock().writeBytes(outputStream);
}
@Override
public InputStream openStream() throws IOException {
TableBlock tableBlock = getTableBlock();
return new ByteArrayInputStream(tableBlock.getBytes());
}
@Override
public long getLength() throws IOException{
TableBlock tableBlock = getTableBlock();
return tableBlock.countBytes();
}
private TableBlock getTableBlock() throws IOException{
if(mCache!=null){
return mCache;
}
TableBlock tableBlock=newInstance();
InputStream inputStream=inputSource.openStream();
JSONObject jsonObject=new JSONObject(inputStream);
StringPoolBuilder poolBuilder=new StringPoolBuilder();
poolBuilder.build(jsonObject);
poolBuilder.apply(tableBlock);
tableBlock.fromJson(jsonObject);
mCache=tableBlock;
return tableBlock;
}
TableBlock newInstance(){
return new TableBlock();
}
public static SingleJsonTableInputSource fromFile(File rootDir, File jsonFile){
String path=ApkUtil.toArchiveResourcePath(rootDir, jsonFile);
FileInputSource fileInputSource=new FileInputSource(jsonFile, path);
return new SingleJsonTableInputSource(fileInputSource);
}
}

View File

@ -0,0 +1,38 @@
package com.reandroid.lib.apk;
import com.reandroid.archive.InputSource;
import com.reandroid.lib.arsc.chunk.TableBlock;
import java.io.*;
public class SplitJsonTableInputSource extends InputSource {
private final File dir;
private TableBlock mCache;
public SplitJsonTableInputSource(File dir) {
super(TableBlock.FILE_NAME);
this.dir=dir;
}
@Override
public long write(OutputStream outputStream) throws IOException {
return getTableBlock().writeBytes(outputStream);
}
@Override
public InputStream openStream() throws IOException {
TableBlock tableBlock = getTableBlock();
return new ByteArrayInputStream(tableBlock.getBytes());
}
@Override
public long getLength() throws IOException{
TableBlock tableBlock = getTableBlock();
return tableBlock.countBytes();
}
private TableBlock getTableBlock() throws IOException {
if(mCache!=null){
return mCache;
}
TableBlockJsonBuilder builder=new TableBlockJsonBuilder();
TableBlock tableBlock=builder.scanDirectory(dir);
mCache=tableBlock;
return tableBlock;
}
}

View File

@ -0,0 +1,135 @@
package com.reandroid.lib.apk;
import com.reandroid.lib.arsc.chunk.PackageBlock;
import com.reandroid.lib.arsc.chunk.TableBlock;
import com.reandroid.lib.arsc.pool.SpecStringPool;
import com.reandroid.lib.arsc.pool.TableStringPool;
import com.reandroid.lib.json.JSONArray;
import com.reandroid.lib.json.JSONObject;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.*;
public class StringPoolBuilder {
private final Map<Byte, Set<String>> mSpecNameMap;
private final Set<String> mTableStrings;
private byte mCurrentPackageId;
private JSONArray mStyledStrings;
public StringPoolBuilder(){
this.mSpecNameMap = new HashMap<>();
this.mTableStrings = new HashSet<>();
}
public void apply(TableBlock tableBlock){
applyTableString(tableBlock.getTableStringPool());
for(byte pkgId:mSpecNameMap.keySet()){
PackageBlock packageBlock=tableBlock.getPackageArray().getOrCreate(pkgId);
applySpecString(packageBlock.getSpecStringPool());
}
}
private void applyTableString(TableStringPool stringPool){
stringPool.fromJson(mStyledStrings);
stringPool.addStrings(getTableString());
stringPool.refresh();
}
private void applySpecString(SpecStringPool stringPool){
byte pkgId= (byte) stringPool.getPackageBlock().getPackageId();
stringPool.addStrings(getSpecString(pkgId));
stringPool.refresh();
}
public void scanDirectory(File resourcesDir) throws IOException {
mCurrentPackageId=0;
List<File> pkgDirList=ApkUtil.listDirectories(resourcesDir);
for(File dir:pkgDirList){
File pkgFile=new File(dir, ApkUtil.PACKAGE_JSON_FILE);
scanFile(pkgFile);
List<File> jsonFileList=ApkUtil.recursiveFiles(dir, ".json");
for(File file:jsonFileList){
if(file.equals(pkgFile)){
continue;
}
scanFile(file);
}
}
}
public void scanFile(File jsonFile) throws IOException {
FileInputStream inputStream=new FileInputStream(jsonFile);
JSONObject jsonObject=new JSONObject(inputStream);
build(jsonObject);
}
public void build(JSONObject jsonObject){
scan(jsonObject);
}
public Set<String> getTableString(){
return mTableStrings;
}
public Set<String> getSpecString(byte pkgId){
return mSpecNameMap.get(pkgId);
}
private void scan(JSONObject jsonObject){
if(jsonObject.has("is_array")){
addSpecName(jsonObject.optString("name"));
}
if(jsonObject.has("value_type")){
if("STRING".equals(jsonObject.getString("value_type"))){
String data= jsonObject.getString("data");
addTableString(data);
}
return;
}else if(jsonObject.has(PackageBlock.NAME_package_id)){
mCurrentPackageId= (byte) jsonObject.getInt(PackageBlock.NAME_package_id);
}
Set<String> keyList = jsonObject.keySet();
for(String key:keyList){
Object obj=jsonObject.get(key);
if(obj instanceof JSONObject){
scan((JSONObject) obj);
continue;
}
if(obj instanceof JSONArray){
JSONArray jsonArray = (JSONArray) obj;
if(TableBlock.NAME_styled_strings.equals(key)){
this.mStyledStrings = jsonArray;
}else {
scan(jsonArray);
}
}
}
}
private void scan(JSONArray jsonArray){
if(jsonArray==null){
return;
}
for(Object obj:jsonArray.getArrayList()){
if(obj instanceof JSONObject){
scan((JSONObject) obj);
continue;
}
if(obj instanceof JSONArray){
scan((JSONArray) obj);
}
}
}
private void addTableString(String name){
if(name==null){
return;
}
mTableStrings.add(name);
}
private void addSpecName(String name){
if(name==null){
return;
}
byte pkgId=mCurrentPackageId;
if(pkgId==0){
throw new IllegalArgumentException("Current package id is 0");
}
Set<String> specNames=mSpecNameMap.get(pkgId);
if(specNames==null){
specNames=new HashSet<>();
mSpecNameMap.put(pkgId, specNames);
}
specNames.add(name);
}
}

View File

@ -0,0 +1,62 @@
package com.reandroid.lib.apk;
import com.reandroid.lib.arsc.chunk.PackageBlock;
import com.reandroid.lib.arsc.chunk.TableBlock;
import com.reandroid.lib.arsc.chunk.TypeBlock;
import com.reandroid.lib.arsc.container.SpecTypePair;
import com.reandroid.lib.json.JSONObject;
import java.io.File;
import java.io.IOException;
public class TableBlockJson {
private final TableBlock tableBlock;
public TableBlockJson(TableBlock tableBlock){
this.tableBlock=tableBlock;
}
public void writeJsonFiles(File outDir) throws IOException {
for(PackageBlock packageBlock: tableBlock.listPackages()){
writeJsonFiles(outDir, packageBlock);
}
}
private void writeJsonFiles(File rootDir, PackageBlock packageBlock) throws IOException {
File pkgDir=new File(rootDir, getDirName(packageBlock));
if(!pkgDir.exists()){
pkgDir.mkdirs();
}
File infoFile=new File(pkgDir, PackageBlock.JSON_FILE_NAME);
JSONObject jsonObject=new JSONObject();
jsonObject.put(PackageBlock.NAME_package_id, packageBlock.getPackageId());
jsonObject.put(PackageBlock.NAME_package_name, packageBlock.getPackageName());
jsonObject.write(infoFile);
for(SpecTypePair specTypePair: packageBlock.listAllSpecTypePair()){
for(TypeBlock typeBlock:specTypePair.getTypeBlockArray().listItems()){
writeJsonFiles(pkgDir, typeBlock);
}
}
}
private void writeJsonFiles(File pkgDir, TypeBlock typeBlock) throws IOException {
String name= getFileName(typeBlock)+".json";
File file=new File(pkgDir, name);
JSONObject jsonObject = typeBlock.toJson();
jsonObject.write(file);
}
private String getFileName(TypeBlock typeBlock){
StringBuilder builder=new StringBuilder();
builder.append(String.format("0x%02x", typeBlock.getTypeId()));
String name= typeBlock.getTypeName();
builder.append('-').append(name);
builder.append(typeBlock.getResConfig().getQualifiers());
return builder.toString();
}
private String getDirName(PackageBlock packageBlock){
StringBuilder builder=new StringBuilder();
builder.append(String.format("0x%02x", packageBlock.getPackageId()));
String name= packageBlock.getPackageName();
if(name!=null){
builder.append('-');
builder.append(name);
}
return builder.toString();
}
}

View File

@ -0,0 +1,63 @@
package com.reandroid.lib.apk;
import com.reandroid.lib.arsc.chunk.PackageBlock;
import com.reandroid.lib.arsc.chunk.TableBlock;
import com.reandroid.lib.arsc.chunk.TypeBlock;
import com.reandroid.lib.arsc.value.ResConfig;
import com.reandroid.lib.json.JSONObject;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.List;
public class TableBlockJsonBuilder {
private final StringPoolBuilder poolBuilder;
public TableBlockJsonBuilder(){
poolBuilder=new StringPoolBuilder();
}
public TableBlock scanDirectory(File resourcesDir) throws IOException {
if(!resourcesDir.isDirectory()){
throw new IOException("No such directory: "+resourcesDir);
}
List<File> pkgDirList=ApkUtil.listDirectories(resourcesDir);
if(pkgDirList.size()==0){
throw new IOException("No package sub directory found in : "+resourcesDir);
}
TableBlock tableBlock=new TableBlock();
poolBuilder.scanDirectory(resourcesDir);
poolBuilder.apply(tableBlock);
for(File pkgDir:pkgDirList){
scanPackageDirectory(tableBlock, pkgDir);
}
tableBlock.refresh();
return tableBlock;
}
private void scanPackageDirectory(TableBlock tableBlock, File pkgDir) throws IOException{
File pkgFile=new File(pkgDir, "package.json");
if(!pkgFile.isFile()){
throw new IOException("Invalid package directory! Package file missing: "+pkgFile);
}
FileInputStream inputStream=new FileInputStream(pkgFile);
JSONObject jsonObject=new JSONObject(inputStream);
int id = jsonObject.getInt(PackageBlock.NAME_package_id);
String name=jsonObject.optString(PackageBlock.NAME_package_name);
PackageBlock pkg=tableBlock.getPackageArray().getOrCreate((byte) id);
pkg.setName(name);
List<File> typeFileList=ApkUtil.listFiles(pkgDir, ".json");
typeFileList.remove(pkgFile);
for(File typeFile:typeFileList){
loadType(pkg, typeFile);
}
}
private void loadType(PackageBlock packageBlock, File typeJsonFile) throws IOException{
FileInputStream inputStream=new FileInputStream(typeJsonFile);
JSONObject jsonObject=new JSONObject(inputStream);
int id= jsonObject.getInt("id");
JSONObject configObj=jsonObject.getJSONObject("config");
ResConfig resConfig=new ResConfig();
resConfig.fromJson(configObj);
TypeBlock typeBlock=packageBlock.getSpecTypePairArray().getOrCreate((byte) id, resConfig);
typeBlock.fromJson(jsonObject);
}
}

View File

@ -0,0 +1,81 @@
package com.reandroid.lib.apk;
import com.reandroid.archive.InputSource;
import com.reandroid.archive.ZipArchive;
import com.reandroid.lib.json.JSONArray;
import com.reandroid.lib.json.JSONConvert;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import java.util.zip.ZipEntry;
public class UncompressedFiles implements JSONConvert<JSONArray> {
private final Set<String> mPathList;
public UncompressedFiles(){
this.mPathList=new HashSet<>();
}
public void apply(ZipArchive archive){
for(InputSource inputSource:archive.listInputSources()){
apply(inputSource);
}
}
public void apply(InputSource inputSource){
if(contains(inputSource.getName()) || contains(inputSource.getAlias())){
inputSource.setMethod(ZipEntry.STORED);
}else {
inputSource.setMethod(ZipEntry.DEFLATED);
}
}
public boolean contains(String path){
return mPathList.contains(path);
}
public void add(ZipArchive zipArchive){
for(InputSource inputSource: zipArchive.listInputSources()){
add(inputSource);
}
}
public void add(InputSource inputSource){
if(inputSource.getMethod()!=ZipEntry.STORED){
return;
}
add(inputSource.getAlias());
}
public void add(String path){
if(path==null || path.length()==0){
return;
}
path=path.replace(File.separatorChar, '/').trim();
while (path.startsWith("/")){
path=path.substring(1);
}
mPathList.add(path);
}
public void clear(){
mPathList.clear();
}
@Override
public JSONArray toJson() {
return new JSONArray(mPathList);
}
@Override
public void fromJson(JSONArray json) {
if(json==null){
return;
}
int length = json.length();
for(int i=0;i<length;i++){
this.add(json.getString(i));
}
}
public void fromJson(File jsonFile) throws IOException {
if(!jsonFile.isFile()){
return;
}
JSONArray jsonArray=new JSONArray(new FileInputStream(jsonFile));
fromJson(jsonArray);
}
public static final String JSON_FILE="uncompressed-files.json";
}

View File

@ -76,7 +76,7 @@ public class EntryBlockArray extends OffsetBlockArray<EntryBlock> implements JSO
}
@Override
public String toString(){
return toJson().toString(4);
return getClass().getSimpleName()+": size="+childesCount();
}
private static final String NAME_id="id";
}

View File

@ -4,6 +4,7 @@ import com.reandroid.lib.arsc.base.BlockArray;
import com.reandroid.lib.arsc.chunk.TypeBlock;
import com.reandroid.lib.arsc.container.SpecTypePair;
import com.reandroid.lib.arsc.value.EntryBlock;
import com.reandroid.lib.arsc.value.ResConfig;
import com.reandroid.lib.json.JSONConvert;
import com.reandroid.lib.json.JSONArray;
import com.reandroid.lib.json.JSONObject;
@ -61,6 +62,10 @@ public class SpecTypePairArray extends BlockArray<SpecTypePair> implements JSONC
}
return pair.getTypeBlock(qualifiers);
}
public TypeBlock getOrCreate(byte typeId, ResConfig resConfig){
SpecTypePair pair=getOrCreate(typeId);
return pair.getTypeBlockArray().getOrCreate(resConfig);
}
public SpecTypePair getOrCreate(byte typeId){
SpecTypePair pair=getPair(typeId);
if(pair!=null){
@ -143,6 +148,52 @@ public class SpecTypePairArray extends BlockArray<SpecTypePair> implements JSONC
}
return results;
}
public byte getSmallestTypeId(){
SpecTypePair[] childes=getChildes();
if(childes==null){
return 0;
}
int result=0;
boolean firstFound=false;
for (int i=0;i<childes.length;i++){
SpecTypePair pair=childes[i];
if(pair==null){
continue;
}
int id=pair.getTypeId();
if(!firstFound){
result=id;
}
firstFound=true;
if(id<result){
result=id;
}
}
return (byte) result;
}
public byte getHighestTypeId(){
SpecTypePair[] childes=getChildes();
if(childes==null){
return 0;
}
int result=0;
boolean firstFound=false;
for (int i=0;i<childes.length;i++){
SpecTypePair pair=childes[i];
if(pair==null){
continue;
}
int id=pair.getTypeId();
if(!firstFound){
result=id;
}
firstFound=true;
if(id<result){
result=id;
}
}
return (byte) result;
}
@Override
public JSONArray toJson() {
JSONArray jsonArray=new JSONArray();

View File

@ -7,6 +7,7 @@ import com.reandroid.lib.json.JSONConvert;
import com.reandroid.lib.json.JSONArray;
import com.reandroid.lib.json.JSONObject;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.List;
@ -18,6 +19,22 @@ public abstract class StringArray<T extends StringItem> extends OffsetBlockArray
this.mUtf8=is_utf8;
setEndBytes((byte)0x00);
}
public List<String> toStringList(){
return new AbstractList<String>() {
@Override
public String get(int i) {
T item=StringArray.this.get(i);
if(item==null){
return null;
}
return item.getHtml();
}
@Override
public int size() {
return childesCount();
}
};
}
public List<T> removeUnusedStrings(){
List<T> allUnused=listUnusedStrings();
remove(allUnused);
@ -53,6 +70,7 @@ public abstract class StringArray<T extends StringItem> extends OffsetBlockArray
protected void refreshChildes(){
// Not required
}
// Only styled strings
@Override
public JSONArray toJson() {
return toJson(true);
@ -74,6 +92,9 @@ public abstract class StringArray<T extends StringItem> extends OffsetBlockArray
if(jsonObject==null){
continue;
}
if(i>750){
i=i+0;
}
jsonArray.put(i, jsonObject);
i++;
}
@ -82,19 +103,9 @@ public abstract class StringArray<T extends StringItem> extends OffsetBlockArray
}
return jsonArray;
}
// Only styled strings
@Override
public void fromJson(JSONArray json) {
clearChildes();
if(json==null){
return;
}
int length = json.length();
ensureSize(length);
for(int i=0; i<length;i++){
T item=get(i);
JSONObject jsonObject = json.getJSONObject(i);
item.fromJson(jsonObject);
throw new IllegalArgumentException(getClass().getSimpleName()+".fromJson() NOT implemented");
}
}
}

View File

@ -56,6 +56,18 @@ public class TypeBlockArray extends BlockArray<TypeBlock> implements JSONConvert
}
return typeBlock.getEntry(entryId);
}
public TypeBlock getOrCreate(ResConfig resConfig){
TypeBlock typeBlock=getTypeBlock(resConfig);
if(typeBlock!=null){
return typeBlock;
}
byte id=getTypeId();
typeBlock=createNext();
typeBlock.setTypeId(id);
ResConfig config=typeBlock.getResConfig();
config.copyFrom(resConfig);
return typeBlock;
}
public TypeBlock getOrCreate(String qualifiers){
TypeBlock typeBlock=getTypeBlock(qualifiers);
if(typeBlock!=null){

View File

@ -183,8 +183,23 @@ public abstract class BlockArray<T extends Block> extends BlockContainer<T> impl
return false;
}
public void remove(Collection<T> blockList){
T[] items=elementData;
if(items==null || items.length==0){
return;
}
int len=items.length;
for(T block:blockList){
remove(block, false);
if(block==null){
continue;
}
int i=block.getIndex();
if(i<0 || i>=len){
continue;
}
if(items[i]!=block){
continue;
}
items[i]=null;
}
trimNullBlocks();
}

View File

@ -62,6 +62,13 @@ abstract class BaseTypeBlock extends BaseChunk {
}
return null;
}
public String getTypeName(){
TypeString typeString=getTypeString();
if(typeString==null){
return null;
}
return typeString.get();
}
public TypeString getTypeString(){
if(mTypeString!=null){
if(mTypeString.getId()==getTypeId()){

View File

@ -4,8 +4,11 @@ import com.reandroid.lib.arsc.array.LibraryInfoArray;
import com.reandroid.lib.arsc.array.SpecTypePairArray;
import com.reandroid.lib.arsc.base.Block;
import com.reandroid.lib.arsc.container.PackageLastBlocks;
import com.reandroid.lib.arsc.container.SingleBlockContainer;
import com.reandroid.lib.arsc.container.SpecTypePair;
import com.reandroid.lib.arsc.group.EntryGroup;
import com.reandroid.lib.arsc.io.BlockLoad;
import com.reandroid.lib.arsc.io.BlockReader;
import com.reandroid.lib.arsc.item.IntegerItem;
import com.reandroid.lib.arsc.item.PackageName;
import com.reandroid.lib.arsc.item.ReferenceItem;
@ -17,17 +20,19 @@ import com.reandroid.lib.arsc.value.LibraryInfo;
import com.reandroid.lib.json.JSONConvert;
import com.reandroid.lib.json.JSONObject;
import java.io.IOException;
import java.util.*;
public class PackageBlock extends BaseChunk implements JSONConvert<JSONObject> {
public class PackageBlock extends BaseChunk implements BlockLoad, JSONConvert<JSONObject> {
private final IntegerItem mPackageId;
private final PackageName mPackageName;
private final IntegerItem mTypeStrings;
private final IntegerItem mLastPublicType;
private final IntegerItem mKeyStrings;
private final IntegerItem mLastPublicKey;
private final IntegerItem mTypeStringPoolOffset;
private final IntegerItem mTypeStringPoolCount;
private final IntegerItem mSpecStringPoolOffset;
private final IntegerItem mSpecStringPoolCount;
private final SingleBlockContainer<IntegerItem> mTypeIdOffsetContainer;
private final IntegerItem mTypeIdOffset;
private final TypeStringPool mTypeStringPool;
@ -44,13 +49,18 @@ public class PackageBlock extends BaseChunk implements JSONConvert<JSONObject>
super(ChunkType.PACKAGE, 3);
this.mPackageId=new IntegerItem();
this.mPackageName=new PackageName();
this.mTypeStrings=new IntegerItem();
this.mLastPublicType=new IntegerItem();
this.mKeyStrings=new IntegerItem();
this.mLastPublicKey=new IntegerItem();
this.mTypeIdOffset=new IntegerItem();
this.mTypeStringPool=new TypeStringPool(false);
this.mTypeStringPoolOffset = new IntegerItem();
this.mTypeStringPoolCount = new IntegerItem();
this.mSpecStringPoolOffset = new IntegerItem();
this.mSpecStringPoolCount = new IntegerItem();
this.mTypeIdOffsetContainer = new SingleBlockContainer<>();
this.mTypeIdOffset = new IntegerItem();
this.mTypeIdOffsetContainer.setItem(mTypeIdOffset);
this.mTypeStringPool=new TypeStringPool(false, mTypeIdOffset);
this.mSpecStringPool=new SpecStringPool(true);
this.mSpecTypePairArray=new SpecTypePairArray();
@ -59,13 +69,15 @@ public class PackageBlock extends BaseChunk implements JSONConvert<JSONObject>
this.mEntriesGroup=new HashMap<>();
mPackageId.setBlockLoad(this);
addToHeader(mPackageId);
addToHeader(mPackageName);
addToHeader(mTypeStrings);
addToHeader(mLastPublicType);
addToHeader(mKeyStrings);
addToHeader(mLastPublicKey);
addToHeader(mTypeIdOffset);
addToHeader(mTypeStringPoolOffset);
addToHeader(mTypeStringPoolCount);
addToHeader(mSpecStringPoolOffset);
addToHeader(mSpecStringPoolCount);
addToHeader(mTypeIdOffsetContainer);
addChild(mTypeStringPool);
addChild(mSpecStringPool);
@ -73,6 +85,16 @@ public class PackageBlock extends BaseChunk implements JSONConvert<JSONObject>
addChild(mPackageLastBlocks);
}
@Override
public void onBlockLoaded(BlockReader reader, Block sender) throws IOException {
if(sender==mPackageId){
int headerSize=getHeaderBlock().getHeaderSize();
if(headerSize!=288){
mTypeIdOffset.set(0);
mTypeIdOffsetContainer.setItem(null);
}
}
}
public void removeEmpty(){
getSpecTypePairArray().removeEmptyPairs();
}
@ -115,33 +137,7 @@ public class PackageBlock extends BaseChunk implements JSONConvert<JSONObject>
public void setPackageName(String name){
mPackageName.set(name);
}
public void setTypeStrings(int i){
mTypeStrings.set(i);
}
public int getLastPublicType(){
return mLastPublicType.get();
}
public void setLastPublicType(int i){
mLastPublicType.set(i);
}
public int getKeyStrings(){
return mKeyStrings.get();
}
public void setKeyStrings(int i){
mKeyStrings.set(i);
}
public int getLastPublicKey(){
return mLastPublicKey.get();
}
public void setLastPublicKey(int i){
mLastPublicKey.set(i);
}
public int getTypeIdOffset(){
return mTypeIdOffset.get();
}
public void setTypeIdOffset(int i){
mTypeIdOffset.set(i);
}
public TypeStringPool getTypeStringPool(){
return mTypeStringPool;
}
@ -250,9 +246,26 @@ public class PackageBlock extends BaseChunk implements JSONConvert<JSONObject>
return getSpecTypePairArray().listItems();
}
private void refreshKeyStrings(){
private void refreshTypeStringPoolOffset(){
int pos=countUpTo(mTypeStringPool);
mTypeStringPoolOffset.set(pos);
}
private void refreshTypeStringPoolCount(){
mTypeStringPoolCount.set(mTypeStringPool.countStrings());
}
private void refreshSpecStringPoolOffset(){
int pos=countUpTo(mSpecStringPool);
mKeyStrings.set(pos);
mSpecStringPoolOffset.set(pos);
}
private void refreshSpecStringCount(){
mSpecStringPoolCount.set(mSpecStringPool.countStrings());
}
private void refreshTypeIdOffset(){
int smallestId=getSpecTypePairArray().getSmallestTypeId();
if(smallestId>0){
smallestId=smallestId-1;
}
mTypeIdOffset.set(smallestId);
}
public void onEntryAdded(EntryBlock entryBlock){
updateEntry(entryBlock);
@ -263,14 +276,18 @@ public class PackageBlock extends BaseChunk implements JSONConvert<JSONObject>
@Override
protected void onChunkRefreshed() {
refreshKeyStrings();
refreshTypeStringPoolOffset();
refreshTypeStringPoolCount();
refreshSpecStringPoolOffset();
refreshSpecStringCount();
refreshTypeIdOffset();
}
@Override
public JSONObject toJson() {
JSONObject jsonObject=new JSONObject();
jsonObject.put(NAME_id, getId());
jsonObject.put(NAME_name, getName());
jsonObject.put(NAME_package_id, getId());
jsonObject.put(NAME_package_name, getName());
jsonObject.put(NAME_specs, getSpecTypePairArray().toJson());
LibraryInfoArray libraryInfoArray = mLibraryBlock.getLibraryInfoArray();
if(libraryInfoArray.childesCount()>0){
@ -280,8 +297,8 @@ public class PackageBlock extends BaseChunk implements JSONConvert<JSONObject>
}
@Override
public void fromJson(JSONObject json) {
setId(json.getInt(NAME_id));
setName(json.getString(NAME_name));
setId(json.getInt(NAME_package_id));
setName(json.getString(NAME_package_name));
getSpecTypePairArray().fromJson(json.getJSONArray(NAME_specs));
LibraryInfoArray libraryInfoArray = mLibraryBlock.getLibraryInfoArray();
libraryInfoArray.fromJson(json.optJSONArray(NAME_libraries));
@ -302,8 +319,9 @@ public class PackageBlock extends BaseChunk implements JSONConvert<JSONObject>
return builder.toString();
}
private static final String NAME_id="id";
private static final String NAME_name="name";
public static final String NAME_package_id = "package_id";
public static final String NAME_package_name = "package_name";
public static final String JSON_FILE_NAME = "package.json";
private static final String NAME_specs="specs";
private static final String NAME_libraries="libraries";
}

View File

@ -130,10 +130,6 @@ public class TableBlock extends BaseChunk implements JSONConvert<JSONObject> {
}
@Override
public void fromJson(JSONObject json) {
JSONArray jsonArray= json.optJSONArray(NAME_styled_strings);
if(jsonArray!=null){
getTableStringPool().fromJson(jsonArray);
}
getPackageArray().fromJson(json.getJSONArray(NAME_packages));
refresh();
}
@ -190,5 +186,5 @@ public class TableBlock extends BaseChunk implements JSONConvert<JSONObject> {
public static final String FILE_NAME="resources.arsc";
private static final String NAME_packages="packages";
private static final String NAME_styled_strings="styled_strings";
public static final String NAME_styled_strings="styled_strings";
}

View File

@ -40,7 +40,7 @@ public class BaseXmlChunk extends BaseChunk {
return mLineNumber.get();
}
public void setCommentReference(int val){
mLineNumber.set(val);
mCommentReference.set(val);
}
public int getCommentReference(){
return mCommentReference.get();
@ -119,6 +119,21 @@ public class BaseXmlChunk extends BaseChunk {
public String getUri(){
return getString(getNamespaceReference());
}
public String getComment(){
return getString(getCommentReference());
}
public void setComment(String comment){
if(comment==null||comment.length()==0){
setCommentReference(-1);
}else {
String old=getComment();
if(comment.equals(old)){
return;
}
ResXmlString xmlString = getOrCreateResXmlString(comment);
setCommentReference(xmlString.getIndex());
}
}
public ResXmlElement getParentResXmlElement(){
Block parent=getParent();
@ -137,6 +152,14 @@ public class BaseXmlChunk extends BaseChunk {
@Override
protected void onChunkRefreshed() {
}
@Override
public void onChunkLoaded(){
super.onChunkLoaded();
if(mCommentReference.get()!=-1){
String junk=getString(mCommentReference.get());
System.out.println(junk);
}
}
@Override
public String toString(){

View File

@ -160,7 +160,7 @@ public class ResXmlBlock extends BaseChunk implements JSONConvert<JSONObject> {
buildResourceIds(attributeList);
Set<String> allStrings=recursiveStrings(json.optJSONObject(ResXmlBlock.NAME_element));
ResXmlStringPool stringPool = getStringPool();
stringPool.addAllStrings(allStrings);
stringPool.addStrings(allStrings);
stringPool.refresh();
}
private void buildResourceIds(List<JSONObject> attributeList){

View File

@ -316,6 +316,20 @@ public class ResXmlElement extends FixedBlockContainer implements JSONConvert<JS
public void setResXmlText(ResXmlText xmlText){
mResXmlTextContainer.setItem(xmlText);
}
public void setResXmlText(String text){
if(text==null){
mResXmlTextContainer.setItem(null);
}else {
ResXmlText xmlText=mResXmlTextContainer.getItem();
if(xmlText==null){
xmlText=new ResXmlText();
mResXmlTextContainer.setItem(xmlText);
ResXmlStartElement start = getStartElement();
xmlText.setLineNumber(start.getLineNumber());
}
xmlText.setText(text);
}
}
private boolean isBalanced(){
return isElementBalanced() && isNamespaceBalanced();
@ -523,6 +537,17 @@ public class ResXmlElement extends FixedBlockContainer implements JSONConvert<JS
jsonObject.put(NAME_namespaces, nsList);
}
jsonObject.put(NAME_name, start.getName());
String comment=start.getComment();
if(comment!=null){
jsonObject.put(NAME_comment, comment);
}
ResXmlText xmlText=getResXmlText();
if(xmlText!=null){
String text=xmlText.getText();
if(text!=null){
jsonObject.put(NAME_text, text);
}
}
String uri=start.getUri();
if(uri!=null){
jsonObject.put(NAME_namespace_uri, uri);
@ -560,6 +585,11 @@ public class ResXmlElement extends FixedBlockContainer implements JSONConvert<JS
}
}
setTag(json.getString(NAME_name));
start.setComment(json.optString(NAME_comment, null));
String text= json.optString(NAME_text, null);
if(text!=null){
setResXmlText(text);
}
String uri = json.optString(NAME_namespace_uri, null);
if(uri!=null){
ResXmlStartNamespace ns = getStartNamespaceByUri(uri);
@ -618,6 +648,8 @@ public class ResXmlElement extends FixedBlockContainer implements JSONConvert<JS
public static final String NS_ANDROID_PREFIX = "android";
static final String NAME_name = "name";
static final String NAME_comment = "comment";
static final String NAME_text = "text";
static final String NAME_namespaces = "namespaces";
static final String NAME_namespace_uri = "namespace_uri";
static final String NAME_namespace_prefix = "namespace_prefix";

View File

@ -11,6 +11,7 @@ public class ResXmlText extends BaseXmlChunk {
super(ChunkType.XML_CDATA, 1);
this.mReserved=new IntegerItem();
addChild(mReserved);
setStringReference(0);
}
public String getText(){
ResXmlString xmlString=getResXmlString(getTextReference());
@ -38,7 +39,7 @@ public class ResXmlText extends BaseXmlChunk {
public String toString(){
String txt=getText();
if(txt!=null){
return "TEXT: line="+getLineNumber()+" >"+txt+"<";
return txt;
}
return super.toString();
}

View File

@ -89,13 +89,6 @@ public class IntegerArray extends BlockItem {
public final int size(){
return getBytesLength()/4;
}
final void add(int value){
int len=getBytesLength();
len=len + 4;
setBytesLength(len, false);
int pos=size()-1;
put(pos, value);
}
public final void put(int index, int value){
int i=index*4;
byte[] bts = getBytesInternal();
@ -104,7 +97,6 @@ public class IntegerArray extends BlockItem {
bts[i+1]= (byte) (value >>> 8 & 0xff);
bts[i]= (byte) (value & 0xff);
}
@Override
public void onBytesChanged() {

View File

@ -6,6 +6,7 @@ public class SpecString extends StringItem {
}
@Override
public StyleItem getStyle(){
// Spec (resource name) don't have style unless to obfuscate/confuse other decompilers
return null;
}
}

View File

@ -173,7 +173,7 @@ public class StringItem extends BlockItem implements JSONConvert<JSONObject> {
if(styleItem==null){
return false;
}
return !styleItem.isNull();
return styleItem.getSpanInfoList().size()>0;
}
public StyleItem getStyle(){
BaseStringPool<?> stringPool=getStringPool();
@ -362,6 +362,6 @@ public class StringItem extends BlockItem implements JSONConvert<JSONObject> {
private final CharsetDecoder UTF16LE_DECODER = StandardCharsets.UTF_16LE.newDecoder();
private final CharsetDecoder UTF8_DECODER = StandardCharsets.UTF_8.newDecoder();
private static final String NAME_string="string";
private static final String NAME_style="style";
public static final String NAME_string="string";
public static final String NAME_style="style";
}

View File

@ -153,6 +153,9 @@ public class StyleItem extends IntegerArray implements JSONConvert<JSONObject> {
@Override
public StyleSpanInfo get(int i) {
int ref=getStringRef(i);
if(ref<=0){
return null;
}
return new StyleSpanInfo(
getStringFromPool(ref),
getFirstChar(i),
@ -192,7 +195,7 @@ public class StyleItem extends IntegerArray implements JSONConvert<JSONObject> {
return null;
}
List<StyleSpanInfo> spanInfoList = getSpanInfoList();
if(spanInfoList.size()==0){
if(isEmpty(spanInfoList)){
return str;
}
StringBuilder builder=new StringBuilder();
@ -202,6 +205,9 @@ public class StyleItem extends IntegerArray implements JSONConvert<JSONObject> {
char ch=allChars[i];
boolean lastAppend=false;
for(StyleSpanInfo info:spanInfoList){
if(info==null){
continue;
}
boolean isLast=(info.getLast()==i);
if(info.getFirst()==i || isLast){
if(isLast && !lastAppend){
@ -221,6 +227,17 @@ public class StyleItem extends IntegerArray implements JSONConvert<JSONObject> {
}
return builder.toString();
}
private boolean isEmpty(List<StyleSpanInfo> spanInfoList){
if(spanInfoList.size()==0){
return true;
}
for(StyleSpanInfo spanInfo:spanInfoList){
if(spanInfo!=null){
return false;
}
}
return true;
}
@Override
public void onBytesChanged() {
}
@ -262,10 +279,16 @@ public class StyleItem extends IntegerArray implements JSONConvert<JSONObject> {
JSONArray jsonArray=new JSONArray();
int i=0;
for(StyleSpanInfo spanInfo:getSpanInfoList()){
if(spanInfo==null){
continue;
}
JSONObject jsonObjectSpan=spanInfo.toJson();
jsonArray.put(i, jsonObjectSpan);
i++;
}
if(i==0){
return null;
}
jsonObject.put(NAME_spans, jsonArray);
return jsonObject;
}
@ -295,5 +318,5 @@ public class StyleItem extends IntegerArray implements JSONConvert<JSONObject> {
private static final int INTEGERS_COUNT = 3;
private static final int END_VALUE=0xFFFFFFFF;
private static final String NAME_spans="spans";
public static final String NAME_spans="spans";
}

View File

@ -10,6 +10,7 @@ public class TypeString extends StringItem {
}
@Override
public StyleItem getStyle(){
// Type don't have style unless to obfuscate/confuse other decompilers
return null;
}
}

View File

@ -78,7 +78,7 @@ public class StyleSpanInfo implements JSONConvert<JSONObject> {
return mTag +" ("+ mFirst +", "+ mLast +")";
}
private static final String NAME_tag="tag";
private static final String NAME_first="first";
private static final String NAME_last="last";
public static final String NAME_tag="tag";
public static final String NAME_first="first";
public static final String NAME_last="last";
}

View File

@ -9,9 +9,10 @@ import com.reandroid.lib.arsc.group.StringGroup;
import com.reandroid.lib.arsc.io.BlockLoad;
import com.reandroid.lib.arsc.io.BlockReader;
import com.reandroid.lib.arsc.item.*;
import com.reandroid.lib.arsc.pool.builder.StyleBuilder;
import com.reandroid.lib.arsc.model.StyleSpanInfo;
import com.reandroid.lib.json.JSONConvert;
import com.reandroid.lib.json.JSONArray;
import com.reandroid.lib.json.JSONObject;
import java.io.IOException;
import java.util.*;
@ -66,31 +67,60 @@ public abstract class BaseStringPool<T extends StringItem> extends BaseChunk imp
mUniqueMap=new HashMap<>();
}
public void addAllStrings(Collection<String> stringList){
public List<String> toStringList(){
return getStringsArray().toStringList();
}
public void addStrings(Collection<String> stringList){
if(stringList==null || stringList.size()==0){
return;
}
Set<String> uniqueSet;
if(stringList instanceof HashSet){
uniqueSet=(HashSet<String>)stringList;
}else {
uniqueSet=new HashSet<>(stringList);
}
refreshUniqueIdMap();
Set<String> keySet=mUniqueMap.keySet();
for(String key:keySet){
uniqueSet.remove(key);
}
List<String> sortedList=new ArrayList<>(stringList);
sortedList.sort(this);
addAllStrings(sortedList);
insertStrings(sortedList);
}
private void insertStrings(List<String> stringList){
StringArray<T> stringsArray = getStringsArray();
int initialSize=stringsArray.childesCount();
stringsArray.ensureSize(initialSize + stringList.size());
int size=stringsArray.childesCount();
int j=0;
for (int i=initialSize;i<size;i++){
T item=stringsArray.get(i);
item.set(stringList.get(j));
j++;
}
public void addAllStrings(List<String> sortedStringList){
// call refreshUniqueIdMap() just in case elements modified somewhere
refreshUniqueIdMap();
for(String str:sortedStringList){
getOrCreate(str);
}
}
// call this after modifying string values
public void refreshUniqueIdMap(){
reUpdateUniqueMap();
mUniqueMap.clear();
T[] allChildes=getStrings();
if(allChildes==null){
return;
}
public void recreateStyles(){
StyleArray styleArray = getStyleArray();
//styleArray.clearChildes();
StringArray<T> stringArray=getStringsArray();
for(T stringItem:stringArray.listItems()){
if(StyleBuilder.hasStyle(stringItem)){
StyleBuilder.buildStyle(stringItem);
int max=allChildes.length;
for(int i=0;i<max;i++){
T item=allChildes[i];
if(item==null){
continue;
}
String str=item.getHtml();
if(str==null){
continue;
}
StringGroup<T> group= getOrCreateGroup(str);
group.add(item);
}
}
public List<T> removeUnusedStrings(){
@ -175,30 +205,6 @@ public abstract class BaseStringPool<T extends StringItem> extends BaseChunk imp
mCountStrings.set(mArrayStrings.childesCount());
return item;
}
private void reUpdateUniqueMap(){
mUniqueMap.clear();
T[] allChildes=getStrings();
if(allChildes==null){
return;
}
int max=allChildes.length;
for(int i=0;i<max;i++){
T item=allChildes[i];
if(item==null){
continue;
}
String str=item.get();
if(str==null){
continue;
}
updateUniqueMap(item);
}
}
private void updateUniqueMap(T item){
String str=item.get();
StringGroup<T> group= getOrCreateGroup(str);
group.add(item);
}
public final StyleItem getStyle(int index){
return mArrayStyles.get(index);
}
@ -259,7 +265,7 @@ public abstract class BaseStringPool<T extends StringItem> extends BaseChunk imp
}
@Override
public void onChunkLoaded() {
reUpdateUniqueMap();
refreshUniqueIdMap();
}
@Override
@ -272,16 +278,121 @@ public abstract class BaseStringPool<T extends StringItem> extends BaseChunk imp
public JSONArray toJson() {
return getStringsArray().toJson();
}
//Only for styled strings
@Override
public void fromJson(JSONArray json) {
getStringsArray().fromJson(json);
refreshUniqueIdMap();
if(json==null){
return;
}
loadStyledStrings(json);
refresh();
}
public void loadStyledStrings(JSONArray jsonArray) {
//Styled strings should be at first rows of string pool thus we clear all before adding
getStringsArray().clearChildes();
getStyleArray().clearChildes();
List<StyledString> styledStringList = StyledString.fromJson(jsonArray);
loadText(styledStringList);
Map<String, Integer> tagIndexMap = loadStyleTags(styledStringList);
loadStyles(styledStringList, tagIndexMap);
refreshUniqueIdMap();
}
private void loadText(List<StyledString> styledStringList) {
StringArray<T> stringsArray = getStringsArray();
int size=styledStringList.size();
stringsArray.ensureSize(size);
for(int i=0;i<size;i++){
StyledString styledString=styledStringList.get(i);
T item=stringsArray.get(i);
item.set(styledString.text);
}
}
private Map<String, Integer> loadStyleTags(List<StyledString> styledStringList) {
Map<String, Integer> indexMap=new HashMap<>();
List<String> tagList=new ArrayList<>(getStyleTags(styledStringList));
tagList.sort(this);
StringArray<T> stringsArray = getStringsArray();
int tagsSize = tagList.size();
int initialSize = stringsArray.childesCount();
stringsArray.ensureSize(initialSize + tagsSize);
for(int i=0;i<tagsSize;i++){
String tag = tagList.get(i);
T item = stringsArray.get(initialSize + i);
item.set(tag);
indexMap.put(tag, item.getIndex());
}
return indexMap;
}
private void loadStyles(List<StyledString> styledStringList, Map<String, Integer> tagIndexMap){
StyleArray styleArray = getStyleArray();
int size=styledStringList.size();
styleArray.ensureSize(size);
for(int i=0;i<size;i++){
StyledString ss = styledStringList.get(i);
StyleItem styleItem = styleArray.get(i);
for(StyleSpanInfo spanInfo:ss.spanInfoList){
int tagIndex=tagIndexMap.get(spanInfo.getTag());
styleItem.addStylePiece(tagIndex, spanInfo.getFirst(), spanInfo.getLast());
}
}
}
private static Set<String> getStyleTags(List<StyledString> styledStringList){
Set<String> results=new HashSet<>();
for(StyledString ss:styledStringList){
for(StyleSpanInfo spanInfo:ss.spanInfoList){
results.add(spanInfo.getTag());
}
}
return results;
}
@Override
public int compare(String s1, String s2) {
return s1.compareTo(s2);
}
private static class StyledString{
final String text;
final List<StyleSpanInfo> spanInfoList;
StyledString(String text, List<StyleSpanInfo> spanInfoList){
this.text=text;
this.spanInfoList=spanInfoList;
}
@Override
public String toString(){
return text;
}
static List<StyledString> fromJson(JSONArray jsonArray){
int length = jsonArray.length();
List<StyledString> results=new ArrayList<>();
for(int i=0;i<length;i++){
if(!jsonArray.getJSONObject(i).has(StringItem.NAME_style)){
System.out.println("Dont have style at i="+i);
}
StyledString styledString=fromJson(jsonArray.getJSONObject(i));
results.add(styledString);
}
return results;
}
static StyledString fromJson(JSONObject jsonObject){
String text= jsonObject.getString(StringItem.NAME_string);
JSONObject style=jsonObject.getJSONObject(StringItem.NAME_style);
JSONArray spansArray=style.getJSONArray(StyleItem.NAME_spans);
List<StyleSpanInfo> spanInfoList = toSpanInfoList(spansArray);
return new StyledString(text, spanInfoList);
}
private static List<StyleSpanInfo> toSpanInfoList(JSONArray jsonArray){
int length = jsonArray.length();
List<StyleSpanInfo> results=new ArrayList<>(length);
for(int i=0;i<length;i++){
JSONObject jsonObject = jsonArray.getJSONObject(i);
StyleSpanInfo spanInfo=new StyleSpanInfo(null, 0,0);
spanInfo.fromJson(jsonObject);
results.add(spanInfo);
}
return results;
}
}
private static final short UTF8_FLAG_VALUE=0x0100;
private static final short FLAG_UTF8 = 0x0100;

View File

@ -2,6 +2,8 @@ package com.reandroid.lib.arsc.pool;
import com.reandroid.lib.arsc.array.SpecStringArray;
import com.reandroid.lib.arsc.array.StringArray;
import com.reandroid.lib.arsc.base.Block;
import com.reandroid.lib.arsc.chunk.PackageBlock;
import com.reandroid.lib.arsc.item.IntegerArray;
import com.reandroid.lib.arsc.item.IntegerItem;
import com.reandroid.lib.arsc.item.SpecString;
@ -15,7 +17,14 @@ public class SpecStringPool extends BaseStringPool<SpecString> {
StringArray<SpecString> newInstance(IntegerArray offsets, IntegerItem itemCount, IntegerItem itemStart, boolean is_utf8) {
return new SpecStringArray(offsets, itemCount, itemStart, is_utf8);
}
@Override
public void recreateStyles(){
public PackageBlock getPackageBlock(){
Block parent=getParent();
while (parent!=null){
if(parent instanceof PackageBlock){
return (PackageBlock) parent;
}
parent=parent.getParent();
}
return null;
}
}

View File

@ -2,20 +2,27 @@ package com.reandroid.lib.arsc.pool;
import com.reandroid.lib.arsc.array.StringArray;
import com.reandroid.lib.arsc.array.TypeStringArray;
import com.reandroid.lib.arsc.group.StringGroup;
import com.reandroid.lib.arsc.header.HeaderBlock;
import com.reandroid.lib.arsc.io.BlockReader;
import com.reandroid.lib.arsc.item.IntegerArray;
import com.reandroid.lib.arsc.item.IntegerItem;
import com.reandroid.lib.arsc.item.TypeString;
import java.io.IOException;
public class TypeStringPool extends BaseStringPool<TypeString> {
public TypeStringPool(boolean is_utf8) {
private final IntegerItem mTypeIdOffset;
public TypeStringPool(boolean is_utf8, IntegerItem typeIdOffset) {
super(is_utf8);
this.mTypeIdOffset = typeIdOffset;
}
public TypeString getById(int id){
return super.get(id-1);
int index=id-mTypeIdOffset.get()-1;
return super.get(index);
}
public TypeString getOrCreate(int typeId, String typeName){
getStringsArray().ensureSize(typeId);
int size=typeId-mTypeIdOffset.get();
getStringsArray().ensureSize(size);
TypeString typeString=getById(typeId);
typeString.set(typeName);
return typeString;
@ -24,7 +31,4 @@ public class TypeStringPool extends BaseStringPool<TypeString> {
StringArray<TypeString> newInstance(IntegerArray offsets, IntegerItem itemCount, IntegerItem itemStart, boolean is_utf8) {
return new TypeStringArray(offsets, itemCount, itemStart, is_utf8);
}
@Override
public void recreateStyles(){
}
}

View File

@ -1,16 +1,107 @@
package com.reandroid.lib.arsc.pool.builder;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
public class SpannedText {
private String mLeftText;
private String mTag;
private String mText;
private String mRightText;
private List<SpannedText> mChildes;
private int mStart;
private int mEnd;
private List<SpannedText> mChildes=new ArrayList<>();
public SpannedText(){
}
public void parse(int start, String text){
int i=text.indexOf('<', start);
public void parse(String text){
char[] allChars=text.toCharArray();
int len=allChars.length;
String firstTag=null;
String endTag=null;
StringBuilder tag=null;
boolean firstTagFound=false;
int posFirst=0;
int posSecond=0;
for(int i=0;i<len;i++){
char ch=allChars[i];
if(tag!=null){
tag.append(ch);
if(ch=='>'){
if(!firstTagFound){
firstTag=tag.toString();
firstTagFound=true;
tag=null;
}else {
endTag=tag.toString();
if(isTagsMatch(firstTag, endTag)){
break;
}
endTag=null;
tag=null;
continue;
}
}
continue;
}
if(ch=='<'){
if(isClosing(allChars, i+1)){
if(!firstTagFound){
tag=null;
continue;
}
}else if(firstTagFound){
firstTagFound=false;
firstTag=null;
}
tag=new StringBuilder();
tag.append(ch);
if(!firstTagFound){
posFirst=i;
}else {
posSecond=i;
}
}
}
if(firstTag==null || endTag==null){
return;
}
mStart=posFirst;
mEnd=posSecond;
StringBuilder builder=new StringBuilder();
builder.append(text, 0, posFirst);
builder.append(text, posFirst+firstTag.length(), posSecond);
builder.append(text.substring(posSecond+endTag.length()));
mText=builder.toString();
}
private boolean isClosing(char[] allChars, int pos){
for(int i=pos;i<allChars.length;i++){
char ch=allChars[i];
if(ch=='/'){
return true;
}
if(ch!=' '){
return false;
}
}
return false;
}
private boolean isTagsMatch(String start, String end){
start=trimStart(start);
end=trimEndTag(end);
return start.equals(end);
}
private String trimStart(String start){
start=start.substring(1, start.length()-1);
int i=start.indexOf(' ');
if(i>0){
start=start.substring(0,i);
}
start=start.trim();
return start;
}
private String trimEndTag(String end){
end=end.substring(1, end.length()-1).trim();
end=end.substring(1).trim();
return end;
}
private static final Pattern PATTERN_TAG=Pattern.compile("^(.*)(<[^/<>]+>)([^<]+)(</[^<>]+>)(.*)$");
}

View File

@ -52,6 +52,9 @@ public abstract class BaseResValue extends BlockItem implements JSONConvert<JSON
@Override
public void onBytesChanged() {
}
void onDataLoaded() {
}
int getInt(int offset){
byte[] bts = getBytesInternal();

View File

@ -50,6 +50,9 @@ public abstract class BaseResValueItem extends BaseResValue implements ResValueI
}
return mReferenceItem;
}
boolean hasTableReference(){
return mReferenceItem!=null;
}
boolean removeTableReference(){
ReferenceItem ref=mReferenceItem;
if(ref==null){

View File

@ -21,14 +21,14 @@ import java.util.List;
public class EntryBlock extends Block implements JSONConvert<JSONObject> {
private ShortItem mHeaderSize;
private ShortItem mFlags;
private ByteItem mFlagEntryType;
private ByteItem mByteFlagsB;
private IntegerItem mSpecReference;
private BaseResValue mResValue;
private boolean mUnLocked;
public EntryBlock() {
super();
}
public ResValueInt setValueAsBoolean(boolean val){
ResValueInt resValueInt;
BaseResValue res = getResValue();
@ -227,12 +227,11 @@ public class EntryBlock extends Block implements JSONConvert<JSONObject> {
removeTableReferences();
removeSpecReferences();
}
public short getFlags(){
return mFlags.get();
private void setEntryTypeFlag(byte b){
mFlagEntryType.set(b);
}
public void setFlags(short sh){
mFlags.set(sh);
private void setByteFlagsB(byte b){
mByteFlagsB.set(b);
}
public IntegerItem getSpecReferenceBlock(){
return mSpecReference;
@ -260,8 +259,8 @@ public class EntryBlock extends Block implements JSONConvert<JSONObject> {
setNull(true);
}else {
setNull(false);
boolean is_complex=(resValue instanceof ResValueBag);
setFlagComplex(is_complex);
boolean is_bag=(resValue instanceof ResValueBag);
setEntryTypeBag(is_bag);
updatePackage();
updateSpecRef();
}
@ -271,7 +270,7 @@ public class EntryBlock extends Block implements JSONConvert<JSONObject> {
return;
}
if(resValue!=null){
resValue.setIndex(3);
resValue.setIndex(4);
resValue.setParent(this);
}
if(mResValue!=null){
@ -284,14 +283,14 @@ public class EntryBlock extends Block implements JSONConvert<JSONObject> {
mResValue=resValue;
}
public void setFlagComplex(boolean is_complex){
public void setEntryTypeBag(boolean is_complex){
if(is_complex){
if(!isFlagsComplex()){
setFlags(FLAG_COMPLEX);
if(!isEntryTypeBag()){
setEntryTypeFlag(FLAG_VALUE_BAG);
}
}else {
if(isFlagsComplex()){
setFlags(FLAG_INT);
if(isEntryTypeBag()){
setEntryTypeFlag(FLAG_VALUE_INT);
}
}
refreshHeaderSize();
@ -314,7 +313,13 @@ public class EntryBlock extends Block implements JSONConvert<JSONObject> {
private void setName(String name){
PackageBlock packageBlock=getPackageBlock();
EntryGroup entryGroup = packageBlock.getEntryGroup(getResourceId());
if(entryGroup!=null){
entryGroup.renameSpec(name);
return;
}
SpecStringPool specStringPool= packageBlock.getSpecStringPool();
SpecString specString=specStringPool.getOrCreate(name);
setSpecReference(specString.getIndex());
}
public String getTypeName(){
TypeString typeString=getTypeString();
@ -437,15 +442,18 @@ public class EntryBlock extends Block implements JSONConvert<JSONObject> {
}
mUnLocked =true;
this.mHeaderSize =new ShortItem();
this.mFlags =new ShortItem();
this.mFlagEntryType =new ByteItem();
this.mByteFlagsB=new ByteItem();
this.mSpecReference = new IntegerItem();
mHeaderSize.setIndex(0);
mFlags.setIndex(1);
mSpecReference.setIndex(2);
mFlagEntryType.setIndex(1);
mByteFlagsB.setIndex(2);
mSpecReference.setIndex(3);
mHeaderSize.setParent(this);
mFlags.setParent(this);
mFlagEntryType.setParent(this);
mByteFlagsB.setParent(this);
mSpecReference.setParent(this);
}
@ -456,16 +464,19 @@ public class EntryBlock extends Block implements JSONConvert<JSONObject> {
removeAllReferences();
mUnLocked =false;
mHeaderSize.setParent(null);
mFlags.setParent(null);
mFlagEntryType.setParent(null);
mByteFlagsB.setParent(null);
mSpecReference.setParent(null);
mHeaderSize.setIndex(-1);
mFlags.setIndex(-1);
mFlagEntryType.setIndex(-1);
mByteFlagsB.setIndex(-1);
mSpecReference.setIndex(-1);
removeResValue();
this.mHeaderSize =null;
this.mFlags =null;
this.mFlagEntryType =null;
this.mByteFlagsB =null;
this.mSpecReference =null;
}
private void removeResValue(){
@ -476,7 +487,7 @@ public class EntryBlock extends Block implements JSONConvert<JSONObject> {
}
}
private void refreshHeaderSize(){
if(isFlagsComplex()){
if(isEntryTypeBag()){
mHeaderSize.set(HEADER_COMPLEX);
}else {
mHeaderSize.set(HEADER_INT);
@ -487,21 +498,15 @@ public class EntryBlock extends Block implements JSONConvert<JSONObject> {
return;
}
BaseResValue resValue;
if(isFlagsComplex()){
if(isEntryTypeBag()){
resValue=new ResValueBag();
}else {
resValue=new ResValueInt();
}
setResValueInternal(resValue);
}
private boolean isFlagsComplex(){
return ((mFlags.get() & FLAG_COMPLEX_MASK) != 0);
}
public boolean isArray(){
return ((mFlags.get() & FLAG_COMPLEX_MASK) != 0);
}
public void setIsArray(boolean is_array){
setFlagComplex(is_array);
private boolean isEntryTypeBag(){
return ((mFlagEntryType.get() & FLAG_BAG_ENTRY) != 0);
}
@Override
public void setNull(boolean is_null){
@ -525,7 +530,8 @@ public class EntryBlock extends Block implements JSONConvert<JSONObject> {
return null;
}
byte[] results=mHeaderSize.getBytes();
results=addBytes(results, mFlags.getBytes());
results=addBytes(results, mFlagEntryType.getBytes());
results=addBytes(results, mByteFlagsB.getBytes());
results=addBytes(results, mSpecReference.getBytes());
results=addBytes(results, mResValue.getBytes());
return results;
@ -558,7 +564,8 @@ public class EntryBlock extends Block implements JSONConvert<JSONObject> {
}
counter.addCount(countBytes());
mHeaderSize.onCountUpTo(counter);
mFlags.onCountUpTo(counter);
mFlagEntryType.onCountUpTo(counter);
mByteFlagsB.onCountUpTo(counter);
mSpecReference.onCountUpTo(counter);
mResValue.onCountUpTo(counter);
}
@ -568,7 +575,8 @@ public class EntryBlock extends Block implements JSONConvert<JSONObject> {
return 0;
}
int result=mHeaderSize.writeBytes(stream);
result+=mFlags.writeBytes(stream);
result+= mFlagEntryType.writeBytes(stream);
result+=mByteFlagsB.writeBytes(stream);
result+= mSpecReference.writeBytes(stream);
result+=mResValue.writeBytes(stream);
return result;
@ -612,12 +620,14 @@ public class EntryBlock extends Block implements JSONConvert<JSONObject> {
setNull(false);
removeResValue();
mHeaderSize.readBytes(reader);
mFlags.readBytes(reader);
mFlagEntryType.readBytes(reader);
mByteFlagsB.readBytes(reader);
mSpecReference.readBytes(reader);
createResValue();
mResValue.readBytes(reader);
updatePackage();
updateSpecRef();
mResValue.onDataLoaded();
}
@Override
public JSONObject toJson() {
@ -626,7 +636,7 @@ public class EntryBlock extends Block implements JSONConvert<JSONObject> {
}
JSONObject jsonObject=new JSONObject();
jsonObject.put(NAME_name, getSpecString().get());
jsonObject.put(NAME_is_array, isArray());
jsonObject.put(NAME_is_array, isEntryTypeBag());
jsonObject.put(NAME_value, getResValue().toJson());
return jsonObject;
}
@ -636,16 +646,16 @@ public class EntryBlock extends Block implements JSONConvert<JSONObject> {
setNull(true);
return;
}
setName(json.getString(NAME_name));
setNull(false);
BaseResValue baseResValue;
if(json.getBoolean(NAME_is_array)){
baseResValue=new ResValueInt();
}else {
baseResValue=new ResValueBag();
}else {
baseResValue=new ResValueInt();
}
setResValue(baseResValue);
setName(json.getString(NAME_name));
baseResValue.fromJson(json.getJSONObject(NAME_value));
mResValue.onDataLoaded();
}
@Override
public String toString(){
@ -698,10 +708,10 @@ public class EntryBlock extends Block implements JSONConvert<JSONObject> {
return builder.toString();
}
private final static short FLAG_COMPLEX_MASK = 0x0001;
private final static byte FLAG_BAG_ENTRY = 0x01;
private final static short FLAG_COMPLEX = 0x0001;
private final static short FLAG_INT = 0x0000;
private final static byte FLAG_VALUE_BAG = 0x0001;
private final static byte FLAG_VALUE_INT = 0x00;
private final static short HEADER_COMPLEX=0x0010;
private final static short HEADER_INT=0x0008;

View File

@ -28,6 +28,13 @@ public class ResConfig extends FixedBlockContainer implements BlockLoad, JSONCon
this.configSize.setBlockLoad(this);
this.mValuesContainer.setBlockLoad(this);
}
public void copyFrom(ResConfig resConfig){
if(resConfig==this||resConfig==null){
return;
}
setConfigSize(resConfig.getConfigSize());
mValuesContainer.putByteArray(0, resConfig.mValuesContainer.toArray());
}
@Override
public void onBlockLoaded(BlockReader reader, Block sender) throws IOException {
if(sender==configSize){
@ -980,11 +987,11 @@ public class ResConfig extends FixedBlockContainer implements BlockLoad, JSONCon
return keyboard;
}
}
return NOKEYS;
return NONE;
}
public static Keyboard fromName(String name){
if(name==null){
return NOKEYS;
return NONE;
}
name=name.trim().toUpperCase();
if(name.equals("12KEY")){
@ -995,7 +1002,7 @@ public class ResConfig extends FixedBlockContainer implements BlockLoad, JSONCon
return keyboard;
}
}
return NOKEYS;
return NONE;
}
}
public enum Navigation{

View File

@ -129,6 +129,12 @@ public class ResValueBag extends BaseResValue {
mResValueBagItemArray.readBytes(reader);
}
@Override
void onDataLoaded() {
for(ResValueBagItem item: mResValueBagItemArray.listItems()){
item.refreshTableReference();
}
}
@Override
public JSONObject toJson() {
if(isNull()){
return null;
@ -147,9 +153,6 @@ public class ResValueBag extends BaseResValue {
@Override
public String toString(){
if(getParent()!=null){
return toJson().toString(4);
}
StringBuilder builder=new StringBuilder();
builder.append(getClass().getSimpleName());
builder.append(": parent=");

View File

@ -102,14 +102,23 @@ public class ResValueBagItem extends BaseResValueItem{
setInt(OFFSET_DATA, data);
afterDataValueChanged();
}
@Override
public void onSetReference(int data){
setInt(OFFSET_DATA, data);
}
private void beforeDataValueChanged(){
if(getValueType()==ValueType.STRING){
removeTableReference();
}
}
private void afterDataValueChanged(){
refreshTableReference();
}
void refreshTableReference(){
if(getValueType()==ValueType.STRING){
addTableReference(getTableStringReference());
}else {
removeTableReference();
}
}
public short getIdHigh(){
@ -195,6 +204,14 @@ public class ResValueBagItem extends BaseResValueItem{
builder.append(String.format("0x%08x", getId()));
builder.append(", data=");
builder.append(String.format("0x%08x", getData()));
if(vt==ValueType.STRING){
TableString ts=getTableString(getData());
if(ts==null){
builder.append(" Null table string");
}else {
builder.append(" \"").append(ts.getHtml()).append("\"");
}
}
return builder.toString();
}
private static final int OFFSET_ID=0;

View File

@ -26,6 +26,16 @@ public class ResValueInt extends BaseResValueItem {
return getData()!=0;
}
@Override
void onDataLoaded() {
if(getValueType()==ValueType.STRING){
if(!hasTableReference()){
addTableReference(getTableStringReference());
}
}else {
removeTableReference();
}
}
@Override
public void setHeaderSize(short size) {
setShort(OFFSET_SIZE, size);
}
@ -74,6 +84,10 @@ public class ResValueInt extends BaseResValueItem {
}
}
@Override
public void onSetReference(int data){
setInt(OFFSET_DATA, data);
}
@Override
public JSONObject toJson() {
if(isNull()){
return null;

View File

@ -13,4 +13,5 @@ public interface ResValueItem {
int getData();
void setData(int data);
void onSetReference(int data);
}

View File

@ -2,6 +2,8 @@ package com.reandroid.lib.arsc.value;
import com.reandroid.lib.arsc.item.ReferenceItem;
import java.util.Objects;
public class ResValueReference implements ReferenceItem {
private final BaseResValueItem resValueItem;
public ResValueReference(BaseResValueItem resValueItem){
@ -12,10 +14,21 @@ public class ResValueReference implements ReferenceItem {
}
@Override
public void set(int val) {
resValueItem.setData(val);
resValueItem.onSetReference(val);
}
@Override
public int get() {
return resValueItem.getData();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ResValueReference that = (ResValueReference) o;
return Objects.equals(resValueItem, that.resValueItem);
}
@Override
public int hashCode() {
return Objects.hash(resValueItem);
}
}

View File

@ -1,21 +0,0 @@
package com.reandroid.lib.common;
import java.util.AbstractList;
public class ROArrayList<T> extends AbstractList<T> {
private final T[] elementData;
public ROArrayList(T[] elementData){
this.elementData=elementData;
}
@Override
public T get(int i) {
return this.elementData[i];
}
@Override
public int size() {
if(this.elementData==null){
return 0;
}
return this.elementData.length;
}
}

View File

@ -1,25 +0,0 @@
package com.reandroid.lib.common;
import java.util.AbstractList;
public class ROSingleList<T> extends AbstractList<T> {
private final T item;
public ROSingleList(T item){
this.item=item;
}
@Override
public T get(int i) {
if(i==0 && this.item!=null){
return this.item;
}
throw new ArrayIndexOutOfBoundsException(getClass().getSimpleName()+": "+i);
}
@Override
public int size() {
if(this.item==null){
return 0;
}
return 1;
}
}

View File

@ -1,6 +1,7 @@
package com.reandroid.lib.json;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.Array;
@ -112,6 +113,16 @@ public class JSONArray extends JSONItem implements Iterable<Object> {
}
this.myArrayList = new ArrayList<Object>(initialCapacity);
}
public JSONArray(InputStream inputStream) throws JSONException {
this(new JSONTokener(inputStream));
try {
inputStream.close();
} catch (IOException ignored) {
}
}
public ArrayList<Object> getArrayList(){
return myArrayList;
}
@Override
public Iterator<Object> iterator() {

View File

@ -1,10 +1,7 @@
package com.reandroid.lib.json;
import java.io.Closeable;
import java.io.*;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
@ -211,6 +208,14 @@ public class JSONObject extends JSONItem{
this.map = new HashMap<String, Object>(initialCapacity);
}
public JSONObject(InputStream inputStream) throws JSONException {
this(new JSONTokener(inputStream));
try {
inputStream.close();
} catch (IOException ignored) {
}
}
public JSONObject accumulate(String key, Object value) throws JSONException {
testValidity(value);
Object object = this.opt(key);

BIN
src/main/resources/fwk/android_resources_30.arsc Executable file → Normal file

Binary file not shown.