/*
* 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.gradle.util.GradleUtil;
import com.android.tools.idea.gradle.util.PropertiesUtil;
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.util.io.FileUtil;
import com.intellij.openapi.vfs.*;
import com.intellij.psi.PsiFile;
import com.intellij.util.containers.OrderedSet;
import org.jetbrains.android.compiler.AndroidCompileUtil;
import org.jetbrains.android.compiler.AndroidDexCompiler;
import org.jetbrains.android.maven.AndroidMavenUtil;
import org.jetbrains.android.sdk.AndroidPlatform;
import org.jetbrains.android.sdk.AndroidSdkAdditionalData;
import org.jetbrains.android.util.AndroidCommonUtils;
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 org.jetbrains.android.sdk.AndroidSdkUtils.isAndroidSdk;
/**
* @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.isGradleProject()) {
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) {
final Module module = facet.getModule();
final VirtualFile[] contentRoots = ModuleRootManager.getInstance(module).getContentRoots();
if (contentRoots.length == 0) {
return null;
}
VirtualFile root = contentRoots[0];
if (contentRoots.length > 1) {
final String moduleFileParentDirPath = FileUtil.toSystemIndependentName(new File(module.getModuleFilePath()).getParent());
final 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) {
final 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 = FileUtil.toSystemIndependentName(moduleDirPath + relativePath);
VirtualFile file = LocalFileSystem.getInstance().findFileByPath(absPath);
if (file != null) {
return file;
}
}
if (lookInContentRoot) {
for (VirtualFile contentRoot : contentRoots) {
String absPath = FileUtil.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) {
final String genPath = getAidlGenSourceRootPath(facet);
return genPath != null
? LocalFileSystem.getInstance().findFileByPath(genPath)
: null;
}
@Nullable
public static VirtualFile getAaptGenDir(@NotNull AndroidFacet facet) {
final String genPath = getAptGenSourceRootPath(facet);
return genPath != null
? LocalFileSystem.getInstance().findFileByPath(genPath)
: null;
}
@Nullable
public static VirtualFile getRenderscriptGenDir(@NotNull AndroidFacet facet) {
final String path = getRenderscriptGenSourceRootPath(facet);
return path != null ? LocalFileSystem.getInstance().findFileByPath(path) : null;
}
@Nullable
public static VirtualFile getBuildconfigGenDir(@NotNull AndroidFacet facet) {
final 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) {
final VirtualFile localFile = JarFileSystem.getInstance().getVirtualFileForJar(child);
if (localFile != null) {
result.add(localFile);
}
}
else {
result.add(child);
}
}
}
}
}
private static void fillExternalLibrariesAndModules(final Module module,
final Set<VirtualFile> outputDirs,
@Nullable final Set<VirtualFile> libraries,
final Set<Module> visited,
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) {
final LibraryOrderEntry libraryOrderEntry = (LibraryOrderEntry)entry;
final 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;
}
final AndroidFacet facet = AndroidFacet.getInstance(depModule);
final boolean libraryProject = facet != null && facet.isLibraryProject();
CompilerModuleExtension extension = CompilerModuleExtension.getInstance(depModule);
if (extension != null) {
VirtualFile classDir = extension.getCompilerOutputPath();
if (libraryProject) {
final VirtualFile tmpArtifactsDir = AndroidDexCompiler.getOutputDirectoryForDex(depModule);
if (tmpArtifactsDir != null) {
final VirtualFile packedClassesJar = tmpArtifactsDir.findChild(AndroidCommonUtils.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, libraries, visited,
!libraryProject || exportedLibrariesOnly, recursive);
}
}
}
}
});
}
@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
final boolean recursive = !AndroidMavenUtil.isMavenizedModule(module);
fillExternalLibrariesAndModules(module, files, libs, new HashSet<Module>(), false, recursive);
addAnnotationsJar(module, libs);
return libs;
}
private static void addAnnotationsJar(Module module, OrderedSet<VirtualFile> libs) {
final Sdk sdk = ModuleRootManager.getInstance(module).getSdk();
if (sdk == null || !isAndroidSdk(sdk)) {
return;
}
final String sdkHomePath = sdk.getHomePath();
if (sdkHomePath == null) {
return;
}
final AndroidSdkAdditionalData data = (AndroidSdkAdditionalData)sdk.getSdkAdditionalData();
if (data == null) {
return;
}
final AndroidPlatform platform = data.getAndroidPlatform();
if (platform != null && platform.needToAddAnnotationsJarToClasspath()) {
final String annotationsJarPath = FileUtil.toSystemIndependentName(sdkHomePath) + AndroidCommonUtils.ANNOTATIONS_JAR_RELATIVE_PATH;
final VirtualFile annotationsJar = LocalFileSystem.getInstance().findFileByPath(annotationsJarPath);
if (annotationsJar != null) {
libs.add(annotationsJar);
}
}
}
@NotNull
public static Set<VirtualFile> getDependentModules(Module module,
VirtualFile moduleOutputDir) {
Set<VirtualFile> files = new HashSet<VirtualFile>();
fillExternalLibrariesAndModules(module, files, null, new HashSet<Module>(), 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 VfsUtilCore.toVirtualFileArray(result);
}
@Nullable
public static String getModuleDirPath(Module module) {
String moduleFilePath = module.getModuleFilePath();
String moduleDirPath = new File(moduleFilePath).getParent();
if (moduleDirPath != null) {
moduleDirPath = FileUtil.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) {
final Module module = facet.getModule();
final VirtualFile[] contentRoots = ModuleRootManager.getInstance(module).getContentRoots();
if (contentRoots.length == 0) {
return null;
}
if (contentRoots.length == 1) {
return contentRoots[0];
}
final VirtualFile manifestFile = getPrimaryManifestFile(facet);
if (manifestFile != null) {
for (VirtualFile root : contentRoots) {
if (VfsUtilCore.isAncestor(root, manifestFile, true)) {
return root;
}
}
}
return contentRoots[0];
}
@Nullable
public static Pair<PropertiesFile, VirtualFile> findPropertyFile(@NotNull final Module module, @NotNull String propertyFileName) {
for (VirtualFile contentRoot : ModuleRootManager.getInstance(module).getContentRoots()) {
final VirtualFile vFile = contentRoot.findChild(propertyFileName);
if (vFile != null) {
final 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()) {
final Pair<Properties, VirtualFile> result = readPropertyFile(contentRoot, propertyFileName);
if (result != null) {
return result;
}
}
return null;
}
@Nullable
public static Pair<Properties, VirtualFile> readProjectPropertyFile(@NotNull Module module) {
final 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) {
final VirtualFile vFile = contentRoot.findChild(propertyFileName);
if (vFile != null) {
try {
File file = VfsUtilCore.virtualToIoFile(vFile);
Properties properties = PropertiesUtil.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) {
final 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) {
final Pair<Properties, VirtualFile> pair = readPropertyFile(module, propertyFileName);
if (pair != null) {
final 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) {
IdeaAndroidProject ideaAndroidProject = facet.getIdeaAndroidProject();
if (ideaAndroidProject != null) {
// For Android-Gradle projects, IdeaAndroidProject is not null.
Variant selectedVariant = ideaAndroidProject.getSelectedVariant();
AndroidArtifact mainArtifact = selectedVariant.getMainArtifact();
AndroidArtifactOutput output = GradleUtil.getOutput(mainArtifact);
File outputFile = output.getOutputFile();
return outputFile.getAbsolutePath();
}
String path = facet.getProperties().APK_PATH;
if (path.length() == 0) {
return AndroidCompileUtil.getOutputPackage(facet.getModule());
}
String moduleDirPath = getModuleDirPath(facet.getModule());
return moduleDirPath != null ? FileUtil.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 FileUtil.getRelativePath(moduleDirPath, path, '/');
}
}