Home | History | Annotate | Download | only in resources
      1 /*
      2  * Copyright (C) 2007 The Android Open Source Project
      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 
     17 package com.android.ide.common.resources;
     18 
     19 import com.android.SdkConstants;
     20 import com.android.annotations.NonNull;
     21 import com.android.annotations.Nullable;
     22 import com.android.ide.common.rendering.api.ResourceValue;
     23 import com.android.ide.common.resources.configuration.Configurable;
     24 import com.android.ide.common.resources.configuration.FolderConfiguration;
     25 import com.android.ide.common.resources.configuration.LanguageQualifier;
     26 import com.android.ide.common.resources.configuration.RegionQualifier;
     27 import com.android.io.IAbstractFile;
     28 import com.android.io.IAbstractFolder;
     29 import com.android.io.IAbstractResource;
     30 import com.android.resources.FolderTypeRelationship;
     31 import com.android.resources.ResourceFolderType;
     32 import com.android.resources.ResourceType;
     33 
     34 import java.io.IOException;
     35 import java.util.ArrayList;
     36 import java.util.Collection;
     37 import java.util.Collections;
     38 import java.util.EnumMap;
     39 import java.util.HashMap;
     40 import java.util.IdentityHashMap;
     41 import java.util.Iterator;
     42 import java.util.List;
     43 import java.util.Map;
     44 import java.util.Set;
     45 import java.util.SortedSet;
     46 import java.util.TreeSet;
     47 
     48 /**
     49  * Base class for resource repository.
     50  *
     51  * A repository is both a file representation of a resource folder and a representation
     52  * of the generated resources, organized by type.
     53  *
     54  * {@link #getResourceFolder(IAbstractFolder)} and {@link #getSourceFiles(ResourceType, String, FolderConfiguration)}
     55  * give access to the folders and files of the resource folder.
     56  *
     57  * {@link #getResources(ResourceType)} gives access to the resources directly.
     58  *
     59  */
     60 public abstract class ResourceRepository {
     61 
     62     protected final Map<ResourceFolderType, List<ResourceFolder>> mFolderMap =
     63         new EnumMap<ResourceFolderType, List<ResourceFolder>>(ResourceFolderType.class);
     64 
     65     protected final Map<ResourceType, Map<String, ResourceItem>> mResourceMap =
     66             new EnumMap<ResourceType, Map<String, ResourceItem>>(
     67             ResourceType.class);
     68 
     69     private final Map<Map<String, ResourceItem>, Collection<ResourceItem>> mReadOnlyListMap =
     70             new IdentityHashMap<Map<String, ResourceItem>, Collection<ResourceItem>>();
     71 
     72     private final boolean mFrameworkRepository;
     73 
     74     protected final IntArrayWrapper mWrapper = new IntArrayWrapper(null);
     75 
     76     /**
     77      * Makes a resource repository
     78      * @param isFrameworkRepository whether the repository is for framework resources.
     79      */
     80     protected ResourceRepository(boolean isFrameworkRepository) {
     81         mFrameworkRepository = isFrameworkRepository;
     82     }
     83 
     84     public boolean isFrameworkRepository() {
     85         return mFrameworkRepository;
     86     }
     87 
     88     /**
     89      * Adds a Folder Configuration to the project.
     90      * @param type The resource type.
     91      * @param config The resource configuration.
     92      * @param folder The workspace folder object.
     93      * @return the {@link ResourceFolder} object associated to this folder.
     94      */
     95     private ResourceFolder add(
     96             @NonNull ResourceFolderType type,
     97             @NonNull FolderConfiguration config,
     98             @NonNull IAbstractFolder folder) {
     99         // get the list for the resource type
    100         List<ResourceFolder> list = mFolderMap.get(type);
    101 
    102         if (list == null) {
    103             list = new ArrayList<ResourceFolder>();
    104 
    105             ResourceFolder cf = new ResourceFolder(type, config, folder, this);
    106             list.add(cf);
    107 
    108             mFolderMap.put(type, list);
    109 
    110             return cf;
    111         }
    112 
    113         // look for an already existing folder configuration.
    114         for (ResourceFolder cFolder : list) {
    115             if (cFolder.mConfiguration.equals(config)) {
    116                 // config already exist. Nothing to be done really, besides making sure
    117                 // the IAbstractFolder object is up to date.
    118                 cFolder.mFolder = folder;
    119                 return cFolder;
    120             }
    121         }
    122 
    123         // If we arrive here, this means we didn't find a matching configuration.
    124         // So we add one.
    125         ResourceFolder cf = new ResourceFolder(type, config, folder, this);
    126         list.add(cf);
    127 
    128         return cf;
    129     }
    130 
    131     /**
    132      * Removes a {@link ResourceFolder} associated with the specified {@link IAbstractFolder}.
    133      * @param type The type of the folder
    134      * @param removedFolder the IAbstractFolder object.
    135      * @param context the scanning context
    136      * @return the {@link ResourceFolder} that was removed, or null if no matches were found.
    137      */
    138     @Nullable
    139     public ResourceFolder removeFolder(
    140             @NonNull ResourceFolderType type,
    141             @NonNull IAbstractFolder removedFolder,
    142             @Nullable ScanningContext context) {
    143         // get the list of folders for the resource type.
    144         List<ResourceFolder> list = mFolderMap.get(type);
    145 
    146         if (list != null) {
    147             int count = list.size();
    148             for (int i = 0 ; i < count ; i++) {
    149                 ResourceFolder resFolder = list.get(i);
    150                 IAbstractFolder folder = resFolder.getFolder();
    151                 if (removedFolder.equals(folder)) {
    152                     // we found the matching ResourceFolder. we need to remove it.
    153                     list.remove(i);
    154 
    155                     // remove its content
    156                     resFolder.dispose(context);
    157 
    158                     return resFolder;
    159                 }
    160             }
    161         }
    162 
    163         return null;
    164     }
    165 
    166     /**
    167      * Returns true if this resource repository contains a resource of the given
    168      * name.
    169      *
    170      * @param url the resource URL
    171      * @return true if the resource is known
    172      */
    173     public boolean hasResourceItem(@NonNull String url) {
    174         assert url.startsWith("@") || url.startsWith("?") : url;
    175 
    176         int typeEnd = url.indexOf('/', 1);
    177         if (typeEnd != -1) {
    178             int nameBegin = typeEnd + 1;
    179 
    180             // Skip @ and @+
    181             int typeBegin = url.startsWith("@+") ? 2 : 1; //$NON-NLS-1$
    182 
    183             int colon = url.lastIndexOf(':', typeEnd);
    184             if (colon != -1) {
    185                 typeBegin = colon + 1;
    186             }
    187             String typeName = url.substring(typeBegin, typeEnd);
    188             ResourceType type = ResourceType.getEnum(typeName);
    189             if (type != null) {
    190                 String name = url.substring(nameBegin);
    191                 return hasResourceItem(type, name);
    192             }
    193         }
    194 
    195         return false;
    196     }
    197 
    198     /**
    199      * Returns true if this resource repository contains a resource of the given
    200      * name.
    201      *
    202      * @param type the type of resource to look up
    203      * @param name the name of the resource
    204      * @return true if the resource is known
    205      */
    206     public boolean hasResourceItem(@NonNull ResourceType type, @NonNull String name) {
    207         Map<String, ResourceItem> map = mResourceMap.get(type);
    208 
    209         if (map != null) {
    210 
    211             ResourceItem resourceItem = map.get(name);
    212             if (resourceItem != null) {
    213                 return true;
    214             }
    215         }
    216 
    217         return false;
    218     }
    219 
    220     /**
    221      * Returns a {@link ResourceItem} matching the given {@link ResourceType} and name. If none
    222      * exist, it creates one.
    223      *
    224      * @param type the resource type
    225      * @param name the name of the resource.
    226      * @return A resource item matching the type and name.
    227      */
    228     @NonNull
    229     protected ResourceItem getResourceItem(@NonNull ResourceType type, @NonNull String name) {
    230         // looking for an existing ResourceItem with this type and name
    231         ResourceItem item = findDeclaredResourceItem(type, name);
    232 
    233         // create one if there isn't one already, or if the existing one is inlined, since
    234         // clearly we need a non inlined one (the inline one is removed too)
    235         if (item == null || item.isDeclaredInline()) {
    236             ResourceItem oldItem = item != null && item.isDeclaredInline() ? item : null;
    237 
    238             item = createResourceItem(name);
    239 
    240             Map<String, ResourceItem> map = mResourceMap.get(type);
    241 
    242             if (map == null) {
    243                 if (isFrameworkRepository()) {
    244                     // Pick initial size for the maps. Also change the load factor to 1.0
    245                     // to avoid rehashing the whole table when we (as expected) get near
    246                     // the known rough size of each resource type map.
    247                     int size;
    248                     switch (type) {
    249                         // Based on counts in API 16. Going back to API 10, the counts
    250                         // are roughly 25-50% smaller (e.g. compared to the top 5 types below
    251                         // the fractions are 1107 vs 1734, 831 vs 1508, 895 vs 1255,
    252                         // 733 vs 1064 and 171 vs 783.
    253                         case PUBLIC:           size = 1734; break;
    254                         case DRAWABLE:         size = 1508; break;
    255                         case STRING:           size = 1255; break;
    256                         case ATTR:             size = 1064; break;
    257                         case STYLE:             size = 783; break;
    258                         case ID:                size = 347; break;
    259                         case DECLARE_STYLEABLE: size = 210; break;
    260                         case LAYOUT:            size = 187; break;
    261                         case COLOR:             size = 120; break;
    262                         case ANIM:               size = 95; break;
    263                         case DIMEN:              size = 81; break;
    264                         case BOOL:               size = 54; break;
    265                         case INTEGER:            size = 52; break;
    266                         case ARRAY:              size = 51; break;
    267                         case PLURALS:            size = 20; break;
    268                         case XML:                size = 14; break;
    269                         case INTERPOLATOR :      size = 13; break;
    270                         case ANIMATOR:            size = 8; break;
    271                         case RAW:                 size = 4; break;
    272                         case MENU:                size = 2; break;
    273                         case MIPMAP:              size = 2; break;
    274                         case FRACTION:            size = 1; break;
    275                         default:
    276                             size = 2;
    277                     }
    278                     map = new HashMap<String, ResourceItem>(size, 1.0f);
    279                 } else {
    280                     map = new HashMap<String, ResourceItem>();
    281                 }
    282                 mResourceMap.put(type, map);
    283             }
    284 
    285             map.put(item.getName(), item);
    286 
    287             if (oldItem != null) {
    288                 map.remove(oldItem.getName());
    289 
    290             }
    291         }
    292 
    293         return item;
    294     }
    295 
    296     /**
    297      * Creates a resource item with the given name.
    298      * @param name the name of the resource
    299      * @return a new ResourceItem (or child class) instance.
    300      */
    301     @NonNull
    302     protected abstract ResourceItem createResourceItem(@NonNull String name);
    303 
    304     /**
    305      * Processes a folder and adds it to the list of existing folders.
    306      * @param folder the folder to process
    307      * @return the ResourceFolder created from this folder, or null if the process failed.
    308      */
    309     @Nullable
    310     public ResourceFolder processFolder(@NonNull IAbstractFolder folder) {
    311         // split the name of the folder in segments.
    312         String[] folderSegments = folder.getName().split(SdkConstants.RES_QUALIFIER_SEP);
    313 
    314         // get the enum for the resource type.
    315         ResourceFolderType type = ResourceFolderType.getTypeByName(folderSegments[0]);
    316 
    317         if (type != null) {
    318             // get the folder configuration.
    319             FolderConfiguration config = FolderConfiguration.getConfig(folderSegments);
    320 
    321             if (config != null) {
    322                 return add(type, config, folder);
    323             }
    324         }
    325 
    326         return null;
    327     }
    328 
    329     /**
    330      * Returns a list of {@link ResourceFolder} for a specific {@link ResourceFolderType}.
    331      * @param type The {@link ResourceFolderType}
    332      */
    333     @Nullable
    334     public List<ResourceFolder> getFolders(@NonNull ResourceFolderType type) {
    335         return mFolderMap.get(type);
    336     }
    337 
    338     @NonNull
    339     public List<ResourceType> getAvailableResourceTypes() {
    340         List<ResourceType> list = new ArrayList<ResourceType>();
    341 
    342         // For each key, we check if there's a single ResourceType match.
    343         // If not, we look for the actual content to give us the resource type.
    344 
    345         for (ResourceFolderType folderType : mFolderMap.keySet()) {
    346             List<ResourceType> types = FolderTypeRelationship.getRelatedResourceTypes(folderType);
    347             if (types.size() == 1) {
    348                 // before we add it we check if it's not already present, since a ResourceType
    349                 // could be created from multiple folders, even for the folders that only create
    350                 // one type of resource (drawable for instance, can be created from drawable/ and
    351                 // values/)
    352                 if (list.contains(types.get(0)) == false) {
    353                     list.add(types.get(0));
    354                 }
    355             } else {
    356                 // there isn't a single resource type out of this folder, so we look for all
    357                 // content.
    358                 List<ResourceFolder> folders = mFolderMap.get(folderType);
    359                 if (folders != null) {
    360                     for (ResourceFolder folder : folders) {
    361                         Collection<ResourceType> folderContent = folder.getResourceTypes();
    362 
    363                         // then we add them, but only if they aren't already in the list.
    364                         for (ResourceType folderResType : folderContent) {
    365                             if (list.contains(folderResType) == false) {
    366                                 list.add(folderResType);
    367                             }
    368                         }
    369                     }
    370                 }
    371             }
    372         }
    373 
    374         return list;
    375     }
    376 
    377     /**
    378      * Returns a list of {@link ResourceItem} matching a given {@link ResourceType}.
    379      * @param type the type of the resource items to return
    380      * @return a non null collection of resource items
    381      */
    382     @NonNull
    383     public Collection<ResourceItem> getResourceItemsOfType(@NonNull ResourceType type) {
    384         Map<String, ResourceItem> map = mResourceMap.get(type);
    385 
    386         if (map == null) {
    387             return Collections.emptyList();
    388         }
    389 
    390         Collection<ResourceItem> roList = mReadOnlyListMap.get(map);
    391         if (roList == null) {
    392             roList = Collections.unmodifiableCollection(map.values());
    393             mReadOnlyListMap.put(map, roList);
    394         }
    395 
    396         return roList;
    397     }
    398 
    399     /**
    400      * Returns whether the repository has resources of a given {@link ResourceType}.
    401      * @param type the type of resource to check.
    402      * @return true if the repository contains resources of the given type, false otherwise.
    403      */
    404     public boolean hasResourcesOfType(@NonNull ResourceType type) {
    405         Map<String, ResourceItem> items = mResourceMap.get(type);
    406         return (items != null && items.size() > 0);
    407     }
    408 
    409     /**
    410      * Returns the {@link ResourceFolder} associated with a {@link IAbstractFolder}.
    411      * @param folder The {@link IAbstractFolder} object.
    412      * @return the {@link ResourceFolder} or null if it was not found.
    413      */
    414     @Nullable
    415     public ResourceFolder getResourceFolder(@NonNull IAbstractFolder folder) {
    416         Collection<List<ResourceFolder>> values = mFolderMap.values();
    417 
    418         if (values.isEmpty()) { // This shouldn't be necessary, but has been observed
    419             try {
    420                 loadResources(folder.getParentFolder());
    421             } catch (IOException e) {
    422                 e.printStackTrace();
    423             }
    424         }
    425 
    426         for (List<ResourceFolder> list : values) {
    427             for (ResourceFolder resFolder : list) {
    428                 IAbstractFolder wrapper = resFolder.getFolder();
    429                 if (wrapper.equals(folder)) {
    430                     return resFolder;
    431                 }
    432             }
    433         }
    434 
    435         return null;
    436     }
    437 
    438     /**
    439      * Returns the {@link ResourceFile} matching the given name, {@link ResourceFolderType} and
    440      * configuration.
    441      * <p/>This only works with files generating one resource named after the file (for instance,
    442      * layouts, bitmap based drawable, xml, anims).
    443      * @return the matching file or <code>null</code> if no match was found.
    444      */
    445     @Nullable
    446     public ResourceFile getMatchingFile(@NonNull String name, @NonNull ResourceFolderType type,
    447             @NonNull FolderConfiguration config) {
    448         // get the folders for the given type
    449         List<ResourceFolder> folders = mFolderMap.get(type);
    450 
    451         // look for folders containing a file with the given name.
    452         ArrayList<ResourceFolder> matchingFolders = new ArrayList<ResourceFolder>(folders.size());
    453 
    454         // remove the folders that do not have a file with the given name.
    455         for (int i = 0 ; i < folders.size(); i++) {
    456             ResourceFolder folder = folders.get(i);
    457 
    458             if (folder.hasFile(name) == true) {
    459                 matchingFolders.add(folder);
    460             }
    461         }
    462 
    463         // from those, get the folder with a config matching the given reference configuration.
    464         Configurable match = config.findMatchingConfigurable(matchingFolders);
    465 
    466         // do we have a matching folder?
    467         if (match instanceof ResourceFolder) {
    468             // get the ResourceFile from the filename
    469             return ((ResourceFolder)match).getFile(name);
    470         }
    471 
    472         return null;
    473     }
    474 
    475     /**
    476      * Returns the list of source files for a given resource.
    477      * Optionally, if a {@link FolderConfiguration} is given, then only the best
    478      * match for this config is returned.
    479      *
    480      * @param type the type of the resource.
    481      * @param name the name of the resource.
    482      * @param referenceConfig an optional config for which only the best match will be returned.
    483      *
    484      * @return a list of files generating this resource or null if it was not found.
    485      */
    486     @Nullable
    487     public List<ResourceFile> getSourceFiles(@NonNull ResourceType type, @NonNull String name,
    488             @Nullable FolderConfiguration referenceConfig) {
    489 
    490         Collection<ResourceItem> items = getResourceItemsOfType(type);
    491 
    492         for (ResourceItem item : items) {
    493             if (name.equals(item.getName())) {
    494                 if (referenceConfig != null) {
    495                     Configurable match = referenceConfig.findMatchingConfigurable(
    496                             item.getSourceFileList());
    497 
    498                     if (match instanceof ResourceFile) {
    499                         return Collections.singletonList((ResourceFile) match);
    500                     }
    501 
    502                     return null;
    503                 }
    504                 return item.getSourceFileList();
    505             }
    506         }
    507 
    508         return null;
    509     }
    510 
    511     /**
    512      * Returns the resources values matching a given {@link FolderConfiguration}.
    513      *
    514      * @param referenceConfig the configuration that each value must match.
    515      * @return a map with guaranteed to contain an entry for each {@link ResourceType}
    516      */
    517     @NonNull
    518     public Map<ResourceType, Map<String, ResourceValue>> getConfiguredResources(
    519             @NonNull FolderConfiguration referenceConfig) {
    520         return doGetConfiguredResources(referenceConfig);
    521     }
    522 
    523     /**
    524      * Returns the resources values matching a given {@link FolderConfiguration} for the current
    525      * project.
    526      *
    527      * @param referenceConfig the configuration that each value must match.
    528      * @return a map with guaranteed to contain an entry for each {@link ResourceType}
    529      */
    530     @NonNull
    531     protected final Map<ResourceType, Map<String, ResourceValue>> doGetConfiguredResources(
    532             @NonNull FolderConfiguration referenceConfig) {
    533 
    534         Map<ResourceType, Map<String, ResourceValue>> map =
    535             new EnumMap<ResourceType, Map<String, ResourceValue>>(ResourceType.class);
    536 
    537         for (ResourceType key : ResourceType.values()) {
    538             // get the local results and put them in the map
    539             map.put(key, getConfiguredResource(key, referenceConfig));
    540         }
    541 
    542         return map;
    543     }
    544 
    545     /**
    546      * Returns the sorted list of languages used in the resources.
    547      */
    548     @NonNull
    549     public SortedSet<String> getLanguages() {
    550         SortedSet<String> set = new TreeSet<String>();
    551 
    552         Collection<List<ResourceFolder>> folderList = mFolderMap.values();
    553         for (List<ResourceFolder> folderSubList : folderList) {
    554             for (ResourceFolder folder : folderSubList) {
    555                 FolderConfiguration config = folder.getConfiguration();
    556                 LanguageQualifier lang = config.getLanguageQualifier();
    557                 if (lang != null) {
    558                     set.add(lang.getShortDisplayValue());
    559                 }
    560             }
    561         }
    562 
    563         return set;
    564     }
    565 
    566     /**
    567      * Returns the sorted list of regions used in the resources with the given language.
    568      * @param currentLanguage the current language the region must be associated with.
    569      */
    570     @NonNull
    571     public SortedSet<String> getRegions(@NonNull String currentLanguage) {
    572         SortedSet<String> set = new TreeSet<String>();
    573 
    574         Collection<List<ResourceFolder>> folderList = mFolderMap.values();
    575         for (List<ResourceFolder> folderSubList : folderList) {
    576             for (ResourceFolder folder : folderSubList) {
    577                 FolderConfiguration config = folder.getConfiguration();
    578 
    579                 // get the language
    580                 LanguageQualifier lang = config.getLanguageQualifier();
    581                 if (lang != null && lang.getShortDisplayValue().equals(currentLanguage)) {
    582                     RegionQualifier region = config.getRegionQualifier();
    583                     if (region != null) {
    584                         set.add(region.getShortDisplayValue());
    585                     }
    586                 }
    587             }
    588         }
    589 
    590         return set;
    591     }
    592 
    593     /**
    594      * Loads the resources from a resource folder.
    595      * <p/>
    596      *
    597      * @param rootFolder The folder to read the resources from. This is the top level
    598      * resource folder (res/)
    599      * @throws IOException
    600      */
    601     public void loadResources(@NonNull IAbstractFolder rootFolder)
    602             throws IOException {
    603         ScanningContext context = new ScanningContext(this);
    604 
    605         IAbstractResource[] files = rootFolder.listMembers();
    606         for (IAbstractResource file : files) {
    607             if (file instanceof IAbstractFolder) {
    608                 IAbstractFolder folder = (IAbstractFolder) file;
    609                 ResourceFolder resFolder = processFolder(folder);
    610 
    611                 if (resFolder != null) {
    612                     // now we process the content of the folder
    613                     IAbstractResource[] children = folder.listMembers();
    614 
    615                     for (IAbstractResource childRes : children) {
    616                         if (childRes instanceof IAbstractFile) {
    617                             resFolder.processFile((IAbstractFile) childRes,
    618                                     ResourceDeltaKind.ADDED, context);
    619                         }
    620                     }
    621                 }
    622             }
    623         }
    624     }
    625 
    626 
    627     protected void removeFile(@NonNull Collection<ResourceType> types,
    628             @NonNull ResourceFile file) {
    629         for (ResourceType type : types) {
    630             removeFile(type, file);
    631         }
    632     }
    633 
    634     protected void removeFile(@NonNull ResourceType type, @NonNull ResourceFile file) {
    635         Map<String, ResourceItem> map = mResourceMap.get(type);
    636         if (map != null) {
    637             Collection<ResourceItem> values = map.values();
    638             for (ResourceItem item : values) {
    639                 item.removeFile(file);
    640             }
    641         }
    642     }
    643 
    644     /**
    645      * Returns a map of (resource name, resource value) for the given {@link ResourceType}.
    646      * <p/>The values returned are taken from the resource files best matching a given
    647      * {@link FolderConfiguration}.
    648      * @param type the type of the resources.
    649      * @param referenceConfig the configuration to best match.
    650      */
    651     @NonNull
    652     private Map<String, ResourceValue> getConfiguredResource(@NonNull ResourceType type,
    653             @NonNull FolderConfiguration referenceConfig) {
    654 
    655         // get the resource item for the given type
    656         Map<String, ResourceItem> items = mResourceMap.get(type);
    657         if (items == null) {
    658             return new HashMap<String, ResourceValue>();
    659         }
    660 
    661         // create the map
    662         HashMap<String, ResourceValue> map = new HashMap<String, ResourceValue>(items.size());
    663 
    664         for (ResourceItem item : items.values()) {
    665             ResourceValue value = item.getResourceValue(type, referenceConfig,
    666                     isFrameworkRepository());
    667             if (value != null) {
    668                 map.put(item.getName(), value);
    669             }
    670         }
    671 
    672         return map;
    673     }
    674 
    675 
    676     /**
    677      * Cleans up the repository of resource items that have no source file anymore.
    678      */
    679     public void postUpdateCleanUp() {
    680         // Since removed files/folders remove source files from existing ResourceItem, loop through
    681         // all resource items and remove the ones that have no source files.
    682 
    683         Collection<Map<String, ResourceItem>> maps = mResourceMap.values();
    684         for (Map<String, ResourceItem> map : maps) {
    685             Set<String> keySet = map.keySet();
    686             Iterator<String> iterator = keySet.iterator();
    687             while (iterator.hasNext()) {
    688                 String name = iterator.next();
    689                 ResourceItem resourceItem = map.get(name);
    690                 if (resourceItem.hasNoSourceFile()) {
    691                     iterator.remove();
    692                 }
    693             }
    694         }
    695     }
    696 
    697     /**
    698      * Looks up an existing {@link ResourceItem} by {@link ResourceType} and name. This
    699      * ignores inline resources.
    700      * @param type the Resource Type.
    701      * @param name the Resource name.
    702      * @return the existing ResourceItem or null if no match was found.
    703      */
    704     @Nullable
    705     private ResourceItem findDeclaredResourceItem(@NonNull ResourceType type,
    706             @NonNull String name) {
    707         Map<String, ResourceItem> map = mResourceMap.get(type);
    708 
    709         if (map != null) {
    710             ResourceItem resourceItem = map.get(name);
    711             if (resourceItem != null && !resourceItem.isDeclaredInline()) {
    712                 return resourceItem;
    713             }
    714         }
    715 
    716         return null;
    717     }
    718 }
    719 
    720