blob: d8204d54111e697d77ff3865735b7f40383a3b0a [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ide.common.resources.deprecated;
import com.android.SdkConstants;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.resources.ResourceValueMap;
import com.android.ide.common.resources.configuration.FolderConfiguration;
import com.android.io.IAbstractFile;
import com.android.io.IAbstractFolder;
import com.android.io.IAbstractResource;
import com.android.resources.ResourceFolderType;
import com.android.resources.ResourceType;
import com.android.tools.layoutlib.annotations.NotNull;
import com.android.tools.layoutlib.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @deprecated This class is part of an obsolete resource repository system that is no longer used
* in production code. The class is preserved temporarily for LayoutLib tests.
*/
@Deprecated
public abstract class ResourceRepository {
private final IAbstractFolder mResourceFolder;
private Map<ResourceFolderType, List<ResourceFolder>> mFolderMap =
new EnumMap<>(ResourceFolderType.class);
protected Map<ResourceType, Map<String, ResourceItem>> mResourceMap =
new EnumMap<>(ResourceType.class);
private final boolean mFrameworkRepository;
private boolean mCleared = true;
private boolean mInitializing;
/**
* Makes a resource repository.
*
* @param resFolder the resource folder of the repository.
* @param isFrameworkRepository whether the repository is for framework resources.
*/
protected ResourceRepository(@NotNull IAbstractFolder resFolder,
boolean isFrameworkRepository) {
mResourceFolder = resFolder;
mFrameworkRepository = isFrameworkRepository;
}
public IAbstractFolder getResFolder() {
return mResourceFolder;
}
public boolean isFrameworkRepository() {
return mFrameworkRepository;
}
private synchronized void clear() {
mCleared = true;
mFolderMap = new EnumMap<>(ResourceFolderType.class);
mResourceMap = new EnumMap<>(ResourceType.class);
}
/**
* Ensures that the repository has been initialized again after a call to
* {@link com.android.ide.common.resources.deprecated.ResourceRepository#clear()}.
*
* @return true if the repository was just re-initialized.
*/
private synchronized boolean ensureInitialized() {
if (mCleared && !mInitializing) {
ScanningContext context = new ScanningContext();
mInitializing = true;
IAbstractResource[] resources = mResourceFolder.listMembers();
for (IAbstractResource res : resources) {
if (res instanceof IAbstractFolder) {
IAbstractFolder folder = (IAbstractFolder)res;
ResourceFolder resFolder = processFolder(folder);
if (resFolder != null) {
// now we process the content of the folder
IAbstractResource[] files = folder.listMembers();
for (IAbstractResource fileRes : files) {
if (fileRes instanceof IAbstractFile) {
IAbstractFile file = (IAbstractFile)fileRes;
resFolder.processFile(file, ResourceDeltaKind.ADDED, context);
}
}
}
}
}
mInitializing = false;
mCleared = false;
return true;
}
return false;
}
/**
* Adds a Folder Configuration to the project.
*
* @param type The resource type.
* @param config The resource configuration.
* @param folder The workspace folder object.
* @return the {@link ResourceFolder} object associated to this folder.
*/
private ResourceFolder add(
@NotNull ResourceFolderType type,
@NotNull FolderConfiguration config,
@NotNull IAbstractFolder folder) {
// get the list for the resource type
List<ResourceFolder> list = mFolderMap.get(type);
if (list == null) {
list = new ArrayList<>();
ResourceFolder cf = new ResourceFolder(type, config, folder, this);
list.add(cf);
mFolderMap.put(type, list);
return cf;
}
// look for an already existing folder configuration.
for (ResourceFolder cFolder : list) {
if (cFolder.mConfiguration.equals(config)) {
// config already exist. Nothing to be done really, besides making sure
// the IAbstractFolder object is up to date.
cFolder.mFolder = folder;
return cFolder;
}
}
// If we arrive here, this means we didn't find a matching configuration.
// So we add one.
ResourceFolder cf = new ResourceFolder(type, config, folder, this);
list.add(cf);
return cf;
}
/**
* Returns a {@link ResourceItem} matching the given {@link ResourceType} and name. If none
* exist, it creates one.
*
* @param type the resource type
* @param name the name of the resource.
* @return A resource item matching the type and name.
*/
@NotNull
public ResourceItem getResourceItem(@NotNull ResourceType type, @NotNull String name) {
ensureInitialized();
// looking for an existing ResourceItem with this type and name
ResourceItem item = findDeclaredResourceItem(type, name);
// create one if there isn't one already, or if the existing one is inlined, since
// clearly we need a non inlined one (the inline one is removed too)
if (item == null) {
item = createResourceItem(name);
Map<String, ResourceItem> map = mResourceMap.get(type);
if (map == null) {
if (isFrameworkRepository()) {
// Pick initial size for the maps. Also change the load factor to 1.0
// to avoid rehashing the whole table when we (as expected) get near
// the known rough size of each resource type map.
int size;
switch (type) {
// Based on counts in API 16. Going back to API 10, the counts
// are roughly 25-50% smaller (e.g. compared to the top 5 types below
// the fractions are 1107 vs 1734, 831 vs 1508, 895 vs 1255,
// 733 vs 1064 and 171 vs 783.
case PUBLIC: size = 1734; break;
case DRAWABLE: size = 1508; break;
case STRING: size = 1255; break;
case ATTR: size = 1064; break;
case STYLE: size = 783; break;
case ID: size = 347; break;
case STYLEABLE:
size = 210;
break;
case LAYOUT: size = 187; break;
case COLOR: size = 120; break;
case ANIM: size = 95; break;
case DIMEN: size = 81; break;
case BOOL: size = 54; break;
case INTEGER: size = 52; break;
case ARRAY: size = 51; break;
case PLURALS: size = 20; break;
case XML: size = 14; break;
case INTERPOLATOR : size = 13; break;
case ANIMATOR: size = 8; break;
case RAW: size = 4; break;
case MENU: size = 2; break;
case MIPMAP: size = 2; break;
case FRACTION: size = 1; break;
default:
size = 2;
}
map = new HashMap<>(size, 1.0f);
} else {
map = new HashMap<>();
}
mResourceMap.put(type, map);
}
map.put(item.getName(), item);
}
return item;
}
/**
* Creates a resource item with the given name.
* @param name the name of the resource
* @return a new ResourceItem (or child class) instance.
*/
@NotNull
protected abstract ResourceItem createResourceItem(@NotNull String name);
/**
* Processes a folder and adds it to the list of existing folders.
* @param folder the folder to process
* @return the ResourceFolder created from this folder, or null if the process failed.
*/
@Nullable
private ResourceFolder processFolder(@NotNull IAbstractFolder folder) {
ensureInitialized();
// split the name of the folder in segments.
String[] folderSegments = folder.getName().split(SdkConstants.RES_QUALIFIER_SEP);
// get the enum for the resource type.
ResourceFolderType type = ResourceFolderType.getTypeByName(folderSegments[0]);
if (type != null) {
// get the folder configuration.
FolderConfiguration config = FolderConfiguration.getConfig(folderSegments);
if (config != null) {
return add(type, config, folder);
}
}
return null;
}
/**
* Returns the resources values matching a given {@link FolderConfiguration}.
*
* @param referenceConfig the configuration that each value must match.
* @return a map with guaranteed to contain an entry for each {@link ResourceType}
*/
@NotNull
public Map<ResourceType, ResourceValueMap> getConfiguredResources(
@NotNull FolderConfiguration referenceConfig) {
ensureInitialized();
return doGetConfiguredResources(referenceConfig);
}
/**
* Returns the resources values matching a given {@link FolderConfiguration} for the current
* project.
*
* @param referenceConfig the configuration that each value must match.
* @return a map with guaranteed to contain an entry for each {@link ResourceType}
*/
@NotNull
private Map<ResourceType, ResourceValueMap> doGetConfiguredResources(
@NotNull FolderConfiguration referenceConfig) {
ensureInitialized();
Map<ResourceType, ResourceValueMap> map =
new EnumMap<>(ResourceType.class);
for (ResourceType key : ResourceType.values()) {
// get the local results and put them in the map
map.put(key, getConfiguredResource(key, referenceConfig));
}
return map;
}
/**
* Loads the resources.
*/
public void loadResources() {
clear();
ensureInitialized();
}
protected void removeFile(@NotNull Collection<ResourceType> types,
@NotNull ResourceFile file) {
ensureInitialized();
for (ResourceType type : types) {
removeFile(type, file);
}
}
private void removeFile(@NotNull ResourceType type, @NotNull ResourceFile file) {
Map<String, ResourceItem> map = mResourceMap.get(type);
if (map != null) {
Collection<ResourceItem> values = map.values();
List<ResourceItem> toDelete = null;
for (ResourceItem item : values) {
item.removeFile(file);
if (item.hasNoSourceFile()) {
if (toDelete == null) {
toDelete = new ArrayList<>(values.size());
}
toDelete.add(item);
}
}
if (toDelete != null) {
for (ResourceItem item : toDelete) {
map.remove(item.getName());
}
}
}
}
/**
* Returns a map of (resource name, resource value) for the given {@link ResourceType}.
* <p>The values returned are taken from the resource files best matching a given
* {@link FolderConfiguration}.
*
* @param type the type of the resources.
* @param referenceConfig the configuration to best match.
*/
@NotNull
private ResourceValueMap getConfiguredResource(@NotNull ResourceType type,
@NotNull FolderConfiguration referenceConfig) {
// get the resource item for the given type
Map<String, ResourceItem> items = mResourceMap.get(type);
if (items == null) {
return ResourceValueMap.create();
}
// create the map
ResourceValueMap map = ResourceValueMap.createWithExpectedSize(items.size());
for (ResourceItem item : items.values()) {
ResourceValue value = item.getResourceValue(type, referenceConfig);
if (value != null) {
map.put(item.getName(), value);
}
}
return map;
}
/**
* Looks up an existing {@link ResourceItem} by {@link ResourceType} and name.
*
* @param type the resource type.
* @param name the resource name.
* @return the existing ResourceItem or null if no match was found.
*/
@Nullable
private ResourceItem findDeclaredResourceItem(
@NotNull ResourceType type, @NotNull String name) {
Map<String, ResourceItem> map = mResourceMap.get(type);
return map != null ? map.get(name) : null;
}
}