xref: /aosp_15_r20/external/robolectric/resources/src/main/java/org/robolectric/res/PackageResourceTable.java (revision e6ba16074e6af37d123cb567d575f496bf0a58ee)
1 package org.robolectric.res;
2 
3 import com.google.common.collect.BiMap;
4 import com.google.common.collect.HashBiMap;
5 import java.io.IOException;
6 import java.io.InputStream;
7 import java.nio.file.Path;
8 import javax.annotation.Nonnull;
9 import org.robolectric.res.android.ResTable_config;
10 import org.robolectric.res.builder.XmlBlock;
11 
12 /** A {@link ResourceTable} for a single package, e.g: "android" / ox01 */
13 public class PackageResourceTable implements ResourceTable {
14 
15   private final ResBunch resources = new ResBunch();
16   private final BiMap<Integer, ResName> resourceTable = HashBiMap.create();
17 
18   private final ResourceIdGenerator androidResourceIdGenerator = new ResourceIdGenerator(0x01);
19   private final String packageName;
20   private int packageIdentifier;
21 
PackageResourceTable(String packageName)22   public PackageResourceTable(String packageName) {
23     this.packageName = packageName;
24   }
25 
26   @Override
getPackageName()27   public String getPackageName() {
28     return packageName;
29   }
30 
getPackageIdentifier()31   int getPackageIdentifier() {
32     return packageIdentifier;
33   }
34 
35   @Override
getResourceId(ResName resName)36   public Integer getResourceId(ResName resName) {
37     Integer id = resourceTable.inverse().get(resName);
38     if (id == null && resName != null && resName.name.contains(".")) {
39       // try again with underscores (in case we're looking in the compile-time resources, where
40       // we haven't read XML declarations and only know what the R.class tells us).
41       id =
42           resourceTable
43               .inverse()
44               .get(new ResName(resName.packageName, resName.type, underscorize(resName.name)));
45     }
46     return id != null ? id : 0;
47   }
48 
49   @Override
getResName(int resourceId)50   public ResName getResName(int resourceId) {
51     return resourceTable.get(resourceId);
52   }
53 
54   @Override
getValue(@onnull ResName resName, ResTable_config config)55   public TypedResource getValue(@Nonnull ResName resName, ResTable_config config) {
56     return resources.get(resName, config);
57   }
58 
59   @Override
getValue(int resId, ResTable_config config)60   public TypedResource getValue(int resId, ResTable_config config) {
61     return resources.get(getResName(resId), config);
62   }
63 
64   @Override
getXml(ResName resName, ResTable_config config)65   public XmlBlock getXml(ResName resName, ResTable_config config) {
66     FileTypedResource fileTypedResource = getFileResource(resName, config);
67     if (fileTypedResource == null || !fileTypedResource.isXml()) {
68       return null;
69     } else {
70       return XmlBlock.create(fileTypedResource.getPath(), resName.packageName);
71     }
72   }
73 
74   @Override
getRawValue(ResName resName, ResTable_config config)75   public InputStream getRawValue(ResName resName, ResTable_config config) {
76     FileTypedResource fileTypedResource = getFileResource(resName, config);
77     if (fileTypedResource == null) {
78       return null;
79     } else {
80       Path file = fileTypedResource.getPath();
81       try {
82         return file == null ? null : Fs.getInputStream(file);
83       } catch (IOException e) {
84         throw new RuntimeException(e);
85       }
86     }
87   }
88 
getFileResource(ResName resName, ResTable_config config)89   private FileTypedResource getFileResource(ResName resName, ResTable_config config) {
90     TypedResource typedResource = resources.get(resName, config);
91     if (!(typedResource instanceof FileTypedResource)) {
92       return null;
93     } else {
94       return (FileTypedResource) typedResource;
95     }
96   }
97 
98   @Override
getRawValue(int resId, ResTable_config config)99   public InputStream getRawValue(int resId, ResTable_config config) {
100     return getRawValue(getResName(resId), config);
101   }
102 
103   @Override
receive(Visitor visitor)104   public void receive(Visitor visitor) {
105     resources.receive(visitor);
106   }
107 
addResource(int resId, String type, String name)108   void addResource(int resId, String type, String name) {
109     if (ResourceIds.isFrameworkResource(resId)) {
110       androidResourceIdGenerator.record(resId, type, name);
111     }
112     ResName resName = new ResName(packageName, type, name);
113     int resIdPackageIdentifier = ResourceIds.getPackageIdentifier(resId);
114     if (getPackageIdentifier() == 0) {
115       this.packageIdentifier = resIdPackageIdentifier;
116     } else if (getPackageIdentifier() != resIdPackageIdentifier) {
117       throw new IllegalArgumentException(
118           "Incompatible package for "
119               + packageName
120               + ":"
121               + type
122               + "/"
123               + name
124               + " with resId "
125               + resIdPackageIdentifier
126               + " to ResourceIndex with packageIdentifier "
127               + getPackageIdentifier());
128     }
129 
130     ResName existingEntry = resourceTable.put(resId, resName);
131     if (existingEntry != null && !existingEntry.equals(resName)) {
132       throw new IllegalArgumentException(
133           "ResId "
134               + Integer.toHexString(resId)
135               + " mapped to both "
136               + resName
137               + " and "
138               + existingEntry);
139     }
140   }
141 
addResource(String type, String name, TypedResource value)142   void addResource(String type, String name, TypedResource value) {
143     ResName resName = new ResName(packageName, type, name);
144 
145     // compound style names were previously registered with underscores (TextAppearance_Small)
146     // because they came from R.style; re-register with dots.
147     ResName resNameWithUnderscores = new ResName(packageName, type, underscorize(name));
148     Integer oldId = resourceTable.inverse().get(resNameWithUnderscores);
149     if (oldId != null) {
150       resourceTable.forcePut(oldId, resName);
151     }
152 
153     Integer id = resourceTable.inverse().get(resName);
154     if (id == null && isAndroidPackage(resName)) {
155       id = androidResourceIdGenerator.generate(type, name);
156       ResName existing = resourceTable.put(id, resName);
157       if (existing != null) {
158         throw new IllegalStateException(resName + " assigned ID to already existing " + existing);
159       }
160     }
161     resources.put(resName, value);
162   }
163 
isAndroidPackage(ResName resName)164   private boolean isAndroidPackage(ResName resName) {
165     return "android".equals(resName.packageName);
166   }
167 
underscorize(String s)168   private static String underscorize(String s) {
169     return s == null ? null : s.replace('.', '_');
170   }
171 }
172