/* * 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 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(); } }