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

com.reandroid.arsc.chunk.PackageBlock 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.ARSCLib;
import com.reandroid.arsc.array.LibraryInfoArray;
import com.reandroid.arsc.array.SpecTypePairArray;
import com.reandroid.arsc.base.Block;
import com.reandroid.arsc.coder.CommonType;
import com.reandroid.arsc.coder.EncodeResult;
import com.reandroid.arsc.coder.ValueCoder;
import com.reandroid.arsc.coder.xml.XmlEncodeException;
import com.reandroid.arsc.container.BlockList;
import com.reandroid.arsc.container.PackageBody;
import com.reandroid.arsc.container.SpecTypePair;
import com.reandroid.arsc.header.PackageHeader;
import com.reandroid.arsc.io.BlockReader;
import com.reandroid.arsc.item.TypeString;
import com.reandroid.arsc.list.OverlayableList;
import com.reandroid.arsc.list.StagedAliasList;
import com.reandroid.arsc.model.ResourceEntry;
import com.reandroid.arsc.model.ResourceLibrary;
import com.reandroid.arsc.model.ResourceName;
import com.reandroid.arsc.model.ResourceType;
import com.reandroid.arsc.pool.SpecStringPool;
import com.reandroid.arsc.pool.TableStringPool;
import com.reandroid.arsc.pool.TypeStringPool;
import com.reandroid.arsc.refactor.ResourceMergeOption;
import com.reandroid.arsc.value.*;
import com.reandroid.common.Namespace;
import com.reandroid.json.JSONArray;
import com.reandroid.json.JSONConvert;
import com.reandroid.json.JSONObject;
import com.reandroid.utils.HexUtil;
import com.reandroid.utils.ObjectsUtil;
import com.reandroid.utils.StringsUtil;
import com.reandroid.utils.collection.*;
import com.reandroid.utils.io.IOUtil;
import com.reandroid.xml.XMLElement;
import com.reandroid.xml.XMLFactory;
import com.reandroid.xml.XMLUtil;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;


public class PackageBlock extends Chunk
        implements ParentChunk,
        JSONConvert,
        Comparable,
        ResourceLibrary {

    private final TypeStringPool mTypeStringPool;
    private final SpecStringPool mSpecStringPool;

    private final PackageBody mBody;

    private String mPrefix;
    private boolean mHasValidPrefix;

    private Object mTag;

    public PackageBlock() {
        super(new PackageHeader(), 3);
        PackageHeader header = getHeaderBlock();

        this.mTypeStringPool=new TypeStringPool(false, header.getTypeIdOffsetItem());
        this.mSpecStringPool=new SpecStringPool(true);

        this.mBody = new PackageBody();

        addChild(mTypeStringPool);
        addChild(mSpecStringPool);
        addChild(mBody);
    }
    public void changePackageId(int packageIdOld, int packageIdNew){
        Iterator iterator = allValues();
        while (iterator.hasNext()){
            ValueItem valueItem = iterator.next();
            changePackageId(valueItem, packageIdOld, packageIdNew);
        }
        if(packageIdOld == getId()){
            setId(packageIdNew);
        }
    }
    public Iterator allValues(){
        return new MergingIterator<>(new ComputeIterator<>(getSpecTypePairs(),
                SpecTypePair::allValues));
    }

    public Object getTag(){
        return mTag;
    }
    public void setTag(Object tag){
        this.mTag = tag;
    }

    public Iterator getTypes() {
        return ComputeIterator.of(getSpecTypePairs(), ResourceType::new);
    }
    public ResourceEntry getResource(int resourceId){
        int packageId = (resourceId >> 24 ) & 0xff;
        if(packageId == 0){
            return null;
        }
        if(packageId == getId()){
            int typeId = (resourceId >> 16 ) & 0xff;
            int entryId = resourceId & 0xffff;
            ResourceEntry resourceEntry = getResource(typeId, entryId);
            if(resourceEntry != null){
                return resourceEntry;
            }
        }
        StagedAliasEntry aliasEntry = searchByStagedResId(resourceId);
        if(aliasEntry == null){
            return null;
        }
        int alias = aliasEntry.getFinalizedResId();
        if(alias == 0 || alias == resourceId){
            return null;
        }
        packageId = (alias >> 24 ) & 0xff;
        if(packageId != getId()){
            return null;
        }
        int typeId = (alias >> 16 ) & 0xff;
        int entryId = alias & 0xffff;
        return getResource(typeId, entryId);
    }
    public ResourceEntry getResource(int typeId, int entryId){
        SpecTypePair specTypePair =
                getSpecTypePair(typeId);
        if(specTypePair == null){
            return null;
        }
        Entry entry = specTypePair.getAnyEntry((short) entryId);
        if(entry == null){
            return null;
        }
        return new ResourceEntry(this, entry.getResourceId());
    }
    public ResourceEntry getResource(ResourceName resourceName) {
        if(resourceName == null || !resourceName.matchesPackageName(getName())) {
            return null;
        }
        return getResource(resourceName.getType(), resourceName.getName());
    }
    public ResourceEntry getResource(String type, String name){
        SpecTypePair specTypePair =
                getSpecTypePair(type);
        if(specTypePair != null){
            return specTypePair.getResource(name);
        }
        return null;
    }
    public ResourceEntry getAttrResource(String name){
        Iterator itr = getAttrSpecs();
        while (itr.hasNext()){
            ResourceEntry resourceEntry = itr.next()
                    .getResource(name);
            if(resourceEntry != null){
                return resourceEntry;
            }
        }
        return null;
    }
    public ResourceEntry getIdResource(String name){
        Iterator itr = getIdSpecs();
        while (itr.hasNext()){
            ResourceEntry resourceEntry = itr.next()
                    .getResource(name);
            if(resourceEntry != null){
                return resourceEntry;
            }
        }
        return null;
    }
    public Iterator getResources() {
        return new IterableIterator(getSpecTypePairs()) {
            @Override
            public Iterator iterator(SpecTypePair element) {
                return element.getResources();
            }
        };
    }
    public Iterator getResources(String type){
        return new IterableIterator(getSpecTypePairs()) {
            @Override
            public Iterator iterator(SpecTypePair element) {
                if(type.equals(element.getTypeName())){
                    return element.getResources();
                }
                return EmptyIterator.of();
            }
        };
    }
    public String buildDecodeDirectoryName(){
        int count = 0;
        TableBlock tableBlock = getTableBlock();
        if(tableBlock != null){
            count = tableBlock.size();
        }
        return DIRECTORY_NAME_PREFIX + StringsUtil.formatNumber(getIndex() + 1, count);
    }
    public boolean hasValidTypeNames(){
        Set unique = new HashSet<>();
        Iterator iterator = getSpecTypePairs();
        while (iterator.hasNext()){
            SpecTypePair specTypePair = iterator.next();
            String typeName = specTypePair.getTypeName();
            if(!CommonType.isCommonTypeName(typeName) || unique.contains(typeName)){
                return false;
            }
            unique.add(typeName);
        }
        return true;
    }
    public boolean removeUnusedSpecs(){
        return getSpecStringPool().removeUnusedStrings();
    }
    public String refreshFull(){
        return refreshFull(true);
    }
    public String refreshFull(boolean elementsRefresh){
        int sizeOld = getHeaderBlock().getChunkSize();
        StringBuilder message = new StringBuilder();
        boolean appendOnce = false;
        if(removeUnusedSpecs()){
            message.append("Removed unused spec strings");
            appendOnce = true;
        }
        getSpecStringPool().sort();
        sortTypes();
        if(!elementsRefresh){
            if(appendOnce){
                return message.toString();
            }
            return null;
        }
        refresh();
        int sizeNew = getHeaderBlock().getChunkSize();
        if(sizeOld != sizeNew){
            if(appendOnce){
                message.append("\n");
            }
            message.append("Package size changed = ");
            message.append(sizeOld);
            message.append(", ");
            message.append(sizeNew);
            appendOnce = true;
        }
        if(appendOnce){
            return message.toString();
        }
        return null;
    }
    public void linkTableStringsInternal(TableStringPool tableStringPool){
        Iterator iterator = getSpecTypePairs();
        while (iterator.hasNext()){
            iterator.next().linkTableStringsInternal(tableStringPool);
        }
    }
    public void linkSpecStringsInternal(SpecStringPool specStringPool){
        Iterator iterator = getSpecTypePairs();
        while (iterator.hasNext()){
            iterator.next().linkSpecStringsInternal(specStringPool);
        }
    }
    public void destroy(){
        getPackageBody().destroy();
        getTypeStringPool().clear();
        getSpecStringPool().clear();
        setId(0);
        setName("");
    }
    public int resolveResourceId(String type, String name){
        return getSpecStringPool().resolveResourceId(type, name);
    }
    public int resolveResourceId(int typeId, String name){
        return getSpecStringPool().resolveResourceId(typeId, name);
    }
    public Entry getEntry(String type, String name){
        Iterator iterator = getEntries(type, name);
        Entry result = null;
        while (iterator.hasNext()){
            Entry entry = iterator.next();
            if(!entry.isNull()){
                return entry;
            }
            if(result == null){
                result = entry;
            }
        }
        return result;
    }
    public Iterator getEntries(String type, String name){
        return getSpecStringPool().getEntries(type, name);
    }
    public Iterator getEntries(int typeId, String name){
        return getSpecStringPool().getEntries(typeId, name);
    }
    public Iterator getEntries(int resourceId){
        return getEntries(resourceId, true);
    }
    public Iterator getEntries(int resourceId, boolean skipNull){
        int packageId = (resourceId >> 24) & 0xff;
        if(packageId != getId()){
            return EmptyIterator.of();
        }
        return getEntries((resourceId >> 16) & 0xff, resourceId & 0xffff, skipNull);
    }
    public Iterator getEntries(int typeId, int entryId){
        return getEntries(typeId, entryId, true);
    }
    public Iterator getEntries(int typeId, int entryId, boolean skipNull){
        SpecTypePair specTypePair = getSpecTypePair(typeId);
        if(specTypePair != null){
            return specTypePair.getEntries(entryId, skipNull);
        }
        return EmptyIterator.of();
    }
    public Entry getEntry(String qualifiers, String type, String name){
        return getSpecTypePairArray().getEntry(qualifiers, type, name);
    }
    public Entry getEntry(ResConfig resConfig, String type, String name){
        return getSpecTypePairArray().getEntry(resConfig, type, name);
    }
    public Entry getOrCreate(String qualifiers, String type, String name){
        return getOrCreate(ResConfig.parse(qualifiers), type, name);
    }
    public Entry getOrCreate(ResConfig resConfig, String typeName, String name){
        SpecTypePair specTypePair = getOrCreateSpecTypePair(typeName);
        TypeBlock typeBlock = specTypePair.getOrCreateTypeBlock(resConfig);
        return typeBlock.getOrCreateEntry(name);
    }
    public TypeBlock getOrCreateTypeBlock(String qualifiers, String typeName){
        SpecTypePair specTypePair = getOrCreateSpecTypePair(typeName);
        return specTypePair.getOrCreateTypeBlock(qualifiers);
    }
    public TypeBlock getOrCreateTypeBlock(ResConfig resConfig, String typeName){
        SpecTypePair specTypePair = getOrCreateSpecTypePair(typeName);
        return specTypePair.getOrCreateTypeBlock(resConfig);
    }
    public SpecTypePair getOrCreateSpecTypePair(int typeId, String typeName){
        getOrCreateTypeString(typeId, typeName);
        return getSpecTypePairArray().getOrCreate((byte) typeId);
    }
    public SpecTypePair getOrCreateSpecTypePair(String typeName){
        return getSpecTypePairArray().getOrCreate(typeName);
    }
    public int getTypeIdOffset(){
        return getHeaderBlock().getTypeIdOffset();
    }
    public BlockList getUnknownChunkList(){
        return mBody.getUnknownChunkList();
    }

    public StagedAliasEntry searchByStagedResId(int stagedResId){
        for(StagedAlias stagedAlias:listStagedAlias()){
            StagedAliasEntry aliasEntry = stagedAlias.searchByStagedResId(stagedResId);
            if(aliasEntry != null){
                return aliasEntry;
            }
        }
        return null;
    }
    public List listStagedAlias(){
        return getStagedAliasList().getChildes();
    }
    public StagedAliasList getStagedAliasList(){
        return mBody.getStagedAliasList();
    }
    public OverlayableList getOverlayableList(){
        return mBody.getOverlayableList();
    }
    public BlockList getOverlayablePolicyList(){
        return mBody.getOverlayablePolicyList();
    }
    public void sortTypes(){
        getSpecTypePairArray().sort();
    }


    @Override
    protected void onPreRefresh() {
        removeEmpty();
        super.onPreRefresh();
    }
    public void removeEmpty(){
        getSpecTypePairArray().removeEmptyPairs();
    }
    public boolean isEmpty(){
        return getId() == 0 && mBody.isEmpty();
    }
    @Override
    public int getId(){
        return getHeaderBlock().getPackageId().get();
    }
    public void setId(int id){
        getHeaderBlock().getPackageId().set(id);
        mPrefix = null;
        mHasValidPrefix = false;
    }
    @Override
    public String getName(){
        return getHeaderBlock().getPackageName().get();
    }
    public void setName(String name){
        getHeaderBlock().getPackageName().set(name);
        mPrefix = null;
        mHasValidPrefix = false;
    }
    @Override
    public String getPrefix(){
        if(mPrefix != null){
            return mPrefix;
        }
        boolean hasValidPrefix;
        String prefix;
        if(getId() == 0x01){
            prefix = ResourceLibrary.PREFIX_ANDROID;
            hasValidPrefix = ResourceLibrary.PREFIX_ANDROID.equals(getName());
        }else {
            prefix = ResourceLibrary.toPrefix(getName());
            hasValidPrefix = Namespace.isValidPrefix(prefix)
                    && !ResourceLibrary.PREFIX_ANDROID.equals(prefix);
            if(!hasValidPrefix){
                prefix = ResourceLibrary.PREFIX_APP;
            }
        }
        mPrefix = prefix;
        mHasValidPrefix = hasValidPrefix;
        return prefix;
    }
    @Override
    public String getUri(){
        if(isAndroid()){
            return ResourceLibrary.URI_ANDROID;
        }
        return ResourceLibrary.URI_RES_AUTO;
    }
    @Override
    public boolean packageNameMatches(String packageName){
        if(packageName == null){
            return false;
        }
        if(packageName.equals(getName())){
            return true;
        }
        if(packageName.equals(getPrefix()) && mHasValidPrefix){
            return true;
        }
        return getLibraryBlock().containsLibraryInfo(packageName);
    }
    private boolean isAndroid(){
        return getId() == 0x01 && ResourceLibrary.PREFIX_ANDROID.equals(getName());
    }
    public TableBlock getTableBlock(){
        Block parent=getParent();
        while(parent!=null){
            if(parent instanceof TableBlock){
                return (TableBlock)parent;
            }
            parent=parent.getParent();
        }
        return null;
    }
    public boolean isMultiPackage(){
        TableBlock tableBlock = getTableBlock();
        if(tableBlock != null) {
            return tableBlock.isMultiPackage();
        }
        return false;
    }
    public String typeNameOf(int typeId){
        TypeString typeString = getTypeStringPool().getById(typeId);
        if(typeString != null){
            return typeString.get();
        }
        return null;
    }
    public int typeIdOf(String typeName){
        return getTypeStringPool().idOf(typeName);
    }
    public TypeString getOrCreateTypeString(int typeId, String typeName){
        return getTypeStringPool().getOrCreate(typeId, typeName);
    }
    public TypeStringPool getTypeStringPool(){
        return mTypeStringPool;
    }
    @Override
    public SpecStringPool getSpecStringPool(){
        return mSpecStringPool;
    }
    @Override
    public TableBlock getMainChunk(){
        return getTableBlock();
    }
    @Override
    public PackageBlock getPackageBlock(){
        return this;
    }
    public PackageBody getPackageBody() {
        return mBody;
    }
    public SpecTypePairArray getSpecTypePairArray(){
        return mBody.getSpecTypePairArray();
    }
    public void trimConfigSizes(int resConfigSize){
        getSpecTypePairArray().trimConfigSizes(resConfigSize);
    }
    public Iterator getLibraryInfo(){
        return getLibraryBlock().iterator();
    }

    public void addLibrary(LibraryBlock libraryBlock){
        if(libraryBlock==null){
            return;
        }
        for(LibraryInfo info:libraryBlock.getLibraryInfoArray().listItems()){
            addLibraryInfo(info);
        }
    }
    public void addLibraryInfo(LibraryInfo info){
        getLibraryBlock().addLibraryInfo(info);
    }
    public LibraryBlock getLibraryBlock(){
        return mBody.getLibraryBlock();
    }
    public Entry getOrCreateEntry(byte typeId, short entryId, String qualifiers){
        return getSpecTypePairArray().getOrCreateEntry(typeId, entryId, qualifiers);
    }
    public Entry getOrCreateEntry(byte typeId, short entryId, ResConfig resConfig){
        return getSpecTypePairArray().getOrCreateEntry(typeId, entryId, resConfig);
    }

    public Entry getAnyEntry(int resourceId){
        int packageId = (resourceId >> 24) & 0xff;
        if(packageId != getId()){
            return null;
        }
        byte typeId = (byte) ((resourceId >> 16) & 0xff);
        short entryId = (short) (resourceId & 0xffff);
        return getSpecTypePairArray().getAnyEntry(typeId, entryId);
    }
    public Entry getEntry(byte typeId, short entryId, String qualifiers){
        return getSpecTypePairArray().getEntry(typeId, entryId, qualifiers);
    }
    public TypeBlock getOrCreateTypeBlock(byte typeId, String qualifiers){
        return getSpecTypePairArray().getOrCreateTypeBlock(typeId, qualifiers);
    }
    public TypeBlock getTypeBlock(byte typeId, String qualifiers){
        return getSpecTypePairArray().getTypeBlock(typeId, qualifiers);
    }

    private Iterator getAttrSpecs(){
        return getSpecTypePairArray().iterator(new Predicate() {
            @Override
            public boolean test(SpecTypePair specTypePair) {
                return specTypePair != null && specTypePair.isTypeAttr();
            }
        });
    }
    private Iterator getIdSpecs(){
        return getSpecTypePairArray().iterator(
                specTypePair -> specTypePair != null && specTypePair.isTypeId());
    }
    public SpecTypePair getSpecTypePair(String typeName){
        return getSpecTypePair(typeIdOf(typeName));
    }
    public SpecTypePair getSpecTypePair(int typeId){
        return getSpecTypePairArray().getSpecTypePair((byte) typeId);
    }

    public Iterable listSpecTypePairs(){
        return getSpecTypePairArray().listItems();
    }
    public Iterator getResConfigs(){
        return new MergingIterator<>(new ComputeIterator<>(getSpecTypePairs(),
                SpecTypePair::getResConfigs));
    }
    public Iterator getSpecTypePairs(){
        return getSpecTypePairArray().iterator();
    }

    private void refreshTypeStringPoolOffset(){
        int pos=countUpTo(mTypeStringPool);
        getHeaderBlock().getTypeStringPoolOffset().set(pos);
    }
    private void refreshTypeStringPoolCount(){
        getHeaderBlock().getTypeStringPoolCount().set(mTypeStringPool.size());
    }
    private void refreshSpecStringPoolOffset(){
        int pos=countUpTo(mSpecStringPool);
        getHeaderBlock().getSpecStringPoolOffset().set(pos);
    }
    private void refreshSpecStringCount(){
        getHeaderBlock().getSpecStringPoolCount().set(mSpecStringPool.size());
    }
    @Override
    public void onChunkLoaded() {
    }

    @Override
    protected void onChunkRefreshed() {
        refreshTypeStringPoolOffset();
        refreshTypeStringPoolCount();
        refreshSpecStringPoolOffset();
        refreshSpecStringCount();
    }

    public void serializePublicXml(File file) throws IOException {
        XmlSerializer serializer = XMLFactory.newSerializer(file);
        serializePublicXml(serializer);
        IOUtil.close(serializer);
    }
    public void serializePublicXml(XmlSerializer serializer) throws IOException {
        serializePublicXml(serializer, true);
    }
    public void serializePublicXml(XmlSerializer serializer, boolean fullDocument) throws IOException {
        if(fullDocument){
            serializer.startDocument("utf-8", null);
            serializer.text("\n");
            serializer.startTag(null, TAG_resources);
            writePackageInfo(serializer);
        }
        serializePublicXmlTypes(serializer);
        if(fullDocument){
            serializer.text("\n");
            serializer.endTag(null, TAG_resources);
            serializer.endDocument();
            IOUtil.close(serializer);
        }
    }
    private void serializePublicXmlTypes(XmlSerializer serializer) throws IOException {
        Iterator iterator = getSpecTypePairs();
        while (iterator.hasNext()){
            iterator.next().serializePublicXml(serializer);
        }
    }
    private void writePackageInfo(XmlSerializer serializer) throws IOException {
        String name = getName();
        if(name != null){
            serializer.attribute(null, ATTR_package, name);
        }
        int id = getId();
        if(id != 0){
            serializer.attribute(null, ATTR_id, HexUtil.toHex2((byte)id));
        }
        TableBlock tableBlock = getTableBlock();
        if(tableBlock !=null && tableBlock.isEmpty() && tableBlock.isNull()){
            serializer.attribute(null, TableBlock.ATTR_null_table, "true");
        }
    }
    public void parsePublicXml(XmlPullParser parser) throws IOException,
            XmlPullParserException {
        getPublicXmlParser()
                .parse(parser);
    }
    public PublicXmlParser getPublicXmlParser(){
        return new PublicXmlParser(this);
    }

    @Override
    public JSONObject toJson() {
        return toJson(true);
    }
    public JSONObject toJson(boolean addTypes) {
        JSONObject jsonObject=new JSONObject();

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

        jsonObject.put(NAME_package_id, getId());
        jsonObject.put(NAME_package_name, getName());
        jsonObject.put(NAME_specs, getSpecTypePairArray().toJson(!addTypes));
        LibraryInfoArray libraryInfoArray = getLibraryBlock().getLibraryInfoArray();
        if(libraryInfoArray.size()>0){
            jsonObject.put(NAME_libraries,libraryInfoArray.toJson());
        }
        StagedAlias stagedAlias =
                StagedAlias.mergeAll(getStagedAliasList().getChildes());
        if(stagedAlias!=null){
            jsonObject.put(NAME_staged_aliases,
                    stagedAlias.getStagedAliasEntryArray().toJson());
        }
        JSONArray jsonArray = getOverlayableList().toJson();
        if(jsonArray!=null){
            jsonObject.put(NAME_overlaybles, jsonArray);
        }
        return jsonObject;
    }
    @Override
    public void fromJson(JSONObject json) {
        int id = json.optInt(NAME_package_id, 0);
        if(id != 0){
            setId(id);
        }
        String name = json.optString(NAME_package_name, null);
        if(name != null){
            setName(name);
        }
        getSpecTypePairArray().fromJson(json.optJSONArray(NAME_specs));
        LibraryInfoArray libraryInfoArray = getLibraryBlock().getLibraryInfoArray();
        libraryInfoArray.fromJson(json.optJSONArray(NAME_libraries));
        if(json.has(NAME_staged_aliases)){
            StagedAlias stagedAlias=new StagedAlias();
            stagedAlias.getStagedAliasEntryArray()
                    .fromJson(json.getJSONArray(NAME_staged_aliases));
            getStagedAliasList().add(stagedAlias);
        }
        if(json.has(NAME_overlaybles)){
            getOverlayableList().fromJson(json.getJSONArray(NAME_overlaybles));
        }
    }
    public void merge(PackageBlock packageBlock){
        if(packageBlock==null||packageBlock==this){
            return;
        }
        if(getId()!=packageBlock.getId()){
            throw new IllegalArgumentException("Can not merge different id packages: "
                    +getId()+"!="+packageBlock.getId());
        }
        setName(packageBlock.getName());
        getLibraryBlock().merge(packageBlock.getLibraryBlock());
        getSpecStringPool().merge(packageBlock.getSpecStringPool());
        getSpecTypePairArray().merge(packageBlock.getSpecTypePairArray());
        getOverlayableList().merge(packageBlock.getOverlayableList());
        getStagedAliasList().merge(packageBlock.getStagedAliasList());
    }

    public ResourceEntry mergeWithName(ResourceMergeOption mergeOption, ResourceEntry resourceEntry) {
        ResourceEntry exist = getResource(resourceEntry.getType(), resourceEntry.getName());
        if(exist != null && !exist.isEmpty()) {
            return exist;
        }
        int id = 0;
        Iterator iterator = resourceEntry.iterator(mergeOption.getKeepEntryConfigs());
        while (iterator.hasNext()) {
            Entry entry = mergeWithName(mergeOption, iterator.next());
            if(id == 0){
                id = entry.getResourceId();
            }
        }
        ResourceEntry result = getResource(id);
        if(result == null){
            result = getTableBlock().getResource(id);
        }
        return result;
    }
    private Entry mergeWithName(ResourceMergeOption mergeOption, Entry entry) {
        Entry result = getOrCreate(entry.getResConfig(), entry.getTypeName(), entry.getName());
        result.mergeWithName(mergeOption, entry);
        return result;
    }

    @Override
    public int compareTo(PackageBlock pkg) {
        return Integer.compare(getId(), pkg.getId());
    }
    public boolean isSimilarTo(PackageBlock packageBlock) {
        if(packageBlock == this) {
            return true;
        }
        if(packageBlock == null || getId() != packageBlock.getId() || !getName().equals(packageBlock.getName())) {
            return false;
        }
        return getTypeStringPool().size() == packageBlock.getTypeStringPool().size() &&
                getSpecStringPool().size() == packageBlock.getSpecStringPool().size();
    }
    @Override
    public String toString(){
        StringBuilder builder = new StringBuilder();
        builder.append(super.toString());
        builder.append(", id=");
        builder.append(HexUtil.toHex2((byte) getId()));
        builder.append(", name=");
        builder.append(getName());
        int libCount = getLibraryBlock().size();
        if(libCount > 0){
            builder.append(", libraries=");
            builder.append(libCount);
        }
        return builder.toString();
    }
    public static boolean isPackageId(int packageId){
        if(packageId == 0){
            return false;
        }
        return packageId > 0 && packageId <= 0xff;
    }
    public static boolean isResourceId(int resourceId){
        if(resourceId == 0){
            return false;
        }
        return (resourceId & 0x00ff0000) != 0
                && (resourceId & 0xff000000) != 0;
    }

    public static void changePackageId(ValueItem valueItem, int packageIdOld, int packageIdNew){
        if(valueItem instanceof AttributeValue){
            changePackageIdName(packageIdOld, packageIdNew, (AttributeValue)valueItem);
        }
        changePackageIdValue(packageIdOld, packageIdNew, valueItem);
    }
    private static void changePackageIdName(int packageIdOld, int packageIdNew, AttributeValue value){
        int resourceId = value.getNameId();
        if(!isResourceId(resourceId)){
            return;
        }
        int id = (resourceId >> 24) & 0xff;
        if(id != packageIdOld){
            return;
        }
        resourceId = resourceId & 0xffffff;
        id = packageIdNew << 24;
        resourceId = id | resourceId;
        value.setNameId(resourceId);
    }
    private static void changePackageIdValue(int packageIdOld, int packageIdNew, ValueItem valueItem){
        ValueType valueType = valueItem.getValueType();
        if(valueType == null || !valueType.isReference()){
            return;
        }
        int resourceId = valueItem.getData();
        if(!isResourceId(resourceId)){
            return;
        }
        int id = (resourceId >> 24) & 0xff;
        if(id != packageIdOld){
            return;
        }
        resourceId = resourceId & 0xffffff;
        id = packageIdNew << 24;
        resourceId = id | resourceId;
        valueItem.setData(resourceId);
    }

    public static int replacePackageId(int resourceId, int packageIdOld, int packageIdNew){
        if(!isResourceId(resourceId)){
            return resourceId;
        }
        int id = (resourceId >> 24) & 0xff;
        if(id != packageIdOld){
            return resourceId;
        }
        resourceId = resourceId & 0xffffff;
        id = packageIdNew << 24;
        resourceId = id | resourceId;
        return resourceId;
    }
    public static class PublicXmlParser{
        private final PackageBlock packageBlock;
        private boolean mInitializeIds = true;
        private PublicXmlParser(PackageBlock packageBlock){
            this.packageBlock = packageBlock;
        }
        public PublicXmlParser setInitializeIds(boolean initializeId) {
            this.mInitializeIds = initializeId;
            return this;
        }
        public PublicXmlParser parse(XmlPullParser parser) throws IOException, XmlPullParserException {
            parseResourcesAttributes(parser);
            parseElements(parser);
            IOUtil.close(parser);
            return this;
        }
        private boolean isInitializeIds(){
            return mInitializeIds;
        }
        private void parseElements(XmlPullParser parser) throws IOException, XmlPullParserException {
            while (XMLUtil.ensureStartTag(parser) == XmlPullParser.START_TAG){
                XMLElement element = XMLElement.parseElement(parser);
                parsePublicTag(element);
            }
        }
        private void parsePublicTag(XMLElement element) throws XmlEncodeException {
            if(!PackageBlock.TAG_public.equals(element.getName())){
                return;
            }
            String id = element.getAttributeValue(ATTR_id);
            String type = element.getAttributeValue(ATTR_type);
            String name = element.getAttributeValue(ATTR_name);
            if(id == null){
                throw new XmlEncodeException("Missing attribute: '" + ATTR_id + "', "
                        + element.getDebugText());
            }
            if(type == null){
                throw new XmlEncodeException("Missing attribute: '" + ATTR_type + "', "
                        + element.getDebugText());
            }
            if(name == null){
                throw new XmlEncodeException("Missing attribute: '" + ATTR_name + "', "
                        + element.getDebugText());
            }
            EncodeResult encodeResult = ValueCoder.encodeHexOrInteger(id.trim());
            if(encodeResult == null){
                throw new XmlEncodeException("Invalid id value: " + element.getDebugText());
            }
            int resourceId = encodeResult.value;
            int packageId = (resourceId >> 24) & 0xff;
            int i = packageBlock.getId();
            if(i == 0){
                packageBlock.setId(packageId);
            }else if(i != packageId){
                return;
            }
            int typeId = (resourceId >> 16) & 0xff;
            if(typeId == 0){
                throw new XmlEncodeException("Type id is zero: '" + id + "', "
                        + element.getDebugText());
            }
            TypeString typeString = packageBlock.getOrCreateTypeString(typeId, type);
            typeId = typeString.getId();
            TypeBlock typeBlock = packageBlock.getOrCreateTypeBlock((byte) typeId, "");
            int entryId = resourceId & 0xffff;
            Entry entry = typeBlock.getOrCreateEntry((short) entryId);
            entry.setName(name, true);
            if(isInitializeIds() && typeBlock.isTypeId()){
                entry.setValueAsBoolean(false);
                ValueHeader header = entry.getHeader();
                header.setPublic(true);
                header.setWeak(true);
            }
        }
        private void parseResourcesAttributes(XmlPullParser parser) throws IOException, XmlPullParserException {
            int event = parser.getEventType();
            boolean documentStarted = false;
            if(event == XmlPullParser.START_DOCUMENT){
                documentStarted = true;
                parser.next();
            }
            event = XMLUtil.ensureStartTag(parser);
            if(event != XmlPullParser.START_TAG){
                throw new XmlEncodeException("Expecting xml state START_TAG but found: "
                        + XMLUtil.toEventName(parser.getEventType()));
            }
            if(PackageBlock.TAG_resources.equals(parser.getName())){
                XMLElement element = new XMLElement(PackageBlock.TAG_resources);
                element.parseAttributes(parser);
                parseResourcesAttributes(element);
                parser.next();
            }else if(documentStarted){
                throw new XmlEncodeException("Expecting  tag but found: " + parser.getName());
            }
        }
        private void parseResourcesAttributes(XMLElement element) throws IOException {
            String packageName = element.getAttributeValue(PackageBlock.ATTR_package);
            if(!StringsUtil.isWhiteSpace(packageName) && !EMPTY_PACKAGE_NAME.equals(packageName)){
                packageBlock.setName(packageName);
            }
            String id = element.getAttributeValue(PackageBlock.ATTR_id);
            if(!StringsUtil.isWhiteSpace(id) && packageBlock.getId() == 0){
                EncodeResult encodeResult = ValueCoder
                        .encodeHexOrInteger(id);
                if(encodeResult == null || !PackageBlock.isPackageId(encodeResult.value)){
                    throw new XmlEncodeException("Invalid id value: '" + element.getDebugText() + "'");
                }
                packageBlock.setId(encodeResult.value);
            }
            String nullTable = element.getAttributeValue(TableBlock.ATTR_null_table);
            if("true".equals(nullTable)){
                packageBlock.getTableBlock().setNull(true);
            }
        }
    }

    public static PackageBlock createEmptyPackage(TableBlock tableBlock){
        PackageBlock packageBlock = new PackageBlock(){
            @Override
            public boolean isEmpty() {
                return true;
            }
            @Override
            public boolean isNull() {
                return true;
            }
            @Override
            public TableBlock getTableBlock() {
                return tableBlock;
            }
            @Override
            public int getId() {
                return 0;
            }
            @Override
            public void setId(int id) {
                if(id != 0){
                    throw new IllegalArgumentException("Can't set id to empty package");
                }
            }
            @Override
            public String getName(){
                return PackageBlock.EMPTY_PACKAGE_NAME;
            }
            @Override
            public void setName(String name) {
                if(name != null && name.length() != 0){
                    throw new IllegalArgumentException("Can't set name to empty package");
                }
            }
            @Override
            public int countBytes() {
                return 0;
            }
            @Override
            public byte[] getBytes() {
                return new byte[0];
            }
            @Override
            public void onReadBytes(BlockReader reader) throws IOException {
                throw new IOException("Can't read on empty package");
            }
            @Override
            public int onWriteBytes(OutputStream stream) throws IOException {
                throw new IOException("Can't write on empty package");
            }
            @Override
            public String toString() {
                return getName();
            }
        };
        packageBlock.setParent(tableBlock);
        return packageBlock;
    }

    public static final String NAME_package_id = ObjectsUtil.of("package_id");
    public static final String NAME_package_name = ObjectsUtil.of("package_name");
    private static final String NAME_specs = ObjectsUtil.of("specs");
    public static final String NAME_libraries = ObjectsUtil.of("libraries");
    public static final String NAME_staged_aliases = ObjectsUtil.of("staged_aliases");
    public static final String NAME_overlaybles = ObjectsUtil.of("overlaybles");

    public static final String JSON_FILE_NAME = ObjectsUtil.of("package.json");
    public static final String DIRECTORY_NAME_PREFIX = ObjectsUtil.of("package_");
    public static final String RES_DIRECTORY_NAME = ObjectsUtil.of("res");
    public static final String VALUES_DIRECTORY_NAME = ObjectsUtil.of("values");

    public static final String PUBLIC_XML = ObjectsUtil.of("public.xml");

    public static final String TAG_public = ObjectsUtil.of("public");
    public static final String TAG_resources = ObjectsUtil.of("resources");
    public static final String ATTR_package = ObjectsUtil.of("package");
    public static final String ATTR_id = ObjectsUtil.of("id");
    public static final String ATTR_type = ObjectsUtil.of("type");
    public static final String ATTR_name = ObjectsUtil.of("name");

    public static final String EMPTY_PACKAGE_NAME = ObjectsUtil.of("empty-package");

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy