com.android.ide.common.resources.ResourceResolver 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) 2011 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;
import static com.android.SdkConstants.ANDROID_STYLE_RESOURCE_PREFIX;
import static com.android.SdkConstants.PREFIX_ANDROID;
import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
import static com.android.SdkConstants.REFERENCE_STYLE;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.ide.common.rendering.api.ArrayResourceValue;
import com.android.ide.common.rendering.api.LayoutLog;
import com.android.ide.common.rendering.api.RenderResources;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.rendering.api.StyleResourceValue;
import com.android.resources.ResourceType;
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.collect.Maps;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ResourceResolver extends RenderResources {
public static final String THEME_NAME = "Theme";
public static final String THEME_NAME_DOT = "Theme.";
public static final String XLIFF_NAMESPACE_PREFIX = "urn:oasis:names:tc:xliff:document:";
public static final String XLIFF_G_TAG = "g";
public static final String ATTR_EXAMPLE = "example";
/**
* Constant passed to {@link #setDeviceDefaults(String)} to indicate the DeviceDefault styles
* should point to the default styles
*/
public static final String LEGACY_THEME = "";
public static final Pattern DEVICE_DEFAULT_PATTERN = Pattern
.compile("(\\p{Alpha}+)?\\.?DeviceDefault\\.?(.+)?");
/**
* Number of indirections we'll follow for resource resolution before assuming there
* is a cyclic dependency error in the input
*/
public static final int MAX_RESOURCE_INDIRECTION = 50;
private final Map> mProjectResources;
private final Map> mFrameworkResources;
private final Map mStyleInheritanceMap =
new HashMap();
private StyleResourceValue mDefaultTheme;
// The resources should be searched in all the themes in the list in order.
private final List mThemes;
private FrameworkResourceIdProvider mFrameworkProvider;
private LayoutLog mLogger;
private String mThemeName;
private boolean mIsProjectTheme;
// AAPT flattens the names by converting '.', '-' and ':' to '_'. These maps undo the
// flattening. We prefer lazy initialization of these maps since they are not used in many
// applications.
@Nullable
private Map mReverseFrameworkStyles;
@Nullable
private Map mReverseProjectStyles;
/** Contains the default parent for DeviceDefault styles (e.g. for API 18, "Holo") */
private String mDeviceDefaultParent = null;
private ResourceResolver(
Map> projectResources,
Map> frameworkResources,
String themeName, boolean isProjectTheme) {
mProjectResources = projectResources;
mFrameworkResources = frameworkResources;
mThemeName = themeName;
mIsProjectTheme = isProjectTheme;
mThemes = new LinkedList();
}
/**
* Creates a new {@link ResourceResolver} object.
*
* @param projectResources the project resources.
* @param frameworkResources the framework resources.
* @param themeName the name of the current theme.
* @param isProjectTheme Is this a project theme?
* @return a new {@link ResourceResolver}
*/
public static ResourceResolver create(
Map> projectResources,
Map> frameworkResources,
String themeName, boolean isProjectTheme) {
ResourceResolver resolver = new ResourceResolver(projectResources, frameworkResources,
themeName, isProjectTheme);
resolver.computeStyleMaps();
return resolver;
}
/**
* This will override the DeviceDefault styles so they point to the given parent styles (e.g. If
* "Material" is passed, Theme.DeviceDefault parent will become Theme.Material). This patches
* all the styles (not only themes) and takes care of the light and dark variants. If {@link
* #LEGACY_THEME} is passed, parents will be directed to the default themes (i.e. Theme).
*/
public void setDeviceDefaults(@NonNull String deviceDefaultParent) {
if (deviceDefaultParent.equals(mDeviceDefaultParent)) {
// No need to patch again with the same parent
return;
}
mDeviceDefaultParent = deviceDefaultParent;
// The joiner will ignore nulls so if the caller specified an empty name, we replace it with
// a null so it gets ignored
String parentName = Strings.emptyToNull(deviceDefaultParent);
// The regexp gets the prefix and suffix if they exist (without the dots)
for (ResourceValue value : mFrameworkResources.get(ResourceType.STYLE).values()) {
Matcher matcher = DEVICE_DEFAULT_PATTERN.matcher(value.getName());
if (!matcher.matches()) {
continue;
}
String newParentStyle =
Joiner.on('.').skipNulls().join(matcher.group(1), parentName,
((matcher.groupCount() > 1) ? matcher.group(2) : null));
patchFrameworkStyleParent(value.getName(), newParentStyle);
}
}
/**
* Updates the parent of a given framework style. This method is used to patch DeviceDefault
* styles when using a CompatibilityTarget
*/
private void patchFrameworkStyleParent(String childStyleName, String parentName) {
Map map = mFrameworkResources.get(ResourceType.STYLE);
if (map != null) {
StyleResourceValue from = (StyleResourceValue)map.get(childStyleName);
StyleResourceValue to = (StyleResourceValue)map.get(parentName);
if (from != null && to != null) {
StyleResourceValue newStyle = new StyleResourceValue(ResourceType.STYLE,
from.getName(), parentName, from.isFramework());
newStyle.replaceWith(from);
mStyleInheritanceMap.put(newStyle, to);
}
}
}
// ---- Methods to help dealing with older LayoutLibs.
public String getThemeName() {
return mThemeName;
}
public boolean isProjectTheme() {
return mIsProjectTheme;
}
public Map> getProjectResources() {
return mProjectResources;
}
public Map> getFrameworkResources() {
return mFrameworkResources;
}
// ---- RenderResources Methods
@Override
public void setFrameworkResourceIdProvider(FrameworkResourceIdProvider provider) {
mFrameworkProvider = provider;
}
@Override
public void setLogger(LayoutLog logger) {
mLogger = logger;
}
@Override
public StyleResourceValue getDefaultTheme() {
return mDefaultTheme;
}
@Override
public void applyStyle(StyleResourceValue theme, boolean useAsPrimary) {
if (theme == null) {
return;
}
if (useAsPrimary) {
mThemes.add(0, theme);
} else {
mThemes.add(theme);
}
}
@Override
public void clearStyles() {
mThemes.clear();
mThemes.add(mDefaultTheme);
}
@Override
public List getAllThemes() {
return mThemes;
}
@Override
public StyleResourceValue getTheme(String name, boolean frameworkTheme) {
ResourceValue theme;
if (frameworkTheme) {
Map frameworkStyleMap = mFrameworkResources.get(
ResourceType.STYLE);
theme = frameworkStyleMap.get(name);
} else {
Map projectStyleMap = mProjectResources.get(ResourceType.STYLE);
theme = projectStyleMap.get(name);
}
if (theme instanceof StyleResourceValue) {
return (StyleResourceValue) theme;
}
return null;
}
@Override
public boolean themeIsParentOf(StyleResourceValue parentTheme, StyleResourceValue childTheme) {
do {
childTheme = mStyleInheritanceMap.get(childTheme);
if (childTheme == null) {
return false;
} else if (childTheme == parentTheme) {
return true;
}
} while (true);
}
@Override
public ResourceValue getFrameworkResource(ResourceType resourceType, String resourceName) {
return getResource(resourceType, resourceName, mFrameworkResources);
}
@Override
public ResourceValue getProjectResource(ResourceType resourceType, String resourceName) {
return getResource(resourceType, resourceName, mProjectResources);
}
@SuppressWarnings("deprecation") // Required to support older layoutlib clients
@Override
@Deprecated
public ResourceValue findItemInStyle(StyleResourceValue style, String attrName) {
// this method is deprecated because it doesn't know about the namespace of the
// attribute so we search for the project namespace first and then in the
// android namespace if needed.
ResourceValue item = findItemInStyle(style, attrName, false);
if (item == null) {
item = findItemInStyle(style, attrName, true);
}
return item;
}
@Override
public ResourceValue findItemInStyle(StyleResourceValue style, String itemName,
boolean isFrameworkAttr) {
return findItemInStyle(style, itemName, isFrameworkAttr, 0);
}
private ResourceValue findItemInStyle(StyleResourceValue style, String itemName,
boolean isFrameworkAttr, int depth) {
ResourceValue item = style.getItem(itemName, isFrameworkAttr);
// if we didn't find it, we look in the parent style (if applicable)
//noinspection VariableNotUsedInsideIf
if (item == null) {
StyleResourceValue parentStyle = mStyleInheritanceMap.get(style);
if (parentStyle != null) {
if (depth >= MAX_RESOURCE_INDIRECTION) {
if (mLogger != null) {
mLogger.error(LayoutLog.TAG_BROKEN,
String.format("Cyclic style parent definitions: %1$s",
computeCyclicStyleChain(style)),
null);
}
return null;
}
return findItemInStyle(parentStyle, itemName, isFrameworkAttr, depth + 1);
}
}
return item;
}
private String computeCyclicStyleChain(StyleResourceValue style) {
StringBuilder sb = new StringBuilder(100);
appendStyleParents(style, new HashSet(), 0, sb);
return sb.toString();
}
private void appendStyleParents(StyleResourceValue style, Set seen,
int depth, StringBuilder sb) {
if (depth >= MAX_RESOURCE_INDIRECTION) {
sb.append("...");
return;
}
boolean haveSeen = seen.contains(style);
seen.add(style);
sb.append('"');
if (style.isFramework()) {
sb.append(PREFIX_ANDROID);
}
sb.append(style.getName());
sb.append('"');
if (haveSeen) {
return;
}
StyleResourceValue parentStyle = mStyleInheritanceMap.get(style);
if (parentStyle != null) {
if (style.getParentStyle() != null) {
sb.append(" specifies parent ");
} else {
sb.append(" implies parent ");
}
appendStyleParents(parentStyle, seen, depth + 1, sb);
}
}
@Override
public ResourceValue findResValue(String reference, boolean forceFrameworkOnly) {
if (reference == null) {
return null;
}
ResourceUrl resource = ResourceUrl.parse(reference);
if (resource != null && resource.hasValidName()) {
if (resource.theme) {
// no theme? no need to go further!
if (mDefaultTheme == null) {
return null;
}
if (resource.type != ResourceType.ATTR) {
// At this time, no support for ?type/name where type is not "attr"
return null;
}
// Now look for the item in the theme, starting with the current one.
return findItemInTheme(resource.name, forceFrameworkOnly || resource.framework);
} else {
return findResValue(resource, forceFrameworkOnly);
}
}
// Looks like the value didn't reference anything. Return null.
return null;
}
@Override
public ResourceValue resolveValue(ResourceType type, String name, String value,
boolean isFrameworkValue) {
if (value == null) {
return null;
}
// get the ResourceValue referenced by this value
ResourceValue resValue = findResValue(value, isFrameworkValue);
// if resValue is null, but value is not null, this means it was not a reference.
// we return the name/value wrapper in a ResourceValue. the isFramework flag doesn't
// matter.
if (resValue == null) {
return new ResourceValue(type, name, value, isFrameworkValue);
}
// we resolved a first reference, but we need to make sure this isn't a reference also.
return resolveResValue(resValue);
}
@Override
public ResourceValue resolveResValue(ResourceValue resValue) {
return resolveResValue(resValue, 0);
}
private ResourceValue resolveResValue(ResourceValue resValue, int depth) {
if (resValue == null) {
return null;
}
String value = resValue.getValue();
if (value == null || resValue instanceof ArrayResourceValue) {
// If there's no value or this an array resource (eg. ), return.
return resValue;
}
// else attempt to find another ResourceValue referenced by this one.
ResourceValue resolvedResValue = findResValue(value, resValue.isFramework());
// if the value did not reference anything, then we simply return the input value
if (resolvedResValue == null) {
return resValue;
}
// detect potential loop due to mishandled namespace in attributes
if (resValue == resolvedResValue || depth >= MAX_RESOURCE_INDIRECTION) {
if (mLogger != null) {
mLogger.error(LayoutLog.TAG_BROKEN,
String.format("Potential stack overflow trying to resolve '%s': cyclic resource definitions? Render may not be accurate.", value),
null);
}
return resValue;
}
// otherwise, we attempt to resolve this new value as well
return resolveResValue(resolvedResValue, depth + 1);
}
// ---- Private helper methods.
/**
* Searches for, and returns a {@link ResourceValue} by its parsed reference.
* @param resource the parsed resource
* @param forceFramework if true
, the method does not search in the
* project resources
*/
private ResourceValue findResValue(ResourceUrl resource, boolean forceFramework) {
// map of ResourceValue for the given type
Map typeMap;
ResourceType resType = resource.type;
String resName = resource.name;
boolean isFramework = forceFramework || resource.framework;
if (!isFramework) {
typeMap = mProjectResources.get(resType);
ResourceValue item = typeMap.get(resName);
if (item != null) {
return item;
}
} else {
typeMap = mFrameworkResources.get(resType);
if (typeMap != null) {
ResourceValue item = typeMap.get(resName);
if (item != null) {
return item;
}
}
// if it was not found and the type is an id, it is possible that the ID was
// generated dynamically when compiling the framework resources.
// Look for it in the R map.
if (mFrameworkProvider != null && resType == ResourceType.ID) {
if (mFrameworkProvider.getId(resType, resName) != null) {
return new ResourceValue(resType, resName, true);
}
}
}
// didn't find the resource anywhere.
if (!resource.create && mLogger != null) {
mLogger.warning(LayoutLog.TAG_RESOURCES_RESOLVE,
"Couldn't resolve resource @" +
(isFramework ? "android:" : "") + resType + "/" + resName,
new ResourceValue(resType, resName, isFramework));
}
return null;
}
private ResourceValue getResource(ResourceType resourceType, String resourceName,
Map> resourceRepository) {
Map typeMap = resourceRepository.get(resourceType);
if (typeMap != null) {
ResourceValue item = typeMap.get(resourceName);
if (item != null) {
item = resolveResValue(item);
return item;
}
}
// didn't find the resource anywhere.
return null;
}
/**
* Compute style information from the given list of style for the project and framework.
*/
private void computeStyleMaps() {
Map projectStyleMap = mProjectResources.get(ResourceType.STYLE);
Map frameworkStyleMap = mFrameworkResources.get(ResourceType.STYLE);
// first, get the theme
ResourceValue theme = null;
// project theme names have been prepended with a *
if (mIsProjectTheme) {
if (projectStyleMap != null) {
theme = projectStyleMap.get(mThemeName);
}
} else {
if (frameworkStyleMap != null) {
theme = frameworkStyleMap.get(mThemeName);
}
}
if (theme instanceof StyleResourceValue) {
// compute the inheritance map for both the project and framework styles
computeStyleInheritance(projectStyleMap.values(), projectStyleMap,
frameworkStyleMap);
// Compute the style inheritance for the framework styles/themes.
// Since, for those, the style parent values do not contain 'android:'
// we want to force looking in the framework style only to avoid using
// similarly named styles from the project.
// To do this, we pass null in lieu of the project style map.
if (frameworkStyleMap != null) {
computeStyleInheritance(frameworkStyleMap.values(), null /*inProjectStyleMap */,
frameworkStyleMap);
}
mDefaultTheme = (StyleResourceValue) theme;
mThemes.clear();
mThemes.add(mDefaultTheme);
}
}
/**
* Compute the parent style for all the styles in a given list.
* @param styles the styles for which we compute the parent.
* @param inProjectStyleMap the map of project styles.
* @param inFrameworkStyleMap the map of framework styles.
*/
private void computeStyleInheritance(Collection styles,
Map inProjectStyleMap,
Map inFrameworkStyleMap) {
// This method will recalculate the inheritance map so any modifications done by
// setDeviceDefault will be lost. Set mDeviceDefaultParent to null so when setDeviceDefault
// is called again, it knows that it needs to modify the inheritance map again.
mDeviceDefaultParent = null;
for (ResourceValue value : styles) {
if (value instanceof StyleResourceValue) {
StyleResourceValue style = (StyleResourceValue)value;
String parentName = getParentName(style);
if (parentName != null) {
StyleResourceValue parentStyle = getStyle(parentName, inProjectStyleMap,
inFrameworkStyleMap);
if (parentStyle != null) {
mStyleInheritanceMap.put(style, parentStyle);
}
}
}
}
}
/**
* Computes the name of the parent style, or null
if the style is a root style.
*/
@Nullable
public static String getParentName(StyleResourceValue style) {
String parentName = style.getParentStyle();
if (parentName != null) {
return parentName;
}
String styleName = style.getName();
int index = styleName.lastIndexOf('.');
if (index != -1) {
return styleName.substring(0, index);
}
return null;
}
@Override
@Nullable
public StyleResourceValue getParent(@NonNull StyleResourceValue style) {
return mStyleInheritanceMap.get(style);
}
@Override
@Nullable
public StyleResourceValue getStyle(@NonNull String styleName, boolean isFramework) {
ResourceValue res;
Map styleMap;
// First check if we can find the style directly.
if (isFramework) {
styleMap = mFrameworkResources.get(ResourceType.STYLE);
} else {
styleMap = mProjectResources.get(ResourceType.STYLE);
}
res = getStyleFromMap(styleMap, styleName);
if (res != null) {
// If the obtained resource is not StyleResourceValue, return null.
return res instanceof StyleResourceValue ? (StyleResourceValue) res : null;
}
// We cannot find the style directly. The style name may have been flattened by AAPT for use
// in the R class. Try and obtain the original name.
String xmlStyleName = getReverseStyleMap(isFramework)
.get(getNormalizedStyleName(styleName));
if (!styleName.equals(xmlStyleName)) {
res = getStyleFromMap(styleMap, xmlStyleName);
}
return res instanceof StyleResourceValue ? (StyleResourceValue) res : null;
}
@Override
@Nullable
public String getXmlName(@NonNull ResourceType type, @NonNull String name,
boolean isFramework) {
if (type != ResourceType.STYLE) {
// The method is currently implemented for styles only.
return null;
}
Map reverseStyles;
reverseStyles = getReverseStyleMap(isFramework);
return reverseStyles.get(name);
}
/**
* Returns the reverse style map using the appropriate resources. It also initializes the map if
* it hasn't been initialized yet.
*/
private Map getReverseStyleMap(boolean isFramework) {
if (isFramework) {
// The reverse style map may need to be initialized.
if (mReverseFrameworkStyles == null) {
Map styleMap = mFrameworkResources.get(ResourceType.STYLE);
mReverseFrameworkStyles = createReverseStyleMap(styleMap.keySet());
}
return mReverseFrameworkStyles;
} else {
if (mReverseProjectStyles == null) {
Map styleMap = mProjectResources.get(ResourceType.STYLE);
mReverseProjectStyles = createReverseStyleMap(styleMap.keySet());
}
return mReverseProjectStyles;
}
}
/**
* Create a map from the normalized form of the style names in {@code styles} to the original
* style name.
*
* @see #getNormalizedStyleName(String)
*/
private static Map createReverseStyleMap(@NonNull Set styles) {
Map reverseStyles = Maps.newHashMapWithExpectedSize(styles.size());
for (String style : styles) {
reverseStyles.put(getNormalizedStyleName(style), style);
}
return reverseStyles;
}
/**
* Flatten the styleName like AAPT by replacing dots, dashes and colons with underscores.
*/
@NonNull
private static String getNormalizedStyleName(@NonNull String styleName) {
return styleName.replace('.', '_').replace('-', '_').replace(':', '_');
}
/**
* Search for the style in the given map and log an error if the obtained resource is not
* {@link StyleResourceValue}.
*
* @return The {@link ResourceValue} found in the map.
*/
@Nullable
private ResourceValue getStyleFromMap(@NonNull Map styleMap,
@NonNull String styleName) {
ResourceValue res;
res = styleMap.get(styleName);
if (res != null) {
if (!(res instanceof StyleResourceValue) && mLogger != null) {
mLogger.error(null, String.format(
"Style %1$s is not of type STYLE (instead %2$s)",
styleName, res.getResourceType().toString()),
null);
}
}
return res;
}
/**
* Searches for and returns the {@link StyleResourceValue} from a given name.
* The format of the name can be:
*
* - [android:]<name>
* - [android:]style/<name>
* - @[android:]style/<name>
*
* @param parentName the name of the style.
* @param inProjectStyleMap the project style map. Can be null
* @param inFrameworkStyleMap the framework style map.
* @return The matching {@link StyleResourceValue} object or null
if not found.
*/
private StyleResourceValue getStyle(String parentName,
Map inProjectStyleMap,
Map inFrameworkStyleMap) {
boolean frameworkOnly = false;
String name = parentName;
// remove the useless @ if it's there
if (name.startsWith(PREFIX_RESOURCE_REF)) {
name = name.substring(PREFIX_RESOURCE_REF.length());
}
// check for framework identifier.
if (name.startsWith(PREFIX_ANDROID)) {
frameworkOnly = true;
name = name.substring(PREFIX_ANDROID.length());
}
// at this point we could have the format /. we want only the name as long as
// the type is style.
if (name.startsWith(REFERENCE_STYLE)) {
name = name.substring(REFERENCE_STYLE.length());
} else if (name.indexOf('/') != -1) {
return null;
}
ResourceValue parent = null;
// if allowed, search in the project resources.
if (!frameworkOnly && inProjectStyleMap != null) {
parent = inProjectStyleMap.get(name);
}
// if not found, then look in the framework resources.
if (parent == null) {
if (inFrameworkStyleMap == null) {
return null;
}
parent = inFrameworkStyleMap.get(name);
}
// make sure the result is the proper class type and return it.
if (parent instanceof StyleResourceValue) {
return (StyleResourceValue)parent;
}
if (mLogger != null) {
mLogger.error(LayoutLog.TAG_RESOURCES_RESOLVE,
String.format("Unable to resolve parent style name: %s", parentName),
null /*data*/);
}
return null;
}
/** Returns true if the given {@link ResourceValue} represents a theme */
public boolean isTheme(
@NonNull ResourceValue value,
@Nullable Map cache) {
return isTheme(value, cache, 0);
}
private boolean isTheme(
@NonNull ResourceValue value,
@Nullable Map cache,
int depth) {
if (cache != null) {
Boolean known = cache.get(value);
if (known != null) {
return known;
}
}
if (value instanceof StyleResourceValue) {
StyleResourceValue srv = (StyleResourceValue) value;
String name = srv.getName();
if (srv.isFramework() && (name.equals(THEME_NAME) || name.startsWith(THEME_NAME_DOT))) {
if (cache != null) {
cache.put(value, true);
}
return true;
}
StyleResourceValue parentStyle = mStyleInheritanceMap.get(srv);
if (parentStyle != null) {
if (depth >= MAX_RESOURCE_INDIRECTION) {
if (mLogger != null) {
mLogger.error(LayoutLog.TAG_BROKEN,
String.format("Cyclic style parent definitions: %1$s",
computeCyclicStyleChain(srv)),
null);
}
return false;
}
boolean result = isTheme(parentStyle, cache, depth + 1);
if (cache != null) {
cache.put(value, result);
}
return result;
}
}
return false;
}
/**
* Returns true if the given {@code themeStyle} extends the theme given by
* {@code parentStyle}
*/
public boolean themeExtends(@NonNull String parentStyle, @NonNull String themeStyle) {
ResourceValue parentValue = findResValue(parentStyle,
parentStyle.startsWith(ANDROID_STYLE_RESOURCE_PREFIX));
if (parentValue instanceof StyleResourceValue) {
ResourceValue themeValue = findResValue(themeStyle,
themeStyle.startsWith(ANDROID_STYLE_RESOURCE_PREFIX));
if (themeValue == parentValue) {
return true;
}
if (themeValue instanceof StyleResourceValue) {
return themeIsParentOf((StyleResourceValue) parentValue,
(StyleResourceValue) themeValue);
}
}
return false;
}
/**
* Creates a new {@link ResourceResolver} which records all resource resolution
* lookups into the given list. Note that it is the responsibility of the caller
* to clear/reset the list between subsequent lookup operations.
*
* @param lookupChain the list to write resource lookups into
* @return a new {@link ResourceResolver}
*/
public ResourceResolver createRecorder(List lookupChain) {
ResourceResolver resolver = new RecordingResourceResolver(
lookupChain, mProjectResources, mFrameworkResources, mThemeName, mIsProjectTheme);
resolver.mFrameworkProvider = mFrameworkProvider;
resolver.mLogger = mLogger;
resolver.mDefaultTheme = mDefaultTheme;
resolver.mStyleInheritanceMap.putAll(mStyleInheritanceMap);
resolver.mThemes.addAll(mThemes);
return resolver;
}
private static class RecordingResourceResolver extends ResourceResolver {
@NonNull private List mLookupChain;
private RecordingResourceResolver(
@NonNull List lookupChain,
@NonNull Map> projectResources,
@NonNull Map> frameworkResources,
@NonNull String themeName, boolean isProjectTheme) {
super(projectResources, frameworkResources, themeName, isProjectTheme);
mLookupChain = lookupChain;
}
@Override
public ResourceValue resolveResValue(ResourceValue resValue) {
if (resValue != null) {
mLookupChain.add(resValue);
}
return super.resolveResValue(resValue);
}
@Override
public ResourceValue findResValue(String reference, boolean forceFrameworkOnly) {
if (!mLookupChain.isEmpty() && reference != null && reference.startsWith(PREFIX_RESOURCE_REF)) {
ResourceValue prev = mLookupChain.get(mLookupChain.size() - 1);
if (!reference.equals(prev.getValue())) {
ResourceValue next = new ResourceValue(prev.getResourceType(), prev.getName(),
prev.isFramework());
next.setValue(reference);
mLookupChain.add(next);
}
}
ResourceValue resValue = super.findResValue(reference, forceFrameworkOnly);
if (resValue != null) {
mLookupChain.add(resValue);
}
return resValue;
}
@Override
public ResourceValue findItemInStyle(StyleResourceValue style, String itemName,
boolean isFrameworkAttr) {
ResourceValue value = super.findItemInStyle(style, itemName, isFrameworkAttr);
if (value != null) {
mLookupChain.add(value);
}
return value;
}
@Override
public ResourceValue findItemInTheme(String attrName, boolean isFrameworkAttr) {
ResourceValue value = super.findItemInTheme(attrName, isFrameworkAttr);
if (value != null) {
mLookupChain.add(value);
}
return value;
}
@Override
public ResourceValue resolveValue(ResourceType type, String name, String value,
boolean isFrameworkValue) {
ResourceValue resourceValue = super.resolveValue(type, name, value, isFrameworkValue);
if (resourceValue != null) {
mLookupChain.add(resourceValue);
}
return resourceValue;
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy