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