blob: 7c3fd4c131ba156e5ef25d1ac4f53f02782d7dc8 [file] [log] [blame]
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
*
* 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.eclipse.adt.internal.resources.manager;
import com.android.SdkConstants;
import com.android.annotations.NonNull;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.resources.IntArrayWrapper;
import com.android.ide.common.resources.ResourceFolder;
import com.android.ide.common.resources.ResourceItem;
import com.android.ide.common.resources.ResourceRepository;
import com.android.ide.common.resources.configuration.FolderConfiguration;
import com.android.ide.eclipse.adt.internal.sdk.ProjectState;
import com.android.ide.eclipse.adt.internal.sdk.Sdk;
import com.android.ide.eclipse.adt.io.IFolderWrapper;
import com.android.io.IAbstractFolder;
import com.android.resources.ResourceType;
import com.android.util.Pair;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
/**
* Represents the resources of a project.
* On top of the regular {@link ResourceRepository} features it provides:
*<ul>
*<li>configured resources contain the resources coming from the libraries.</li>
*<li>resolution to and from resource integer (compiled value in R.java).</li>
*<li>handles resource integer for non existing values of type ID. This is used when rendering.</li>
*<li>layouts that have no been saved yet. This is handled by generating dynamic IDs
* on the fly.</li>
*</ul>
*/
@SuppressWarnings("deprecation")
public class ProjectResources extends ResourceRepository {
// project resources are defined as 0x7FXX#### where XX is the resource type (layout, drawable,
// etc...). Using FF as the type allows for 255 resource types before we get a collision
// which should be fine.
private final static int DYNAMIC_ID_SEED_START = 0x7fff0000;
/** Map of (name, id) for resources of type {@link ResourceType#ID} coming from R.java */
private Map<ResourceType, Map<String, Integer>> mResourceValueMap;
/** Map of (id, [name, resType]) for all resources coming from R.java */
private Map<Integer, Pair<ResourceType, String>> mResIdValueToNameMap;
/** Map of (int[], name) for styleable resources coming from R.java */
private Map<IntArrayWrapper, String> mStyleableValueToNameMap;
private final DynamicIdMap mDynamicIdMap = new DynamicIdMap(DYNAMIC_ID_SEED_START);
private final IntArrayWrapper mWrapper = new IntArrayWrapper(null);
private final IProject mProject;
public static ProjectResources create(IProject project) {
IFolder resFolder = project.getFolder(SdkConstants.FD_RESOURCES);
return new ProjectResources(project, new IFolderWrapper(resFolder));
}
/**
* Makes a ProjectResources for a given <var>project</var>.
* @param project the project.
*/
private ProjectResources(IProject project, IAbstractFolder resFolder) {
super(resFolder, false /*isFrameworkRepository*/);
mProject = project;
}
/**
* Returns the resources values matching a given {@link FolderConfiguration}, this will
* include library dependency.
*
* @param referenceConfig the configuration that each value must match.
* @return a map with guaranteed to contain an entry for each {@link ResourceType}
*/
@Override
@NonNull
public Map<ResourceType, Map<String, ResourceValue>> getConfiguredResources(
@NonNull FolderConfiguration referenceConfig) {
ensureInitialized();
Map<ResourceType, Map<String, ResourceValue>> resultMap =
new EnumMap<ResourceType, Map<String, ResourceValue>>(ResourceType.class);
// if the project contains libraries, we need to add the libraries resources here
// so that they are accessible to the layout rendering.
if (mProject != null) {
ProjectState state = Sdk.getProjectState(mProject);
if (state != null) {
List<IProject> libraries = state.getFullLibraryProjects();
ResourceManager resMgr = ResourceManager.getInstance();
// because aapt put all the library in their order in this array, the first
// one will have priority over the 2nd one. So it's better to loop in the inverse
// order and fill the map with resources that will be overwritten by higher
// priority resources
for (int i = libraries.size() - 1 ; i >= 0 ; i--) {
IProject library = libraries.get(i);
ProjectResources libRes = resMgr.getProjectResources(library);
if (libRes != null) {
// get the library resources, and only the library, not the dependencies
// so call doGetConfiguredResources() directly.
Map<ResourceType, Map<String, ResourceValue>> libMap =
libRes.doGetConfiguredResources(referenceConfig);
// we don't want to simply replace the whole map, but instead merge the
// content of any sub-map
for (Entry<ResourceType, Map<String, ResourceValue>> libEntry :
libMap.entrySet()) {
// get the map currently in the result map for this resource type
Map<String, ResourceValue> tempMap = resultMap.get(libEntry.getKey());
if (tempMap == null) {
// since there's no current map for this type, just add the map
// directly coming from the library resources
resultMap.put(libEntry.getKey(), libEntry.getValue());
} else {
// already a map for this type. add the resources from the
// library, this will override existing value, which is why
// we loop in a specific library order.
tempMap.putAll(libEntry.getValue());
}
}
}
}
}
}
// now the project resources themselves.
Map<ResourceType, Map<String, ResourceValue>> thisProjectMap =
doGetConfiguredResources(referenceConfig);
// now merge the maps.
for (Entry<ResourceType, Map<String, ResourceValue>> entry : thisProjectMap.entrySet()) {
ResourceType type = entry.getKey();
Map<String, ResourceValue> typeMap = resultMap.get(type);
if (typeMap == null) {
resultMap.put(type, entry.getValue());
} else {
typeMap.putAll(entry.getValue());
}
}
return resultMap;
}
/**
* Returns the {@link ResourceFolder} associated with a {@link IFolder}.
* @param folder The {@link IFolder} object.
* @return the {@link ResourceFolder} or null if it was not found.
*
* @see ResourceRepository#getResourceFolder(com.android.io.IAbstractFolder)
*/
public ResourceFolder getResourceFolder(IFolder folder) {
return getResourceFolder(new IFolderWrapper(folder));
}
/**
* Resolves a compiled resource id into the resource name and type
* @param id the resource integer id.
* @return a {@link Pair} of 2 strings { name, type } or null if the id could not be resolved
*/
public Pair<ResourceType, String> resolveResourceId(int id) {
Pair<ResourceType, String> result = null;
if (mResIdValueToNameMap != null) {
result = mResIdValueToNameMap.get(id);
}
if (result == null) {
synchronized (mDynamicIdMap) {
result = mDynamicIdMap.resolveId(id);
}
}
return result;
}
/**
* Resolves a compiled styleable id of type int[] into the styleable name.
*/
public String resolveStyleable(int[] id) {
if (mStyleableValueToNameMap != null) {
mWrapper.set(id);
return mStyleableValueToNameMap.get(mWrapper);
}
return null;
}
/**
* Returns the integer id of a resource given its type and name.
* <p/>If the resource is of type {@link ResourceType#ID} and does not exist in the
* internal map, then new id values are dynamically generated (and stored so that queries
* with the same names will return the same value).
*/
public Integer getResourceId(ResourceType type, String name) {
Integer result = null;
if (mResourceValueMap != null) {
Map<String, Integer> map = mResourceValueMap.get(type);
if (map != null) {
result = map.get(name);
}
}
if (result == null) {
synchronized (mDynamicIdMap) {
result = mDynamicIdMap.getId(type, name);
}
}
return result;
}
/**
* Resets the list of dynamic Ids. This list is used by
* {@link #getResourceId(String, String)} when the resource query is an ID that doesn't
* exist (for example for ID automatically generated in layout files that are not saved yet.)
* <p/>This method resets those dynamic ID and must be called whenever the actual list of IDs
* change.
*/
public void resetDynamicIds() {
synchronized (mDynamicIdMap) {
mDynamicIdMap.reset(DYNAMIC_ID_SEED_START);
}
}
@Override
@NonNull
protected ResourceItem createResourceItem(@NonNull String name) {
return new ResourceItem(name);
}
/**
* Sets compiled resource information.
*
* @param resIdValueToNameMap a map of compiled resource id to resource name.
* The map is acquired by the {@link ProjectResources} object.
* @param styleableValueMap a map of (int[], name) for the styleable information. The map is
* acquired by the {@link ProjectResources} object.
* @param resourceValueMap a map of (name, id) for resources of type {@link ResourceType#ID}.
* The list is acquired by the {@link ProjectResources} object.
*/
void setCompiledResources(Map<Integer, Pair<ResourceType, String>> resIdValueToNameMap,
Map<IntArrayWrapper, String> styleableValueMap,
Map<ResourceType, Map<String, Integer>> resourceValueMap) {
mResourceValueMap = resourceValueMap;
mResIdValueToNameMap = resIdValueToNameMap;
mStyleableValueToNameMap = styleableValueMap;
resetDynamicIds();
}
}