| /* |
| * Copyright (C) 2007 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 com.android.annotations.VisibleForTesting; |
| import com.android.annotations.VisibleForTesting.Visibility; |
| import com.android.ide.common.resources.configuration.Configurable; |
| import com.android.ide.common.resources.configuration.FolderConfiguration; |
| import com.android.io.IAbstractFile; |
| import com.android.io.IAbstractFolder; |
| import com.android.resources.FolderTypeRelationship; |
| import com.android.resources.ResourceFolderType; |
| import com.android.resources.ResourceType; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| /** |
| * Resource Folder class. Contains list of {@link ResourceFile}s, |
| * the {@link FolderConfiguration}, and a link to the {@link IAbstractFolder} object. |
| */ |
| public final class ResourceFolder implements Configurable { |
| final ResourceFolderType mType; |
| final FolderConfiguration mConfiguration; |
| IAbstractFolder mFolder; |
| List<ResourceFile> mFiles = null; |
| Map<String, ResourceFile> mNames = null; |
| private final ResourceRepository mRepository; |
| |
| /** |
| * Creates a new {@link ResourceFolder} |
| * @param type The type of the folder |
| * @param config The configuration of the folder |
| * @param folder The associated {@link IAbstractFolder} object. |
| * @param repository The associated {@link ResourceRepository} |
| */ |
| protected ResourceFolder(ResourceFolderType type, FolderConfiguration config, |
| IAbstractFolder folder, ResourceRepository repository) { |
| mType = type; |
| mConfiguration = config; |
| mFolder = folder; |
| mRepository = repository; |
| } |
| |
| /** |
| * Processes a file and adds it to its parent folder resource. |
| * |
| * @param file the underlying resource file. |
| * @param kind the file change kind. |
| * @param context a context object with state for the current update, such |
| * as a place to stash errors encountered |
| * @return the {@link ResourceFile} that was created. |
| */ |
| public ResourceFile processFile(IAbstractFile file, ResourceDeltaKind kind, |
| ScanningContext context) { |
| // look for this file if it's already been created |
| ResourceFile resFile = getFile(file, context); |
| |
| if (resFile == null) { |
| if (kind != ResourceDeltaKind.REMOVED) { |
| // create a ResourceFile for it. |
| |
| resFile = createResourceFile(file); |
| resFile.load(context); |
| |
| // add it to the folder |
| addFile(resFile); |
| } |
| } else { |
| if (kind == ResourceDeltaKind.REMOVED) { |
| removeFile(resFile, context); |
| } else { |
| resFile.update(context); |
| } |
| } |
| |
| return resFile; |
| } |
| |
| private ResourceFile createResourceFile(IAbstractFile file) { |
| // check if that's a single or multi resource type folder. For now we define this by |
| // the number of possible resource type output by files in the folder. |
| // We have a special case for layout/menu folders which can also generate IDs. |
| // This does |
| // not make the difference between several resource types from a single file or |
| // the ability to have 2 files in the same folder generating 2 different types of |
| // resource. The former is handled by MultiResourceFile properly while we don't |
| // handle the latter. If we were to add this behavior we'd have to change this call. |
| List<ResourceType> types = FolderTypeRelationship.getRelatedResourceTypes(mType); |
| |
| ResourceFile resFile = null; |
| if (types.size() == 1) { |
| resFile = new SingleResourceFile(file, this); |
| } else if (types.contains(ResourceType.LAYOUT)) { |
| resFile = new IdGeneratingResourceFile(file, this, ResourceType.LAYOUT); |
| } else if (types.contains(ResourceType.MENU)) { |
| resFile = new IdGeneratingResourceFile(file, this, ResourceType.MENU); |
| } else { |
| resFile = new MultiResourceFile(file, this); |
| } |
| return resFile; |
| } |
| |
| /** |
| * Adds a {@link ResourceFile} to the folder. |
| * @param file The {@link ResourceFile}. |
| */ |
| @VisibleForTesting(visibility=Visibility.PROTECTED) |
| public void addFile(ResourceFile file) { |
| if (mFiles == null) { |
| int initialSize = 16; |
| if (mRepository.isFrameworkRepository()) { |
| String name = mFolder.getName(); |
| // Pick some reasonable initial sizes for framework data structures |
| // since they are typically (a) large and (b) their sizes are roughly known |
| // in advance |
| switch (mType) { |
| case DRAWABLE: { |
| // See if it's one of the -mdpi, -hdpi etc folders which |
| // are large (~1250 items) |
| int index = name.indexOf('-'); |
| if (index == -1) { |
| initialSize = 230; // "drawable" folder |
| } else { |
| index = name.indexOf('-', index + 1); |
| if (index == -1) { |
| // One of the "drawable-<density>" folders |
| initialSize = 1260; |
| } else { |
| // "drawable-sw600dp-hdpi" etc |
| initialSize = 30; |
| } |
| } |
| break; |
| } |
| case LAYOUT: { |
| // The main layout folder has about ~185 layouts in it; |
| // the others are small |
| if (name.indexOf('-') == -1) { |
| initialSize = 200; |
| } |
| break; |
| } |
| case VALUES: { |
| if (name.indexOf('-') == -1) { |
| initialSize = 32; |
| } else { |
| initialSize = 4; |
| } |
| break; |
| } |
| case ANIM: initialSize = 85; break; |
| case COLOR: initialSize = 32; break; |
| case RAW: initialSize = 4; break; |
| default: |
| // Stick with the 16 default |
| break; |
| } |
| } |
| |
| mFiles = new ArrayList<ResourceFile>(initialSize); |
| mNames = new HashMap<String, ResourceFile>(initialSize, 2.0f); |
| } |
| |
| mFiles.add(file); |
| mNames.put(file.getFile().getName(), file); |
| } |
| |
| protected void removeFile(ResourceFile file, ScanningContext context) { |
| file.dispose(context); |
| mFiles.remove(file); |
| mNames.remove(file.getFile().getName()); |
| } |
| |
| protected void dispose(ScanningContext context) { |
| if (mFiles != null) { |
| for (ResourceFile file : mFiles) { |
| file.dispose(context); |
| } |
| |
| mFiles.clear(); |
| mNames.clear(); |
| } |
| } |
| |
| /** |
| * Returns the {@link IAbstractFolder} associated with this object. |
| */ |
| public IAbstractFolder getFolder() { |
| return mFolder; |
| } |
| |
| /** |
| * Returns the {@link ResourceFolderType} of this object. |
| */ |
| public ResourceFolderType getType() { |
| return mType; |
| } |
| |
| public ResourceRepository getRepository() { |
| return mRepository; |
| } |
| |
| /** |
| * Returns the list of {@link ResourceType}s generated by the files inside this folder. |
| */ |
| public Collection<ResourceType> getResourceTypes() { |
| ArrayList<ResourceType> list = new ArrayList<ResourceType>(); |
| |
| if (mFiles != null) { |
| for (ResourceFile file : mFiles) { |
| Collection<ResourceType> types = file.getResourceTypes(); |
| |
| // loop through those and add them to the main list, |
| // if they are not already present |
| for (ResourceType resType : types) { |
| if (list.indexOf(resType) == -1) { |
| list.add(resType); |
| } |
| } |
| } |
| } |
| |
| return list; |
| } |
| |
| @Override |
| public FolderConfiguration getConfiguration() { |
| return mConfiguration; |
| } |
| |
| /** |
| * Returns whether the folder contains a file with the given name. |
| * @param name the name of the file. |
| */ |
| public boolean hasFile(String name) { |
| if (mNames != null && mNames.containsKey(name)) { |
| return true; |
| } |
| |
| // Note: mNames.containsKey(name) is faster, but doesn't give the same result; this |
| // method seems to be called on this ResourceFolder before it has been processed, |
| // so we need to use the file system check instead: |
| return mFolder.hasFile(name); |
| } |
| |
| /** |
| * Returns the {@link ResourceFile} matching a {@link IAbstractFile} object. |
| * |
| * @param file The {@link IAbstractFile} object. |
| * @param context a context object with state for the current update, such |
| * as a place to stash errors encountered |
| * @return the {@link ResourceFile} or null if no match was found. |
| */ |
| private ResourceFile getFile(IAbstractFile file, ScanningContext context) { |
| assert mFolder.equals(file.getParentFolder()); |
| |
| if (mNames != null) { |
| ResourceFile resFile = mNames.get(file.getName()); |
| if (resFile != null) { |
| return resFile; |
| } |
| } |
| |
| // If the file actually exists, the resource folder may not have been |
| // scanned yet; add it lazily |
| if (file.exists()) { |
| ResourceFile resFile = createResourceFile(file); |
| resFile.load(context); |
| addFile(resFile); |
| return resFile; |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Returns the {@link ResourceFile} matching a given name. |
| * @param filename The name of the file to return. |
| * @return the {@link ResourceFile} or <code>null</code> if no match was found. |
| */ |
| public ResourceFile getFile(String filename) { |
| if (mNames != null) { |
| ResourceFile resFile = mNames.get(filename); |
| if (resFile != null) { |
| return resFile; |
| } |
| } |
| |
| // If the file actually exists, the resource folder may not have been |
| // scanned yet; add it lazily |
| IAbstractFile file = mFolder.getFile(filename); |
| if (file != null && file.exists()) { |
| ResourceFile resFile = createResourceFile(file); |
| resFile.load(new ScanningContext(mRepository)); |
| addFile(resFile); |
| return resFile; |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Returns whether a file in the folder is generating a resource of a specified type. |
| * @param type The {@link ResourceType} being looked up. |
| */ |
| public boolean hasResources(ResourceType type) { |
| // Check if the folder type is able to generate resource of the type that was asked. |
| // this is a first check to avoid going through the files. |
| List<ResourceFolderType> folderTypes = FolderTypeRelationship.getRelatedFolders(type); |
| |
| boolean valid = false; |
| for (ResourceFolderType rft : folderTypes) { |
| if (rft == mType) { |
| valid = true; |
| break; |
| } |
| } |
| |
| if (valid) { |
| if (mFiles != null) { |
| for (ResourceFile f : mFiles) { |
| if (f.hasResources(type)) { |
| return true; |
| } |
| } |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| public String toString() { |
| return mFolder.toString(); |
| } |
| } |