/* * Copyright 2000-2012 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 com.intellij.openapi.roots.ui.configuration; import com.intellij.compiler.ModuleCompilerUtil; import com.intellij.ide.actions.ImportModuleAction; import com.intellij.ide.util.newProjectWizard.AddModuleWizard; import com.intellij.ide.util.projectWizard.ModuleBuilder; import com.intellij.openapi.Disposable; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.fileChooser.FileChooser; import com.intellij.openapi.fileChooser.FileChooserDescriptor; import com.intellij.openapi.module.ModifiableModuleModel; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.options.ConfigurationException; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectBundle; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.roots.ContentEntry; import com.intellij.openapi.roots.ModifiableRootModel; import com.intellij.openapi.roots.ModuleRootModel; import com.intellij.openapi.roots.impl.ModifiableModelCommitter; import com.intellij.openapi.roots.ui.configuration.actions.ModuleDeleteProvider; import com.intellij.openapi.roots.ui.configuration.projectRoot.StructureConfigurableContext; import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.ModuleProjectStructureElement; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.Pair; import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.packaging.artifacts.Artifact; import com.intellij.packaging.artifacts.ModifiableArtifactModel; import com.intellij.util.Consumer; import com.intellij.util.containers.HashMap; import com.intellij.util.graph.GraphGenerator; import consulo.annotations.RequiredDispatchThread; import consulo.ide.newProject.NewProjectDialog; import consulo.ide.newProject.actions.NewProjectAction; import consulo.moduleImport.ModuleImportContext; import consulo.moduleImport.ModuleImportProvider; import consulo.roots.ContentFolderScopes; import consulo.roots.ui.configuration.ProjectStructureDialog; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.awt.*; import java.util.*; import java.util.List; /** * @author Eugene Zhuravlev * Date: Dec 15, 2003 */ public class ModulesConfigurator implements ModulesProvider, ModuleEditor.ChangeListener { private static final Logger LOG = Logger.getInstance("#" + ModulesConfigurator.class.getName()); private final Project myProject; private final List<ModuleEditor> myModuleEditors = new ArrayList<ModuleEditor>(); private final Comparator<ModuleEditor> myModuleEditorComparator = new Comparator<ModuleEditor>() { @Override public int compare(ModuleEditor editor1, ModuleEditor editor2) { return ModulesAlphaComparator.INSTANCE.compare(editor1.getModule(), editor2.getModule()); } @SuppressWarnings({"EqualsWhichDoesntCheckParameterClass"}) public boolean equals(Object o) { return false; } }; private boolean myModified = false; private ModifiableModuleModel myModuleModel; private boolean myModuleModelCommitted = false; private StructureConfigurableContext myContext; private final List<ModuleEditor.ChangeListener> myAllModulesChangeListeners = new ArrayList<ModuleEditor.ChangeListener>(); public ModulesConfigurator(Project project) { myProject = project; myModuleModel = ModuleManager.getInstance(myProject).getModifiableModel(); } public void setContext(final StructureConfigurableContext context) { myContext = context; } @RequiredDispatchThread public void disposeUIResources() { ApplicationManager.getApplication().runWriteAction(new Runnable() { @Override public void run() { for (final ModuleEditor moduleEditor : myModuleEditors) { Disposer.dispose(moduleEditor); } myModuleEditors.clear(); myModuleModel.dispose(); } }); } @Override @NotNull public Module[] getModules() { return myModuleModel.getModules(); } @Override @Nullable public Module getModule(String name) { final Module moduleByName = myModuleModel.findModuleByName(name); if (moduleByName != null) { return moduleByName; } return myModuleModel.getModuleToBeRenamed(name); //if module was renamed } @Nullable public ModuleEditor getModuleEditor(Module module) { for (final ModuleEditor moduleEditor : myModuleEditors) { if (module.equals(moduleEditor.getModule())) { return moduleEditor; } } return null; } @Override public ModuleRootModel getRootModel(@NotNull Module module) { return getOrCreateModuleEditor(module).getRootModel(); } public ModuleEditor getOrCreateModuleEditor(Module module) { LOG.assertTrue(getModule(module.getName()) != null, "Module has been deleted"); ModuleEditor editor = getModuleEditor(module); if (editor == null) { editor = doCreateModuleEditor(module); } return editor; } private ModuleEditor doCreateModuleEditor(final Module module) { final ModuleEditor moduleEditor = new HeaderHidingTabbedModuleEditor(myProject, this, module); myModuleEditors.add(moduleEditor); moduleEditor.addChangeListener(this); Disposer.register(moduleEditor, new Disposable() { @Override public void dispose() { moduleEditor.removeChangeListener(ModulesConfigurator.this); } }); return moduleEditor; } @RequiredDispatchThread public void resetModuleEditors() { myModuleModel = ModuleManager.getInstance(myProject).getModifiableModel(); ApplicationManager.getApplication().runWriteAction(new Runnable() { @Override public void run() { if (!myModuleEditors.isEmpty()) { LOG.error("module editors was not disposed"); myModuleEditors.clear(); } final Module[] modules = myModuleModel.getModules(); if (modules.length > 0) { for (Module module : modules) { getOrCreateModuleEditor(module); } Collections.sort(myModuleEditors, myModuleEditorComparator); } } }); myModified = false; } @Override public void moduleStateChanged(final ModifiableRootModel moduleRootModel) { for (ModuleEditor.ChangeListener listener : myAllModulesChangeListeners) { listener.moduleStateChanged(moduleRootModel); } myContext.getDaemonAnalyzer().queueUpdate(new ModuleProjectStructureElement(myContext, moduleRootModel.getModule())); } public void addAllModuleChangeListener(ModuleEditor.ChangeListener listener) { myAllModulesChangeListeners.add(listener); } public GraphGenerator<ModuleRootModel> createGraphGenerator() { final Map<Module, ModuleRootModel> models = new HashMap<Module, ModuleRootModel>(); for (ModuleEditor moduleEditor : myModuleEditors) { models.put(moduleEditor.getModule(), moduleEditor.getRootModel()); } return ModuleCompilerUtil.createGraphGenerator(models); } @RequiredDispatchThread public void apply() throws ConfigurationException { // validate content and source roots final Map<VirtualFile, String> contentRootToModuleNameMap = new HashMap<VirtualFile, String>(); final Map<VirtualFile, VirtualFile> srcRootsToContentRootMap = new HashMap<VirtualFile, VirtualFile>(); for (final ModuleEditor moduleEditor : myModuleEditors) { final ModifiableRootModel rootModel = moduleEditor.getModifiableRootModel(); final ContentEntry[] contents = rootModel.getContentEntries(); for (ContentEntry contentEntry : contents) { final VirtualFile contentRoot = contentEntry.getFile(); if (contentRoot == null) { continue; } final String moduleName = moduleEditor.getName(); final String previousName = contentRootToModuleNameMap.put(contentRoot, moduleName); if (previousName != null && !previousName.equals(moduleName)) { throw new ConfigurationException( ProjectBundle.message("module.paths.validation.duplicate.content.error", contentRoot.getPresentableUrl(), previousName, moduleName)); } final VirtualFile[] sourceAndTestFiles = contentEntry.getFolderFiles(ContentFolderScopes.all(false)); for (VirtualFile srcRoot : sourceAndTestFiles) { final VirtualFile anotherContentRoot = srcRootsToContentRootMap.put(srcRoot, contentRoot); if (anotherContentRoot != null) { final String problematicModule; final String correctModule; if (VfsUtilCore.isAncestor(anotherContentRoot, contentRoot, true)) { problematicModule = contentRootToModuleNameMap.get(anotherContentRoot); correctModule = contentRootToModuleNameMap.get(contentRoot); } else { problematicModule = contentRootToModuleNameMap.get(contentRoot); correctModule = contentRootToModuleNameMap.get(anotherContentRoot); } throw new ConfigurationException(ProjectBundle.message("module.paths.validation.duplicate.source.root.error", problematicModule, srcRoot.getPresentableUrl(), correctModule)); } } } } // additional validation: directories marked as src roots must belong to the same module as their corresponding content root for (Map.Entry<VirtualFile, VirtualFile> entry : srcRootsToContentRootMap.entrySet()) { final VirtualFile srcRoot = entry.getKey(); final VirtualFile correspondingContent = entry.getValue(); final String expectedModuleName = contentRootToModuleNameMap.get(correspondingContent); for (VirtualFile candidateContent = srcRoot; candidateContent != null && !candidateContent.equals(correspondingContent); candidateContent = candidateContent.getParent()) { final String moduleName = contentRootToModuleNameMap.get(candidateContent); if (moduleName != null && !moduleName.equals(expectedModuleName)) { throw new ConfigurationException(ProjectBundle .message("module.paths.validation.source.root.belongs.to.another.module.error", srcRoot.getPresentableUrl(), expectedModuleName, moduleName)); } } } final List<ModifiableRootModel> models = new ArrayList<ModifiableRootModel>(myModuleEditors.size()); for (ModuleEditor moduleEditor : myModuleEditors) { moduleEditor.canApply(); } for (final ModuleEditor moduleEditor : myModuleEditors) { final ModifiableRootModel model = moduleEditor.apply(); if (model != null) { models.add(model); } } ApplicationManager.getApplication().runWriteAction(new Runnable() { @Override public void run() { try { final ModifiableRootModel[] rootModels = models.toArray(new ModifiableRootModel[models.size()]); ModifiableModelCommitter.multiCommit(rootModels, myModuleModel); myModuleModelCommitted = true; } finally { myModuleModel = ModuleManager.getInstance(myProject).getModifiableModel(); myModuleModelCommitted = false; } } }); myModified = false; } public void setModified(final boolean modified) { myModified = modified; } public ModifiableModuleModel getModuleModel() { return myModuleModel; } public boolean isModuleModelCommitted() { return myModuleModelCommitted; } public boolean deleteModule(final Module module) { ModuleEditor moduleEditor = getModuleEditor(module); if (moduleEditor == null) return true; return doRemoveModule(moduleEditor); } @Nullable @RequiredDispatchThread @SuppressWarnings("unchecked") public List<Module> addModule(Component parent, boolean anImport) { if (myProject.isDefault()) return null; if (anImport) { Pair<ModuleImportProvider, ModuleImportContext> pair = runModuleWizard(parent, true); if (pair != null) { ModuleImportProvider importProvider = pair.getFirst(); ModuleImportContext importContext = pair.getSecond(); assert importProvider != null; assert importContext != null; final ModifiableArtifactModel artifactModel = ProjectStructureConfigurable.getInstance(myProject).getArtifactsStructureConfigurable().getModifiableArtifactModel(); List<Module> commitedModules = importProvider.commit(importContext, myProject, myModuleModel, this, artifactModel); ApplicationManager.getApplication().runWriteAction(() -> { for (Module module : commitedModules) { getOrCreateModuleEditor(module); } }); return commitedModules; } } else { FileChooserDescriptor fileChooserDescriptor = new FileChooserDescriptor(false, true, false, false, false, false) { @RequiredDispatchThread @Override public boolean isFileSelectable(VirtualFile file) { if (!super.isFileSelectable(file)) { return false; } for (Module module : myModuleModel.getModules()) { VirtualFile moduleDir = module.getModuleDir(); if (moduleDir != null && moduleDir.equals(file)) { return false; } } return true; } }; fileChooserDescriptor.setTitle(ProjectBundle.message("choose.module.home")); final VirtualFile moduleDir = FileChooser.chooseFile(fileChooserDescriptor, myProject, null); if (moduleDir == null) { return null; } final NewProjectDialog dialog = new NewProjectDialog(myProject, moduleDir); Module newModule; if (dialog.showAndGet()) { newModule = NewProjectAction.doCreate(dialog.getProjectPanel(), myModuleModel, moduleDir, false); } else { newModule = null; } if (newModule == null) { return null; } ApplicationManager.getApplication().runWriteAction(() -> { getOrCreateModuleEditor(newModule); Collections.sort(myModuleEditors, myModuleEditorComparator); }); processModuleCountChanged(); return Collections.singletonList(newModule); } return null; } @RequiredDispatchThread private Module createModule(final ModuleBuilder builder) { final Exception[] ex = new Exception[]{null}; final Module module = ApplicationManager.getApplication().runWriteAction(new Computable<Module>() { @Override @SuppressWarnings({"ConstantConditions"}) public Module compute() { try { return builder.createModule(myModuleModel); } catch (Exception e) { ex[0] = e; return null; } } }); if (ex[0] != null) { Messages.showErrorDialog(ProjectBundle.message("module.add.error.message", ex[0].getMessage()), ProjectBundle.message("module.add.error.title")); } return module; } @Nullable @RequiredDispatchThread public Module addModule(final ModuleBuilder moduleBuilder) { final Module module = createModule(moduleBuilder); if (module != null) { ApplicationManager.getApplication().runWriteAction(new Runnable() { @Override public void run() { getOrCreateModuleEditor(module); Collections.sort(myModuleEditors, myModuleEditorComparator); } }); processModuleCountChanged(); } return module; } @Nullable Pair<ModuleImportProvider, ModuleImportContext> runModuleWizard(Component dialogParent, boolean anImport) { AddModuleWizard wizard; if (anImport) { wizard = ImportModuleAction.selectFileAndCreateWizard(myProject, dialogParent); if (wizard == null) return null; if (wizard.getStepCount() == 0) { return Pair.create(wizard.getImportProvider(), wizard.getWizardContext().getModuleImportContext(wizard.getImportProvider())); } } else { wizard = new AddModuleWizard(dialogParent, myProject, this); } wizard.show(); if (wizard.isOK()) { final ModuleImportProvider<?> builder = wizard.getImportProvider(); if (builder instanceof ModuleBuilder) { final ModuleBuilder moduleBuilder = (ModuleBuilder)builder; if (moduleBuilder.getName() == null) { moduleBuilder.setName(wizard.getProjectName()); } if (moduleBuilder.getModuleDirPath() == null) { moduleBuilder.setModuleDirPath(wizard.getModuleDirPath()); } } if (!builder.validate(myProject, myProject)) { return null; } return Pair.create(wizard.getImportProvider(), wizard.getWizardContext().getModuleImportContext(builder)); } return null; } private boolean doRemoveModule(@NotNull ModuleEditor selectedEditor) { String question; if (myModuleEditors.size() == 1) { question = ProjectBundle.message("module.remove.last.confirmation"); } else { question = ProjectBundle.message("module.remove.confirmation", selectedEditor.getModule().getName()); } int result = Messages.showYesNoDialog(myProject, question, ProjectBundle.message("module.remove.confirmation.title"), Messages.getQuestionIcon()); if (result != Messages.YES) { return false; } // do remove myModuleEditors.remove(selectedEditor); // destroyProcess removed module final Module moduleToRemove = selectedEditor.getModule(); // remove all dependencies on the module that is about to be removed List<ModifiableRootModel> modifiableRootModels = new ArrayList<ModifiableRootModel>(); for (final ModuleEditor moduleEditor : myModuleEditors) { final ModifiableRootModel modifiableRootModel = moduleEditor.getModifiableRootModelProxy(); modifiableRootModels.add(modifiableRootModel); } // destroyProcess editor ModuleDeleteProvider.removeModule(moduleToRemove, null, modifiableRootModels, myModuleModel); processModuleCountChanged(); Disposer.dispose(selectedEditor); return true; } private void processModuleCountChanged() { for (ModuleEditor moduleEditor : myModuleEditors) { moduleEditor.fireModuleStateChanged(); } } public void processModuleCompilerOutputChanged(String baseUrl) { for (ModuleEditor moduleEditor : myModuleEditors) { moduleEditor.updateCompilerOutputPathChanged(baseUrl, moduleEditor.getName()); } } public boolean isModified() { if (myModuleModel.isChanged()) { return true; } for (ModuleEditor moduleEditor : myModuleEditors) { if (moduleEditor.isModified()) { return true; } } return myModified; } public static boolean showArtifactSettings(@NotNull Project project, @Nullable final Artifact artifact) { final ProjectStructureConfigurable configurable = ProjectStructureConfigurable.getInstance(project); return ProjectStructureDialog.show(project, new Consumer<ProjectStructureConfigurable>() { @Override public void consume(ProjectStructureConfigurable config) { configurable.select(artifact, true); } }); } public static boolean showSdkSettings(@NotNull Project project, @NotNull final Sdk sdk) { final ProjectStructureConfigurable configurable = ProjectStructureConfigurable.getInstance(project); return ProjectStructureDialog.show(project, new Consumer<ProjectStructureConfigurable>() { @Override public void consume(ProjectStructureConfigurable config) { configurable.select(sdk, true); } }); } public static boolean showDialog(Project project, @Nullable final String moduleToSelect, @Nullable final String editorNameToSelect) { return ProjectStructureDialog.show(project, new Consumer<ProjectStructureConfigurable>() { @Override public void consume(ProjectStructureConfigurable config) { config.select(moduleToSelect, editorNameToSelect, true); } }); } public void moduleRenamed(Module module, final String oldName, final String name) { for (ModuleEditor moduleEditor : myModuleEditors) { if (module == moduleEditor.getModule() && Comparing.strEqual(moduleEditor.getName(), oldName)) { moduleEditor.setModuleName(name); moduleEditor.updateCompilerOutputPathChanged(ProjectStructureConfigurable.getInstance(myProject).getProjectConfigurable().getCompilerOutputUrl(), name); myContext.getDaemonAnalyzer().queueUpdate(new ModuleProjectStructureElement(myContext, module)); return; } } } public StructureConfigurableContext getContext() { return myContext; } }