blob: d6464c8078680e52a23cbdc244d4e2e1bead3dfa [file] [log] [blame]
/*
* 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();
}
}