blob: 226c834413e737d8ccec42b417068986a66b36d8 [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 org.jetbrains.android.facet;
import com.android.builder.model.*;
import com.android.tools.idea.model.AndroidModel;
import com.google.common.base.Function;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.containers.HashSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import static com.android.SdkConstants.ANDROID_MANIFEST_XML;
/**
* Like {@link SourceProvider}, but for IntelliJ, which means it provides
* {@link VirtualFile} references rather than {@link File} references.
*
* @see AndroidSourceType
*/
public abstract class IdeaSourceProvider {
private IdeaSourceProvider() {
}
@NotNull
public static IdeaSourceProvider create(@NotNull SourceProvider provider) {
return new IdeaSourceProvider.Gradle(provider);
}
@NotNull
private static List<IdeaSourceProvider> createAll(@NotNull List<SourceProvider> providers) {
List<IdeaSourceProvider> ideaProviders = Lists.newArrayList();
for (SourceProvider provider : providers) {
ideaProviders.add(create(provider));
}
return ideaProviders;
}
@NotNull
public static IdeaSourceProvider create(@NotNull final AndroidFacet facet) {
return new IdeaSourceProvider.Legacy(facet);
}
@NotNull
public abstract String getName();
@Nullable
public abstract VirtualFile getManifestFile();
@NotNull
public abstract Collection<VirtualFile> getJavaDirectories();
@NotNull
public abstract Collection<VirtualFile> getResourcesDirectories();
@NotNull
public abstract Collection<VirtualFile> getAidlDirectories();
@NotNull
public abstract Collection<VirtualFile> getRenderscriptDirectories();
@NotNull
public abstract Collection<VirtualFile> getJniDirectories();
@NotNull
public abstract Collection<VirtualFile> getJniLibsDirectories();
@NotNull
public abstract Collection<VirtualFile> getResDirectories();
@NotNull
public abstract Collection<VirtualFile> getAssetsDirectories();
/** {@linkplain IdeaSourceProvider} for a Gradle projects */
private static class Gradle extends IdeaSourceProvider {
private final SourceProvider myProvider;
private VirtualFile myManifestFile;
private File myManifestIoFile;
private Gradle(@NotNull SourceProvider provider) {
myProvider = provider;
}
@NotNull
@Override
public String getName() {
return myProvider.getName();
}
@Nullable
@Override
public VirtualFile getManifestFile() {
File manifestFile = myProvider.getManifestFile();
if (myManifestFile == null || !FileUtil.filesEqual(manifestFile, myManifestIoFile)) {
myManifestIoFile = manifestFile;
myManifestFile = VfsUtil.findFileByIoFile(manifestFile, true);
}
return myManifestFile;
}
/** Convert a set of IO files into a set of equivalent virtual files */
private static Collection<VirtualFile> convertFileSet(@NotNull Collection<File> fileSet) {
Collection<VirtualFile> result = Sets.newHashSetWithExpectedSize(fileSet.size());
LocalFileSystem fileSystem = LocalFileSystem.getInstance();
for (File file : fileSet) {
VirtualFile virtualFile = fileSystem.findFileByIoFile(file);
if (virtualFile != null) {
result.add(virtualFile);
}
}
return result;
}
@NotNull
@Override
public Collection<VirtualFile> getJavaDirectories() {
return convertFileSet(myProvider.getJavaDirectories());
}
@NotNull
@Override
public Collection<VirtualFile> getResourcesDirectories() {
return convertFileSet(myProvider.getResourcesDirectories());
}
@NotNull
@Override
public Collection<VirtualFile> getAidlDirectories() {
return convertFileSet(myProvider.getAidlDirectories());
}
@NotNull
@Override
public Collection<VirtualFile> getRenderscriptDirectories() {
return convertFileSet(myProvider.getRenderscriptDirectories());
}
@NotNull
@Override
public Collection<VirtualFile> getJniDirectories() {
// Even though the model has separate methods to get the C and Cpp directories,
// they both return the same set of folders. So we combine them here.
Set<VirtualFile> jniDirectories = Sets.newHashSet();
jniDirectories.addAll(convertFileSet(myProvider.getCDirectories()));
jniDirectories.addAll(convertFileSet(myProvider.getCppDirectories()));
return jniDirectories;
}
@NotNull
@Override
public Collection<VirtualFile> getJniLibsDirectories() {
return convertFileSet(myProvider.getJniLibsDirectories());
}
@NotNull
@Override
public Collection<VirtualFile> getResDirectories() {
// TODO: Perform some caching; this method gets called a lot!
return convertFileSet(myProvider.getResDirectories());
}
@NotNull
@Override
public Collection<VirtualFile> getAssetsDirectories() {
return convertFileSet(myProvider.getAssetsDirectories());
}
/**
* Compares another source provider with this for equality. Returns true if the specified object is also a Gradle source provider,
* has the same name, and the same set of source locations.
*/
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Gradle that = (Gradle)o;
if (!myProvider.getName().equals(that.getName())) return false;
if (!myProvider.getManifestFile().getPath().equals(that.myProvider.getManifestFile().getPath())) return false;
return true;
}
/**
* Returns the hash code for this source provider. The hash code simply provides the hash of the manifest file's location,
* but this follows the required contract that if two source providers are equal, their hash codes will be the same.
*/
@Override
public int hashCode() {
return myProvider.getManifestFile().getPath().hashCode();
}
}
/** {@linkplain IdeaSourceProvider} for a legacy (non-Gradle) Android project */
private static class Legacy extends IdeaSourceProvider {
@NotNull private final AndroidFacet myFacet;
private Legacy(@NotNull AndroidFacet facet) {
myFacet = facet;
}
@NotNull
@Override
public String getName() {
return "";
}
@Nullable
@Override
public VirtualFile getManifestFile() {
Module module = myFacet.getModule();
VirtualFile file = AndroidRootUtil.getFileByRelativeModulePath(module, myFacet.getProperties().MANIFEST_FILE_RELATIVE_PATH, true);
if (file != null) {
return file;
}
// Not calling AndroidRootUtil.getMainContentRoot(myFacet) because that method can
// recurse into this same method if it can't find a content root. (This scenario
// applies when we're looking for manifests in for example a temporary file system,
// as tested by ResourceTypeInspectionTest#testLibraryRevocablePermission)
VirtualFile[] contentRoots = ModuleRootManager.getInstance(module).getContentRoots();
if (contentRoots.length == 1) {
return contentRoots[0].findChild(ANDROID_MANIFEST_XML);
}
return null;
}
@NotNull
@Override
public Collection<VirtualFile> getJavaDirectories() {
Module module = myFacet.getModule();
Collection<VirtualFile> dirs = new HashSet<VirtualFile>();
Collections.addAll(dirs, ModuleRootManager.getInstance(module).getContentRoots());
return dirs;
}
@NotNull
@Override
public Collection<VirtualFile> getResourcesDirectories() {
return Collections.emptySet();
}
@NotNull
@Override
public Collection<VirtualFile> getAidlDirectories() {
final VirtualFile dir = AndroidRootUtil.getAidlGenDir(myFacet);
assert dir != null;
return Collections.singleton(dir);
}
@NotNull
@Override
public Collection<VirtualFile> getRenderscriptDirectories() {
final VirtualFile dir = AndroidRootUtil.getRenderscriptGenDir(myFacet);
assert dir != null;
return Collections.singleton(dir);
}
@NotNull
@Override
public Collection<VirtualFile> getJniDirectories() {
return Collections.emptySet();
}
@NotNull
@Override
public Collection<VirtualFile> getJniLibsDirectories() {
return Collections.emptySet();
}
@NotNull
@Override
public Collection<VirtualFile> getResDirectories() {
String resRelPath = myFacet.getProperties().RES_FOLDER_RELATIVE_PATH;
final VirtualFile dir = AndroidRootUtil.getFileByRelativeModulePath(myFacet.getModule(), resRelPath, true);
if (dir != null) {
return Collections.singleton(dir);
} else {
return Collections.emptySet();
}
}
@NotNull
@Override
public Collection<VirtualFile> getAssetsDirectories() {
final VirtualFile dir = AndroidRootUtil.getAssetsDir(myFacet);
assert dir != null;
return Collections.singleton(dir);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Legacy that = (Legacy)o;
return myFacet.equals(that.myFacet);
}
@Override
public int hashCode() {
return myFacet.hashCode();
}
}
/**
* Returns a list of source providers, in the overlay order (meaning that later providers
* override earlier providers when they redefine resources) for the currently selected variant.
* <p>
* Note that the list will never be empty; there is always at least one source provider.
* <p>
* The overlay source order is defined by the Android Gradle plugin.
*/
@NotNull
public static List<IdeaSourceProvider> getCurrentSourceProviders(@NotNull AndroidFacet facet) {
if (!facet.requiresAndroidModel()) {
return Collections.singletonList(facet.getMainIdeaSourceProvider());
}
AndroidModel androidModel = facet.getAndroidModel();
if (androidModel != null) {
return createAll(androidModel.getActiveSourceProviders());
}
return Collections.emptyList();
}
@NotNull
public static List<IdeaSourceProvider> getCurrentTestSourceProviders(@NotNull AndroidFacet facet) {
if (!facet.requiresAndroidModel()) {
return Collections.emptyList();
}
AndroidModel androidModel = facet.getAndroidModel();
if (androidModel != null) {
return createAll(androidModel.getTestSourceProviders());
}
return Collections.emptyList();
}
private Collection<VirtualFile> getAllSourceFolders() {
List<VirtualFile> srcDirectories = Lists.newArrayList();
srcDirectories.addAll(getJavaDirectories());
srcDirectories.addAll(getResDirectories());
srcDirectories.addAll(getAidlDirectories());
srcDirectories.addAll(getRenderscriptDirectories());
srcDirectories.addAll(getAssetsDirectories());
srcDirectories.addAll(getJniDirectories());
srcDirectories.addAll(getJniLibsDirectories());
return srcDirectories;
}
private static Collection<File> getAllSourceFolders(SourceProvider provider) {
List<File> srcDirectories = Lists.newArrayList();
srcDirectories.addAll(provider.getJavaDirectories());
srcDirectories.addAll(provider.getResDirectories());
srcDirectories.addAll(provider.getAidlDirectories());
srcDirectories.addAll(provider.getRenderscriptDirectories());
srcDirectories.addAll(provider.getAssetsDirectories());
srcDirectories.addAll(provider.getCDirectories());
srcDirectories.addAll(provider.getCppDirectories());
srcDirectories.addAll(provider.getJniLibsDirectories());
return srcDirectories;
}
/**
* Returns true iff this SourceProvider provides the source folder that contains the given file.
*/
public boolean containsFile(@NotNull VirtualFile file) {
Collection<VirtualFile> srcDirectories = getAllSourceFolders();
if (file.equals(getManifestFile())) {
return true;
}
for (VirtualFile container : srcDirectories) {
if (!container.exists()) {
continue;
}
if (VfsUtilCore.isAncestor(container, file, false /* allow them to be the same */)) {
return true;
}
// Check the flavor root directories
if (file.equals(container.getParent())) {
return true;
}
}
return false;
}
/**
* Returns true if this SourceProvider has one or more source folders contained by (or equal to)
* the given folder.
*/
public static boolean isContainedBy(@NotNull SourceProvider provider, @NotNull File targetFolder) {
Collection<File> srcDirectories = getAllSourceFolders(provider);
for (File container : srcDirectories) {
if (FileUtil.isAncestor(targetFolder, container, false)) {
return true;
}
if (!container.exists()) {
continue;
}
if (VfsUtilCore.isAncestor(targetFolder, container, false /* allow them to be the same */)) {
return true;
}
}
return false;
}
/**
* Returns true iff this SourceProvider provides the source folder that contains the given file.
*/
public static boolean containsFile(@NotNull SourceProvider provider, @NotNull File file) {
Collection<File> srcDirectories = getAllSourceFolders(provider);
if (FileUtil.filesEqual(provider.getManifestFile(), file)) {
return true;
}
for (File container : srcDirectories) {
// Check the flavor root directories
File parent = container.getParentFile();
if (parent != null && parent.isDirectory() && FileUtil.filesEqual(parent, file)) {
return true;
}
// Don't do ancestry checking if this file doesn't exist
if (!container.exists()) {
continue;
}
if (VfsUtilCore.isAncestor(container, file, false /* allow them to be the same */)) {
return true;
}
}
return false;
}
/**
* Returns true if this SourceProvider has one or more source folders contained by (or equal to)
* the given folder.
*/
public boolean isContainedBy(@NotNull VirtualFile targetFolder) {
Collection<VirtualFile> srcDirectories = getAllSourceFolders();
for (VirtualFile container : srcDirectories) {
if (!container.exists()) {
continue;
}
if (VfsUtilCore.isAncestor(targetFolder, container, false /* allow them to be the same */)) {
return true;
}
}
return false;
}
/**
* Returns an iterable of all source providers, for the given facet,
* in the overlay order (meaning that later providers
* override earlier providers when they redefine resources.)
* <p>
* Note that the list will never be empty; there is always at least one source provider.
* <p>
* The overlay source order is defined by the Android Gradle plugin.
*/
@NotNull
public static List<SourceProvider> getAllSourceProviders(@NotNull AndroidFacet facet) {
if (!facet.requiresAndroidModel() || facet.getAndroidModel() == null) {
return Collections.singletonList(facet.getMainSourceProvider());
}
return facet.getAndroidModel().getAllSourceProviders();
}
/**
* Returns a list of all IDEA source providers, for the given facet, in the overlay order
* (meaning that later providers override earlier providers when they redefine resources.)
* <p>
* Note that the list will never be empty; there is always at least one source provider.
* <p>
* The overlay source order is defined by the Android Gradle plugin.
*
* This method should be used when only on-disk source sets are required. It will return
* empty source sets for all other source providers (since VirtualFiles MUST exist on disk).
*/
@NotNull
public static List<IdeaSourceProvider> getAllIdeaSourceProviders(@NotNull AndroidFacet facet) {
if (!facet.requiresAndroidModel() || facet.getAndroidModel() == null) {
return Collections.singletonList(facet.getMainIdeaSourceProvider());
}
return createAll(getAllSourceProviders(facet));
}
/**
* Returns a list of all IDEA source providers that contain, or are contained by, the given file.
* For example, with the file structure:
* <pre>
* src
* main
* aidl
* myfile.aidl
* free
* aidl
* myoverlay.aidl
* </pre>
*
* With target file == "myoverlay.aidl" the returned list would be ['free'], but if target file == "src",
* the returned list would be ['main', 'free'] since both of those source providers have source folders which
* are descendants of "src."
*/
@NotNull
public static List<IdeaSourceProvider> getIdeaSourceProvidersForFile(@NotNull AndroidFacet facet,
@Nullable VirtualFile targetFolder,
@Nullable IdeaSourceProvider defaultIdeaSourceProvider) {
List<IdeaSourceProvider> sourceProviderList = Lists.newArrayList();
if (targetFolder != null) {
// Add source providers that contain the file (if any) and any that have files under the given folder
for (IdeaSourceProvider provider : getAllIdeaSourceProviders(facet)) {
if (provider.containsFile(targetFolder) || provider.isContainedBy(targetFolder)) {
sourceProviderList.add(provider);
}
}
}
if (sourceProviderList.isEmpty() && defaultIdeaSourceProvider != null) {
sourceProviderList.add(defaultIdeaSourceProvider);
}
return sourceProviderList;
}
/**
* Returns a list of all source providers that contain, or are contained by, the given file.
* For example, with the file structure:
* <pre>
* src
* main
* aidl
* myfile.aidl
* free
* aidl
* myoverlay.aidl
* </pre>
*
* With target file == "myoverlay.aidl" the returned list would be ['free'], but if target file == "src",
* the returned list would be ['main', 'free'] since both of those source providers have source folders which
* are descendants of "src."
*/
@NotNull
public static List<SourceProvider> getSourceProvidersForFile(@NotNull AndroidFacet facet, @Nullable VirtualFile targetFolder,
@Nullable SourceProvider defaultSourceProvider) {
List<SourceProvider> sourceProviderList = Lists.newArrayList();
if (targetFolder != null) {
File targetIoFolder = VfsUtilCore.virtualToIoFile(targetFolder);
// Add source providers that contain the file (if any) and any that have files under the given folder
for (SourceProvider provider : getAllSourceProviders(facet)) {
if (containsFile(provider, targetIoFolder) || isContainedBy(provider, targetIoFolder)) {
sourceProviderList.add(provider);
}
}
}
if (sourceProviderList.isEmpty() && defaultSourceProvider != null) {
sourceProviderList.add(defaultSourceProvider);
}
return sourceProviderList;
}
/** Returns true if the given candidate file is a manifest file in the given module */
public static boolean isManifestFile(@NotNull AndroidFacet facet, @Nullable VirtualFile candidate) {
if (candidate == null) {
return false;
}
if (facet.requiresAndroidModel()) {
for (IdeaSourceProvider provider : getCurrentSourceProviders(facet)) {
if (candidate.equals(provider.getManifestFile())) {
return true;
}
}
return false;
} else {
return candidate.equals(facet.getMainIdeaSourceProvider().getManifestFile());
}
}
/** Returns the manifest files in the given module */
@NotNull
public static List<VirtualFile> getManifestFiles(@NotNull AndroidFacet facet) {
VirtualFile main = facet.getMainIdeaSourceProvider().getManifestFile();
if (!facet.requiresAndroidModel()) {
return main != null ? Collections.singletonList(main) : Collections.<VirtualFile>emptyList();
}
List<VirtualFile> files = Lists.newArrayList();
if (main != null) {
files.add(main);
}
for (IdeaSourceProvider provider : getCurrentSourceProviders(facet)) {
VirtualFile manifest = provider.getManifestFile();
if (manifest != null) {
files.add(manifest);
}
}
return files;
}
public static Function<IdeaSourceProvider, List<VirtualFile>> MANIFEST_PROVIDER = new Function<IdeaSourceProvider, List<VirtualFile>>() {
@Override
public List<VirtualFile> apply(IdeaSourceProvider provider) {
VirtualFile manifestFile = provider.getManifestFile();
return manifestFile == null ? Collections.<VirtualFile>emptyList() : Collections.singletonList(manifestFile);
}
};
public static Function<IdeaSourceProvider, List<VirtualFile>> RES_PROVIDER = new Function<IdeaSourceProvider, List<VirtualFile>>() {
@Override
public List<VirtualFile> apply(IdeaSourceProvider provider) {
return Lists.newArrayList(provider.getResDirectories());
}
};
public static Function<IdeaSourceProvider, List<VirtualFile>> JAVA_PROVIDER = new Function<IdeaSourceProvider, List<VirtualFile>>() {
@Override
public List<VirtualFile> apply(IdeaSourceProvider provider) {
return Lists.newArrayList(provider.getJavaDirectories());
}
};
public static Function<IdeaSourceProvider, List<VirtualFile>> RESOURCES_PROVIDER = new Function<IdeaSourceProvider, List<VirtualFile>>() {
@Override
public List<VirtualFile> apply(IdeaSourceProvider provider) {
return Lists.newArrayList(provider.getResourcesDirectories());
}
};
public static Function<IdeaSourceProvider, List<VirtualFile>> AIDL_PROVIDER = new Function<IdeaSourceProvider, List<VirtualFile>>() {
@Override
public List<VirtualFile> apply(IdeaSourceProvider provider) {
return Lists.newArrayList(provider.getAidlDirectories());
}
};
public static Function<IdeaSourceProvider, List<VirtualFile>> JNI_PROVIDER = new Function<IdeaSourceProvider, List<VirtualFile>>() {
@Override
public List<VirtualFile> apply(IdeaSourceProvider provider) {
return Lists.newArrayList(provider.getJniDirectories());
}
};
public static Function<IdeaSourceProvider, List<VirtualFile>> JNI_LIBS_PROVIDER = new Function<IdeaSourceProvider, List<VirtualFile>>() {
@Override
public List<VirtualFile> apply(IdeaSourceProvider provider) {
return Lists.newArrayList(provider.getJniLibsDirectories());
}
};
public static Function<IdeaSourceProvider, List<VirtualFile>> ASSETS_PROVIDER = new Function<IdeaSourceProvider, List<VirtualFile>>() {
@Override
public List<VirtualFile> apply(IdeaSourceProvider provider) {
return Lists.newArrayList(provider.getAssetsDirectories());
}
};
public static Function<IdeaSourceProvider, List<VirtualFile>> RS_PROVIDER = new Function<IdeaSourceProvider, List<VirtualFile>>() {
@Override
public List<VirtualFile> apply(IdeaSourceProvider provider) {
return Lists.newArrayList(provider.getRenderscriptDirectories());
}
};
}