blob: 49e68dd4aead1ba20b75c37b95481c11db65ef42 [file] [log] [blame]
/*
* Copyright 2000-2010 JetBrains s.r.o.
*
* 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.SdkConstants;
import com.android.builder.model.AndroidArtifact;
import com.android.builder.model.AndroidArtifactOutput;
import com.android.builder.model.Variant;
import com.android.tools.idea.AndroidPsiUtils;
import com.android.tools.idea.gradle.IdeaAndroidProject;
import com.android.tools.idea.model.AndroidModel;
import com.intellij.ide.highlighter.ArchiveFileType;
import com.intellij.lang.properties.psi.PropertiesFile;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.roots.*;
import com.intellij.openapi.roots.libraries.Library;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.vfs.JarFileSystem;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiFile;
import com.intellij.util.containers.OrderedSet;
import org.jetbrains.android.compiler.AndroidDexCompiler;
import org.jetbrains.android.sdk.AndroidPlatform;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.util.*;
import static com.android.tools.idea.gradle.util.GradleUtil.getOutput;
import static com.android.tools.idea.gradle.util.PropertiesUtil.getProperties;
import static com.intellij.openapi.util.io.FileUtil.getRelativePath;
import static com.intellij.openapi.util.io.FileUtil.*;
import static com.intellij.openapi.vfs.VfsUtilCore.isAncestor;
import static com.intellij.openapi.vfs.VfsUtilCore.*;
import static org.jetbrains.android.compiler.AndroidCompileUtil.getOutputPackage;
import static org.jetbrains.android.maven.AndroidMavenUtil.isMavenizedModule;
import static org.jetbrains.android.sdk.AndroidSdkUtils.isAndroidSdk;
import static org.jetbrains.android.util.AndroidCommonUtils.ANNOTATIONS_JAR_RELATIVE_PATH;
import static org.jetbrains.android.util.AndroidCommonUtils.CLASSES_JAR_FILE_NAME;
/**
* @author Eugene.Kudelevsky
*/
public class AndroidRootUtil {
private static final Logger LOG = Logger.getInstance("#org.jetbrains.android.facet.AndroidRootUtil");
@NonNls public static final String DEFAULT_PROPERTIES_FILE_NAME = "default.properties";
private AndroidRootUtil() {
}
/**
* Returns the main manifest file of the module.
*
* @deprecated Modules can have multiple manifests. If you really want the main manifest
* of the module, use {@link #getPrimaryManifestFile(AndroidFacet)}, but to test if
* a given file is a manifest, or to process all of them, use
* {@link IdeaSourceProvider#isManifestFile(AndroidFacet, VirtualFile)} or
* {@link IdeaSourceProvider#getManifestFiles(AndroidFacet)}.
*/
@Nullable
@Deprecated
public static VirtualFile getManifestFile(@NotNull AndroidFacet facet) {
if (facet.requiresAndroidModel()) {
return facet.getMainIdeaSourceProvider().getManifestFile();
}
return getFileByRelativeModulePath(facet.getModule(), facet.getProperties().MANIFEST_FILE_RELATIVE_PATH, true);
}
/**
* Returns the main manifest file of the module. Note that a module can have multiple
* manifests so only use this if you really know you need to only look at the main manifests.
* To look at all manifests, use {@link IdeaSourceProvider#getManifestFiles(AndroidFacet)}.
*/
@Nullable
public static VirtualFile getPrimaryManifestFile(@NotNull AndroidFacet facet) {
return facet.getMainIdeaSourceProvider().getManifestFile();
}
@Nullable
public static VirtualFile getCustomManifestFileForCompiler(@NotNull AndroidFacet facet) {
return getFileByRelativeModulePath(facet.getModule(), facet.getProperties().CUSTOM_COMPILER_MANIFEST, false);
}
// DO NOT get PSI or DOM from this file, because it may be excluded (f.ex. it can be in /target/ directory)
@Nullable
public static VirtualFile getManifestFileForCompiler(@NotNull AndroidFacet facet) {
return facet.getProperties().USE_CUSTOM_COMPILER_MANIFEST ? getCustomManifestFileForCompiler(facet) : getPrimaryManifestFile(facet);
}
/**
* @deprecated You must use {@link AndroidFacet#getAllResourceDirectories()} instead
*/
@Deprecated
@Nullable
public static VirtualFile getResourceDir(@NotNull AndroidFacet facet) {
return facet.getPrimaryResourceDir();
}
@Nullable
private static String suggestResourceDirPath(@NotNull AndroidFacet facet) {
Module module = facet.getModule();
VirtualFile[] contentRoots = ModuleRootManager.getInstance(module).getContentRoots();
if (contentRoots.length == 0) {
return null;
}
VirtualFile root = contentRoots[0];
if (contentRoots.length > 1) {
String moduleFileParentDirPath = toSystemIndependentName(new File(module.getModuleFilePath()).getParent());
VirtualFile moduleFileParentDir = LocalFileSystem.getInstance().findFileByPath(moduleFileParentDirPath);
if (moduleFileParentDir != null) {
for (VirtualFile contentRoot : contentRoots) {
if (Comparing.equal(contentRoot, moduleFileParentDir)) {
root = contentRoot;
}
}
}
}
return root.getPath() + facet.getProperties().RES_FOLDER_RELATIVE_PATH;
}
@Nullable
public static String getResourceDirPath(@NotNull AndroidFacet facet) {
VirtualFile resourceDir = getResourceDir(facet);
return resourceDir != null ? resourceDir.getPath() : suggestResourceDirPath(facet);
}
@Nullable
public static VirtualFile getFileByRelativeModulePath(Module module, String relativePath, boolean lookInContentRoot) {
if (relativePath == null || relativePath.length() == 0) {
return null;
}
VirtualFile[] contentRoots = ModuleRootManager.getInstance(module).getContentRoots();
String moduleDirPath = new File(module.getModuleFilePath()).getParent();
if (moduleDirPath != null) {
String absPath = toSystemIndependentName(moduleDirPath + relativePath);
VirtualFile file = LocalFileSystem.getInstance().findFileByPath(absPath);
if (file != null) {
return file;
}
}
if (lookInContentRoot) {
for (VirtualFile contentRoot : contentRoots) {
String absPath = toSystemIndependentName(contentRoot.getPath() + relativePath);
VirtualFile file = LocalFileSystem.getInstance().findFileByPath(absPath);
if (file != null) {
return file;
}
}
}
return null;
}
@Nullable
public static VirtualFile getAssetsDir(@NotNull AndroidFacet facet) {
return getFileByRelativeModulePath(facet.getModule(), facet.getProperties().ASSETS_FOLDER_RELATIVE_PATH, false);
}
@Nullable
public static VirtualFile getLibsDir(@NotNull AndroidFacet facet) {
return getFileByRelativeModulePath(facet.getModule(), facet.getProperties().LIBS_FOLDER_RELATIVE_PATH, false);
}
@Nullable
public static VirtualFile getAidlGenDir(@NotNull AndroidFacet facet) {
String genPath = getAidlGenSourceRootPath(facet);
return genPath != null ? LocalFileSystem.getInstance().findFileByPath(genPath) : null;
}
@Nullable
public static VirtualFile getAaptGenDir(@NotNull AndroidFacet facet) {
String genPath = getAptGenSourceRootPath(facet);
return genPath != null ? LocalFileSystem.getInstance().findFileByPath(genPath) : null;
}
@Nullable
public static VirtualFile getRenderscriptGenDir(@NotNull AndroidFacet facet) {
String path = getRenderscriptGenSourceRootPath(facet);
return path != null ? LocalFileSystem.getInstance().findFileByPath(path) : null;
}
@Nullable
public static VirtualFile getBuildconfigGenDir(@NotNull AndroidFacet facet) {
String path = getBuildconfigGenSourceRootPath(facet);
return path != null ? LocalFileSystem.getInstance().findFileByPath(path) : null;
}
// works even if there is no Android facet in a module
@Nullable
public static VirtualFile getStandardGenDir(@NotNull Module module) {
return getFileByRelativeModulePath(module, '/' + SdkConstants.FD_GEN_SOURCES, false);
}
private static void collectClassFilesAndJars(@NotNull VirtualFile root,
@NotNull Set<VirtualFile> result,
@NotNull Set<VirtualFile> visited) {
if (!visited.add(root)) {
return;
}
for (VirtualFile child : root.getChildren()) {
if (child.exists()) {
if (child.isDirectory()) {
collectClassFilesAndJars(child, result, visited);
}
else if ("jar".equals(child.getExtension()) || "class".equals(child.getExtension())) {
if (child.getFileSystem() instanceof JarFileSystem) {
VirtualFile localFile = JarFileSystem.getInstance().getVirtualFileForJar(child);
if (localFile != null) {
result.add(localFile);
}
}
else {
result.add(child);
}
}
}
}
}
private static void fillExternalLibrariesAndModules(@NotNull final Module module,
@NotNull final Set<VirtualFile> outputDirs,
@NotNull final Set<Module> visited,
@Nullable final Set<VirtualFile> libraries,
final boolean exportedLibrariesOnly,
final boolean recursive) {
if (!visited.add(module)) {
return;
}
ApplicationManager.getApplication().runReadAction(new Runnable() {
@Override
public void run() {
ModuleRootManager manager = ModuleRootManager.getInstance(module);
for (OrderEntry entry : manager.getOrderEntries()) {
if (!(entry instanceof ExportableOrderEntry) || ((ExportableOrderEntry)entry).getScope() != DependencyScope.COMPILE) {
continue;
}
if (libraries != null && entry instanceof LibraryOrderEntry) {
LibraryOrderEntry libraryOrderEntry = (LibraryOrderEntry)entry;
Library library = libraryOrderEntry.getLibrary();
if (library != null && (!exportedLibrariesOnly || libraryOrderEntry.isExported())) {
for (VirtualFile file : library.getFiles(OrderRootType.CLASSES)) {
if (!file.exists()) {
continue;
}
if (file.getFileType() instanceof ArchiveFileType) {
if (file.getFileSystem() instanceof JarFileSystem) {
VirtualFile localFile = JarFileSystem.getInstance().getVirtualFileForJar(file);
if (localFile != null) {
libraries.add(localFile);
}
}
else {
libraries.add(file);
}
}
else if (file.isDirectory() && !(file.getFileSystem() instanceof JarFileSystem)) {
collectClassFilesAndJars(file, libraries, new HashSet<VirtualFile>());
}
}
}
}
else if (entry instanceof ModuleOrderEntry) {
Module depModule = ((ModuleOrderEntry)entry).getModule();
if (depModule == null) {
continue;
}
AndroidFacet facet = AndroidFacet.getInstance(depModule);
boolean libraryProject = facet != null && facet.isLibraryProject();
CompilerModuleExtension extension = CompilerModuleExtension.getInstance(depModule);
if (extension != null) {
VirtualFile classDir = extension.getCompilerOutputPath();
if (libraryProject) {
VirtualFile tmpArtifactsDir = AndroidDexCompiler.getOutputDirectoryForDex(depModule);
if (tmpArtifactsDir != null) {
VirtualFile packedClassesJar = tmpArtifactsDir.findChild(CLASSES_JAR_FILE_NAME);
if (packedClassesJar != null) {
outputDirs.add(packedClassesJar);
}
}
}
// do not support android-app->android-app compile dependencies
else if (facet == null && !outputDirs.contains(classDir) && classDir != null && classDir.exists()) {
outputDirs.add(classDir);
}
}
if (recursive) {
fillExternalLibrariesAndModules(depModule, outputDirs, visited, libraries, !libraryProject || exportedLibrariesOnly, true);
}
}
}
}
});
}
@NotNull
public static List<VirtualFile> getExternalLibraries(Module module) {
Set<VirtualFile> files = new HashSet<VirtualFile>();
OrderedSet<VirtualFile> libs = new OrderedSet<VirtualFile>();
// In a module imported from Maven dependencies are transitive, so we don't need to traverse all dependency tree
// and compute all jars referred by library modules. Moreover it would be incorrect,
// because Maven has dependency resolving algorithm based on versioning
boolean recursive = !isMavenizedModule(module);
fillExternalLibrariesAndModules(module, files, new HashSet<Module>(), libs, false, recursive);
addAnnotationsJar(module, libs);
return libs;
}
private static void addAnnotationsJar(@NotNull Module module, @NotNull OrderedSet<VirtualFile> libs) {
Sdk sdk = ModuleRootManager.getInstance(module).getSdk();
if (sdk == null || !isAndroidSdk(sdk)) {
return;
}
String sdkHomePath = sdk.getHomePath();
if (sdkHomePath == null) {
return;
}
AndroidPlatform platform = AndroidPlatform.getInstance(module);
if (platform != null && platform.needToAddAnnotationsJarToClasspath()) {
String annotationsJarPath = toSystemIndependentName(sdkHomePath) + ANNOTATIONS_JAR_RELATIVE_PATH;
VirtualFile annotationsJar = LocalFileSystem.getInstance().findFileByPath(annotationsJarPath);
if (annotationsJar != null) {
libs.add(annotationsJar);
}
}
}
@NotNull
public static Set<VirtualFile> getDependentModules(@NotNull Module module, @NotNull VirtualFile moduleOutputDir) {
Set<VirtualFile> files = new HashSet<VirtualFile>();
fillExternalLibrariesAndModules(module, files, new HashSet<Module>(), null, false, true);
files.remove(moduleOutputDir);
return files;
}
@NotNull
public static VirtualFile[] getResourceOverlayDirs(@NotNull AndroidFacet facet) {
List<String> overlayFolders = facet.getProperties().RES_OVERLAY_FOLDERS;
List<VirtualFile> result = new ArrayList<VirtualFile>();
for (String overlayFolder : overlayFolders) {
VirtualFile overlayDir = getFileByRelativeModulePath(facet.getModule(), overlayFolder, true);
if (overlayDir != null) {
result.add(overlayDir);
}
}
return toVirtualFileArray(result);
}
@Nullable
public static String getModuleDirPath(Module module) {
String moduleFilePath = module.getModuleFilePath();
String moduleDirPath = new File(moduleFilePath).getParent();
if (moduleDirPath != null) {
moduleDirPath = toSystemIndependentName(moduleDirPath);
}
return moduleDirPath;
}
@Nullable
public static String getRenderscriptGenSourceRootPath(@NotNull AndroidFacet facet) {
// todo: return correct path for mavenized module when it'll be supported
return getAidlGenSourceRootPath(facet);
}
@Nullable
public static String getBuildconfigGenSourceRootPath(@NotNull AndroidFacet facet) {
// todo: return correct path for mavenized module when it'll be supported
return getAptGenSourceRootPath(facet);
}
@Nullable
public static VirtualFile getMainContentRoot(@NotNull AndroidFacet facet) {
Module module = facet.getModule();
VirtualFile[] contentRoots = ModuleRootManager.getInstance(module).getContentRoots();
if (contentRoots.length == 0) {
return null;
}
if (contentRoots.length == 1) {
return contentRoots[0];
}
VirtualFile manifestFile = getPrimaryManifestFile(facet);
if (manifestFile != null) {
for (VirtualFile root : contentRoots) {
if (isAncestor(root, manifestFile, true)) {
return root;
}
}
}
return contentRoots[0];
}
@Nullable
public static Pair<PropertiesFile, VirtualFile> findPropertyFile(@NotNull Module module, @NotNull String propertyFileName) {
for (VirtualFile contentRoot : ModuleRootManager.getInstance(module).getContentRoots()) {
VirtualFile vFile = contentRoot.findChild(propertyFileName);
if (vFile != null) {
PsiFile psiFile = AndroidPsiUtils.getPsiFileSafely(module.getProject(), vFile);
if (psiFile instanceof PropertiesFile) {
return Pair.create((PropertiesFile)psiFile, vFile);
}
}
}
return null;
}
@SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
@Nullable
public static Pair<Properties, VirtualFile> readPropertyFile(@NotNull Module module, @NotNull String propertyFileName) {
for (VirtualFile contentRoot : ModuleRootManager.getInstance(module).getContentRoots()) {
Pair<Properties, VirtualFile> result = readPropertyFile(contentRoot, propertyFileName);
if (result != null) {
return result;
}
}
return null;
}
@Nullable
public static Pair<Properties, VirtualFile> readProjectPropertyFile(@NotNull Module module) {
Pair<Properties, VirtualFile> pair = readPropertyFile(module, SdkConstants.FN_PROJECT_PROPERTIES);
return pair != null ? pair : readPropertyFile(module, DEFAULT_PROPERTIES_FILE_NAME);
}
@SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
@Nullable
private static Pair<Properties, VirtualFile> readPropertyFile(@NotNull VirtualFile contentRoot, @NotNull String propertyFileName) {
VirtualFile vFile = contentRoot.findChild(propertyFileName);
if (vFile != null) {
try {
File file = virtualToIoFile(vFile);
Properties properties = getProperties(file);
return Pair.create(properties, vFile);
}
catch (IOException e) {
LOG.info(e);
}
}
return null;
}
@Nullable
public static Pair<Properties, VirtualFile> readProjectPropertyFile(@NotNull VirtualFile contentRoot) {
Pair<Properties, VirtualFile> pair = readPropertyFile(contentRoot, SdkConstants.FN_PROJECT_PROPERTIES);
return pair != null ? pair : readPropertyFile(contentRoot, DEFAULT_PROPERTIES_FILE_NAME);
}
@SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
@Nullable
public static Pair<String, VirtualFile> getPropertyValue(@NotNull Module module,
@NotNull String propertyFileName,
@NotNull String propertyKey) {
Pair<Properties, VirtualFile> pair = readPropertyFile(module, propertyFileName);
if (pair != null) {
String value = pair.first.getProperty(propertyKey);
if (value != null) {
return Pair.create(value, pair.second);
}
}
return null;
}
@Nullable
public static Pair<String, VirtualFile> getProjectPropertyValue(@NotNull Module module, @NotNull String propertyName) {
Pair<String, VirtualFile> result = getPropertyValue(module, SdkConstants.FN_PROJECT_PROPERTIES, propertyName);
return result != null ? result : getPropertyValue(module, DEFAULT_PROPERTIES_FILE_NAME, propertyName);
}
@Nullable
public static String getAptGenSourceRootPath(@NotNull AndroidFacet facet) {
String path = facet.getProperties().GEN_FOLDER_RELATIVE_PATH_APT;
if (path.length() == 0) return null;
String moduleDirPath = getModuleDirPath(facet.getModule());
return moduleDirPath != null ? moduleDirPath + path : null;
}
@Nullable
public static String getAidlGenSourceRootPath(@NotNull AndroidFacet facet) {
String path = facet.getProperties().GEN_FOLDER_RELATIVE_PATH_AIDL;
if (path.length() == 0) return null;
String moduleDirPath = getModuleDirPath(facet.getModule());
return moduleDirPath != null ? moduleDirPath + path : null;
}
@Nullable
public static String getApkPath(@NotNull AndroidFacet facet) {
AndroidModel androidModel = facet.getAndroidModel();
if (androidModel != null) {
// For Android-Gradle projects, AndroidModel is not null.
AndroidArtifact mainArtifact = androidModel.getMainArtifact();
AndroidArtifactOutput output = getOutput(mainArtifact);
File outputFile = output.getMainOutputFile().getOutputFile();
return outputFile.getAbsolutePath();
}
String path = facet.getProperties().APK_PATH;
if (path.length() == 0) {
return getOutputPackage(facet.getModule());
}
String moduleDirPath = getModuleDirPath(facet.getModule());
return moduleDirPath != null ? toSystemDependentName(moduleDirPath + path) : null;
}
@Nullable
public static String getPathRelativeToModuleDir(@NotNull Module module, @NotNull String path) {
String moduleDirPath = getModuleDirPath(module);
if (moduleDirPath == null) {
return null;
}
if (moduleDirPath.equals(path)) {
return "";
}
return getRelativePath(moduleDirPath, path, '/');
}
}