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