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