Home | History | Annotate | Download | only in res
      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 
     24   public PackageResourceTable(String packageName) {
     25     this.packageName = packageName;
     26   }
     27 
     28   @Override
     29   public String getPackageName() {
     30     return packageName;
     31   }
     32 
     33   int getPackageIdentifier() {
     34     return packageIdentifier;
     35   }
     36 
     37   @Override
     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
     52   public ResName getResName(int resourceId) {
     53     return resourceTable.get(resourceId);
     54   }
     55 
     56   @Override
     57   public TypedResource getValue(@Nonnull ResName resName, ResTable_config config) {
     58     return resources.get(resName, config);
     59   }
     60 
     61   @Override
     62   public TypedResource getValue(int resId, ResTable_config config) {
     63     return resources.get(getResName(resId), config);
     64   }
     65 
     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 
     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 
     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
     99   public InputStream getRawValue(int resId, ResTable_config config) {
    100     return getRawValue(getResName(resId), config);
    101   }
    102 
    103   @Override
    104   public void receive(Visitor visitor) {
    105     resources.receive(visitor);
    106   }
    107 
    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 
    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 
    148   private boolean isAndroidPackage(ResName resName) {
    149     return "android".equals(resName.packageName);
    150   }
    151 
    152   private static String underscorize(String s) {
    153     return s == null ? null : s.replace('.', '_');
    154   }
    155 }
    156