• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007 The Android Open Source Project
3  *
4  * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
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.eclipse.adt.internal.resources.manager;
18 
19 import com.android.ide.eclipse.adt.internal.resources.IResourceRepository;
20 import com.android.ide.eclipse.adt.internal.resources.ResourceItem;
21 import com.android.ide.eclipse.adt.internal.resources.ResourceType;
22 import com.android.ide.eclipse.adt.internal.resources.configurations.FolderConfiguration;
23 import com.android.ide.eclipse.adt.internal.resources.configurations.LanguageQualifier;
24 import com.android.ide.eclipse.adt.internal.resources.configurations.RegionQualifier;
25 import com.android.ide.eclipse.adt.internal.resources.configurations.ResourceQualifier;
26 import com.android.ide.eclipse.adt.internal.resources.manager.files.IAbstractFolder;
27 import com.android.layoutlib.api.IResourceValue;
28 import com.android.layoutlib.utils.ResourceValue;
29 
30 import org.eclipse.core.resources.IFolder;
31 
32 import java.util.ArrayList;
33 import java.util.Collection;
34 import java.util.Collections;
35 import java.util.HashMap;
36 import java.util.List;
37 import java.util.Map;
38 import java.util.Set;
39 import java.util.SortedSet;
40 import java.util.TreeSet;
41 
42 /**
43  * Represents the resources of a project. This is a file view of the resources, with handling
44  * for the alternate resource types. For a compiled view use CompiledResources.
45  */
46 public class ProjectResources implements IResourceRepository {
47     private final HashMap<ResourceFolderType, List<ResourceFolder>> mFolderMap =
48         new HashMap<ResourceFolderType, List<ResourceFolder>>();
49 
50     private final HashMap<ResourceType, List<ProjectResourceItem>> mResourceMap =
51         new HashMap<ResourceType, List<ProjectResourceItem>>();
52 
53     /** Map of (name, id) for resources of type {@link ResourceType#ID} coming from R.java */
54     private Map<String, Map<String, Integer>> mResourceValueMap;
55     /** Map of (id, [name, resType]) for all resources coming from R.java */
56     private Map<Integer, String[]> mResIdValueToNameMap;
57     /** Map of (int[], name) for styleable resources coming from R.java */
58     private Map<IntArrayWrapper, String> mStyleableValueToNameMap;
59 
60     /** Cached list of {@link IdResourceItem}. This is mix of IdResourceItem created by
61      * {@link MultiResourceFile} for ids coming from XML files under res/values and
62      * {@link IdResourceItem} created manually, from the list coming from R.java */
63     private final ArrayList<IdResourceItem> mIdResourceList = new ArrayList<IdResourceItem>();
64 
65     private final boolean mIsFrameworkRepository;
66 
67     private final IntArrayWrapper mWrapper = new IntArrayWrapper(null);
68 
ProjectResources(boolean isFrameworkRepository)69     public ProjectResources(boolean isFrameworkRepository) {
70         mIsFrameworkRepository = isFrameworkRepository;
71     }
72 
isSystemRepository()73     public boolean isSystemRepository() {
74         return mIsFrameworkRepository;
75     }
76 
77     /**
78      * Adds a Folder Configuration to the project.
79      * @param type The resource type.
80      * @param config The resource configuration.
81      * @param folder The workspace folder object.
82      * @return the {@link ResourceFolder} object associated to this folder.
83      */
add(ResourceFolderType type, FolderConfiguration config, IAbstractFolder folder)84     protected ResourceFolder add(ResourceFolderType type, FolderConfiguration config,
85             IAbstractFolder folder) {
86         // get the list for the resource type
87         List<ResourceFolder> list = mFolderMap.get(type);
88 
89         if (list == null) {
90             list = new ArrayList<ResourceFolder>();
91 
92             ResourceFolder cf = new ResourceFolder(type, config, folder, mIsFrameworkRepository);
93             list.add(cf);
94 
95             mFolderMap.put(type, list);
96 
97             return cf;
98         }
99 
100         // look for an already existing folder configuration.
101         for (ResourceFolder cFolder : list) {
102             if (cFolder.mConfiguration.equals(config)) {
103                 // config already exist. Nothing to be done really, besides making sure
104                 // the IFolder object is up to date.
105                 cFolder.mFolder = folder;
106                 return cFolder;
107             }
108         }
109 
110         // If we arrive here, this means we didn't find a matching configuration.
111         // So we add one.
112         ResourceFolder cf = new ResourceFolder(type, config, folder, mIsFrameworkRepository);
113         list.add(cf);
114 
115         return cf;
116     }
117 
118     /**
119      * Removes a {@link ResourceFolder} associated with the specified {@link IAbstractFolder}.
120      * @param type The type of the folder
121      * @param folder the IFolder object.
122      */
removeFolder(ResourceFolderType type, IFolder folder)123     protected void removeFolder(ResourceFolderType type, IFolder folder) {
124         // get the list of folders for the resource type.
125         List<ResourceFolder> list = mFolderMap.get(type);
126 
127         if (list != null) {
128             int count = list.size();
129             for (int i = 0 ; i < count ; i++) {
130                 ResourceFolder resFolder = list.get(i);
131                 if (resFolder.getFolder().getIFolder().equals(folder)) {
132                     // we found the matching ResourceFolder. we need to remove it.
133                     list.remove(i);
134 
135                     // we now need to invalidate this resource type.
136                     // The easiest way is to touch one of the other folders of the same type.
137                     if (list.size() > 0) {
138                         list.get(0).touch();
139                     } else {
140                         // if the list is now empty, and we have a single ResouceType out of this
141                         // ResourceFolderType, then we are done.
142                         // However, if another ResourceFolderType can generate similar ResourceType
143                         // than this, we need to update those ResourceTypes as well.
144                         // For instance, if the last "drawable-*" folder is deleted, we need to
145                         // refresh the ResourceItem associated with ResourceType.DRAWABLE.
146                         // Those can be found in ResourceFolderType.DRAWABLE but also in
147                         // ResourceFolderType.VALUES.
148                         // If we don't find a single folder to touch, then it's fine, as the top
149                         // level items (the list of generated resource types) is not cached
150                         // (for now)
151 
152                         // get the lists of ResourceTypes generated by this ResourceFolderType
153                         ResourceType[] resTypes = FolderTypeRelationship.getRelatedResourceTypes(
154                                 type);
155 
156                         // for each of those, make sure to find one folder to touch so that the
157                         // list of ResourceItem associated with the type is rebuilt.
158                         for (ResourceType resType : resTypes) {
159                             // get the list of folder that can generate this type
160                             ResourceFolderType[] folderTypes =
161                                 FolderTypeRelationship.getRelatedFolders(resType);
162 
163                             // we only need to touch one folder in any of those (since it's one
164                             // folder per type, not per folder type).
165                             for (ResourceFolderType folderType : folderTypes) {
166                                 List<ResourceFolder> resFolders = mFolderMap.get(folderType);
167 
168                                 if (resFolders != null && resFolders.size() > 0) {
169                                     resFolders.get(0).touch();
170                                     break;
171                                 }
172                             }
173                         }
174                     }
175 
176                     // we're done updating/touching, we can stop
177                     break;
178                 }
179             }
180         }
181     }
182 
183 
184     /**
185      * Returns a list of {@link ResourceFolder} for a specific {@link ResourceFolderType}.
186      * @param type The {@link ResourceFolderType}
187      */
getFolders(ResourceFolderType type)188     public List<ResourceFolder> getFolders(ResourceFolderType type) {
189         return mFolderMap.get(type);
190     }
191 
192     /* (non-Javadoc)
193      * @see com.android.ide.eclipse.editors.resources.IResourceRepository#getAvailableResourceTypes()
194      */
getAvailableResourceTypes()195     public ResourceType[] getAvailableResourceTypes() {
196         ArrayList<ResourceType> list = new ArrayList<ResourceType>();
197 
198         // For each key, we check if there's a single ResourceType match.
199         // If not, we look for the actual content to give us the resource type.
200 
201         for (ResourceFolderType folderType : mFolderMap.keySet()) {
202             ResourceType types[] = FolderTypeRelationship.getRelatedResourceTypes(folderType);
203             if (types.length == 1) {
204                 // before we add it we check if it's not already present, since a ResourceType
205                 // could be created from multiple folders, even for the folders that only create
206                 // one type of resource (drawable for instance, can be created from drawable/ and
207                 // values/)
208                 if (list.indexOf(types[0]) == -1) {
209                     list.add(types[0]);
210                 }
211             } else {
212                 // there isn't a single resource type out of this folder, so we look for all
213                 // content.
214                 List<ResourceFolder> folders = mFolderMap.get(folderType);
215                 if (folders != null) {
216                     for (ResourceFolder folder : folders) {
217                         Collection<ResourceType> folderContent = folder.getResourceTypes();
218 
219                         // then we add them, but only if they aren't already in the list.
220                         for (ResourceType folderResType : folderContent) {
221                             if (list.indexOf(folderResType) == -1) {
222                                 list.add(folderResType);
223                             }
224                         }
225                     }
226                 }
227             }
228         }
229 
230         // in case ResourceType.ID haven't been added yet because there's no id defined
231         // in XML, we check on the list of compiled id resources.
232         if (list.indexOf(ResourceType.ID) == -1 && mResourceValueMap != null) {
233             Map<String, Integer> map = mResourceValueMap.get(ResourceType.ID.getName());
234             if (map != null && map.size() > 0) {
235                 list.add(ResourceType.ID);
236             }
237         }
238 
239         // at this point the list is full of ResourceType defined in the files.
240         // We need to sort it.
241         Collections.sort(list);
242 
243         return list.toArray(new ResourceType[list.size()]);
244     }
245 
246     /* (non-Javadoc)
247      * @see com.android.ide.eclipse.editors.resources.IResourceRepository#getResources(com.android.ide.eclipse.common.resources.ResourceType)
248      */
getResources(ResourceType type)249     public ProjectResourceItem[] getResources(ResourceType type) {
250         checkAndUpdate(type);
251 
252         if (type == ResourceType.ID) {
253             synchronized (mIdResourceList) {
254                 return mIdResourceList.toArray(new ProjectResourceItem[mIdResourceList.size()]);
255             }
256         }
257 
258         List<ProjectResourceItem> items = mResourceMap.get(type);
259 
260         return items.toArray(new ProjectResourceItem[items.size()]);
261     }
262 
263     /* (non-Javadoc)
264      * @see com.android.ide.eclipse.editors.resources.IResourceRepository#hasResources(com.android.ide.eclipse.common.resources.ResourceType)
265      */
hasResources(ResourceType type)266     public boolean hasResources(ResourceType type) {
267         checkAndUpdate(type);
268 
269         if (type == ResourceType.ID) {
270             synchronized (mIdResourceList) {
271                 return mIdResourceList.size() > 0;
272             }
273         }
274 
275         List<ProjectResourceItem> items = mResourceMap.get(type);
276         return (items != null && items.size() > 0);
277     }
278 
279     /**
280      * Returns the {@link ResourceFolder} associated with a {@link IFolder}.
281      * @param folder The {@link IFolder} object.
282      * @return the {@link ResourceFolder} or null if it was not found.
283      */
getResourceFolder(IFolder folder)284     public ResourceFolder getResourceFolder(IFolder folder) {
285         for (List<ResourceFolder> list : mFolderMap.values()) {
286             for (ResourceFolder resFolder : list) {
287                 if (resFolder.getFolder().getIFolder().equals(folder)) {
288                     return resFolder;
289                 }
290             }
291         }
292 
293         return null;
294     }
295 
296     /**
297      * Returns the {@link ResourceFile} matching the given name, {@link ResourceFolderType} and
298      * configuration.
299      * <p/>This only works with files generating one resource named after the file (for instance,
300      * layouts, bitmap based drawable, xml, anims).
301      * @return the matching file or <code>null</code> if no match was found.
302      */
getMatchingFile(String name, ResourceFolderType type, FolderConfiguration config)303     public ResourceFile getMatchingFile(String name, ResourceFolderType type,
304             FolderConfiguration config) {
305         // get the folders for the given type
306         List<ResourceFolder> folders = mFolderMap.get(type);
307 
308         // look for folders containing a file with the given name.
309         ArrayList<ResourceFolder> matchingFolders = new ArrayList<ResourceFolder>();
310 
311         // remove the folders that do not have a file with the given name, or if their config
312         // is incompatible.
313         for (int i = 0 ; i < folders.size(); i++) {
314             ResourceFolder folder = folders.get(i);
315 
316             if (folder.hasFile(name) == true) {
317                 matchingFolders.add(folder);
318             }
319         }
320 
321         // from those, get the folder with a config matching the given reference configuration.
322         Resource match = findMatchingConfiguredResource(matchingFolders, config);
323 
324         // do we have a matching folder?
325         if (match instanceof ResourceFolder) {
326             // get the ResourceFile from the filename
327             return ((ResourceFolder)match).getFile(name);
328         }
329 
330         return null;
331     }
332 
333     /**
334      * Returns the resources values matching a given {@link FolderConfiguration}.
335      * @param referenceConfig the configuration that each value must match.
336      */
getConfiguredResources( FolderConfiguration referenceConfig)337     public Map<String, Map<String, IResourceValue>> getConfiguredResources(
338             FolderConfiguration referenceConfig) {
339 
340         Map<String, Map<String, IResourceValue>> map =
341             new HashMap<String, Map<String, IResourceValue>>();
342 
343         // special case for Id since there's a mix of compiled id (declared inline) and id declared
344         // in the XML files.
345         if (mIdResourceList.size() > 0) {
346             Map<String, IResourceValue> idMap = new HashMap<String, IResourceValue>();
347             String idType = ResourceType.ID.getName();
348             for (IdResourceItem id : mIdResourceList) {
349                 // FIXME: cache the ResourceValue!
350                 idMap.put(id.getName(), new ResourceValue(idType, id.getName(),
351                         mIsFrameworkRepository));
352             }
353 
354             map.put(ResourceType.ID.getName(), idMap);
355         }
356 
357         Set<ResourceType> keys = mResourceMap.keySet();
358         for (ResourceType key : keys) {
359             // we don't process ID resources since we already did it above.
360             if (key != ResourceType.ID) {
361                 map.put(key.getName(), getConfiguredResource(key, referenceConfig));
362             }
363         }
364 
365         return map;
366     }
367 
368     /**
369      * Loads all the resources. Essentially this forces to load the values from the
370      * {@link ResourceFile} objects to make sure they are up to date and loaded
371      * in {@link #mResourceMap}.
372      */
loadAll()373     public void loadAll() {
374         // gets all the resource types available.
375         ResourceType[] types = getAvailableResourceTypes();
376 
377         // loop on them and load them
378         for (ResourceType type: types) {
379             checkAndUpdate(type);
380         }
381     }
382 
383     /**
384      * Resolves a compiled resource id into the resource name and type
385      * @param id
386      * @return an array of 2 strings { name, type } or null if the id could not be resolved
387      */
resolveResourceValue(int id)388     public String[] resolveResourceValue(int id) {
389         if (mResIdValueToNameMap != null) {
390             return mResIdValueToNameMap.get(id);
391         }
392 
393         return null;
394     }
395 
396     /**
397      * Resolves a compiled resource id of type int[] into the resource name.
398      */
resolveResourceValue(int[] id)399     public String resolveResourceValue(int[] id) {
400         if (mStyleableValueToNameMap != null) {
401             mWrapper.set(id);
402             return mStyleableValueToNameMap.get(mWrapper);
403         }
404 
405         return null;
406     }
407 
408     /**
409      * Returns the value of a resource by its type and name.
410      */
getResourceValue(String type, String name)411     public Integer getResourceValue(String type, String name) {
412         if (mResourceValueMap != null) {
413             Map<String, Integer> map = mResourceValueMap.get(type);
414             if (map != null) {
415                 return map.get(name);
416             }
417         }
418 
419         return null;
420     }
421 
422     /**
423      * Returns the sorted list of languages used in the resources.
424      */
getLanguages()425     public SortedSet<String> getLanguages() {
426         SortedSet<String> set = new TreeSet<String>();
427 
428         Collection<List<ResourceFolder>> folderList = mFolderMap.values();
429         for (List<ResourceFolder> folderSubList : folderList) {
430             for (ResourceFolder folder : folderSubList) {
431                 FolderConfiguration config = folder.getConfiguration();
432                 LanguageQualifier lang = config.getLanguageQualifier();
433                 if (lang != null) {
434                     set.add(lang.getStringValue());
435                 }
436             }
437         }
438 
439         return set;
440     }
441 
442     /**
443      * Returns the sorted list of regions used in the resources with the given language.
444      * @param currentLanguage the current language the region must be associated with.
445      */
getRegions(String currentLanguage)446     public SortedSet<String> getRegions(String currentLanguage) {
447         SortedSet<String> set = new TreeSet<String>();
448 
449         Collection<List<ResourceFolder>> folderList = mFolderMap.values();
450         for (List<ResourceFolder> folderSubList : folderList) {
451             for (ResourceFolder folder : folderSubList) {
452                 FolderConfiguration config = folder.getConfiguration();
453 
454                 // get the language
455                 LanguageQualifier lang = config.getLanguageQualifier();
456                 if (lang != null && lang.getStringValue().equals(currentLanguage)) {
457                     RegionQualifier region = config.getRegionQualifier();
458                     if (region != null) {
459                         set.add(region.getStringValue());
460                     }
461                 }
462             }
463         }
464 
465         return set;
466     }
467 
468     /**
469      * Returns a map of (resource name, resource value) for the given {@link ResourceType}.
470      * <p/>The values returned are taken from the resource files best matching a given
471      * {@link FolderConfiguration}.
472      * @param type the type of the resources.
473      * @param referenceConfig the configuration to best match.
474      */
getConfiguredResource(ResourceType type, FolderConfiguration referenceConfig)475     private Map<String, IResourceValue> getConfiguredResource(ResourceType type,
476             FolderConfiguration referenceConfig) {
477         // get the resource item for the given type
478         List<ProjectResourceItem> items = mResourceMap.get(type);
479 
480         // create the map
481         HashMap<String, IResourceValue> map = new HashMap<String, IResourceValue>();
482 
483         for (ProjectResourceItem item : items) {
484             // get the source files generating this resource
485             List<ResourceFile> list = item.getSourceFileList();
486 
487             // look for the best match for the given configuration
488             Resource match = findMatchingConfiguredResource(list, referenceConfig);
489 
490             if (match instanceof ResourceFile) {
491                 ResourceFile matchResFile = (ResourceFile)match;
492 
493                 // get the value of this configured resource.
494                 IResourceValue value = matchResFile.getValue(type, item.getName());
495 
496                 if (value != null) {
497                     map.put(item.getName(), value);
498                 }
499             }
500         }
501 
502         return map;
503     }
504 
505     /**
506      * Returns the best matching {@link Resource}.
507      * @param resources the list of {@link Resource} to choose from.
508      * @param referenceConfig the {@link FolderConfiguration} to match.
509      * @see http://d.android.com/guide/topics/resources/resources-i18n.html#best-match
510      */
findMatchingConfiguredResource(List<? extends Resource> resources, FolderConfiguration referenceConfig)511     private Resource findMatchingConfiguredResource(List<? extends Resource> resources,
512             FolderConfiguration referenceConfig) {
513         //
514         // 1: eliminate resources that contradict the reference configuration
515         // 2: pick next qualifier type
516         // 3: check if any resources use this qualifier, if no, back to 2, else move on to 4.
517         // 4: eliminate resources that don't use this qualifier.
518         // 5: if more than one resource left, go back to 2.
519         //
520         // The precedence of the qualifiers is more important than the number of qualifiers that
521         // exactly match the device.
522 
523         // 1: eliminate resources that contradict
524         ArrayList<Resource> matchingResources = new ArrayList<Resource>();
525         for (int i = 0 ; i < resources.size(); i++) {
526             Resource res = resources.get(i);
527 
528             if (res.getConfiguration().isMatchFor(referenceConfig)) {
529                 matchingResources.add(res);
530             }
531         }
532 
533         // if there is only one match, just take it
534         if (matchingResources.size() == 1) {
535             return matchingResources.get(0);
536         } else if (matchingResources.size() == 0) {
537             return null;
538         }
539 
540         // 2. Loop on the qualifiers, and eliminate matches
541         final int count = FolderConfiguration.getQualifierCount();
542         for (int q = 0 ; q < count ; q++) {
543             // look to see if one resource has this qualifier.
544             // At the same time also record the best match value for the qualifier (if applicable).
545 
546             // The reference value, to find the best match.
547             // Note that this qualifier could be null. In which case any qualifier found in the
548             // possible match, will all be considered best match.
549             ResourceQualifier referenceQualifier = referenceConfig.getQualifier(q);
550 
551             boolean found = false;
552             ResourceQualifier bestMatch = null; // this is to store the best match.
553             for (Resource res : matchingResources) {
554                 ResourceQualifier qualifier = res.getConfiguration().getQualifier(q);
555                 if (qualifier != null) {
556                     // set the flag.
557                     found = true;
558 
559                     // Now check for a best match. If the reference qualifier is null ,
560                     // any qualifier is a "best" match (we don't need to record all of them.
561                     // Instead the non compatible ones are removed below)
562                     if (referenceQualifier != null) {
563                         if (qualifier.isBetterMatchThan(bestMatch, referenceQualifier)) {
564                             bestMatch = qualifier;
565                         }
566                     }
567                 }
568             }
569 
570             // 4. If a resources has a qualifier at the current index, remove all the resources that
571             // do not have one, or whose qualifier value does not equal the best match found above
572             // unless there's no reference qualifier, in which case they are all considered
573             // "best" match.
574             if (found) {
575                 for (int i = 0 ; i < matchingResources.size(); ) {
576                     Resource res = matchingResources.get(i);
577                     ResourceQualifier qualifier = res.getConfiguration().getQualifier(q);
578 
579                     if (qualifier == null) {
580                         // this resources has no qualifier of this type: rejected.
581                         matchingResources.remove(res);
582                     } else if (referenceQualifier != null && bestMatch != null &&
583                             bestMatch.equals(qualifier) == false) {
584                         // there's a reference qualifier and there is a better match for it than
585                         // this resource, so we reject it.
586                         matchingResources.remove(res);
587                     } else {
588                         // looks like we keep this resource, move on to the next one.
589                         i++;
590                     }
591                 }
592 
593                 // at this point we may have run out of matching resources before going
594                 // through all the qualifiers.
595                 if (matchingResources.size() < 2) {
596                     break;
597                 }
598             }
599         }
600 
601         // Because we accept resources whose configuration have qualifiers where the reference
602         // configuration doesn't, we can end up with more than one match. In this case, we just
603         // take the first one.
604         if (matchingResources.size() == 0) {
605             return null;
606         }
607         return matchingResources.get(0);
608     }
609 
610     /**
611      * Checks if the list of {@link ResourceItem}s for the specified {@link ResourceType} needs
612      * to be updated.
613      * @param type the Resource Type.
614      */
checkAndUpdate(ResourceType type)615     private void checkAndUpdate(ResourceType type) {
616         // get the list of folder that can output this type
617         ResourceFolderType[] folderTypes = FolderTypeRelationship.getRelatedFolders(type);
618 
619         for (ResourceFolderType folderType : folderTypes) {
620             List<ResourceFolder> folders = mFolderMap.get(folderType);
621 
622             if (folders != null) {
623                 for (ResourceFolder folder : folders) {
624                     if (folder.isTouched()) {
625                         // if this folder is touched we need to update all the types that can
626                         // be generated from a file in this folder.
627                         // This will include 'type' obviously.
628                         ResourceType[] resTypes = FolderTypeRelationship.getRelatedResourceTypes(
629                                 folderType);
630                         for (ResourceType resType : resTypes) {
631                             update(resType);
632                         }
633                         return;
634                     }
635                 }
636             }
637         }
638     }
639 
640     /**
641      * Updates the list of {@link ResourceItem} objects associated with a {@link ResourceType}.
642      * This will reset the touch status of all the folders that can generate this resource type.
643      * @param type the Resource Type.
644      */
update(ResourceType type)645     private void update(ResourceType type) {
646         // get the cache list, and lets make a backup
647         List<ProjectResourceItem> items = mResourceMap.get(type);
648         List<ProjectResourceItem> backup = new ArrayList<ProjectResourceItem>();
649 
650         if (items == null) {
651             items = new ArrayList<ProjectResourceItem>();
652             mResourceMap.put(type, items);
653         } else {
654             // backup the list
655             backup.addAll(items);
656 
657             // we reset the list itself.
658             items.clear();
659         }
660 
661         // get the list of folder that can output this type
662         ResourceFolderType[] folderTypes = FolderTypeRelationship.getRelatedFolders(type);
663 
664         for (ResourceFolderType folderType : folderTypes) {
665             List<ResourceFolder> folders = mFolderMap.get(folderType);
666 
667             if (folders != null) {
668                 for (ResourceFolder folder : folders) {
669                     items.addAll(folder.getResources(type, this));
670                     folder.resetTouch();
671                 }
672             }
673         }
674 
675         // now items contains the new list. We "merge" it with the backup list.
676         // Basically, we need to keep the old instances of ResourceItem (where applicable),
677         // but replace them by the content of the new items.
678         // This will let the resource explorer keep the expanded state of the nodes whose data
679         // is a ResourceItem object.
680         if (backup.size() > 0) {
681             // this is not going to change as we're only replacing instances.
682             int count = items.size();
683 
684             for (int i = 0 ; i < count;) {
685                 // get the "new" item
686                 ProjectResourceItem item = items.get(i);
687 
688                 // look for a similar item in the old list.
689                 ProjectResourceItem foundOldItem = null;
690                 for (ProjectResourceItem oldItem : backup) {
691                     if (oldItem.getName().equals(item.getName())) {
692                         foundOldItem = oldItem;
693                         break;
694                     }
695                 }
696 
697                 if (foundOldItem != null) {
698                     // erase the data of the old item with the data from the new one.
699                     foundOldItem.replaceWith(item);
700 
701                     // remove the old and new item from their respective lists
702                     items.remove(i);
703                     backup.remove(foundOldItem);
704 
705                     // add the old item to the new list
706                     items.add(foundOldItem);
707                 } else {
708                     // this is a new item, we skip to the next object
709                     i++;
710                 }
711             }
712         }
713 
714         // if this is the ResourceType.ID, we create the actual list, from this list and
715         // the compiled resource list.
716         if (type == ResourceType.ID) {
717             mergeIdResources();
718         } else {
719             // else this is the list that will actually be displayed, so we sort it.
720             Collections.sort(items);
721         }
722     }
723 
724     /**
725      * Looks up an existing {@link ProjectResourceItem} by {@link ResourceType} and name.
726      * @param type the Resource Type.
727      * @param name the Resource name.
728      * @return the existing ResourceItem or null if no match was found.
729      */
findResourceItem(ResourceType type, String name)730     protected ProjectResourceItem findResourceItem(ResourceType type, String name) {
731         List<ProjectResourceItem> list = mResourceMap.get(type);
732 
733         for (ProjectResourceItem item : list) {
734             if (name.equals(item.getName())) {
735                 return item;
736             }
737         }
738 
739         return null;
740     }
741 
742     /**
743      * Sets compiled resource information.
744      * @param resIdValueToNameMap a map of compiled resource id to resource name.
745      *  The map is acquired by the {@link ProjectResources} object.
746      * @param styleableValueMap
747      * @param resourceValueMap a map of (name, id) for resources of type {@link ResourceType#ID}.
748      * The list is acquired by the {@link ProjectResources} object.
749      */
setCompiledResources(Map<Integer, String[]> resIdValueToNameMap, Map<IntArrayWrapper, String> styleableValueMap, Map<String, Map<String, Integer>> resourceValueMap)750     void setCompiledResources(Map<Integer, String[]> resIdValueToNameMap,
751             Map<IntArrayWrapper, String> styleableValueMap,
752             Map<String, Map<String, Integer>> resourceValueMap) {
753         mResourceValueMap = resourceValueMap;
754         mResIdValueToNameMap = resIdValueToNameMap;
755         mStyleableValueToNameMap = styleableValueMap;
756         mergeIdResources();
757     }
758 
759     /**
760      * Merges the list of ID resource coming from R.java and the list of ID resources
761      * coming from XML declaration into the cached list {@link #mIdResourceList}.
762      */
mergeIdResources()763     void mergeIdResources() {
764         // get the list of IDs coming from XML declaration. Those ids are present in
765         // mCompiledIdResources already, so we'll need to use those instead of creating
766         // new IdResourceItem
767         List<ProjectResourceItem> xmlIdResources = mResourceMap.get(ResourceType.ID);
768 
769         synchronized (mIdResourceList) {
770             // copy the currently cached items.
771             ArrayList<IdResourceItem> oldItems = new ArrayList<IdResourceItem>();
772             oldItems.addAll(mIdResourceList);
773 
774             // empty the current list
775             mIdResourceList.clear();
776 
777             // get the list of compile id resources.
778             Map<String, Integer> idMap = null;
779             if (mResourceValueMap != null) {
780                 idMap = mResourceValueMap.get(ResourceType.ID.getName());
781             }
782 
783             if (idMap == null) {
784                 if (xmlIdResources != null) {
785                     for (ProjectResourceItem resourceItem : xmlIdResources) {
786                         // check the actual class just for safety.
787                         if (resourceItem instanceof IdResourceItem) {
788                             mIdResourceList.add((IdResourceItem)resourceItem);
789                         }
790                     }
791                 }
792             } else {
793                 // loop on the full list of id, and look for a match in the old list,
794                 // in the list coming from XML (in case a new XML item was created.)
795 
796                 Set<String> idSet = idMap.keySet();
797 
798                 idLoop: for (String idResource : idSet) {
799                     // first look in the XML list in case an id went from inline to XML declared.
800                     if (xmlIdResources != null) {
801                         for (ProjectResourceItem resourceItem : xmlIdResources) {
802                             if (resourceItem instanceof IdResourceItem &&
803                                     resourceItem.getName().equals(idResource)) {
804                                 mIdResourceList.add((IdResourceItem)resourceItem);
805                                 continue idLoop;
806                             }
807                         }
808                     }
809 
810                     // if we haven't found it, look in the old items.
811                     int count = oldItems.size();
812                     for (int i = 0 ; i < count ; i++) {
813                         IdResourceItem resourceItem = oldItems.get(i);
814                         if (resourceItem.getName().equals(idResource)) {
815                             oldItems.remove(i);
816                             mIdResourceList.add(resourceItem);
817                             continue idLoop;
818                         }
819                     }
820 
821                     // if we haven't found it, it looks like it's a new id that was
822                     // declared inline.
823                     mIdResourceList.add(new IdResourceItem(idResource,
824                             true /* isDeclaredInline */));
825                 }
826             }
827 
828             // now we sort the list
829             Collections.sort(mIdResourceList);
830         }
831     }
832 }
833