All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.reandroid.arsc.chunk.TableBlock Maven / Gradle / Ivy

/*
 *  Copyright (C) 2022 github.com/REAndroid
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.reandroid.arsc.chunk;

import com.reandroid.arsc.ApkFile;
import com.reandroid.arsc.BuildInfo;
import com.reandroid.arsc.array.PackageArray;
import com.reandroid.arsc.model.ResourceEntry;
import com.reandroid.arsc.header.HeaderBlock;
import com.reandroid.arsc.header.InfoHeader;
import com.reandroid.arsc.header.TableHeader;
import com.reandroid.arsc.io.BlockReader;
import com.reandroid.arsc.pool.TableStringPool;
import com.reandroid.arsc.value.*;
import com.reandroid.common.ReferenceResolver;
import com.reandroid.json.JSONConvert;
import com.reandroid.json.JSONArray;
import com.reandroid.json.JSONObject;
import com.reandroid.utils.*;
import com.reandroid.utils.collection.CombiningIterator;
import com.reandroid.utils.collection.EmptyIterator;
import com.reandroid.utils.collection.FilterIterator;
import com.reandroid.utils.collection.IterableIterator;
import com.reandroid.utils.io.IOUtil;
import com.reandroid.xml.XMLUtil;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import java.io.*;
import java.util.*;
import java.util.function.Predicate;

public class TableBlock extends Chunk
        implements MainChunk, JSONConvert {
    private final TableStringPool mTableStringPool;
    private final PackageArray mPackageArray;
    private final List mFrameWorks;
    private ApkFile mApkFile;
    private ReferenceResolver referenceResolver;
    private PackageBlock mCurrentPackage;

    public TableBlock() {
        super(new TableHeader(), 2);
        TableHeader header = getHeaderBlock();
        this.mTableStringPool = new TableStringPool(true);
        this.mPackageArray = new PackageArray(header.getPackageCount());
        this.mFrameWorks = new ArrayList<>();
        addChild(mTableStringPool);
        addChild(mPackageArray);
    }

    public PackageBlock getCurrentPackage(){
        return mCurrentPackage;
    }
    public void setCurrentPackage(PackageBlock packageBlock){
        mCurrentPackage = packageBlock;
    }
    public PackageBlock getPackageBlockByTag(Object tag){
        for(PackageBlock packageBlock : listPackages()){
            if(Objects.equals(tag, packageBlock.getTag())){
                return packageBlock;
            }
        }
        return null;
    }
    public Iterator getResources(){
        return new IterableIterator(getPackages()) {
            @Override
            public Iterator iterator(PackageBlock element) {
                return element.getResources();
            }
        };
    }
    public ResourceEntry getResource(int resourceId){
        if(resourceId == 0){
            return null;
        }
        Iterator iterator = getAllPackages();
        while (iterator.hasNext()){
            PackageBlock packageBlock = iterator.next();
            ResourceEntry resourceEntry = packageBlock.getResource(resourceId);
            if(resourceEntry != null){
                return resourceEntry;
            }
        }
        int staged = resolveStagedAlias(resourceId, 0);
        if(staged == 0 || staged == resourceId){
            return null;
        }
        iterator = getAllPackages();
        while (iterator.hasNext()){
            PackageBlock packageBlock = iterator.next();
            ResourceEntry resourceEntry = packageBlock.getResource(staged);
            if(resourceEntry != null){
                return resourceEntry;
            }
        }
        return null;
    }
    public ResourceEntry getResource(PackageBlock context, int resourceId){
        if(resourceId == 0){
            return null;
        }
        Iterator iterator = getAllPackages(context);
        while (iterator.hasNext()){
            PackageBlock packageBlock = iterator.next();
            ResourceEntry resourceEntry = packageBlock.getResource(resourceId);
            if(resourceEntry != null){
                return resourceEntry;
            }
        }
        int staged = resolveStagedAlias(resourceId, 0);
        if(staged == 0 || staged == resourceId){
            return null;
        }
        iterator = getAllPackages(context);
        while (iterator.hasNext()){
            PackageBlock packageBlock = iterator.next();
            ResourceEntry resourceEntry = packageBlock.getResource(staged);
            if(resourceEntry != null){
                return resourceEntry;
            }
        }
        return null;
    }
    public ResourceEntry getResource(String packageName, String type, String name){
        Iterator iterator = getAllPackages(packageName);
        while (iterator.hasNext()){
            PackageBlock packageBlock = iterator.next();
            ResourceEntry resourceEntry = packageBlock.getResource(type, name);
            if(resourceEntry != null){
                return resourceEntry;
            }
        }
        return null;
    }
    public ResourceEntry getResource(PackageBlock context, String type, String name){
        Iterator iterator = getAllPackages(context);
        while (iterator.hasNext()){
            PackageBlock packageBlock = iterator.next();
            ResourceEntry resourceEntry = packageBlock.getResource(type, name);
            if(resourceEntry != null){
                return resourceEntry;
            }
        }
        return null;
    }
    public ResourceEntry getResource(PackageBlock context, String packageName, String type, String name){
        Iterator iterator = getAllPackages(context, packageName);
        while (iterator.hasNext()){
            PackageBlock packageBlock = iterator.next();
            ResourceEntry resourceEntry = packageBlock.getResource(type, name);
            if(resourceEntry != null){
                return resourceEntry;
            }
        }
        return null;
    }
    public ResourceEntry getLocalResource(int resourceId){
        return getLocalResource( null, resourceId);
    }
    public ResourceEntry getLocalResource(PackageBlock context, int resourceId){
        Iterator iterator = getPackages(context);
        while (iterator.hasNext()){
            PackageBlock packageBlock = iterator.next();
            ResourceEntry resourceEntry = packageBlock.getResource(resourceId);
            if(resourceEntry != null){
                return resourceEntry;
            }
        }
        return null;
    }
    public ResourceEntry getLocalResource(PackageBlock context, String type, String name){
        Iterator iterator = getPackages(context);
        while (iterator.hasNext()){
            PackageBlock packageBlock = iterator.next();
            ResourceEntry resourceEntry = packageBlock.getResource(type, name);
            if(resourceEntry != null){
                return resourceEntry;
            }
        }
        return null;
    }
    public ResourceEntry getLocalResource(String type, String name){
        return getLocalResource((String) null, type, name);
    }
    public ResourceEntry getLocalResource(String packageName, String type, String name){
        Iterator iterator = getPackages(packageName);
        while (iterator.hasNext()){
            PackageBlock packageBlock = iterator.next();
            ResourceEntry resourceEntry = packageBlock.getResource(type, name);
            if(resourceEntry != null){
                return resourceEntry;
            }
        }
        return null;
    }
    public ResourceEntry getAttrResource(String prefix, String name){
        Iterator iterator = getAllPackages(prefix);
        while (iterator.hasNext()){
            PackageBlock packageBlock = iterator.next();
            ResourceEntry resourceEntry = packageBlock
                    .getAttrResource(name);
            if(resourceEntry != null){
                return resourceEntry;
            }
        }
        if(prefix != null){
            return getAttrResource(null, name);
        }
        return null;
    }
    public ResourceEntry getAttrResource(PackageBlock context, String prefix, String name){
        Iterator iterator = getAllPackages(context, prefix);
        while (iterator.hasNext()){
            PackageBlock packageBlock = iterator.next();
            ResourceEntry resourceEntry = packageBlock
                    .getAttrResource(name);
            if(resourceEntry != null){
                return resourceEntry;
            }
        }
        if(prefix != null){
            return getAttrResource(null, name);
        }
        return null;
    }
    public ResourceEntry getIdResource(PackageBlock context, String prefix, String name){
        Iterator iterator = getAllPackages(context, prefix);
        while (iterator.hasNext()){
            PackageBlock packageBlock = iterator.next();
            ResourceEntry resourceEntry = packageBlock
                    .getIdResource(name);
            if(resourceEntry != null){
                return resourceEntry;
            }
        }
        if(prefix != null){
            return getAttrResource(null, name);
        }
        return null;
    }
    public int resolveResourceId(String packageName, String type, String name){
        Iterator iterator = getEntries(packageName, type, name);
        if(iterator.hasNext()){
            return iterator.next().getResourceId();
        }
        return 0;
    }
    public Entry getEntry(String packageName, String type, String name){
        Iterator iterator = getAllPackages(packageName);
        Entry result = null;
        while (iterator.hasNext()){
            Entry entry = iterator.next().getEntry(type, name);
            if(entry == null){
                continue;
            }
            if(!entry.isNull()){
                return entry;
            }
            if(result == null){
                result = entry;
            }
        }
        return result;
    }
    public Iterator getEntries(int resourceId){
        return getEntries(resourceId, true);
    }
    public Iterator getEntries(int resourceId, boolean skipNull){

        final int packageId = (resourceId >> 24) & 0xff;
        final int typeId = (resourceId >> 16) & 0xff;
        final int entryId = resourceId & 0xffff;
        return new IterableIterator(getAllPackages(packageId)) {
            @Override
            public Iterator iterator(PackageBlock element) {
                if(super.getCount() > 0){
                    super.stop();
                    return null;
                }
                return element.getEntries(typeId, entryId, skipNull);
            }
        };
    }
    public Iterator getEntries(String packageName, String type, String name){
        return new IterableIterator(getAllPackages(packageName)) {
            @Override
            public Iterator iterator(PackageBlock element) {
                if(super.getCount() > 0){
                    super.stop();
                    return null;
                }
                return element.getEntries(type, name);
            }
        };
    }
    public Iterator getPackages(String packageName){
        return new  FilterIterator(getPackages()) {
            @Override
            public boolean test(PackageBlock packageBlock){
                if(packageName != null && packageName.length() > 0){
                    return packageBlock.packageNameMatches(packageName);
                }
                return TableBlock.this == packageBlock.getTableBlock();
            }
        };
    }
    public Iterator getPackages(int packageId){
        if(packageId == 0){
            return EmptyIterator.of();
        }
        return new FilterIterator(getPackages()) {
            @Override
            public boolean test(PackageBlock packageBlock){
                return packageId == packageBlock.getId();
            }
        };
    }
    public Iterator getPackages(){
        return getPackages((PackageBlock) null);
    }
    public Iterator getPackages(PackageBlock context){
        PackageBlock current;
        if(context == null){
            current = getCurrentPackage();
        }else {
            current = context;
        }
        Iterator iterator = getPackageArray().iterator();
        if(current == null){
            return iterator;
        }
        return new CombiningIterator<>(
                SingleIterator.of(current),
                new FilterIterator.Except<>(iterator, current)
        );
    }
    public Iterator getAllPackages(){
        return getAllPackages((PackageBlock) null);
    }
    public Iterator getAllPackages(PackageBlock context, String packageName){
        return new  FilterIterator(getAllPackages(context)) {
            @Override
            public boolean test(PackageBlock packageBlock){
                if(packageName != null){
                    return packageBlock.packageNameMatches(packageName);
                }
                return TableBlock.this == packageBlock.getTableBlock();
            }
        };
    }
    public Iterator getAllPackages(PackageBlock context){
        return new CombiningIterator<>(getPackages(context),
                new IterableIterator(frameworkIterator()) {
                    @Override
                    public Iterator iterator(TableBlock element) {
                        return element.getPackages();
                    }
                });
    }
    public Iterator getAllPackages(int packageId){
        return new  FilterIterator(getAllPackages()) {
            @Override
            public boolean test(PackageBlock packageBlock){
                return packageId == packageBlock.getId();
            }
        };
    }
    public Iterator getAllPackages(String packageName){
        return new  FilterIterator(getAllPackages()) {
            @Override
            public boolean test(PackageBlock packageBlock){
                if(packageName != null){
                    return packageBlock.packageNameMatches(packageName);
                }
                return TableBlock.this == packageBlock.getTableBlock();
            }
        };
    }
    public int removeUnusedSpecs(){
        int result = 0;
        for(PackageBlock packageBlock : listPackages()){
            result += packageBlock.removeUnusedSpecs();
        }
        return result;
    }
    public String refreshFull(){
        int sizeOld = getHeaderBlock().getChunkSize();
        StringBuilder message = new StringBuilder();
        boolean appendOnce = false;
        int count = getTableStringPool().removeUnusedStrings().size();
        if(count != 0){
            message.append("Removed unused table strings = ");
            message.append(count);
            appendOnce = true;
        }
        for(PackageBlock packageBlock : listPackages()){
            String packageMessage = packageBlock.refreshFull(false);
            if(packageMessage == null){
                continue;
            }
            if(appendOnce){
                message.append("\n");
            }
            message.append("Package: ");
            message.append(packageBlock.getName());
            message.append("\n  ");
            packageMessage = packageMessage.replaceAll("\n", "\n  ");
            message.append(packageMessage);
            appendOnce = true;
        }
        refresh();
        int sizeNew = getHeaderBlock().getChunkSize();
        if(sizeOld != sizeNew){
            if(appendOnce){
                message.append("\n");
            }
            message.append("Table size changed = ");
            message.append(sizeOld);
            message.append(", ");
            message.append(sizeNew);
            appendOnce = true;
        }
        if(appendOnce){
            return message.toString();
        }
        return null;
    }
    public void linkTableStringsInternal(TableStringPool tableStringPool){
        for(PackageBlock packageBlock : listPackages()){
            packageBlock.linkTableStringsInternal(tableStringPool);
        }
    }
    public List resolveReference(int referenceId){
        return resolveReference(referenceId, null);
    }
    public List resolveReferenceWithConfig(int referenceId, ResConfig resConfig){
        ReferenceResolver resolver = this.referenceResolver;
        if(resolver == null){
            resolver = new ReferenceResolver(this);
            this.referenceResolver = resolver;
        }
        return resolver.resolveWithConfig(referenceId, resConfig);
    }
    public List resolveReference(int referenceId, Predicate filter){
        ReferenceResolver resolver = this.referenceResolver;
        if(resolver == null){
            resolver = new ReferenceResolver(this);
            this.referenceResolver = resolver;
        }
        return resolver.resolveAll(referenceId, filter);
    }
    public void destroy(){
        getPackageArray().destroy();
        getStringPool().destroy();
        clearFrameworks();
        refresh();
    }
    public int countPackages(){
        return getPackageArray().childesCount();
    }

    public PackageBlock pickOne(){
        PackageBlock current = getCurrentPackage();
        if(current != null && current.getTableBlock() == this){
            return current;
        }
        return getPackageArray().pickOne();
    }
    public PackageBlock pickOne(int packageId){
        return getPackageArray().pickOne(packageId);
    }
    public void sortPackages(){
        getPackageArray().sort();
    }
    public Collection listPackages(){
        return getPackageArray().listItems();
    }
    @Override
    public TableStringPool getStringPool() {
        return mTableStringPool;
    }
    @Override
    public ApkFile getApkFile(){
        return mApkFile;
    }
    @Override
    public void setApkFile(ApkFile apkFile){
        this.mApkFile = apkFile;
    }
    @Override
    public TableBlock getTableBlock() {
        return this;
    }

    public TableStringPool getTableStringPool(){
        return mTableStringPool;
    }
    public PackageBlock getPackageBlockById(int pkgId){
        return getPackageArray().getPackageBlockById(pkgId);
    }
    public PackageBlock newPackage(int id, String name){
        PackageBlock packageBlock = getPackageArray().createNext();
        packageBlock.setId(id);
        if(name != null){
            packageBlock.setName(name);
        }
        return packageBlock;
    }
    public PackageBlock getOrCreatePackage(int id, String name){
        PackageBlock packageBlockId = getPackageArray()
                .getPackageBlockById(id);
        if(packageBlockId == null){
            return newPackage(id, name);
        }
        if(name == null){
            return packageBlockId;
        }
        PackageBlock packageBlockName = getPackageArray()
                .getPackageBlockByName(name);
        if(packageBlockId == packageBlockName){
            return packageBlockId;
        }
        return newPackage(id, name);
    }
    public PackageArray getPackageArray(){
        return mPackageArray;
    }
    public void trimConfigSizes(int resConfigSize){
        for(PackageBlock packageBlock : listPackages()){
            packageBlock.trimConfigSizes(resConfigSize);
        }
    }

    private void refreshPackageCount(){
        int count = getPackageArray().childesCount();
        getHeaderBlock().getPackageCount().set(count);
    }
    @Override
    protected void onChunkRefreshed() {
        refreshPackageCount();
    }
    @Override
    public void onReadBytes(BlockReader reader) throws IOException {
        TableHeader tableHeader = getHeaderBlock();
        tableHeader.readBytes(reader);
        if(tableHeader.getChunkType()!=ChunkType.TABLE){
            throw new IOException("Not resource table: "+tableHeader);
        }
        boolean stringPoolLoaded=false;
        InfoHeader infoHeader = reader.readHeaderBlock();
        PackageArray packageArray=mPackageArray;
        packageArray.clearChildes();
        while(infoHeader!=null && reader.isAvailable()){
            ChunkType chunkType=infoHeader.getChunkType();
            if(chunkType==ChunkType.STRING){
                if(!stringPoolLoaded){
                    mTableStringPool.readBytes(reader);
                    stringPoolLoaded=true;
                }
            }else if(chunkType==ChunkType.PACKAGE){
                PackageBlock packageBlock=packageArray.createNext();
                packageBlock.readBytes(reader);
            }else {
                UnknownChunk unknownChunk=new UnknownChunk();
                unknownChunk.readBytes(reader);
                addChild(unknownChunk);
            }
            infoHeader=reader.readHeaderBlock();
        }
        reader.close();
    }

    public void readBytes(File file) throws IOException{
        BlockReader reader=new BlockReader(file);
        super.readBytes(reader);
    }
    public void readBytes(InputStream inputStream) throws IOException{
        BlockReader reader=new BlockReader(inputStream);
        super.readBytes(reader);
    }
    public final int writeBytes(File file) throws IOException{
        if(isNull()){
            throw new IOException("Can NOT save null block");
        }
        File dir=file.getParentFile();
        if(dir!=null && !dir.exists()){
            dir.mkdirs();
        }
        OutputStream outputStream=new FileOutputStream(file);
        int length = super.writeBytes(outputStream);
        outputStream.close();
        return length;
    }
    public int searchResourceIdAlias(int resourceId){
        return resolveStagedAlias(resourceId, 0);
    }
    public int resolveStagedAlias(int stagedResId, int def){
        StagedAliasEntry stagedAliasEntry = getStagedAlias(stagedResId);
        if(stagedAliasEntry != null){
            return stagedAliasEntry.getFinalizedResId();
        }
        return def;
    }
    public StagedAliasEntry getStagedAlias(int stagedResId){
        Iterator iterator = getAllPackages();
        while (iterator.hasNext()){
            PackageBlock packageBlock = iterator.next();
            StagedAliasEntry stagedAliasEntry =
                    packageBlock.searchByStagedResId(stagedResId);
            if(stagedAliasEntry != null){
                return stagedAliasEntry;
            }
        }
        return null;
    }
    public List getFrameWorks(){
        return mFrameWorks;
    }
    public Iterator frameworkIterator(){
        List frameworkList = getFrameWorks();
        if(frameworkList.size() == 0){
            return EmptyIterator.of();
        }
        return frameworkList.iterator();
    }
    public boolean isAndroid(){
        PackageBlock packageBlock = pickOne();
        if(packageBlock == null){
            return false;
        }
        return "android".equals(packageBlock.getName())
                && packageBlock.getId() == 0x01;
    }
    public boolean hasFramework(){
        return getFrameWorks().size() != 0;
    }
    public void addFramework(TableBlock tableBlock){
        if(tableBlock==null||tableBlock==this){
            return;
        }
        for(TableBlock frm:tableBlock.getFrameWorks()){
            if(frm==this || frm==tableBlock || tableBlock.equals(frm)){
                return;
            }
        }
        mFrameWorks.add(tableBlock);
    }
    public void removeFramework(TableBlock tableBlock){
        mFrameWorks.remove(tableBlock);
    }
    public void clearFrameworks(){
        mFrameWorks.clear();
    }
    public PackageBlock parsePublicXml(XmlPullParser parser) throws IOException,
            XmlPullParserException {
        PackageBlock packageBlock = newPackage(0, null);
        packageBlock.parsePublicXml(parser);
        return packageBlock;
    }
    @Override
    public JSONObject toJson() {
        JSONObject jsonObject=new JSONObject();

        jsonObject.put(BuildInfo.NAME_arsc_lib_version, BuildInfo.getVersion());

        jsonObject.put(NAME_packages, getPackageArray().toJson());
        JSONArray jsonArray = getStringPool().toJson();
        if(jsonArray!=null){
            jsonObject.put(NAME_styled_strings, jsonArray);
        }
        return jsonObject;
    }
    @Override
    public void fromJson(JSONObject json) {
        getPackageArray().fromJson(json.getJSONArray(NAME_packages));
        refresh();
    }
    public void merge(TableBlock tableBlock){
        if(tableBlock==null||tableBlock==this){
            return;
        }
        if(countPackages()==0 && getStringPool().countStrings()==0){
            getStringPool().merge(tableBlock.getStringPool());
        }
        getPackageArray().merge(tableBlock.getPackageArray());
        refresh();
    }
    @Override
    public String toString(){
        StringBuilder builder=new StringBuilder();
        builder.append(getClass().getSimpleName());
        builder.append(": packages = ");
        builder.append(mPackageArray.childesCount());
        builder.append(", size = ");
        builder.append(getHeaderBlock().getChunkSize());
        builder.append(" bytes");
        return builder.toString();
    }

    @Deprecated
    public static TableBlock loadWithAndroidFramework(InputStream inputStream) throws IOException{
        return load(inputStream);
    }
    public static TableBlock load(File file) throws IOException{
        return load(new FileInputStream(file));
    }
    public static TableBlock load(InputStream inputStream) throws IOException{
        TableBlock tableBlock=new TableBlock();
        tableBlock.readBytes(inputStream);
        return tableBlock;
    }

    public static boolean isResTableBlock(File file){
        if(file==null){
            return false;
        }
        boolean result=false;
        try {
            InputStream inputStream=new FileInputStream(file);
            result=isResTableBlock(inputStream);
            inputStream.close();
        } catch (IOException ignored) {
        }
        return result;
    }
    public static boolean isResTableBlock(InputStream inputStream){
        try {
            HeaderBlock headerBlock= BlockReader.readHeaderBlock(inputStream);
            return isResTableBlock(headerBlock);
        } catch (IOException ignored) {
            return false;
        }
    }
    public static boolean isResTableBlock(BlockReader blockReader){
        if(blockReader==null){
            return false;
        }
        try {
            HeaderBlock headerBlock = blockReader.readHeaderBlock();
            return isResTableBlock(headerBlock);
        } catch (IOException ignored) {
            return false;
        }
    }
    public static boolean isResTableBlock(HeaderBlock headerBlock){
        if(headerBlock==null){
            return false;
        }
        ChunkType chunkType=headerBlock.getChunkType();
        return chunkType==ChunkType.TABLE;
    }
    public static final String FILE_NAME = "resources.arsc";
    public static final String FILE_NAME_JSON = "resources.arsc.json";

    private static final String NAME_packages = "packages";
    public static final String NAME_styled_strings = "styled_strings";

    public static final String JSON_FILE_NAME = "resources.arsc.json";
    public static final String DIRECTORY_NAME = "resources";

    public static final String RES_JSON_DIRECTORY_NAME = "res-json";
    public static final String RES_FILES_DIRECTORY_NAME = "res-files";
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy