blob: 50e09d2900decaecdf7ab6d8c88047a8049ddcef [file] [log] [blame]
/*
* Copyright (C) 2013 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.tools.idea.rendering;
import com.android.annotations.VisibleForTesting;
import com.google.common.collect.*;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.android.facet.ResourceFolderManager;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* Resource repository for a single module (which can possibly have multiple resource folders)
*/
public final class ModuleResourceRepository extends MultiResourceRepository {
private final AndroidFacet myFacet;
private ModuleResourceRepository(@NotNull AndroidFacet facet,
@NotNull List<? extends LocalResourceRepository> delegates) {
super(facet.getModule().getName(), delegates);
myFacet = facet;
}
/**
* Returns the Android resources specific to this module, not including resources in any
* dependent modules or any AAR libraries
*
* @param module the module to look up resources for
* @param createIfNecessary if true, create the app resources if necessary, otherwise only return if already computed
* @return the resource repository
*/
@Nullable
public static LocalResourceRepository getModuleResources(@NotNull Module module, boolean createIfNecessary) {
AndroidFacet facet = AndroidFacet.getInstance(module);
if (facet != null) {
return facet.getModuleResources(createIfNecessary);
}
return null;
}
/**
* Returns the Android resources specific to this module, not including resources in any
* dependent modules or any AAR libraries
*
* @param facet the module facet to look up resources for
* @param createIfNecessary if true, create the app resources if necessary, otherwise only return if already computed
* @return the resource repository
*/
@Contract("!null, true -> !null")
@Nullable
public static LocalResourceRepository getModuleResources(@NotNull AndroidFacet facet, boolean createIfNecessary) {
return facet.getModuleResources(createIfNecessary);
}
/**
* Creates a new resource repository for the given module, <b>not</b> including its dependent modules.
*
* @param facet the facet for the module
* @return the resource repository
*/
@NotNull
public static LocalResourceRepository create(@NotNull final AndroidFacet facet) {
boolean gradleProject = facet.requiresAndroidModel();
if (!gradleProject) {
// Always just a single resource folder: simple
VirtualFile primaryResourceDir = facet.getPrimaryResourceDir();
if (primaryResourceDir == null) {
return new EmptyRepository();
}
return ResourceFolderRegistry.get(facet, primaryResourceDir);
}
ResourceFolderManager folderManager = facet.getResourceFolderManager();
List<VirtualFile> resourceDirectories = folderManager.getFolders();
List<LocalResourceRepository> resources = Lists.newArrayListWithExpectedSize(resourceDirectories.size());
for (VirtualFile resourceDirectory : resourceDirectories) {
ResourceFolderRepository repository = ResourceFolderRegistry.get(facet, resourceDirectory);
resources.add(repository);
}
DynamicResourceValueRepository dynamicResources = DynamicResourceValueRepository.create(facet);
resources.add(dynamicResources);
// We create a ModuleResourceRepository even if resources.isEmpty(), because we may
// dynamically add children to it later (in updateRoots)
final ModuleResourceRepository repository = new ModuleResourceRepository(facet, resources);
// If the model is not yet ready, we may get an incomplete set of resource
// directories, so in that case update the repository when the model is available.
folderManager.addListener(new ResourceFolderManager.ResourceFolderListener() {
@Override
public void resourceFoldersChanged(@NotNull AndroidFacet facet, @NotNull List<VirtualFile> folders,
@NotNull Collection<VirtualFile> added, @NotNull Collection<VirtualFile> removed) {
repository.updateRoots();
}
});
return repository;
}
private void updateRoots() {
updateRoots(myFacet.getResourceFolderManager().getFolders());
}
@VisibleForTesting
void updateRoots(List<VirtualFile> resourceDirectories) {
// Non-folder repositories: Always kept last in the list
List<LocalResourceRepository> other = null;
// Compute current roots
Map<VirtualFile, ResourceFolderRepository> map = Maps.newHashMap();
for (LocalResourceRepository repository : myChildren) {
if (repository instanceof ResourceFolderRepository) {
ResourceFolderRepository folderRepository = (ResourceFolderRepository)repository;
VirtualFile resourceDir = folderRepository.getResourceDir();
map.put(resourceDir, folderRepository);
} else {
assert repository instanceof DynamicResourceValueRepository;
if (other == null) {
other = Lists.newArrayList();
}
other.add(repository);
}
}
// Compute new resource directories (it's possible for just the order to differ, or
// for resource dirs to have been added and/or removed)
Set<VirtualFile> newDirs = Sets.newHashSet(resourceDirectories);
List<LocalResourceRepository> resources = Lists.newArrayListWithExpectedSize(newDirs.size() + (other != null ? other.size() : 0));
for (VirtualFile dir : resourceDirectories) {
ResourceFolderRepository repository = map.get(dir);
if (repository == null) {
repository = ResourceFolderRegistry.get(myFacet, dir);
}
else {
map.remove(dir);
}
resources.add(repository);
}
if (other != null) {
resources.addAll(other);
}
if (resources.equals(myChildren)) {
// Nothing changed (including order); nothing to do
assert map.isEmpty(); // shouldn't have created any new ones
return;
}
for (ResourceFolderRepository removed : map.values()) {
removed.removeParent(this);
}
setChildren(resources);
}
/**
* For testing: creates a project with a given set of resource roots; this allows tests to check
* this repository without creating a gradle project setup etc
*/
@VisibleForTesting
@NotNull
public static ModuleResourceRepository createForTest(@NotNull final AndroidFacet facet, @NotNull List<VirtualFile> resourceDirectories) {
assert ApplicationManager.getApplication().isUnitTestMode();
List<ResourceFolderRepository> resources = Lists.newArrayListWithExpectedSize(resourceDirectories.size());
for (VirtualFile resourceDirectory : resourceDirectories) {
ResourceFolderRepository repository = ResourceFolderRegistry.get(facet, resourceDirectory);
resources.add(repository);
}
return new ModuleResourceRepository(facet, resources);
}
private static class EmptyRepository extends MultiResourceRepository {
public EmptyRepository() {
super("", Collections.<LocalResourceRepository>emptyList());
}
@Override
protected void setChildren(@NotNull List<? extends LocalResourceRepository> children) {
myChildren = children;
}
}
}