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); } } } }