blob: d5b369f9b6b8d89958bbea8e174a580596e00b54 [file] [log] [blame]
package org.jetbrains.android.importDependencies;
import com.intellij.CommonBundle;
import com.intellij.ProjectTopics;
import com.intellij.ide.highlighter.ModuleFileType;
import com.intellij.ide.util.newProjectWizard.SourcePathsStep;
import com.intellij.ide.util.projectWizard.importSources.JavaModuleSourceRoot;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.module.ModuleUtil;
import com.intellij.openapi.project.ModuleAdapter;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ContentEntry;
import com.intellij.openapi.roots.ModifiableRootModel;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.startup.StartupManager;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.ArrayUtil;
import com.intellij.util.containers.HashMap;
import com.intellij.util.containers.HashSet;
import com.intellij.util.containers.OrderedSet;
import com.intellij.util.messages.MessageBusConnection;
import org.jetbrains.android.facet.AndroidRootUtil;
import org.jetbrains.android.util.AndroidBundle;
import org.jetbrains.android.util.AndroidUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.*;
/**
* @author Eugene.Kudelevsky
*/
public class ImportDependenciesUtil {
private static final Logger LOG = Logger.getInstance("#org.jetbrains.android.importDependencies.ImportDependenciesUtil");
private static final Key<Boolean> WAIT_FOR_IMPORTING_DEPENDENCIES_KEY = new Key<Boolean>("WAIT_FOR_IMPORTING_DEPENDENCIES_KEY");
private static final Object LOCK = new Object();
private ImportDependenciesUtil() {
}
public static void importDependencies(@NotNull final Module module,
final boolean updateBackwardDependencies) {
synchronized (LOCK) {
final Project project = module.getProject();
module.putUserData(WAIT_FOR_IMPORTING_DEPENDENCIES_KEY, Boolean.TRUE);
if (project.getUserData(WAIT_FOR_IMPORTING_DEPENDENCIES_KEY) != Boolean.TRUE) {
project.putUserData(WAIT_FOR_IMPORTING_DEPENDENCIES_KEY, Boolean.TRUE);
StartupManager.getInstance(project).runWhenProjectIsInitialized(new Runnable() {
@Override
public void run() {
// todo: this doesn't work in module configurator after 'Apply' button pressed
if (module.isLoaded()) {
importDependenciesForMarkedModules(project, updateBackwardDependencies);
}
else {
final MessageBusConnection connection = module.getMessageBus().connect();
connection.subscribe(ProjectTopics.MODULES, new ModuleAdapter() {
@Override
public void moduleAdded(final Project project, final Module addedModule) {
if (module.equals(addedModule)) {
connection.disconnect();
importDependenciesForMarkedModules(project, updateBackwardDependencies);
}
}
});
}
}
});
}
}
}
private static void importDependenciesForMarkedModules(final Project project, final boolean updateBackwardDependencies) {
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
doImportDependenciesForMarkedModules(project, updateBackwardDependencies);
}
});
}
private static void doImportDependenciesForMarkedModules(Project project, boolean updateBackwardDependencies) {
if (project.getUserData(WAIT_FOR_IMPORTING_DEPENDENCIES_KEY) != Boolean.TRUE) {
return;
}
project.putUserData(WAIT_FOR_IMPORTING_DEPENDENCIES_KEY, null);
final List<Module> modulesToProcess = new ArrayList<Module>();
for (Module module : ModuleManager.getInstance(project).getModules()) {
if (module.getUserData(WAIT_FOR_IMPORTING_DEPENDENCIES_KEY) == Boolean.TRUE) {
module.putUserData(WAIT_FOR_IMPORTING_DEPENDENCIES_KEY, null);
modulesToProcess.add(module);
}
}
doImportDependencies(project, modulesToProcess, updateBackwardDependencies);
}
public static void doImportDependencies(@NotNull Project project, @NotNull List<Module> modules, boolean updateBackwardDependencies) {
final List<ImportDependenciesTask> tasks = new OrderedSet<ImportDependenciesTask>();
final List<MyUnresolvedDependency> unresolvedDependencies = new ArrayList<MyUnresolvedDependency>();
for (Module module : modules) {
importDependencies(module, updateBackwardDependencies, tasks, unresolvedDependencies);
}
final Map<VirtualFile, ModuleProvidingTask> libDir2ModuleProvidingTask = new HashMap<VirtualFile, ModuleProvidingTask>();
for (ImportDependenciesTask task : tasks) {
if (task instanceof ModuleProvidingTask) {
final ModuleProvidingTask moduleProvidingTask = (ModuleProvidingTask)task;
libDir2ModuleProvidingTask.put(moduleProvidingTask.getContentRoot(), moduleProvidingTask);
}
}
for (MyUnresolvedDependency unresolvedDependency : unresolvedDependencies) {
final ModuleProvidingTask taskProvidingDepModule = libDir2ModuleProvidingTask.get(unresolvedDependency.myLibDir);
if (taskProvidingDepModule != null) {
tasks.add(new AddModuleDependencyTask(unresolvedDependency.myModuleProvider,
ModuleProvider.create(taskProvidingDepModule)));
}
}
if (tasks.size() > 0) {
doImportDependencies(project, tasks);
}
}
private static void importDependencies(Module module,
boolean updateBackwardDependencies,
List<ImportDependenciesTask> tasks,
List<MyUnresolvedDependency> unresolvedDependencies) {
importDependencies(module, null, tasks, unresolvedDependencies);
if (updateBackwardDependencies) {
importBackwardDependencies(module, tasks, unresolvedDependencies);
}
}
private static void doImportDependencies(@NotNull Project project, @NotNull List<ImportDependenciesTask> tasks) {
final ImportDependenciesDialog dialog = new ImportDependenciesDialog(project, tasks);
dialog.show();
if (dialog.getExitCode() != DialogWrapper.OK_EXIT_CODE) {
return;
}
final List<ImportDependenciesTask> selectedTasks = dialog.getSelectedTasks();
final StringBuilder messageBuilder = new StringBuilder();
boolean failed = false;
final List<CreateNewModuleTask> createNewModuleTasks = new ArrayList<CreateNewModuleTask>();
for (ImportDependenciesTask selectedTask : selectedTasks) {
final Exception error = selectedTask.perform();
if (error != null) {
LOG.info(error);
if (messageBuilder.length() > 0) {
messageBuilder.append('\n');
}
messageBuilder.append(error.getMessage());
failed = true;
}
else if (selectedTask instanceof CreateNewModuleTask) {
createNewModuleTasks.add((CreateNewModuleTask)selectedTask);
}
}
if (createNewModuleTasks.size() > 0) {
final List<JavaModuleSourceRoot> sourceRoots = new ArrayList<JavaModuleSourceRoot>();
for (CreateNewModuleTask task : createNewModuleTasks) {
final String contentRootPath = task.getContentRoot().getPath();
sourceRoots.addAll(SourcePathsStep.calculateSourceRoots(contentRootPath));
}
if (sourceRoots.size() > 0) {
final ImportSourceRootsDialog sourceRootsDialog = new ImportSourceRootsDialog(project, sourceRoots);
sourceRootsDialog.show();
if (sourceRootsDialog.getExitCode() == DialogWrapper.OK_EXIT_CODE) {
addSourceRoots(project, sourceRootsDialog.getMarkedElements());
}
}
}
if (failed) {
Messages.showErrorDialog(project, AndroidBundle.message("android.import.dependencies.error.message.header") +
"\n" +
messageBuilder.toString(), CommonBundle.getErrorTitle());
}
}
private static void addSourceRoots(final Project project, final List<JavaModuleSourceRoot> sourceRoots) {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
for (JavaModuleSourceRoot sourceRootTrinity : sourceRoots) {
final String path = sourceRootTrinity.getDirectory().getPath();
final VirtualFile sourceRoot = LocalFileSystem.getInstance().refreshAndFindFileByPath(FileUtil.toSystemIndependentName(path));
if (sourceRoot == null) {
LOG.debug(new Exception("Cannot find source root " + path));
continue;
}
final Module module = ModuleUtil.findModuleForFile(sourceRoot, project);
if (module == null) {
LOG.debug(new Exception("Cannot find module for file " + sourceRoot.getPath()));
continue;
}
final ModifiableRootModel model = ModuleRootManager.getInstance(module).getModifiableModel();
final ContentEntry[] entries = model.getContentEntries();
if (entries.length > 0) {
entries[0].addSourceFolder(sourceRoot, false, sourceRootTrinity.getPackagePrefix());
}
else {
LOG.debug(new Exception("Module " + module.getName() + " has no content entries"));
}
model.commit();
}
}
});
}
@Nullable
private static VirtualFile findModuleFileChild(@NotNull VirtualFile dir) {
for (VirtualFile child : dir.getChildren()) {
if (child.getFileType() instanceof ModuleFileType) {
return child;
}
}
return null;
}
private static class MyUnresolvedDependency {
final ModuleProvider myModuleProvider;
final VirtualFile myLibDir;
private MyUnresolvedDependency(ModuleProvider moduleProvider, VirtualFile libDir) {
myModuleProvider = moduleProvider;
myLibDir = libDir;
}
}
private static void importDependencies(@NotNull Module module,
@Nullable Module allowedDepModule,
@NotNull List<ImportDependenciesTask> tasks,
@NotNull List<MyUnresolvedDependency> unresolvedDependencies) {
final Project project = module.getProject();
final ModuleProvider moduleProvider = ModuleProvider.create(module);
final Pair<Properties, VirtualFile> pair = AndroidRootUtil.readProjectPropertyFile(module);
if (pair != null) {
doImportDependencies(module, allowedDepModule, tasks, unresolvedDependencies, project, moduleProvider, pair);
}
}
private static void importDependenciesForNewModule(@NotNull Project project,
@NotNull ModuleProvider newModuleProvider,
@NotNull VirtualFile newModuleContentRoot,
@NotNull List<ImportDependenciesTask> tasks,
@NotNull List<MyUnresolvedDependency> unresolvedDependencies) {
final Pair<Properties, VirtualFile> properties =
AndroidRootUtil.readProjectPropertyFile(newModuleContentRoot);
if (properties != null) {
doImportDependencies(null, null, tasks, unresolvedDependencies, project, newModuleProvider, properties);
}
}
private static void doImportDependencies(@Nullable Module module,
@Nullable Module allowedDepModule,
@NotNull List<ImportDependenciesTask> tasks,
@NotNull List<MyUnresolvedDependency> unresolvedDependencies,
@NotNull Project project,
@NotNull ModuleProvider moduleProvider,
@NotNull Pair<Properties, VirtualFile> defaultProperties) {
for (VirtualFile libDir : getLibDirs(defaultProperties)) {
final Module depModule = ModuleUtil.findModuleForFile(libDir, project);
if (depModule != null) {
if ((allowedDepModule == null || allowedDepModule == depModule) &&
ArrayUtil.find(ModuleRootManager.getInstance(depModule).getContentRoots(), libDir) >= 0 &&
!(module != null && ModuleRootManager.getInstance(module).isDependsOn(depModule))) {
tasks.add(new AddModuleDependencyTask(moduleProvider, ModuleProvider.create(depModule)));
}
}
else {
final VirtualFile libModuleFile = findModuleFileChild(libDir);
final ModuleProvidingTask task = libModuleFile != null && new File(libModuleFile.getPath()).exists()
? new ImportModuleTask(project, libModuleFile.getPath(), libDir)
: new CreateNewModuleTask(project, libDir);
if (!tasks.contains(task)) {
tasks.add(task);
final ModuleProvider newModuleProvider = ModuleProvider.create(task);
tasks.add(new AddModuleDependencyTask(moduleProvider, newModuleProvider));
importDependenciesForNewModule(project, newModuleProvider, libDir, tasks, unresolvedDependencies);
}
else {
unresolvedDependencies.add(new MyUnresolvedDependency(moduleProvider, libDir));
}
}
}
}
@NotNull
public static Set<VirtualFile> getLibDirs(@NotNull Pair<Properties, VirtualFile> properties) {
final Set<VirtualFile> resultSet = new HashSet<VirtualFile>();
final VirtualFile baseDir = properties.second.getParent();
String libDirPath;
int i = 1;
do {
libDirPath = properties.first.getProperty(AndroidUtils.ANDROID_LIBRARY_REFERENCE_PROPERTY_PREFIX + i);
if (libDirPath != null) {
final VirtualFile libDir = AndroidUtils.findFileByAbsoluteOrRelativePath(baseDir, FileUtil.toSystemIndependentName(libDirPath));
if (libDir != null) {
resultSet.add(libDir);
}
}
i++;
}
while (libDirPath != null);
return resultSet;
}
private static void importBackwardDependencies(@NotNull Module module, @NotNull List<ImportDependenciesTask> tasks,
@NotNull List<MyUnresolvedDependency> unresolvedDependencies) {
for (Module module1 : ModuleManager.getInstance(module.getProject()).getModules()) {
if (module1 != module) {
importDependencies(module1, module, tasks, unresolvedDependencies);
}
}
}
}