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.SdkConstants;
20 import com.android.annotations.NonNull;
21 import com.android.ide.common.rendering.api.ResourceValue;
22 import com.android.ide.common.resources.IntArrayWrapper;
23 import com.android.ide.common.resources.ResourceFolder;
24 import com.android.ide.common.resources.ResourceItem;
25 import com.android.ide.common.resources.ResourceRepository;
26 import com.android.ide.common.resources.configuration.FolderConfiguration;
27 import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
28 import com.android.ide.eclipse.adt.internal.sdk.Sdk;
29 import com.android.ide.eclipse.adt.io.IFolderWrapper;
30 import com.android.io.IAbstractFolder;
31 import com.android.resources.ResourceType;
32 import com.android.util.Pair;
33 
34 import org.eclipse.core.resources.IFolder;
35 import org.eclipse.core.resources.IProject;
36 
37 import java.util.EnumMap;
38 import java.util.List;
39 import java.util.Map;
40 import java.util.Map.Entry;
41 
42 /**
43  * Represents the resources of a project.
44  * On top of the regular {@link ResourceRepository} features it provides:
45  *<ul>
46  *<li>configured resources contain the resources coming from the libraries.</li>
47  *<li>resolution to and from resource integer (compiled value in R.java).</li>
48  *<li>handles resource integer for non existing values of type ID. This is used when rendering.</li>
49  *<li>layouts that have no been saved yet. This is handled by generating dynamic IDs
50  *       on the fly.</li>
51  *</ul>
52  */
53 @SuppressWarnings("deprecation")
54 public class ProjectResources extends ResourceRepository {
55     // project resources are defined as 0x7FXX#### where XX is the resource type (layout, drawable,
56     // etc...). Using FF as the type allows for 255 resource types before we get a collision
57     // which should be fine.
58     private final static int DYNAMIC_ID_SEED_START = 0x7fff0000;
59 
60     /** Map of (name, id) for resources of type {@link ResourceType#ID} coming from R.java */
61     private Map<ResourceType, Map<String, Integer>> mResourceValueMap;
62     /** Map of (id, [name, resType]) for all resources coming from R.java */
63     private Map<Integer, Pair<ResourceType, String>> mResIdValueToNameMap;
64     /** Map of (int[], name) for styleable resources coming from R.java */
65     private Map<IntArrayWrapper, String> mStyleableValueToNameMap;
66 
67     private final DynamicIdMap mDynamicIdMap = new DynamicIdMap(DYNAMIC_ID_SEED_START);
68     private final IntArrayWrapper mWrapper = new IntArrayWrapper(null);
69     private final IProject mProject;
70 
create(IProject project)71     public static ProjectResources create(IProject project) {
72         IFolder resFolder = project.getFolder(SdkConstants.FD_RESOURCES);
73 
74         return new ProjectResources(project, new IFolderWrapper(resFolder));
75     }
76 
77     /**
78      * Makes a ProjectResources for a given <var>project</var>.
79      * @param project the project.
80      */
ProjectResources(IProject project, IAbstractFolder resFolder)81     private ProjectResources(IProject project, IAbstractFolder resFolder) {
82         super(resFolder, false /*isFrameworkRepository*/);
83         mProject = project;
84     }
85 
86     /**
87      * Returns the resources values matching a given {@link FolderConfiguration}, this will
88      * include library dependency.
89      *
90      * @param referenceConfig the configuration that each value must match.
91      * @return a map with guaranteed to contain an entry for each {@link ResourceType}
92      */
93     @Override
94     @NonNull
getConfiguredResources( @onNull FolderConfiguration referenceConfig)95     public Map<ResourceType, Map<String, ResourceValue>> getConfiguredResources(
96             @NonNull FolderConfiguration referenceConfig) {
97         ensureInitialized();
98 
99         Map<ResourceType, Map<String, ResourceValue>> resultMap =
100             new EnumMap<ResourceType, Map<String, ResourceValue>>(ResourceType.class);
101 
102         // if the project contains libraries, we need to add the libraries resources here
103         // so that they are accessible to the layout rendering.
104         if (mProject != null) {
105             ProjectState state = Sdk.getProjectState(mProject);
106             if (state != null) {
107                 List<IProject> libraries = state.getFullLibraryProjects();
108 
109                 ResourceManager resMgr = ResourceManager.getInstance();
110 
111                 // because aapt put all the library in their order in this array, the first
112                 // one will have priority over the 2nd one. So it's better to loop in the inverse
113                 // order and fill the map with resources that will be overwritten by higher
114                 // priority resources
115                 for (int i = libraries.size() - 1 ; i >= 0 ; i--) {
116                     IProject library = libraries.get(i);
117 
118                     ProjectResources libRes = resMgr.getProjectResources(library);
119                     if (libRes != null) {
120                         // get the library resources, and only the library, not the dependencies
121                         // so call doGetConfiguredResources() directly.
122                         Map<ResourceType, Map<String, ResourceValue>> libMap =
123                                 libRes.doGetConfiguredResources(referenceConfig);
124 
125                         // we don't want to simply replace the whole map, but instead merge the
126                         // content of any sub-map
127                         for (Entry<ResourceType, Map<String, ResourceValue>> libEntry :
128                                 libMap.entrySet()) {
129 
130                             // get the map currently in the result map for this resource type
131                             Map<String, ResourceValue> tempMap = resultMap.get(libEntry.getKey());
132                             if (tempMap == null) {
133                                 // since there's no current map for this type, just add the map
134                                 // directly coming from the library resources
135                                 resultMap.put(libEntry.getKey(), libEntry.getValue());
136                             } else {
137                                 // already a map for this type. add the resources from the
138                                 // library, this will override existing value, which is why
139                                 // we loop in a specific library order.
140                                 tempMap.putAll(libEntry.getValue());
141                             }
142                         }
143                     }
144                 }
145             }
146         }
147 
148         // now the project resources themselves.
149         Map<ResourceType, Map<String, ResourceValue>> thisProjectMap =
150                 doGetConfiguredResources(referenceConfig);
151 
152         // now merge the maps.
153         for (Entry<ResourceType, Map<String, ResourceValue>> entry : thisProjectMap.entrySet()) {
154             ResourceType type = entry.getKey();
155             Map<String, ResourceValue> typeMap = resultMap.get(type);
156             if (typeMap == null) {
157                 resultMap.put(type, entry.getValue());
158             } else {
159                 typeMap.putAll(entry.getValue());
160             }
161         }
162 
163         return resultMap;
164     }
165 
166     /**
167      * Returns the {@link ResourceFolder} associated with a {@link IFolder}.
168      * @param folder The {@link IFolder} object.
169      * @return the {@link ResourceFolder} or null if it was not found.
170      *
171      * @see ResourceRepository#getResourceFolder(com.android.io.IAbstractFolder)
172      */
getResourceFolder(IFolder folder)173     public ResourceFolder getResourceFolder(IFolder folder) {
174         return getResourceFolder(new IFolderWrapper(folder));
175     }
176 
177     /**
178      * Resolves a compiled resource id into the resource name and type
179      * @param id the resource integer id.
180      * @return a {@link Pair} of 2 strings { name, type } or null if the id could not be resolved
181      */
resolveResourceId(int id)182     public Pair<ResourceType, String> resolveResourceId(int id) {
183         Pair<ResourceType, String> result = null;
184         if (mResIdValueToNameMap != null) {
185             result = mResIdValueToNameMap.get(id);
186         }
187 
188         if (result == null) {
189             synchronized (mDynamicIdMap) {
190                 result = mDynamicIdMap.resolveId(id);
191             }
192         }
193 
194         return result;
195     }
196 
197     /**
198      * Resolves a compiled styleable id of type int[] into the styleable name.
199      */
resolveStyleable(int[] id)200     public String resolveStyleable(int[] id) {
201         if (mStyleableValueToNameMap != null) {
202             mWrapper.set(id);
203             return mStyleableValueToNameMap.get(mWrapper);
204         }
205 
206         return null;
207     }
208 
209     /**
210      * Returns the integer id of a resource given its type and name.
211      * <p/>If the resource is of type {@link ResourceType#ID} and does not exist in the
212      * internal map, then new id values are dynamically generated (and stored so that queries
213      * with the same names will return the same value).
214      */
getResourceId(ResourceType type, String name)215     public Integer getResourceId(ResourceType type, String name) {
216         Integer result = null;
217         if (mResourceValueMap != null) {
218             Map<String, Integer> map = mResourceValueMap.get(type);
219             if (map != null) {
220                 result = map.get(name);
221             }
222         }
223 
224         if (result == null) {
225             synchronized (mDynamicIdMap) {
226                 result = mDynamicIdMap.getId(type, name);
227             }
228         }
229 
230         return result;
231     }
232 
233     /**
234      * Resets the list of dynamic Ids. This list is used by
235      * {@link #getResourceId(String, String)} when the resource query is an ID that doesn't
236      * exist (for example for ID automatically generated in layout files that are not saved yet.)
237      * <p/>This method resets those dynamic ID and must be called whenever the actual list of IDs
238      * change.
239      */
resetDynamicIds()240     public void resetDynamicIds() {
241         synchronized (mDynamicIdMap) {
242             mDynamicIdMap.reset(DYNAMIC_ID_SEED_START);
243         }
244     }
245 
246     @Override
247     @NonNull
createResourceItem(@onNull String name)248     protected ResourceItem createResourceItem(@NonNull String name) {
249         return new ResourceItem(name);
250     }
251 
252     /**
253      * Sets compiled resource information.
254      *
255      * @param resIdValueToNameMap a map of compiled resource id to resource name.
256      *    The map is acquired by the {@link ProjectResources} object.
257      * @param styleableValueMap a map of (int[], name) for the styleable information. The map is
258      *    acquired by the {@link ProjectResources} object.
259      * @param resourceValueMap a map of (name, id) for resources of type {@link ResourceType#ID}.
260      *    The list is acquired by the {@link ProjectResources} object.
261      */
setCompiledResources(Map<Integer, Pair<ResourceType, String>> resIdValueToNameMap, Map<IntArrayWrapper, String> styleableValueMap, Map<ResourceType, Map<String, Integer>> resourceValueMap)262     void setCompiledResources(Map<Integer, Pair<ResourceType, String>> resIdValueToNameMap,
263             Map<IntArrayWrapper, String> styleableValueMap,
264             Map<ResourceType, Map<String, Integer>> resourceValueMap) {
265         mResourceValueMap = resourceValueMap;
266         mResIdValueToNameMap = resIdValueToNameMap;
267         mStyleableValueToNameMap = styleableValueMap;
268 
269         resetDynamicIds();
270     }
271 }
272