/ app / src / main / java / com / reandroid / identifiers / PackageIdentifier.java
PackageIdentifier.java
  1  /*
  2   *  Copyright (C) 2022 github.com/REAndroid
  3   *
  4   *  Licensed under the Apache License, Version 2.0 (the "License");
  5   *  you may not use this file except in compliance with the License.
  6   *  You may obtain a copy of the License at
  7   *
  8   *      http://www.apache.org/licenses/LICENSE-2.0
  9   *
 10   * Unless required by applicable law or agreed to in writing, software
 11   * distributed under the License is distributed on an "AS IS" BASIS,
 12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13   * See the License for the specific language governing permissions and
 14   * limitations under the License.
 15   */
 16  package com.reandroid.identifiers;
 17  
 18  import com.reandroid.arsc.chunk.PackageBlock;
 19  import com.reandroid.arsc.chunk.TypeBlock;
 20  import com.reandroid.arsc.container.SpecTypePair;
 21  import com.reandroid.arsc.model.ResourceEntry;
 22  import com.reandroid.utils.HexUtil;
 23  import com.reandroid.utils.io.IOUtil;
 24  import com.reandroid.arsc.value.Entry;
 25  import com.reandroid.json.JSONObject;
 26  import com.reandroid.xml.XMLFactory;
 27  import org.xmlpull.v1.XmlPullParser;
 28  import org.xmlpull.v1.XmlPullParserException;
 29  import org.xmlpull.v1.XmlSerializer;
 30  
 31  import java.io.*;
 32  import java.nio.charset.StandardCharsets;
 33  import java.util.ArrayList;
 34  import java.util.Iterator;
 35  import java.util.List;
 36  
 37  public class PackageIdentifier extends IdentifierMap<TypeIdentifier>{
 38      private PackageBlock mPackageBlock;
 39      private int mPackageLoadStamp;
 40      public PackageIdentifier(int id, String name){
 41          super(id, name);
 42      }
 43      public PackageIdentifier(){
 44          this(0, null);
 45      }
 46  
 47      private void initializePackageJson(PackageBlock packageBlock){
 48          File jsonFile = searchPackageJsonFromTag();
 49          if(jsonFile == null){
 50              return;
 51          }
 52          try {
 53              JSONObject jsonObject = new JSONObject(new FileInputStream(jsonFile));
 54              packageBlock.fromJson(jsonObject);
 55              if(getName() == null){
 56                  setName(packageBlock.getName());
 57              }
 58          } catch (FileNotFoundException ignored) {
 59          }
 60      }
 61      // public.xml file is assumed to be stored via setTag during loadPublicXml(File)
 62      private File searchPackageJsonFromTag(){
 63          Object tag = getTag();
 64          if(!(tag instanceof File)){
 65              return null;
 66          }
 67          File publicXml = (File) tag;
 68          File dir = publicXml.getParentFile();
 69          //values
 70          if(dir == null || !"values".equals(dir.getName())){
 71              return null;
 72          }
 73          dir = dir.getParentFile();
 74          //res
 75          if(dir == null){
 76              return null;
 77          }
 78          dir = dir.getParentFile();
 79          if(dir == null){
 80              return null;
 81          }
 82          File json = new File(dir, "package.json");
 83          if(!json.isFile()){
 84              return null;
 85          }
 86          return json;
 87      }
 88      public List<ResourceIdentifier> listDuplicateResources(){
 89          List<ResourceIdentifier> results = new ArrayList<>();
 90          for(TypeIdentifier typeIdentifier : list()){
 91              results.addAll(typeIdentifier.listDuplicates());
 92          }
 93          return results;
 94      }
 95      public boolean hasDuplicateResources(){
 96          for(TypeIdentifier typeIdentifier : getItems()){
 97              if(typeIdentifier.hasDuplicates()){
 98                  return true;
 99              }
100          }
101          return false;
102      }
103      public List<ResourceIdentifier> ensureUniqueResourceNames(){
104          List<ResourceIdentifier> results = new ArrayList<>();
105          for(TypeIdentifier typeIdentifier : list()){
106              results.addAll(typeIdentifier.ensureUniqueResourceNames());
107          }
108          return results;
109      }
110      public void setResourceNamesToPackage(){
111          setResourceNamesToPackage(getPackageBlock());
112      }
113      public void setResourceNamesToPackage(PackageBlock packageBlock){
114          if(packageBlock == null){
115              return;
116          }
117          for(SpecTypePair specTypePair:packageBlock.listSpecTypePairs()){
118              Iterator<ResourceEntry> itr = specTypePair.getResources();
119              while (itr.hasNext()){
120                  setResourceNamesToEntry(itr.next());
121              }
122          }
123      }
124      public void setResourceNamesToEntry(ResourceEntry resourceEntry){
125          ResourceIdentifier ri = getResourceIdentifier(resourceEntry.getResourceId());
126          if(ri == null){
127              return;
128          }
129          resourceEntry.setName(ri.getName());
130      }
131      public ResourceIdentifier getResourceIdentifier(int resourceId){
132          TypeIdentifier typeIdentifier = get((resourceId >> 16) & 0xff);
133          if(typeIdentifier != null){
134              return typeIdentifier.get(resourceId & 0xffff);
135          }
136          return null;
137      }
138      public ResourceIdentifier getResourceIdentifier(String type, String name){
139          TypeIdentifier typeIdentifier = get(type);
140          if(typeIdentifier != null){
141              return typeIdentifier.get(name);
142          }
143          return null;
144      }
145      public int getResourcesCount(){
146          int result = 0;
147          for(TypeIdentifier ti : getItems()){
148              result += ti.size();
149          }
150          return result;
151      }
152  
153      public void writePublicXml(File file) throws IOException {
154          XmlSerializer serializer = XMLFactory.newSerializer(file);
155          write(serializer);
156          IOUtil.close(serializer);
157      }
158      public void writePublicXml(OutputStream outputStream) throws IOException {
159          XmlSerializer serializer = XMLFactory.newSerializer(outputStream);
160          serializer.setOutput(outputStream, StandardCharsets.UTF_8.name());
161          write(serializer);
162      }
163      public void write(XmlSerializer serializer) throws IOException {
164          serializer.startDocument("utf-8", null);
165          serializer.text("\n");
166          serializer.startTag(null, XML_TAG_RESOURCES);
167          writePackageInfo(serializer);
168          writeTypes(serializer);
169          serializer.text("\n");
170          serializer.endTag(null, XML_TAG_RESOURCES);
171          serializer.endDocument();
172          closeSerializer(serializer);
173      }
174      private void writePackageInfo(XmlSerializer serializer) throws IOException {
175          String name = getName();
176          if(name != null){
177              serializer.attribute(null, XML_ATTRIBUTE_PACKAGE, name);
178          }
179          int id = getId();
180          if(id != 0){
181              serializer.attribute(null, XML_ATTRIBUTE_ID, HexUtil.toHex2((byte)id));
182          }
183      }
184      private void writeTypes(XmlSerializer serializer) throws IOException {
185          for(TypeIdentifier typeIdentifier : list()){
186              typeIdentifier.write(serializer);
187          }
188      }
189      public void load(PackageBlock packageBlock){
190          setId(packageBlock.getId());
191          setName(packageBlock.getName());
192          loadEntryGroups(packageBlock);
193          setPackageBlock(packageBlock);
194      }
195      private void loadEntryGroups(PackageBlock packageBlock){
196          for(SpecTypePair specTypePair:packageBlock.listSpecTypePairs()){
197              Iterator<ResourceEntry> itr = specTypePair.getResources();
198              while (itr.hasNext()){
199                  add(itr.next());
200              }
201          }
202      }
203      public void loadPublicXml(File file) throws IOException, XmlPullParserException {
204          XmlPullParser parser = XMLFactory.newPullParser(file);
205          loadPublicXml(parser);
206      }
207      public void loadPublicXml(InputStream inputStream) throws IOException, XmlPullParserException {
208          XmlPullParser parser = XMLFactory.newPullParser(inputStream);
209          loadPublicXml(parser);
210      }
211      public void loadPublicXml(Reader reader) throws IOException, XmlPullParserException {
212          XmlPullParser parser = XMLFactory.newPullParser(reader);
213          loadPublicXml(parser);
214      }
215      public void loadPublicXml(XmlPullParser parser) throws IOException, XmlPullParserException {
216          boolean resourcesFound = false;
217          int event;
218          while ((event = parser.nextToken()) != XmlPullParser.END_DOCUMENT){
219              if(event != XmlPullParser.START_TAG){
220                  continue;
221              }
222              if(!resourcesFound){
223                  resourcesFound = parser.getName().equals(XML_TAG_RESOURCES);
224                  if(!resourcesFound){
225                      throw new XmlPullParserException("Invalid public.xml, expecting first tag '"
226                              + getName() + "' " + parser.getPositionDescription());
227                  }
228                  loadPackageInfo(parser);
229                  continue;
230              }
231              parseEntry(parser);
232          }
233          closeParser(parser);
234      }
235      private void closeParser(XmlPullParser parser){
236          if(!(parser instanceof Closeable)){
237              return;
238          }
239          Closeable closeable = (Closeable)parser;
240          try {
241              closeable.close();
242          } catch (IOException ignored) {
243          }
244      }
245      private void closeSerializer(XmlSerializer serializer){
246          if(!(serializer instanceof Closeable)){
247              return;
248          }
249          Closeable closeable = (Closeable)serializer;
250          try {
251              closeable.close();
252          } catch (IOException ignored) {
253          }
254      }
255      private void loadPackageInfo(XmlPullParser parser){
256          int count = parser.getAttributeCount();
257          for(int i = 0; i < count; i++){
258              if(XML_ATTRIBUTE_PACKAGE.equals(parser.getAttributeName(i))){
259                  setName(parser.getAttributeValue(i));
260              }else if(XML_ATTRIBUTE_ID.equals(parser.getAttributeName(i))){
261                  int id = Integer.decode(parser.getAttributeValue(i));
262                  if(id != 0){
263                      setId(id);
264                  }
265              }
266          }
267      }
268      private void parseEntry(XmlPullParser parser) throws XmlPullParserException {
269          if(!XML_TAG_PUBLIC.equals(parser.getName())){
270              throw new XmlPullParserException("Invalid tag, expecting '"
271                      + XML_TAG_PUBLIC + "' " + parser.getPositionDescription());
272          }
273          String resourceIdStr = null;
274          String typeName = null;
275          String entryName = null;
276          int count = parser.getAttributeCount();
277          for(int i = 0; i < count; i++){
278              String attrName = parser.getAttributeName(i);
279              String value = parser.getAttributeValue(i);
280              if(XML_ATTRIBUTE_ID.equals(attrName)){
281                  resourceIdStr = value;
282              }else if(XML_ATTRIBUTE_TYPE.equals(attrName)){
283                  typeName = value;
284              }else if(XML_ATTRIBUTE_NAME.equals(attrName)){
285                  entryName = value;
286              }
287          }
288          if(typeName == null){
289              throw new XmlPullParserException("Missing attribute '"
290                      + XML_ATTRIBUTE_TYPE + "' " + parser.getPositionDescription());
291          }
292          if(resourceIdStr == null){
293              throw new XmlPullParserException("Missing attribute '"
294                      + XML_ATTRIBUTE_ID + "' " + parser.getPositionDescription());
295          }
296          if(entryName == null){
297              throw new XmlPullParserException("Missing attribute '"
298                      + XML_ATTRIBUTE_NAME + "' " + parser.getPositionDescription());
299          }
300  
301          int resourceId = (int) Long.decode(resourceIdStr).longValue();
302          int packageId = (resourceId >> 24) & 0xff;
303          int typeId = (resourceId >> 16) & 0xff;
304          int entryId = resourceId & 0xffff;
305  
306          TypeIdentifier typeIdentifier = getOrCreate(typeId, typeName);
307          ResourceIdentifier entry = new ResourceIdentifier(entryId, entryName);
308          typeIdentifier.add(entry);
309          if(getId() == 0){
310              setId(packageId);
311          }
312      }
313  
314      public PackageBlock getPackageBlock() {
315          return mPackageBlock;
316      }
317      public void setPackageBlock(PackageBlock packageBlock) {
318          this.mPackageBlock = packageBlock;
319      }
320  
321      public void add(ResourceEntry resourceEntry){
322          add(resourceEntry.get());
323      }
324      public void add(Entry entry){
325          if(entry == null || entry.isNull()){
326              return;
327          }
328          TypeBlock typeBlock = entry.getTypeBlock();
329          TypeIdentifier typeIdentifier = getOrCreate(typeBlock.getId(), typeBlock.getTypeName());
330          ResourceIdentifier resourceIdentifier = new ResourceIdentifier(entry.getId(), entry.getName());
331          typeIdentifier.add(resourceIdentifier);
332      }
333      private TypeIdentifier getOrCreate(int typeId, String typeName){
334          TypeIdentifier identifier = get(typeId);
335          if(identifier == null){
336              return super.add(new TypeIdentifier(typeId, typeName));
337          }
338          if(typeName !=null && identifier.getName() == null){
339              identifier.setName(typeName);
340              identifier = super.add(identifier);
341          }
342          return identifier;
343      }
344      @Override
345      public void clear(){
346          for(TypeIdentifier identifier : getItems()){
347              identifier.clear();
348          }
349          super.clear();
350      }
351  
352      public int renameSpecs(){
353          int result = 0;
354          for(TypeIdentifier ti : getItems()){
355              int renamed = ti.renameSpecs();
356              result = result + renamed;
357          }
358          return result;
359      }
360      public int renameDuplicateSpecs(){
361          int result = 0;
362          for(TypeIdentifier ti : getItems()){
363              int renamed = ti.renameDuplicateSpecs();
364              result = result + renamed;
365          }
366          return result;
367      }
368      public int renameBadSpecs(){
369          int result = 0;
370          for(TypeIdentifier ti : getItems()){
371              int renamed = ti.renameBadSpecs();
372              result = result + renamed;
373          }
374          return result;
375      }
376      public String validateSpecNames(){
377          int duplicates = renameDuplicateSpecs();
378          int bad = renameBadSpecs();
379          if(duplicates == 0 && bad == 0){
380              return null;
381          }
382          return "Spec names validated, duplicates = " + duplicates
383                  + ", bad = " + bad;
384      }
385      @Override
386      void setCaseInsensitive(boolean caseInsensitive){
387          super.setCaseInsensitive(caseInsensitive);
388          for(TypeIdentifier ti : getItems()){
389              ti.setCaseInsensitive(caseInsensitive);
390          }
391      }
392  }