1 /*
2  * Copyright (C) 2022 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 package com.android.ide.common.resources.deprecated;
17 
18 import com.android.SdkConstants;
19 import com.android.ide.common.rendering.api.ResourceValue;
20 import com.android.ide.common.resources.ResourceValueMap;
21 import com.android.ide.common.resources.configuration.FolderConfiguration;
22 import com.android.io.IAbstractFolder;
23 import com.android.io.IAbstractResource;
24 import com.android.resources.ResourceFolderType;
25 import com.android.resources.ResourceType;
26 import com.android.tools.layoutlib.annotations.NotNull;
27 import com.android.tools.layoutlib.annotations.Nullable;
28 
29 import java.util.ArrayList;
30 import java.util.Collection;
31 import java.util.EnumMap;
32 import java.util.HashMap;
33 import java.util.List;
34 import java.util.Map;
35 
36 /**
37  * @deprecated This class is part of an obsolete resource repository system that is no longer used
38  *     in production code. The class is preserved temporarily for LayoutLib tests.
39  */
40 @Deprecated
41 public abstract class ResourceRepository {
42     private final TestFolderWrapper mResourceFolder;
43 
44     private Map<ResourceFolderType, List<ResourceFolder>> mFolderMap =
45             new EnumMap<>(ResourceFolderType.class);
46 
47     protected Map<ResourceType, Map<String, ResourceItem>> mResourceMap =
48             new EnumMap<>(ResourceType.class);
49 
50     private final boolean mFrameworkRepository;
51     private boolean mCleared = true;
52     private boolean mInitializing;
53 
54     /**
55      * Makes a resource repository.
56      *
57      * @param resFolder the resource folder of the repository.
58      * @param isFrameworkRepository whether the repository is for framework resources.
59      */
ResourceRepository(@otNull TestFolderWrapper resFolder, boolean isFrameworkRepository)60     protected ResourceRepository(@NotNull TestFolderWrapper resFolder,
61             boolean isFrameworkRepository) {
62         mResourceFolder = resFolder;
63         mFrameworkRepository = isFrameworkRepository;
64     }
65 
getResFolder()66     public TestFolderWrapper getResFolder() {
67         return mResourceFolder;
68     }
69 
isFrameworkRepository()70     public boolean isFrameworkRepository() {
71         return mFrameworkRepository;
72     }
73 
clear()74     private synchronized void clear() {
75         mCleared = true;
76         mFolderMap = new EnumMap<>(ResourceFolderType.class);
77         mResourceMap = new EnumMap<>(ResourceType.class);
78     }
79 
80     /**
81      * Ensures that the repository has been initialized again after a call to
82      * {@link com.android.ide.common.resources.deprecated.ResourceRepository#clear()}.
83      *
84      * @return true if the repository was just re-initialized.
85      */
ensureInitialized()86     private synchronized boolean ensureInitialized() {
87         if (mCleared && !mInitializing) {
88             ScanningContext context = new ScanningContext();
89             mInitializing = true;
90 
91             IAbstractResource[] resources = mResourceFolder.listMembers();
92 
93             for (IAbstractResource res : resources) {
94                 if (res instanceof TestFolderWrapper) {
95                     TestFolderWrapper folder = (TestFolderWrapper)res;
96                     ResourceFolder resFolder = processFolder(folder);
97 
98                     if (resFolder != null) {
99                         // now we process the content of the folder
100                         IAbstractResource[] files = folder.listMembers();
101 
102                         for (IAbstractResource fileRes : files) {
103                             if (fileRes instanceof TestFileWrapper) {
104                                 TestFileWrapper file = (TestFileWrapper) fileRes;
105 
106                                 resFolder.processFile(file, ResourceDeltaKind.ADDED, context);
107                             }
108                         }
109                     }
110                 }
111             }
112 
113             mInitializing = false;
114             mCleared = false;
115             return true;
116         }
117 
118         return false;
119     }
120 
121     /**
122      * Adds a Folder Configuration to the project.
123      *
124      * @param type The resource type.
125      * @param config The resource configuration.
126      * @param folder The workspace folder object.
127      * @return the {@link ResourceFolder} object associated to this folder.
128      */
add( @otNull ResourceFolderType type, @NotNull FolderConfiguration config, @NotNull IAbstractFolder folder)129     private ResourceFolder add(
130             @NotNull ResourceFolderType type,
131             @NotNull FolderConfiguration config,
132             @NotNull IAbstractFolder folder) {
133         // get the list for the resource type
134         List<ResourceFolder> list = mFolderMap.get(type);
135 
136         if (list == null) {
137             list = new ArrayList<>();
138 
139             ResourceFolder cf = new ResourceFolder(type, config, folder, this);
140             list.add(cf);
141 
142             mFolderMap.put(type, list);
143 
144             return cf;
145         }
146 
147         // look for an already existing folder configuration.
148         for (ResourceFolder cFolder : list) {
149             if (cFolder.mConfiguration.equals(config)) {
150                 // config already exist. Nothing to be done really, besides making sure
151                 // the IAbstractFolder object is up to date.
152                 cFolder.mFolder = folder;
153                 return cFolder;
154             }
155         }
156 
157         // If we arrive here, this means we didn't find a matching configuration.
158         // So we add one.
159         ResourceFolder cf = new ResourceFolder(type, config, folder, this);
160         list.add(cf);
161 
162         return cf;
163     }
164 
165     /**
166      * Returns a {@link ResourceItem} matching the given {@link ResourceType} and name. If none
167      * exist, it creates one.
168      *
169      * @param type the resource type
170      * @param name the name of the resource.
171      * @return A resource item matching the type and name.
172      */
173     @NotNull
getResourceItem(@otNull ResourceType type, @NotNull String name)174     public ResourceItem getResourceItem(@NotNull ResourceType type, @NotNull String name) {
175         ensureInitialized();
176 
177         // looking for an existing ResourceItem with this type and name
178         ResourceItem item = findDeclaredResourceItem(type, name);
179 
180         // create one if there isn't one already, or if the existing one is inlined, since
181         // clearly we need a non inlined one (the inline one is removed too)
182         if (item == null) {
183             item = createResourceItem(name);
184 
185             Map<String, ResourceItem> map = mResourceMap.get(type);
186 
187             if (map == null) {
188                 if (isFrameworkRepository()) {
189                     // Pick initial size for the maps. Also change the load factor to 1.0
190                     // to avoid rehashing the whole table when we (as expected) get near
191                     // the known rough size of each resource type map.
192                     int size;
193                     switch (type) {
194                         // Based on counts in API 16. Going back to API 10, the counts
195                         // are roughly 25-50% smaller (e.g. compared to the top 5 types below
196                         // the fractions are 1107 vs 1734, 831 vs 1508, 895 vs 1255,
197                         // 733 vs 1064 and 171 vs 783.
198                         case PUBLIC:           size = 1734; break;
199                         case DRAWABLE:         size = 1508; break;
200                         case STRING:           size = 1255; break;
201                         case ATTR:             size = 1064; break;
202                         case STYLE:             size = 783; break;
203                         case ID:                size = 347; break;
204                         case STYLEABLE:
205                             size = 210;
206                             break;
207                         case LAYOUT:            size = 187; break;
208                         case COLOR:             size = 120; break;
209                         case ANIM:               size = 95; break;
210                         case DIMEN:              size = 81; break;
211                         case BOOL:               size = 54; break;
212                         case INTEGER:            size = 52; break;
213                         case ARRAY:              size = 51; break;
214                         case PLURALS:            size = 20; break;
215                         case XML:                size = 14; break;
216                         case INTERPOLATOR :      size = 13; break;
217                         case ANIMATOR:            size = 8; break;
218                         case RAW:                 size = 4; break;
219                         case MENU:                size = 2; break;
220                         case MIPMAP:              size = 2; break;
221                         case FRACTION:            size = 1; break;
222                         default:
223                             size = 2;
224                     }
225                     map = new HashMap<>(size, 1.0f);
226                 } else {
227                     map = new HashMap<>();
228                 }
229                 mResourceMap.put(type, map);
230             }
231 
232             map.put(item.getName(), item);
233         }
234 
235         return item;
236     }
237 
238     /**
239      * Creates a resource item with the given name.
240      * @param name the name of the resource
241      * @return a new ResourceItem (or child class) instance.
242      */
243     @NotNull
createResourceItem(@otNull String name)244     protected abstract ResourceItem createResourceItem(@NotNull String name);
245 
246     /**
247      * Processes a folder and adds it to the list of existing folders.
248      * @param folder the folder to process
249      * @return the ResourceFolder created from this folder, or null if the process failed.
250      */
251     @Nullable
processFolder(@otNull TestFolderWrapper folder)252     private ResourceFolder processFolder(@NotNull TestFolderWrapper folder) {
253         ensureInitialized();
254 
255         // split the name of the folder in segments.
256         String[] folderSegments = folder.getName().split(SdkConstants.RES_QUALIFIER_SEP);
257 
258         // get the enum for the resource type.
259         ResourceFolderType type = ResourceFolderType.getTypeByName(folderSegments[0]);
260 
261         if (type != null) {
262             // get the folder configuration.
263             FolderConfiguration config = FolderConfiguration.getConfig(folderSegments);
264 
265             if (config != null) {
266                 return add(type, config, folder);
267             }
268         }
269 
270         return null;
271     }
272 
273     /**
274      * Returns the resources values matching a given {@link FolderConfiguration}.
275      *
276      * @param referenceConfig the configuration that each value must match.
277      * @return a map with guaranteed to contain an entry for each {@link ResourceType}
278      */
279     @NotNull
getConfiguredResources( @otNull FolderConfiguration referenceConfig)280     public Map<ResourceType, ResourceValueMap> getConfiguredResources(
281             @NotNull FolderConfiguration referenceConfig) {
282         ensureInitialized();
283 
284         return doGetConfiguredResources(referenceConfig);
285     }
286 
287     /**
288      * Returns the resources values matching a given {@link FolderConfiguration} for the current
289      * project.
290      *
291      * @param referenceConfig the configuration that each value must match.
292      * @return a map with guaranteed to contain an entry for each {@link ResourceType}
293      */
294     @NotNull
doGetConfiguredResources( @otNull FolderConfiguration referenceConfig)295     private Map<ResourceType, ResourceValueMap> doGetConfiguredResources(
296             @NotNull FolderConfiguration referenceConfig) {
297         ensureInitialized();
298 
299         Map<ResourceType, ResourceValueMap> map =
300             new EnumMap<>(ResourceType.class);
301 
302         for (ResourceType key : ResourceType.values()) {
303             // get the local results and put them in the map
304             map.put(key, getConfiguredResource(key, referenceConfig));
305         }
306 
307         return map;
308     }
309 
310     /**
311      * Loads the resources.
312      */
loadResources()313     public void loadResources() {
314         clear();
315         ensureInitialized();
316     }
317 
removeFile(@otNull Collection<ResourceType> types, @NotNull ResourceFile file)318     protected void removeFile(@NotNull Collection<ResourceType> types,
319             @NotNull ResourceFile file) {
320         ensureInitialized();
321 
322         for (ResourceType type : types) {
323             removeFile(type, file);
324         }
325     }
326 
removeFile(@otNull ResourceType type, @NotNull ResourceFile file)327     private void removeFile(@NotNull ResourceType type, @NotNull ResourceFile file) {
328         Map<String, ResourceItem> map = mResourceMap.get(type);
329         if (map != null) {
330             Collection<ResourceItem> values = map.values();
331             List<ResourceItem> toDelete = null;
332             for (ResourceItem item : values) {
333                 item.removeFile(file);
334                 if (item.hasNoSourceFile()) {
335                     if (toDelete == null) {
336                         toDelete = new ArrayList<>(values.size());
337                     }
338                     toDelete.add(item);
339                 }
340             }
341             if (toDelete != null) {
342                 for (ResourceItem item : toDelete) {
343                     map.remove(item.getName());
344                 }
345             }
346         }
347     }
348 
349     /**
350      * Returns a map of (resource name, resource value) for the given {@link ResourceType}.
351      * <p>The values returned are taken from the resource files best matching a given
352      * {@link FolderConfiguration}.
353      *
354      * @param type the type of the resources.
355      * @param referenceConfig the configuration to best match.
356      */
357     @NotNull
getConfiguredResource(@otNull ResourceType type, @NotNull FolderConfiguration referenceConfig)358     private ResourceValueMap getConfiguredResource(@NotNull ResourceType type,
359             @NotNull FolderConfiguration referenceConfig) {
360         // get the resource item for the given type
361         Map<String, ResourceItem> items = mResourceMap.get(type);
362         if (items == null) {
363             return ResourceValueMap.create();
364         }
365 
366         // create the map
367         ResourceValueMap map = ResourceValueMap.createWithExpectedSize(items.size());
368 
369         for (ResourceItem item : items.values()) {
370             ResourceValue value = item.getResourceValue(type, referenceConfig);
371             if (value != null) {
372                 map.put(item.getName(), value);
373             }
374         }
375 
376         return map;
377     }
378 
379     /**
380      * Looks up an existing {@link ResourceItem} by {@link ResourceType} and name.
381      *
382      * @param type the resource type.
383      * @param name the resource name.
384      * @return the existing ResourceItem or null if no match was found.
385      */
386     @Nullable
findDeclaredResourceItem( @otNull ResourceType type, @NotNull String name)387     private ResourceItem findDeclaredResourceItem(
388             @NotNull ResourceType type, @NotNull String name) {
389         Map<String, ResourceItem> map = mResourceMap.get(type);
390         return map != null ? map.get(name) : null;
391     }
392 }
393 
394