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 javax.annotation.Nonnull; 8 import org.robolectric.res.android.ResTable_config; 9 import org.robolectric.res.builder.XmlBlock; 10 11 /** 12 * A {@link ResourceTable} for a single package, e.g: "android" / ox01 13 */ 14 public class PackageResourceTable implements ResourceTable { 15 16 private final ResBunch resources = new ResBunch(); 17 private final BiMap<Integer, ResName> resourceTable = HashBiMap.create(); 18 19 private final ResourceIdGenerator androidResourceIdGenerator = new ResourceIdGenerator(0x01); 20 private final String packageName; 21 private int packageIdentifier; 22 23 PackageResourceTable(String packageName)24 public PackageResourceTable(String packageName) { 25 this.packageName = packageName; 26 } 27 28 @Override getPackageName()29 public String getPackageName() { 30 return packageName; 31 } 32 getPackageIdentifier()33 int getPackageIdentifier() { 34 return packageIdentifier; 35 } 36 37 @Override getResourceId(ResName resName)38 public Integer getResourceId(ResName resName) { 39 Integer id = resourceTable.inverse().get(resName); 40 if (id == null && resName != null && resName.name.contains(".")) { 41 // try again with underscores (in case we're looking in the compile-time resources, where 42 // we haven't read XML declarations and only know what the R.class tells us). 43 id = 44 resourceTable 45 .inverse() 46 .get(new ResName(resName.packageName, resName.type, underscorize(resName.name))); 47 } 48 return id != null ? id : 0; 49 } 50 51 @Override getResName(int resourceId)52 public ResName getResName(int resourceId) { 53 return resourceTable.get(resourceId); 54 } 55 56 @Override getValue(@onnull ResName resName, ResTable_config config)57 public TypedResource getValue(@Nonnull ResName resName, ResTable_config config) { 58 return resources.get(resName, config); 59 } 60 61 @Override getValue(int resId, ResTable_config config)62 public TypedResource getValue(int resId, ResTable_config config) { 63 return resources.get(getResName(resId), config); 64 } 65 getXml(ResName resName, ResTable_config config)66 @Override public XmlBlock getXml(ResName resName, ResTable_config config) { 67 FileTypedResource fileTypedResource = getFileResource(resName, config); 68 if (fileTypedResource == null || !fileTypedResource.isXml()) { 69 return null; 70 } else { 71 return XmlBlock.create(fileTypedResource.getFsFile(), resName.packageName); 72 } 73 } 74 getRawValue(ResName resName, ResTable_config config)75 @Override public InputStream getRawValue(ResName resName, ResTable_config config) { 76 FileTypedResource fileTypedResource = getFileResource(resName, config); 77 if (fileTypedResource == null) { 78 return null; 79 } else { 80 FsFile file = fileTypedResource.getFsFile(); 81 try { 82 return file == null ? null : file.getInputStream(); 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("Incompatible package for " + packageName + ":" + type + "/" + name + " with resId " + resIdPackageIdentifier + " to ResourceIndex with packageIdentifier " + getPackageIdentifier()); 118 } 119 120 ResName existingEntry = resourceTable.put(resId, resName); 121 if (existingEntry != null && !existingEntry.equals(resName)) { 122 throw new IllegalArgumentException("ResId " + Integer.toHexString(resId) + " mapped to both " + resName + " and " + existingEntry); 123 } 124 } 125 addResource(String type, String name, TypedResource value)126 void addResource(String type, String name, TypedResource value) { 127 ResName resName = new ResName(packageName, type, name); 128 129 // compound style names were previously registered with underscores (TextAppearance_Small) 130 // because they came from R.style; re-register with dots. 131 ResName resNameWithUnderscores = new ResName(packageName, type, underscorize(name)); 132 Integer oldId = resourceTable.inverse().get(resNameWithUnderscores); 133 if (oldId != null) { 134 resourceTable.forcePut(oldId, resName); 135 } 136 137 Integer id = resourceTable.inverse().get(resName); 138 if (id == null && isAndroidPackage(resName)) { 139 id = androidResourceIdGenerator.generate(type, name); 140 ResName existing = resourceTable.put(id, resName); 141 if (existing != null) { 142 throw new IllegalStateException(resName + " assigned ID to already existing " + existing); 143 } 144 } 145 resources.put(resName, value); 146 } 147 isAndroidPackage(ResName resName)148 private boolean isAndroidPackage(ResName resName) { 149 return "android".equals(resName.packageName); 150 } 151 underscorize(String s)152 private static String underscorize(String s) { 153 return s == null ? null : s.replace('.', '_'); 154 } 155 } 156