/* * 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.BuildInfo; import com.reandroid.arsc.array.LibraryInfoArray; import com.reandroid.arsc.array.SpecTypePairArray; import com.reandroid.arsc.base.Block; import com.reandroid.arsc.container.BlockList; import com.reandroid.arsc.container.PackageBody; import com.reandroid.arsc.container.SpecTypePair; import com.reandroid.arsc.group.EntryGroup; import com.reandroid.arsc.header.PackageHeader; import com.reandroid.arsc.list.OverlayableList; import com.reandroid.arsc.list.StagedAliasList; import com.reandroid.arsc.pool.SpecStringPool; import com.reandroid.arsc.pool.TypeStringPool; import com.reandroid.arsc.value.Entry; import com.reandroid.arsc.value.LibraryInfo; import com.reandroid.arsc.value.ResConfig; import com.reandroid.arsc.value.StagedAliasEntry; import com.reandroid.json.JSONArray; import com.reandroid.json.JSONConvert; import com.reandroid.json.JSONObject; import java.util.*; public class PackageBlock extends Chunk implements ParentChunk, JSONConvert, Comparable { private final TypeStringPool mTypeStringPool; private final SpecStringPool mSpecStringPool; private final PackageBody mBody; private final Map mEntriesGroup; private boolean entryGroupMapLocked; 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(); this.mEntriesGroup = new HashMap<>(); this.entryGroupMapLocked = true; addChild(mTypeStringPool); addChild(mSpecStringPool); addChild(mBody); } public void destroy(){ getEntriesGroupMap().clear(); getPackageBody().destroy(); getTypeStringPool().destroy(); getSpecStringPool().destroy(); setId(0); setName(""); } public Entry getOrCreate(String qualifiers, String type, String name){ ResConfig resConfig = new ResConfig(); resConfig.parseQualifiers(qualifiers); return getOrCreate(resConfig, type, name); } public Entry getOrCreate(ResConfig resConfig, String type, String name){ SpecTypePair specTypePair = getOrCreateSpecType(type); TypeBlock typeBlock = specTypePair.getOrCreateTypeBlock(resConfig); return typeBlock.getOrCreateEntry(name); } public SpecTypePair getOrCreateSpecType(String type){ int last = 0; for(SpecTypePair specTypePair:listAllSpecTypePair()){ if(type.equals(specTypePair.getTypeName())){ return specTypePair; } int id = specTypePair.getId(); if(id>last){ last=id; } } last++; getTypeStringPool().getOrCreate(last, type); return getSpecTypePairArray().getOrCreate((byte) last); } public int getTypeIdOffset(){ return getHeaderBlock().getTypeIdOffset(); } public BlockList getUnknownChunkList(){ return mBody.getUnknownChunkList(); } public StagedAliasEntry searchByStagedResId(int stagedResId){ for(StagedAlias stagedAlias:getStagedAliasList().getChildes()){ StagedAliasEntry entry=stagedAlias.getStagedAliasEntryArray() .searchByStagedResId(stagedResId); if(entry!=null){ return entry; } } 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(); } public void removeEmpty(){ getSpecTypePairArray().removeEmptyPairs(); } public boolean isEmpty(){ return getSpecTypePairArray().isEmpty(); } public int getId(){ return getHeaderBlock().getPackageId().get(); } public void setId(byte id){ setId(0xff & id); } public void setId(int id){ getHeaderBlock().getPackageId().set(id); } public String getName(){ return getHeaderBlock().getPackageName().get(); } public void setName(String name){ getHeaderBlock().getPackageName().set(name); } public TableBlock getTableBlock(){ Block parent=getParent(); while(parent!=null){ if(parent instanceof TableBlock){ return (TableBlock)parent; } parent=parent.getParent(); } return null; } public TypeStringPool getTypeStringPool(){ return mTypeStringPool; } @Override public SpecStringPool getSpecStringPool(){ return mSpecStringPool; } @Override public TableBlock getMainChunk(){ return getTableBlock(); } public PackageBody getPackageBody() { return mBody; } public SpecTypePairArray getSpecTypePairArray(){ return mBody.getSpecTypePairArray(); } public Collection listLibraryInfo(){ return getLibraryBlock().listLibraryInfo(); } 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 Set listResourceIds(){ return getEntriesGroupMap().keySet(); } public Entry getOrCreateEntry(byte typeId, short entryId, String qualifiers){ return getSpecTypePairArray().getOrCreateEntry(typeId, entryId, qualifiers); } 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); } public boolean isEntryGroupMapLocked() { return entryGroupMapLocked; } private void unlockEntryGroup() { synchronized (this){ if(!this.entryGroupMapLocked){ return; } System.err.println("\nUnlocking EntryGroupMap ..."); this.entryGroupMapLocked = false; Map map = this.mEntriesGroup; map.clear(); createEntryGroupMap(map); System.err.println("\nEntryGroupMap unlocked!"); } } private void createEntryGroupMap(Map map){ map.clear(); for(SpecTypePair specTypePair:listAllSpecTypePair()){ map.putAll(specTypePair.createEntryGroups()); } } public Map getEntriesGroupMap(){ unlockEntryGroup(); return mEntriesGroup; } public Collection listEntryGroup(){ return getEntriesGroupMap().values(); } /** * Searches entries by resource id from local map, then if not find * search by alias resource id * */ public EntryGroup getEntryGroup(int resourceId){ if(resourceId==0){ return null; } EntryGroup entryGroup=getEntriesGroupMap().get(resourceId); if(entryGroup!=null){ return entryGroup; } StagedAliasEntry stagedAliasEntry = searchByStagedResId(resourceId); if(stagedAliasEntry!=null){ return getEntriesGroupMap() .get(stagedAliasEntry.getFinalizedResId()); } return null; } public void updateEntry(Entry entry){ if(isEntryGroupMapLocked()){ return; } if(entry == null || entry.isNull()){ return; } int resourceId = entry.getResourceId(); Map map = getEntriesGroupMap(); EntryGroup group = map.get(resourceId); if(group == null){ group = new EntryGroup(resourceId); map.put(resourceId, group); } group.add(entry); } public void removeEntryGroup(Entry entry){ if(entry == null){ return; } int resourceId = entry.getResourceId(); Map map = getEntriesGroupMap(); EntryGroup group = map.get(resourceId); if(group == null){ return; } group.remove(entry); if(group.size() == 0){ map.remove(resourceId); } } public List listEntries(byte typeId, int entryId){ List results=new ArrayList<>(); for(SpecTypePair pair:listSpecTypePair(typeId)){ results.addAll(pair.listEntries(entryId)); } return results; } public List listSpecTypePair(byte typeId){ List results=new ArrayList<>(); for(SpecTypePair pair:listAllSpecTypePair()){ if(typeId==pair.getTypeId()){ results.add(pair); } } return results; } public Collection listAllSpecTypePair(){ return getSpecTypePairArray().listItems(); } private void refreshTypeStringPoolOffset(){ int pos=countUpTo(mTypeStringPool); getHeaderBlock().getTypeStringPoolOffset().set(pos); } private void refreshTypeStringPoolCount(){ getHeaderBlock().getTypeStringPoolCount().set(mTypeStringPool.countStrings()); } private void refreshSpecStringPoolOffset(){ int pos=countUpTo(mSpecStringPool); getHeaderBlock().getSpecStringPoolOffset().set(pos); } private void refreshSpecStringCount(){ getHeaderBlock().getSpecStringPoolCount().set(mSpecStringPool.countStrings()); } private void refreshTypeIdOffset(){ // TODO: find solution //int largest=getSpecTypePairArray().getHighestTypeId(); //int count=getTypeStringPool().countStrings(); //getHeaderBlock().getTypeIdOffset().set(count-largest); getHeaderBlock().getTypeIdOffsetItem().set(0); } public void onEntryAdded(Entry entry){ updateEntry(entry); } @Override public void onChunkLoaded() { } @Override protected void onChunkRefreshed() { refreshTypeStringPoolOffset(); refreshTypeStringPoolCount(); refreshSpecStringPoolOffset(); refreshSpecStringCount(); refreshTypeIdOffset(); } @Override public JSONObject toJson() { return toJson(true); } public JSONObject toJson(boolean addTypes) { JSONObject jsonObject=new JSONObject(); jsonObject.put(BuildInfo.NAME_arsc_lib_version, BuildInfo.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.childesCount()>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) { setId(json.getInt(NAME_package_id)); setName(json.getString(NAME_package_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()); mergeSpecStringPool(packageBlock); getSpecTypePairArray().merge(packageBlock.getSpecTypePairArray()); getOverlayableList().merge(packageBlock.getOverlayableList()); getStagedAliasList().merge(packageBlock.getStagedAliasList()); } private void mergeSpecStringPool(PackageBlock coming){ this.getSpecStringPool().addStrings( coming.getSpecStringPool().toStringList()); } /** * It is allowed to have duplicate type name therefore it is not recommend to use this. * Lets depreciate to warn developer */ @Deprecated public SpecTypePair searchByTypeName(String typeName){ return getSpecTypePairArray().searchByTypeName(typeName); } @Override public int compareTo(PackageBlock pkg) { return Integer.compare(getId(), pkg.getId()); } @Override public String toString(){ StringBuilder builder=new StringBuilder(); builder.append(super.toString()); builder.append(", id="); builder.append(String.format("0x%02x", getId())); builder.append(", name="); builder.append(getName()); int libCount=getLibraryBlock().getLibraryCount(); if(libCount>0){ builder.append(", libraries="); builder.append(libCount); } return builder.toString(); } public static final String NAME_package_id = "package_id"; public static final String NAME_package_name = "package_name"; public static final String JSON_FILE_NAME = "package.json"; private static final String NAME_specs = "specs"; public static final String NAME_libraries = "libraries"; public static final String NAME_staged_aliases = "staged_aliases"; public static final String NAME_overlaybles = "overlaybles"; }