com.android.ide.common.res2.AbstractResourceRepository Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of sdk-common Show documentation
Show all versions of sdk-common Show documentation
sdk-common library used by other Android tools libraries.
/*
* Copyright (C) 2013 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.res2;
import static com.android.SdkConstants.ATTR_REF_PREFIX;
import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
import static com.android.SdkConstants.PREFIX_THEME_REF;
import static com.android.SdkConstants.RESOURCE_CLZ_ATTR;
import static com.android.ide.common.resources.ResourceResolver.MAX_RESOURCE_INDIRECTION;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.resources.ResourceUrl;
import com.android.ide.common.resources.configuration.Configurable;
import com.android.ide.common.resources.configuration.FolderConfiguration;
import com.android.ide.common.resources.configuration.LocaleQualifier;
import com.android.resources.ResourceType;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import javax.xml.parsers.DocumentBuilderFactory;
public abstract class AbstractResourceRepository {
private final boolean mFramework;
private class RepositoryMerger implements MergeConsumer {
@Override
public void start(@NonNull DocumentBuilderFactory factory)
throws ConsumerException {
}
@Override
public void end() throws ConsumerException {
}
@Override
public void addItem(@NonNull ResourceItem item) throws ConsumerException {
if (item.isTouched()) {
AbstractResourceRepository.this.addItem(item);
}
}
@Override
public void removeItem(@NonNull ResourceItem removedItem, @Nullable ResourceItem replacedBy)
throws ConsumerException {
AbstractResourceRepository.this.removeItem(removedItem);
}
@Override
public boolean ignoreItemInMerge(ResourceItem item) {
// we never ignore any item.
return false;
}
}
public AbstractResourceRepository(boolean isFramework) {
mFramework = isFramework;
}
public boolean isFramework() {
return mFramework;
}
@NonNull
public MergeConsumer createMergeConsumer() {
return new RepositoryMerger();
}
@NonNull
protected abstract Map> getMap();
@Nullable
protected abstract ListMultimap getMap(ResourceType type, boolean create);
@NonNull
protected ListMultimap getMap(ResourceType type) {
//noinspection ConstantConditions
return getMap(type, true); // Won't return null if create is false
}
@NonNull
public Map> getItems() {
return getMap();
}
/** Lock used to protect map access */
protected static final Object ITEM_MAP_LOCK = new Object();
// TODO: Rename to getResourceItemList?
@Nullable
public List getResourceItem(@NonNull ResourceType resourceType,
@NonNull String resourceName) {
synchronized (ITEM_MAP_LOCK) {
ListMultimap map = getMap(resourceType, false);
if (map != null) {
return map.get(resourceName);
}
}
return null;
}
@NonNull
public Collection getItemsOfType(@NonNull ResourceType type) {
synchronized (ITEM_MAP_LOCK) {
Multimap map = getMap(type, false);
if (map == null) {
return Collections.emptyList();
}
return Collections.unmodifiableCollection(map.keySet());
}
}
/**
* Returns true if this resource repository contains a resource of the given
* name.
*
* @param url the resource URL
* @return true if the resource is known
*/
public boolean hasResourceItem(@NonNull String url) {
// Handle theme references
if (url.startsWith(PREFIX_THEME_REF)) {
String remainder = url.substring(PREFIX_THEME_REF.length());
if (url.startsWith(ATTR_REF_PREFIX)) {
url = PREFIX_RESOURCE_REF + url.substring(PREFIX_THEME_REF.length());
return hasResourceItem(url);
}
int colon = url.indexOf(':');
if (colon != -1) {
// Convert from ?android:progressBarStyleBig to ?android:attr/progressBarStyleBig
if (remainder.indexOf('/', colon) == -1) {
remainder = remainder.substring(0, colon) + RESOURCE_CLZ_ATTR + '/'
+ remainder.substring(colon);
}
url = PREFIX_RESOURCE_REF + remainder;
return hasResourceItem(url);
} else {
int slash = url.indexOf('/');
if (slash == -1) {
url = PREFIX_RESOURCE_REF + RESOURCE_CLZ_ATTR + '/' + remainder;
return hasResourceItem(url);
}
}
}
if (!url.startsWith(PREFIX_RESOURCE_REF)) {
return false;
}
assert url.startsWith("@") || url.startsWith("?") : url;
int typeEnd = url.indexOf('/', 1);
if (typeEnd != -1) {
int nameBegin = typeEnd + 1;
// Skip @ and @+
int typeBegin = url.startsWith("@+") ? 2 : 1; //$NON-NLS-1$
int colon = url.lastIndexOf(':', typeEnd);
if (colon != -1) {
typeBegin = colon + 1;
}
String typeName = url.substring(typeBegin, typeEnd);
ResourceType type = ResourceType.getEnum(typeName);
if (type != null) {
String name = url.substring(nameBegin);
return hasResourceItem(type, name);
}
}
return false;
}
/**
* Returns true if this resource repository contains a resource of the given
* name.
*
* @param resourceType the type of resource to look up
* @param resourceName the name of the resource
* @return true if the resource is known
*/
public boolean hasResourceItem(@NonNull ResourceType resourceType,
@NonNull String resourceName) {
synchronized (ITEM_MAP_LOCK) {
ListMultimap map = getMap(resourceType, false);
if (map != null) {
List itemList = map.get(resourceName);
return itemList != null && !itemList.isEmpty();
}
}
return false;
}
/**
* Returns whether the repository has resources of a given {@link ResourceType}.
* @param resourceType the type of resource to check.
* @return true if the repository contains resources of the given type, false otherwise.
*/
public boolean hasResourcesOfType(@NonNull ResourceType resourceType) {
synchronized (ITEM_MAP_LOCK) {
ListMultimap map = getMap(resourceType, false);
return map != null && !map.isEmpty();
}
}
@NonNull
public List getAvailableResourceTypes() {
synchronized (ITEM_MAP_LOCK) {
return Lists.newArrayList(getMap().keySet());
}
}
/**
* Returns the {@link ResourceFile} matching the given name, {@link ResourceType} and
* configuration.
*
* This only works with files generating one resource named after the file
* (for instance, layouts, bitmap based drawable, xml, anims).
*
* @param name the resource name
* @param type the folder type search for
* @param config the folder configuration to match for
* @return the matching file or null
if no match was found.
*/
@Nullable
public ResourceFile getMatchingFile(
@NonNull String name,
@NonNull ResourceType type,
@NonNull FolderConfiguration config) {
List matchingFiles = getMatchingFiles(name, type, config);
return matchingFiles.isEmpty() ? null : matchingFiles.get(0);
}
/**
* Returns a list of {@link ResourceFile} matching the given name, {@link ResourceType} and
* configuration. This ignores the qualifiers which are missing from the configuration.
*
* This only works with files generating one resource named after the file (for instance,
* layouts, bitmap based drawable, xml, anims).
*
* @param name the resource name
* @param type the folder type search for
* @param config the folder configuration to match for
*
* @see #getMatchingFile(String, ResourceType, FolderConfiguration)
*/
@NonNull
public List getMatchingFiles(
@NonNull String name,
@NonNull ResourceType type,
@NonNull FolderConfiguration config) {
return getMatchingFiles(name, type, config, new HashSet(), 0);
}
@NonNull
private List getMatchingFiles(
@NonNull String name,
@NonNull ResourceType type,
@NonNull FolderConfiguration config,
@NonNull Set seenNames,
int depth) {
assert !seenNames.contains(name);
if (depth >= MAX_RESOURCE_INDIRECTION) {
return Collections.emptyList();
}
List output;
synchronized (ITEM_MAP_LOCK) {
ListMultimap typeItems = getMap(type, false);
if (typeItems == null) {
return Collections.emptyList();
}
seenNames.add(name);
output = new ArrayList();
List matchingItems = typeItems.get(name);
List matches = config.findMatchingConfigurables(matchingItems);
for (ResourceItem match : matches) {
// if match is an alias, check if the name is in seen names.
ResourceValue resourceValue = match.getResourceValue(isFramework());
if (resourceValue != null) {
String value = resourceValue.getValue();
if (value != null && value.startsWith(PREFIX_RESOURCE_REF)) {
ResourceUrl url = ResourceUrl.parse(value);
if (url != null && url.type == type && url.framework == isFramework()) {
if (!seenNames.contains(url.name)) {
// This resource alias needs to be resolved again.
output.addAll(getMatchingFiles(
url.name, type, config, seenNames, depth + 1));
}
continue;
}
}
}
output.add(match.getSource());
}
}
return output;
}
/**
* 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}
*/
@NonNull
public Map> getConfiguredResources(
@NonNull FolderConfiguration referenceConfig) {
Map> map = Maps.newEnumMap(ResourceType.class);
synchronized (ITEM_MAP_LOCK) {
Map> itemMap = getMap();
for (ResourceType key : ResourceType.values()) {
// get the local results and put them in the map
map.put(key, getConfiguredResources(itemMap, key, referenceConfig));
}
}
return map;
}
/**
* Returns a map of (resource name, resource value) for the given {@link ResourceType}.
* 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.
*/
@NonNull
public Map getConfiguredResources(
@NonNull ResourceType type,
@NonNull FolderConfiguration referenceConfig) {
return getConfiguredResources(getMap(), type, referenceConfig);
}
@NonNull
public Map getConfiguredResources(
@NonNull Map> itemMap,
@NonNull ResourceType type,
@NonNull FolderConfiguration referenceConfig) {
// get the resource item for the given type
ListMultimap items = itemMap.get(type);
if (items == null) {
return Maps.newHashMap();
}
Set keys = items.keySet();
// create the map
Map map = Maps.newHashMapWithExpectedSize(keys.size());
for (String key : keys) {
List keyItems = items.get(key);
// look for the best match for the given configuration
// the match has to be of type ResourceFile since that's what the input list contains
ResourceItem match = (ResourceItem) referenceConfig.findMatchingConfigurable(keyItems);
if (match != null) {
ResourceValue value = match.getResourceValue(mFramework);
if (value != null) {
map.put(match.getName(), value);
}
}
}
return map;
}
@Nullable
public ResourceValue getConfiguredValue(
@NonNull ResourceType type,
@NonNull String name,
@NonNull FolderConfiguration referenceConfig) {
// get the resource item for the given type
ListMultimap items = getMap(type, false);
if (items == null) {
return null;
}
List keyItems = items.get(name);
if (keyItems == null) {
return null;
}
// look for the best match for the given configuration
// the match has to be of type ResourceFile since that's what the input list contains
ResourceItem match = (ResourceItem) referenceConfig.findMatchingConfigurable(keyItems);
return match != null ? match.getResourceValue(mFramework) : null;
}
private void addItem(@NonNull ResourceItem item) {
synchronized (ITEM_MAP_LOCK) {
ListMultimap map = getMap(item.getType());
if (!map.containsEntry(item.getName(), item)) {
map.put(item.getName(), item);
}
}
}
private void removeItem(@NonNull ResourceItem removedItem) {
synchronized (ITEM_MAP_LOCK) {
Multimap map = getMap(removedItem.getType(), false);
if (map != null) {
map.remove(removedItem.getName(), removedItem);
}
}
}
/**
* Returns the sorted list of languages used in the resources.
*/
@NonNull
public SortedSet getLanguages() {
SortedSet set = new TreeSet();
// As an optimization we could just look for values since that's typically where
// the languages are defined -- not on layouts, menus, etc -- especially if there
// are no translations for it
Set qualifiers = Sets.newHashSet();
synchronized (ITEM_MAP_LOCK) {
for (ListMultimap map : getMap().values()) {
for (ResourceItem item : map.values()) {
qualifiers.add(item.getQualifiers());
}
}
}
for (String s : qualifiers) {
FolderConfiguration configuration = FolderConfiguration.getConfigForQualifierString(s);
if (configuration != null) {
LocaleQualifier locale = configuration.getLocaleQualifier();
if (locale != null) {
set.add(locale.getLanguage());
}
}
}
return set;
}
/**
* Returns the sorted list of languages used in the resources.
*/
@NonNull
public SortedSet getLocales() {
SortedSet set = new TreeSet();
// As an optimization we could just look for values since that's typically where
// the languages are defined -- not on layouts, menus, etc -- especially if there
// are no translations for it
Set qualifiers = Sets.newHashSet();
synchronized (ITEM_MAP_LOCK) {
for (ListMultimap map : getMap().values()) {
for (ResourceItem item : map.values()) {
qualifiers.add(item.getQualifiers());
}
}
}
for (String s : qualifiers) {
FolderConfiguration configuration = FolderConfiguration.getConfigForQualifierString(s);
if (configuration != null) {
LocaleQualifier locale = configuration.getLocaleQualifier();
if (locale != null) {
set.add(locale);
}
}
}
return set;
}
/**
* Returns the sorted list of regions used in the resources with the given language.
* @param currentLanguage the current language the region must be associated with.
*/
@NonNull
public SortedSet getRegions(@NonNull String currentLanguage) {
SortedSet set = new TreeSet();
// As an optimization we could just look for values since that's typically where
// the languages are defined -- not on layouts, menus, etc -- especially if there
// are no translations for it
Set qualifiers = Sets.newHashSet();
synchronized (ITEM_MAP_LOCK) {
for (ListMultimap map : getMap().values()) {
for (ResourceItem item : map.values()) {
qualifiers.add(item.getQualifiers());
}
}
}
for (String s : qualifiers) {
FolderConfiguration configuration = FolderConfiguration.getConfigForQualifierString(s);
if (configuration != null) {
LocaleQualifier locale = configuration.getLocaleQualifier();
if (locale != null && locale.getRegion() != null
&& locale.getLanguage().equals(currentLanguage)) {
set.add(locale.getRegion());
}
}
}
return set;
}
public void clear() {
getMap().clear();
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy