diff --git a/build.gradle b/build.gradle index 4a8f1cf..5a93315 100755 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ apply plugin: 'java-library' group 'com.reandroid.lib.arsc' -version '1.0.6' +version '1.0.7' java { sourceCompatibility JavaVersion.VERSION_1_8 diff --git a/src/main/java/com/reandroid/lib/apk/ApkBundle.java b/src/main/java/com/reandroid/lib/apk/ApkBundle.java new file mode 100644 index 0000000..ccf9192 --- /dev/null +++ b/src/main/java/com/reandroid/lib/apk/ApkBundle.java @@ -0,0 +1,129 @@ +package com.reandroid.lib.apk; + +import com.reandroid.archive.APKArchive; +import com.reandroid.lib.arsc.chunk.TableBlock; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.*; + +public class ApkBundle { + private final Map mModulesMap; + private APKLogger apkLogger; + public ApkBundle(){ + this.mModulesMap=new HashMap<>(); + } + + public ApkModule mergeModules() throws IOException { + List moduleList=getApkModuleList(); + if(moduleList.size()==0){ + throw new FileNotFoundException("Nothing to merge, empty modules"); + } + ApkModule result=new ApkModule("merged", new APKArchive()); + result.setAPKLogger(apkLogger); + ApkModule base=getBaseModule(); + if(base==null){ + base=getLargestTableModule(); + } + result.merge(base); + for(ApkModule module:moduleList){ + if(module==base){ + continue; + } + result.merge(module); + } + if(result.hasTableBlock()){ + TableBlock tableBlock=result.getTableBlock(); + tableBlock.sortPackages(); + tableBlock.refresh(); + } + result.sortApkFiles(); + return result; + } + private ApkModule getLargestTableModule() throws IOException { + ApkModule apkModule=null; + int chunkSize=0; + for(ApkModule module:getApkModuleList()){ + if(!module.hasTableBlock()){ + continue; + } + TableBlock tableBlock=module.getTableBlock(); + int size=tableBlock.getHeaderBlock().getChunkSize(); + if(apkModule==null || size>chunkSize){ + chunkSize=size; + apkModule=module; + } + } + return apkModule; + } + public ApkModule getBaseModule(){ + for(ApkModule module:getApkModuleList()){ + if(module.isBaseModule()){ + return module; + } + } + return null; + } + public List getApkModuleList(){ + return new ArrayList<>(mModulesMap.values()); + } + public void loadApkDirectory(File dir) throws IOException { + if(!dir.isDirectory()){ + throw new FileNotFoundException("No such directory: "+dir); + } + List apkList=ApkUtil.listFiles(dir, ".apk"); + if(apkList.size()==0){ + throw new FileNotFoundException("No '*.apk' files in directory: "+dir); + } + logMessage("Found apk files: "+apkList.size()); + for(File file:apkList){ + logVerbose("Loading: "+file.getName()); + String name=ApkUtil.toModuleName(file); + ApkModule module=ApkModule.loadApkFile(file, name); + module.setAPKLogger(apkLogger); + addModule(module); + } + } + public void addModule(ApkModule apkModule){ + String name=apkModule.getModuleName(); + mModulesMap.remove(name); + mModulesMap.put(name, apkModule); + } + public boolean containsApkModule(String moduleName){ + return mModulesMap.containsKey(moduleName); + } + public ApkModule removeApkModule(String moduleName){ + return mModulesMap.remove(moduleName); + } + public ApkModule getApkModule(String moduleName){ + return mModulesMap.get(moduleName); + } + public List listModuleNames(){ + return new ArrayList<>(mModulesMap.keySet()); + } + public int countModules(){ + return mModulesMap.size(); + } + public Collection getModules(){ + return mModulesMap.values(); + } + public void setAPKLogger(APKLogger logger) { + this.apkLogger = logger; + } + private void logMessage(String msg) { + if(apkLogger!=null){ + apkLogger.logMessage(msg); + } + } + private void logError(String msg, Throwable tr) { + if(apkLogger!=null){ + apkLogger.logError(msg, tr); + } + } + private void logVerbose(String msg) { + if(apkLogger!=null){ + apkLogger.logVerbose(msg); + } + } +} diff --git a/src/main/java/com/reandroid/lib/apk/ApkModule.java b/src/main/java/com/reandroid/lib/apk/ApkModule.java index d0adc39..265329c 100644 --- a/src/main/java/com/reandroid/lib/apk/ApkModule.java +++ b/src/main/java/com/reandroid/lib/apk/ApkModule.java @@ -2,6 +2,7 @@ package com.reandroid.lib.apk; import com.reandroid.archive.*; import com.reandroid.lib.arsc.array.PackageArray; +import com.reandroid.lib.arsc.chunk.BaseChunk; import com.reandroid.lib.arsc.chunk.PackageBlock; import com.reandroid.lib.arsc.chunk.TableBlock; import com.reandroid.lib.arsc.chunk.xml.AndroidManifestBlock; @@ -30,6 +31,16 @@ public class ApkModule { this.mUncompressedFiles=new UncompressedFiles(); this.mUncompressedFiles.addPath(apkArchive); } + public List listDexFiles(){ + List results=new ArrayList<>(); + for(InputSource source:getApkArchive().listInputSources()){ + if(DexFileInputSource.isDexName(source.getAlias())){ + results.add(new DexFileInputSource(source.getAlias(), source)); + } + } + DexFileInputSource.sort(results); + return results; + } public boolean isBaseModule(){ if(!hasAndroidManifestBlock()){ return false; @@ -223,6 +234,9 @@ public class ApkModule { tableBlock=((SplitJsonTableInputSource)inputSource).getTableBlock(); }else if(inputSource instanceof SingleJsonTableInputSource){ tableBlock=((SingleJsonTableInputSource)inputSource).getTableBlock(); + }else if(inputSource instanceof BlockInputSource){ + BaseChunk block = ((BlockInputSource) inputSource).getBlock(); + tableBlock=(TableBlock) block; }else { InputStream inputStream = inputSource.openStream(); if(loadDefaultFramework){ @@ -246,6 +260,78 @@ public class ApkModule { this.loadDefaultFramework = loadDefaultFramework; } + public void merge(ApkModule module) throws IOException { + if(module==null||module==this){ + return; + } + logMessage("Merging: "+module.getModuleName()); + mergeDexFiles(module); + mergeTable(module); + mergeFiles(module); + getUncompressedFiles().merge(module.getUncompressedFiles()); + } + private void mergeTable(ApkModule module) throws IOException { + if(!module.hasTableBlock()){ + return; + } + logMessage("Merging resource table: "+module.getModuleName()); + TableBlock exist; + if(!hasTableBlock()){ + exist=new TableBlock(); + BlockInputSource inputSource=new BlockInputSource<>(TableBlock.FILE_NAME, exist); + getApkArchive().add(inputSource); + }else{ + exist=getTableBlock(); + } + TableBlock coming=module.getTableBlock(); + exist.merge(coming); + } + private void mergeFiles(ApkModule module) throws IOException { + APKArchive archiveExist = getApkArchive(); + APKArchive archiveComing = module.getApkArchive(); + Map comingAlias=ApkUtil.toAliasMap(archiveComing.listInputSources()); + Map existAlias=ApkUtil.toAliasMap(archiveExist.listInputSources()); + UncompressedFiles uf=getUncompressedFiles(); + for(InputSource inputSource:comingAlias.values()){ + if(existAlias.containsKey(inputSource.getAlias())||existAlias.containsKey(inputSource.getName())){ + continue; + } + if(DexFileInputSource.isDexName(inputSource.getName())){ + continue; + } + logVerbose("Added: "+inputSource.getAlias()); + archiveExist.add(inputSource); + uf.addPath(inputSource); + } + } + private void mergeDexFiles(ApkModule module){ + List existList=listDexFiles(); + List comingList=module.listDexFiles(); + APKArchive archive=getApkArchive(); + int index=0; + if(existList.size()>0){ + index=existList.get(existList.size()-1).getDexNumber(); + if(index==0){ + index=2; + }else { + index++; + } + } + for(DexFileInputSource source:comingList){ + String name=DexFileInputSource.getDexName(index); + DexFileInputSource add=new DexFileInputSource(name, source.getInputSource()); + archive.add(add); + logMessage("Added ["+module.getModuleName()+"] " + +source.getAlias()+" -> "+name); + index++; + if(index==1){ + index=2; + } + } + } + void sortApkFiles(){ + sortApkFiles(new ArrayList<>(getApkArchive().listInputSources())); + } public void setAPKLogger(APKLogger logger) { this.apkLogger = logger; } @@ -264,6 +350,10 @@ public class ApkModule { apkLogger.logVerbose(msg); } } + @Override + public String toString(){ + return getModuleName(); + } public static ApkModule loadApkFile(File apkFile) throws IOException { return loadApkFile(apkFile, ApkUtil.DEF_MODULE_NAME); } @@ -271,4 +361,39 @@ public class ApkModule { APKArchive archive=APKArchive.loadZippedApk(apkFile); return new ApkModule(moduleName, archive); } + private static void sortApkFiles(List sourceList){ + Comparator cmp=new Comparator() { + @Override + public int compare(InputSource in1, InputSource in2) { + return getSortName(in1).compareTo(getSortName(in2)); + } + }; + sourceList.sort(cmp); + int i=0; + for(InputSource inputSource:sourceList){ + inputSource.setSort(i); + i++; + } + } + private static String getSortName(InputSource inputSource){ + String name=inputSource.getAlias(); + StringBuilder builder=new StringBuilder(); + if(name.equals(AndroidManifestBlock.FILE_NAME)){ + builder.append("0 "); + }else if(name.equals(TableBlock.FILE_NAME)){ + builder.append("1 "); + }else if(name.startsWith("classes")){ + builder.append("2 "); + }else if(name.startsWith("res/")){ + builder.append("3 "); + }else if(name.startsWith("lib/")){ + builder.append("4 "); + }else if(name.startsWith("assets/")){ + builder.append("5 "); + }else { + builder.append("6 "); + } + builder.append(name.toLowerCase()); + return builder.toString(); + } } diff --git a/src/main/java/com/reandroid/lib/apk/ApkUtil.java b/src/main/java/com/reandroid/lib/apk/ApkUtil.java index a5b9dbf..d30a631 100644 --- a/src/main/java/com/reandroid/lib/apk/ApkUtil.java +++ b/src/main/java/com/reandroid/lib/apk/ApkUtil.java @@ -1,8 +1,9 @@ package com.reandroid.lib.apk; +import com.reandroid.archive.InputSource; + import java.io.File; -import java.util.ArrayList; -import java.util.List; +import java.util.*; public class ApkUtil { public static String replaceRootDir(String path, String dirName){ @@ -106,6 +107,21 @@ public class ApkUtil { } return results; } + public static String toModuleName(File file){ + String name=file.getName(); + int i=name.lastIndexOf('.'); + if(i>0){ + name=name.substring(0,i); + } + return name; + } + public static Map toAliasMap(Collection sourceList){ + Map results=new HashMap<>(); + for(InputSource inputSource:sourceList){ + results.put(inputSource.getAlias(), inputSource); + } + 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"; diff --git a/src/main/java/com/reandroid/lib/apk/DexFileInputSource.java b/src/main/java/com/reandroid/lib/apk/DexFileInputSource.java new file mode 100644 index 0000000..785656e --- /dev/null +++ b/src/main/java/com/reandroid/lib/apk/DexFileInputSource.java @@ -0,0 +1,51 @@ +package com.reandroid.lib.apk; + +import com.reandroid.archive.InputSource; + +import java.util.Comparator; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class DexFileInputSource extends RenamedInputSource implements Comparable{ + public DexFileInputSource(String name, InputSource inputSource){ + super(name, inputSource); + } + public int getDexNumber(){ + return getDexNumber(getAlias()); + } + @Override + public int compareTo(DexFileInputSource source) { + return Integer.compare(getDexNumber(), source.getDexNumber()); + } + public static void sort(List sourceList){ + sourceList.sort(new Comparator() { + @Override + public int compare(DexFileInputSource s1, DexFileInputSource s2) { + return s1.compareTo(s2); + } + }); + } + public static boolean isDexName(String name){ + return getDexNumber(name)>=0; + } + static String getDexName(int i){ + if(i==0){ + return "classes.dex"; + } + return "classes"+i+".dex"; + } + static int getDexNumber(String name){ + Matcher matcher=PATTERN.matcher(name); + if(!matcher.find()){ + return -1; + } + String num=matcher.group(1); + if(num.length()==0){ + return 0; + } + return Integer.parseInt(num); + } + private static final Pattern PATTERN=Pattern.compile("^classes([0-9]*)\\.dex$"); + +} diff --git a/src/main/java/com/reandroid/lib/apk/RenamedInputSource.java b/src/main/java/com/reandroid/lib/apk/RenamedInputSource.java new file mode 100644 index 0000000..5fbc0b9 --- /dev/null +++ b/src/main/java/com/reandroid/lib/apk/RenamedInputSource.java @@ -0,0 +1,40 @@ +package com.reandroid.lib.apk; + +import com.reandroid.archive.InputSource; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +public class RenamedInputSource extends InputSource { + private final T inputSource; + public RenamedInputSource(String name, T input){ + super(name); + this.inputSource=input; + super.setMethod(input.getMethod()); + super.setSort(input.getSort()); + } + public T getInputSource() { + return inputSource; + } + @Override + public void close(InputStream inputStream) throws IOException { + getInputSource().close(inputStream); + } + @Override + public long getLength() throws IOException { + return getInputSource().getLength(); + } + @Override + public long getCrc() throws IOException { + return getInputSource().getCrc(); + } + @Override + public long write(OutputStream outputStream) throws IOException { + return getInputSource().write(outputStream); + } + @Override + public InputStream openStream() throws IOException { + return getInputSource().openStream(); + } +} diff --git a/src/main/java/com/reandroid/lib/apk/TableBlockJsonBuilder.java b/src/main/java/com/reandroid/lib/apk/TableBlockJsonBuilder.java index df18f4e..8832b51 100644 --- a/src/main/java/com/reandroid/lib/apk/TableBlockJsonBuilder.java +++ b/src/main/java/com/reandroid/lib/apk/TableBlockJsonBuilder.java @@ -30,6 +30,7 @@ public class TableBlockJsonBuilder { for(File pkgDir:pkgDirList){ scanPackageDirectory(tableBlock, pkgDir); } + tableBlock.sortPackages(); tableBlock.refresh(); return tableBlock; } diff --git a/src/main/java/com/reandroid/lib/apk/UncompressedFiles.java b/src/main/java/com/reandroid/lib/apk/UncompressedFiles.java index 70bcf13..4022414 100644 --- a/src/main/java/com/reandroid/lib/apk/UncompressedFiles.java +++ b/src/main/java/com/reandroid/lib/apk/UncompressedFiles.java @@ -159,6 +159,17 @@ public class UncompressedFiles implements JSONConvert { } } } + public void merge(UncompressedFiles uf){ + if(uf==null||uf==this){ + return; + } + for(String path: uf.mPathList){ + addPath(path); + } + for(String ext:uf.mExtensionList){ + addExtension(ext); + } + } public void fromJson(File jsonFile) throws IOException { if(!jsonFile.isFile()){ return; diff --git a/src/main/java/com/reandroid/lib/arsc/array/EntryBlockArray.java b/src/main/java/com/reandroid/lib/arsc/array/EntryBlockArray.java index 0a77cbe..bb104cd 100755 --- a/src/main/java/com/reandroid/lib/arsc/array/EntryBlockArray.java +++ b/src/main/java/com/reandroid/lib/arsc/array/EntryBlockArray.java @@ -1,12 +1,18 @@ package com.reandroid.lib.arsc.array; +import com.reandroid.lib.arsc.chunk.TypeBlock; import com.reandroid.lib.arsc.item.IntegerArray; import com.reandroid.lib.arsc.item.IntegerItem; +import com.reandroid.lib.arsc.value.BaseResValue; import com.reandroid.lib.arsc.value.EntryBlock; +import com.reandroid.lib.arsc.value.ResValueInt; +import com.reandroid.lib.arsc.value.ValueType; import com.reandroid.lib.json.JSONConvert; import com.reandroid.lib.json.JSONArray; import com.reandroid.lib.json.JSONObject; +import java.util.Iterator; + public class EntryBlockArray extends OffsetBlockArray implements JSONConvert { public EntryBlockArray(IntegerArray offsets, IntegerItem itemCount, IntegerItem itemStart){ @@ -56,7 +62,6 @@ public class EntryBlockArray extends OffsetBlockArray implements JSO } return jsonArray; } - @Override public void fromJson(JSONArray json) { clearChildes(); @@ -74,6 +79,34 @@ public class EntryBlockArray extends OffsetBlockArray implements JSO } refreshCountAndStart(); } + public void merge(EntryBlockArray entryBlockArray){ + if(entryBlockArray==null||entryBlockArray==this||entryBlockArray.isEmpty()){ + return; + } + ensureSize(entryBlockArray.childesCount()); + Iterator itr=entryBlockArray.iterator(true); + while (itr.hasNext()){ + EntryBlock comingBlock=itr.next(); + EntryBlock existingBlock=get(comingBlock.getIndex()); + if(shouldMerge(existingBlock, comingBlock)){ + existingBlock.merge(comingBlock); + } + } + } + private boolean shouldMerge(EntryBlock exist, EntryBlock coming){ + if(exist.isNull()){ + return true; + } + if(coming.isNull()){ + return false; + } + BaseResValue resVal = coming.getResValue(); + if(resVal instanceof ResValueInt){ + ValueType valueType=((ResValueInt)resVal).getValueType(); + return valueType!=ValueType.INT_BOOLEAN; + } + return true; + } @Override public String toString(){ return getClass().getSimpleName()+": size="+childesCount(); diff --git a/src/main/java/com/reandroid/lib/arsc/array/LibraryInfoArray.java b/src/main/java/com/reandroid/lib/arsc/array/LibraryInfoArray.java index 7e1efd9..d1a33b5 100755 --- a/src/main/java/com/reandroid/lib/arsc/array/LibraryInfoArray.java +++ b/src/main/java/com/reandroid/lib/arsc/array/LibraryInfoArray.java @@ -15,6 +15,25 @@ public class LibraryInfoArray extends BlockArray implements JSONCon public LibraryInfoArray(IntegerItem infoCount){ this.mInfoCount=infoCount; } + public LibraryInfo getOrCreate(int pkgId){ + LibraryInfo info=getById(pkgId); + if(info!=null){ + return info; + } + int index=childesCount(); + ensureSize(index+1); + info=get(index); + info.setPackageId(pkgId); + return info; + } + public LibraryInfo getById(int pkgId){ + for(LibraryInfo info:listItems()){ + if(pkgId==info.getPackageId()){ + return info; + } + } + return null; + } @Override public LibraryInfo newInstance() { return new LibraryInfo(); @@ -61,4 +80,13 @@ public class LibraryInfoArray extends BlockArray implements JSONCon libraryInfo.fromJson(jsonObject); } } + public void merge(LibraryInfoArray infoArray){ + if(infoArray==null||infoArray==this||infoArray.childesCount()==0){ + return; + } + for(LibraryInfo info:infoArray.listItems()){ + LibraryInfo exist=getOrCreate(info.getPackageId()); + exist.merge(info); + } + } } diff --git a/src/main/java/com/reandroid/lib/arsc/array/PackageArray.java b/src/main/java/com/reandroid/lib/arsc/array/PackageArray.java index df2696b..219f745 100755 --- a/src/main/java/com/reandroid/lib/arsc/array/PackageArray.java +++ b/src/main/java/com/reandroid/lib/arsc/array/PackageArray.java @@ -11,14 +11,22 @@ import com.reandroid.lib.json.JSONArray; import com.reandroid.lib.json.JSONObject; import java.io.IOException; +import java.util.Comparator; import java.util.Iterator; -public class PackageArray extends BlockArray implements BlockLoad, JSONConvert { +public class PackageArray extends BlockArray + implements BlockLoad, JSONConvert, Comparator { private final IntegerItem mPackageCount; public PackageArray(IntegerItem packageCount){ this.mPackageCount=packageCount; mPackageCount.setBlockLoad(this); } + public void sort(){ + for(PackageBlock packageBlock:listItems()){ + packageBlock.sortTypes(); + } + sort(this); + } public PackageBlock getOrCreate(byte pkgId){ PackageBlock packageBlock=getPackageBlockById(pkgId); if(packageBlock!=null){ @@ -88,4 +96,17 @@ public class PackageArray extends BlockArray implements BlockLoad, packageBlock.fromJson(jsonObject); } } + public void merge(PackageArray packageArray){ + if(packageArray==null||packageArray==this){ + return; + } + for(PackageBlock packageBlock:packageArray.listItems()){ + PackageBlock exist=getOrCreate((byte) packageBlock.getId()); + exist.merge(packageBlock); + } + } + @Override + public int compare(PackageBlock p1, PackageBlock p2) { + return p1.compareTo(p2); + } } diff --git a/src/main/java/com/reandroid/lib/arsc/array/ResValueBagItemArray.java b/src/main/java/com/reandroid/lib/arsc/array/ResValueBagItemArray.java index 63d9fc7..ec3caaf 100755 --- a/src/main/java/com/reandroid/lib/arsc/array/ResValueBagItemArray.java +++ b/src/main/java/com/reandroid/lib/arsc/array/ResValueBagItemArray.java @@ -1,6 +1,7 @@ package com.reandroid.lib.arsc.array; import com.reandroid.lib.arsc.base.BlockArray; +import com.reandroid.lib.arsc.value.ResValueBag; import com.reandroid.lib.arsc.value.ResValueBagItem; import com.reandroid.lib.json.JSONConvert; import com.reandroid.lib.json.JSONArray; @@ -22,6 +23,13 @@ public class ResValueBagItemArray extends BlockArray implements @Override protected void onRefreshed() { + } + @Override + public void clearChildes(){ + for(ResValueBagItem bagItem:listItems()){ + bagItem.onRemoved(); + } + super.clearChildes(); } @Override public JSONArray toJson() { @@ -47,4 +55,17 @@ public class ResValueBagItemArray extends BlockArray implements get(i).fromJson(json.getJSONObject(i)); } } + public void merge(ResValueBagItemArray bagItemArray){ + if(bagItemArray==null||bagItemArray==this){ + return; + } + clearChildes(); + int count=bagItemArray.childesCount(); + ensureSize(count); + for(int i=0;i specTypePair.fromJson(jsonObject); } } + public void merge(SpecTypePairArray pairArray){ + if(pairArray==null || pairArray==this){ + return; + } + for(SpecTypePair typePair:pairArray.listItems()){ + if(typePair.isEmpty()){ + continue; + } + SpecTypePair exist=getOrCreate(typePair.getTypeId()); + exist.merge(typePair); + } + } @Override public int compare(SpecTypePair typePair1, SpecTypePair typePair2) { return typePair1.compareTo(typePair2); diff --git a/src/main/java/com/reandroid/lib/arsc/array/StyleArray.java b/src/main/java/com/reandroid/lib/arsc/array/StyleArray.java index 1051164..89fb1c6 100755 --- a/src/main/java/com/reandroid/lib/arsc/array/StyleArray.java +++ b/src/main/java/com/reandroid/lib/arsc/array/StyleArray.java @@ -58,16 +58,27 @@ public class StyleArray extends OffsetBlockArray implements JSONConve public JSONArray toJson() { if(childesCount()==0){ return null; - } - int i=0; - for(StyleItem styleItem:listItems()){ - } return null; } @Override public void fromJson(JSONArray json) { + } + public void merge(StyleArray styleArray){ + if(styleArray==null||styleArray==this){ + return; + } + if(childesCount()!=0){ + return; + } + int count=styleArray.childesCount(); + ensureSize(count); + for(int i=0;i typeBlock.fromJson(jsonObject); } } + public void merge(TypeBlockArray typeBlockArray){ + if(typeBlockArray==null||typeBlockArray==this){ + return; + } + for(TypeBlock typeBlock:typeBlockArray.listItems()){ + TypeBlock block=getOrCreate(typeBlock.getResConfig()); + block.merge(typeBlock); + } + } @Override public int compare(TypeBlock typeBlock1, TypeBlock typeBlock2) { return typeBlock1.compareTo(typeBlock2); diff --git a/src/main/java/com/reandroid/lib/arsc/chunk/LibraryBlock.java b/src/main/java/com/reandroid/lib/arsc/chunk/LibraryBlock.java index 27e0cae..4f34391 100755 --- a/src/main/java/com/reandroid/lib/arsc/chunk/LibraryBlock.java +++ b/src/main/java/com/reandroid/lib/arsc/chunk/LibraryBlock.java @@ -50,10 +50,15 @@ public class LibraryBlock extends BaseChunk { mLibCount.set(count); mLibraryInfoArray.setChildesCount(count); } - @Override protected void onChunkRefreshed() { mLibCount.set(mLibraryInfoArray.childesCount()); } + public void merge(LibraryBlock libraryBlock){ + if(libraryBlock==null||libraryBlock==this){ + return; + } + getLibraryInfoArray().merge(libraryBlock.getLibraryInfoArray()); + } } diff --git a/src/main/java/com/reandroid/lib/arsc/chunk/PackageBlock.java b/src/main/java/com/reandroid/lib/arsc/chunk/PackageBlock.java index 7482b04..6048a2b 100755 --- a/src/main/java/com/reandroid/lib/arsc/chunk/PackageBlock.java +++ b/src/main/java/com/reandroid/lib/arsc/chunk/PackageBlock.java @@ -1,6 +1,7 @@ package com.reandroid.lib.arsc.chunk; import com.reandroid.lib.arsc.array.LibraryInfoArray; +import com.reandroid.lib.arsc.array.PackageArray; import com.reandroid.lib.arsc.array.SpecTypePairArray; import com.reandroid.lib.arsc.base.Block; import com.reandroid.lib.arsc.container.PackageLastBlocks; @@ -24,7 +25,8 @@ import java.io.IOException; import java.util.*; -public class PackageBlock extends BaseChunk implements BlockLoad, JSONConvert { +public class PackageBlock extends BaseChunk + implements BlockLoad, JSONConvert, Comparable { private final IntegerItem mPackageId; private final PackageName mPackageName; @@ -136,7 +138,7 @@ public class PackageBlock extends BaseChunk implements BlockLoad, JSONConvert listLibraryInfo(){ - return mLibraryBlock.listLibraryInfo(); + return getLibraryBlock().listLibraryInfo(); } public void addLibrary(LibraryBlock libraryBlock){ @@ -148,7 +150,10 @@ public class PackageBlock extends BaseChunk implements BlockLoad, JSONConvert listResourceIds(){ return mEntriesGroup.keySet(); @@ -291,6 +296,22 @@ public class PackageBlock extends BaseChunk implements BlockLoad, JSONConvert getSpecBlock().setTypeId((byte) json.getInt("id")); getTypeBlockArray().fromJson(json.getJSONArray("types")); } + public void merge(SpecTypePair typePair){ + if(typePair==null||typePair==this){ + return; + } + if(getTypeId() != typePair.getTypeId()){ + throw new IllegalArgumentException("Can not merge different id types: " + +getTypeId()+"!="+typePair.getTypeId()); + } + getTypeBlockArray().merge(typePair.getTypeBlockArray()); + } @Override public int compareTo(SpecTypePair specTypePair) { return Integer.compare(getTypeId(), specTypePair.getTypeId()); diff --git a/src/main/java/com/reandroid/lib/arsc/item/ByteItem.java b/src/main/java/com/reandroid/lib/arsc/item/ByteItem.java index 1e82146..45aa2c0 100755 --- a/src/main/java/com/reandroid/lib/arsc/item/ByteItem.java +++ b/src/main/java/com/reandroid/lib/arsc/item/ByteItem.java @@ -5,6 +5,23 @@ public class ByteItem extends BlockItem { public ByteItem() { super(1); } + public boolean getBit(int index){ + return ((get()>>index) & 0x1) == 1; + } + public void putBit(int index, boolean bit){ + int val=get(); + int left=val>>index; + if(bit){ + left=left|0x1; + }else { + left=left & 0xFE; + } + left=left<>index) & val; + val=left|right; + set((byte) val); + } public void set(byte b){ getBytesInternal()[0]=b; } diff --git a/src/main/java/com/reandroid/lib/arsc/item/StyleItem.java b/src/main/java/com/reandroid/lib/arsc/item/StyleItem.java index 88567b2..bf7f607 100755 --- a/src/main/java/com/reandroid/lib/arsc/item/StyleItem.java +++ b/src/main/java/com/reandroid/lib/arsc/item/StyleItem.java @@ -307,6 +307,14 @@ public class StyleItem extends IntegerArray implements JSONConvert { addSpanInfo(spanInfo.getTag(), spanInfo.getFirst(), spanInfo.getLast()); } } + public void merge(StyleItem styleItem){ + if(styleItem==null||styleItem==this){ + return; + } + for(int[] info:styleItem.getIntSpanInfoList()){ + addStylePiece(info[0], info[1], info[2]); + } + } @Override public String toString(){ return "Spans count = "+getSpanInfoList().size(); diff --git a/src/main/java/com/reandroid/lib/arsc/pool/TableStringPool.java b/src/main/java/com/reandroid/lib/arsc/pool/TableStringPool.java index 3c6ad36..3bdb95b 100755 --- a/src/main/java/com/reandroid/lib/arsc/pool/TableStringPool.java +++ b/src/main/java/com/reandroid/lib/arsc/pool/TableStringPool.java @@ -15,4 +15,22 @@ public class TableStringPool extends BaseStringPool { StringArray newInstance(IntegerArray offsets, IntegerItem itemCount, IntegerItem itemStart, boolean is_utf8) { return new TableStringArray(offsets, itemCount, itemStart, is_utf8); } + public void merge(TableStringPool stringPool){ + if(stringPool==null||stringPool==this){ + return; + } + StringArray existArray = getStringsArray(); + if(existArray.childesCount()!=0){ + return; + } + StringArray comingArray = stringPool.getStringsArray(); + int count=comingArray.childesCount(); + existArray.ensureSize(count); + for(int i=0;i { @@ -228,41 +231,23 @@ public class EntryBlock extends Block implements JSONConvert { removeSpecReferences(); } public void setEntryTypeBag(boolean b){ - int val=mFlagEntryType.get(); - if(b){ - val=val|0x1; - }else { - val=val&0xFE; - } - mFlagEntryType.set((byte) val); + mFlagEntryType.putBit(0, b); refreshHeaderSize(); } public boolean isEntryTypeBag(){ - return ((mFlagEntryType.get() & 0x1) != 0); + return mFlagEntryType.getBit(0); } public void setEntryTypeShared(boolean b){ - int val=mFlagEntryType.get(); - if(b){ - val=val|0x2; - }else { - val=val&0xFD; - } - mFlagEntryType.set((byte) val); + mFlagEntryType.putBit(1, b); } public boolean isEntryTypeShared(){ - return (mFlagEntryType.get() & 0x2) !=0; + return mFlagEntryType.getBit(1); } public void setEntryTypePublic(boolean b){ - int val=mFlagEntryType.get(); - if(b){ - val=val|0x4; - }else { - val=val&0xFB; - } - mFlagEntryType.set((byte) val); + mFlagEntryType.putBit(2, b); } public boolean isEntryTypePublic(){ - return (mFlagEntryType.get() & 0x4) !=0; + return mFlagEntryType.getBit(2); } private void setByteFlagsB(byte b){ mByteFlagsB.set(b); @@ -310,6 +295,7 @@ public class EntryBlock extends Block implements JSONConvert { if(mResValue!=null){ mResValue.setIndex(-1); mResValue.setParent(null); + mResValue.onRemoved(); } if(resValue!=null){ setNull(false); @@ -685,6 +671,41 @@ public class EntryBlock extends Block implements JSONConvert { baseResValue.fromJson(json.getJSONObject(NAME_value)); mResValue.onDataLoaded(); } + public void merge(EntryBlock entryBlock){ + if(entryBlock==null||entryBlock==this||entryBlock.isNull()){ + return; + } + String name=entryBlock.getName(); + if(name==null){ + name=""; + } + if(!entryBlock.isEntryTypeBag()){ + ResValueInt comingValue = (ResValueInt) entryBlock.getResValue(); + ResValueInt exist = getOrCreateResValueInt(); + setResValue(exist); + exist.merge(comingValue); + }else { + ResValueBag comingValue = (ResValueBag) entryBlock.getResValue(); + ResValueBag exist=getOrCreateResValueBag(); + setResValue(exist); + exist.merge(comingValue); + } + SpecString spec = getPackageBlock() + .getSpecStringPool().getOrCreate(name); + setSpecReference(spec.getIndex()); + } + private ResValueBag getOrCreateResValueBag(){ + if(mResValue instanceof ResValueBag){ + return (ResValueBag) mResValue; + } + return new ResValueBag(); + } + private ResValueInt getOrCreateResValueInt(){ + if(mResValue instanceof ResValueInt){ + return (ResValueInt) mResValue; + } + return new ResValueInt(); + } @Override public String toString(){ StringBuilder builder=new StringBuilder(); diff --git a/src/main/java/com/reandroid/lib/arsc/value/LibraryInfo.java b/src/main/java/com/reandroid/lib/arsc/value/LibraryInfo.java index 487e318..b9ad367 100755 --- a/src/main/java/com/reandroid/lib/arsc/value/LibraryInfo.java +++ b/src/main/java/com/reandroid/lib/arsc/value/LibraryInfo.java @@ -1,5 +1,6 @@ package com.reandroid.lib.arsc.value; +import com.reandroid.lib.arsc.array.LibraryInfoArray; import com.reandroid.lib.arsc.base.Block; import com.reandroid.lib.arsc.base.BlockCounter; import com.reandroid.lib.arsc.io.BlockReader; @@ -88,6 +89,16 @@ public class LibraryInfo extends Block implements JSONConvert { setPackageId(json.getInt("id")); setPackageName(json.getString("name")); } + public void merge(LibraryInfo info){ + if(info==null||info==this){ + return; + } + if(getPackageId()!=info.getPackageId()){ + throw new IllegalArgumentException("Can not add different id libraries: " + +getPackageId()+"!="+info.getPackageId()); + } + setPackageName(info.getPackageName()); + } @Override public String toString(){ StringBuilder builder=new StringBuilder(); diff --git a/src/main/java/com/reandroid/lib/arsc/value/ResValueBag.java b/src/main/java/com/reandroid/lib/arsc/value/ResValueBag.java index 14a6c3d..02fc0a8 100755 --- a/src/main/java/com/reandroid/lib/arsc/value/ResValueBag.java +++ b/src/main/java/com/reandroid/lib/arsc/value/ResValueBag.java @@ -113,7 +113,6 @@ public class ResValueBag extends BaseResValue { if(isNull()){ return 0; } - refreshCount(); int result; result=mParentId.writeBytes(writer); result+=mCount.writeBytes(writer); @@ -150,6 +149,14 @@ public class ResValueBag extends BaseResValue { getResValueBagItemArray().fromJson(json.getJSONArray(NAME_items)); refreshCount(); } + public void merge(ResValueBag resValueBag){ + if(resValueBag==null||resValueBag==this){ + return; + } + setParentId(resValueBag.getParentId()); + getResValueBagItemArray().merge(resValueBag.getResValueBagItemArray()); + refreshCount(); + } @Override public String toString(){ diff --git a/src/main/java/com/reandroid/lib/arsc/value/ResValueBagItem.java b/src/main/java/com/reandroid/lib/arsc/value/ResValueBagItem.java index 16c4521..01fbb6e 100755 --- a/src/main/java/com/reandroid/lib/arsc/value/ResValueBagItem.java +++ b/src/main/java/com/reandroid/lib/arsc/value/ResValueBagItem.java @@ -11,6 +11,10 @@ public class ResValueBagItem extends BaseResValueItem{ super(BYTES_COUNT); setHeaderSize(BYTES_SIZE); } + @Override + public void onRemoved(){ + removeTableReference(); + } public String getValueAsString(){ return getTableString(getData()).getHtml(); } @@ -186,6 +190,19 @@ public class ResValueBagItem extends BaseResValueItem{ setData(json.getInt(NAME_data)); } } + public void merge(ResValueBagItem bagItem){ + if(bagItem==null||bagItem==this){ + return; + } + setId(bagItem.getId()); + ValueType valueType=bagItem.getValueType(); + if(valueType==ValueType.STRING){ + setValueAsString(bagItem.getValueAsString()); + }else { + setType(valueType); + setData(bagItem.getData()); + } + } @Override public String toString(){ diff --git a/src/main/java/com/reandroid/lib/arsc/value/ResValueInt.java b/src/main/java/com/reandroid/lib/arsc/value/ResValueInt.java index 42a86d6..0f5eacd 100755 --- a/src/main/java/com/reandroid/lib/arsc/value/ResValueInt.java +++ b/src/main/java/com/reandroid/lib/arsc/value/ResValueInt.java @@ -1,6 +1,7 @@ package com.reandroid.lib.arsc.value; import com.reandroid.lib.arsc.decoder.ValueDecoder; +import com.reandroid.lib.arsc.item.SpecString; import com.reandroid.lib.arsc.item.TableString; import com.reandroid.lib.json.JSONObject; @@ -9,6 +10,9 @@ public class ResValueInt extends BaseResValueItem { super(BYTES_COUNT); setHeaderSize(BYTES_SIZE); } + protected void onRemoved(){ + removeTableReference(); + } public String getValueAsString(){ return getString(getData()); } @@ -116,6 +120,18 @@ public class ResValueInt extends BaseResValueItem { setData(json.getInt(NAME_data)); } } + public void merge(ResValueInt resValueInt){ + if(resValueInt==null||resValueInt==this){ + return; + } + ValueType valueType=resValueInt.getValueType(); + if(valueType==ValueType.STRING){ + setValueAsString(resValueInt.getValueAsString()); + }else { + setType(valueType); + setData(resValueInt.getData()); + } + } @Override public String toString(){ StringBuilder builder=new StringBuilder();