blob: 3bda0d59f263cd7739c01a213752821f698eb844 [file] [log] [blame]
/*
* Copyright 2000-2014 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.facet.Facet;
import com.intellij.facet.FacetModel;
import com.intellij.facet.impl.ProjectFacetsConfigurator;
import com.intellij.facet.impl.ui.FacetEditorImpl;
import com.intellij.ide.actions.ImportModuleAction;
import com.intellij.ide.projectWizard.NewProjectWizard;
import com.intellij.ide.util.newProjectWizard.AbstractProjectWizard;
import com.intellij.ide.util.projectWizard.ModuleBuilder;
import com.intellij.ide.util.projectWizard.ProjectBuilder;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
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.options.ShowSettingsUtil;
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.ModuleStructureConfigurable;
import com.intellij.openapi.roots.ui.configuration.projectRoot.ProjectSdksModel;
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.Disposer;
import com.intellij.openapi.util.ThrowableComputable;
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.projectImport.ProjectImportBuilder;
import com.intellij.util.containers.HashMap;
import com.intellij.util.graph.GraphGenerator;
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 ProjectFacetsConfigurator myFacetsConfigurator;
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;
myFacetsConfigurator = createFacetsConfigurator();
}
public ProjectFacetsConfigurator getFacetsConfigurator() {
return myFacetsConfigurator;
}
public void disposeUIResources() {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
for (final ModuleEditor moduleEditor : myModuleEditors) {
Disposer.dispose(moduleEditor);
}
myModuleEditors.clear();
myModuleModel.dispose();
if (myFacetsConfigurator != null) {
myFacetsConfigurator.disposeEditors();
}
}
});
}
@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) {
@Override
public ProjectFacetsConfigurator getFacetsConfigurator() {
return myFacetsConfigurator;
}
};
myModuleEditors.add(moduleEditor);
moduleEditor.addChangeListener(this);
Disposer.register(moduleEditor, new Disposable() {
@Override
public void dispose() {
moduleEditor.removeChangeListener(ModulesConfigurator.this);
}
});
return moduleEditor;
}
@Override
public FacetModel getFacetModel(@NotNull Module module) {
return myFacetsConfigurator.getOrCreateModifiableModel(module);
}
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);
}
}
});
myFacetsConfigurator.resetEditors();
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);
}
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)
);
}
for (VirtualFile srcRoot : contentEntry.getSourceFolderFiles()) {
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();
}
final Map<Sdk, Sdk> modifiedToOriginalMap = new HashMap<Sdk, Sdk>();
final ProjectSdksModel projectJdksModel = ProjectStructureConfigurable.getInstance(myProject).getProjectJdksModel();
for (Map.Entry<Sdk, Sdk> entry : projectJdksModel.getProjectSdks().entrySet()) {
modifiedToOriginalMap.put(entry.getValue(), entry.getKey());
}
for (final ModuleEditor moduleEditor : myModuleEditors) {
final ModifiableRootModel model = moduleEditor.apply();
if (model != null) {
if (!model.isSdkInherited()) {
// make sure the sdk is set to original SDK stored in the JDK Table
final Sdk modelSdk = model.getSdk();
if (modelSdk != null) {
final Sdk original = modifiedToOriginalMap.get(modelSdk);
if (original != null) {
model.setSdk(original);
}
}
}
models.add(model);
}
}
myFacetsConfigurator.applyEditors();
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;
myFacetsConfigurator.commitFacets();
}
finally {
ModuleStructureConfigurable.getInstance(myProject).getFacetEditorFacade().clearMaps(false);
myFacetsConfigurator = createFacetsConfigurator();
myModuleModel = ModuleManager.getInstance(myProject).getModifiableModel();
myModuleModelCommitted = false;
}
}
});
myModified = false;
}
private ProjectFacetsConfigurator createFacetsConfigurator() {
return new ProjectFacetsConfigurator(myContext, myFacetsConfigurator);
}
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
public List<Module> addModule(Component parent, boolean anImport) {
if (myProject.isDefault()) return null;
final ProjectBuilder builder = runModuleWizard(parent, anImport);
if (builder != null ) {
final List<Module> modules = new ArrayList<Module>();
final List<Module> commitedModules;
if (builder instanceof ProjectImportBuilder<?>) {
final ModifiableArtifactModel artifactModel =
ProjectStructureConfigurable.getInstance(myProject).getArtifactsStructureConfigurable().getModifiableArtifactModel();
commitedModules = ((ProjectImportBuilder<?>)builder).commit(myProject, myModuleModel, this, artifactModel);
}
else {
commitedModules = builder.commit(myProject, myModuleModel, this);
}
if (commitedModules != null) {
modules.addAll(commitedModules);
}
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
for (Module module : modules) {
getOrCreateModuleEditor(module);
}
}
});
return modules;
}
return null;
}
private Module createModule(final ModuleBuilder builder) {
try {
return ApplicationManager.getApplication().runWriteAction(new ThrowableComputable<Module, Exception>() {
@Override
public Module compute() throws Exception {
return builder.createModule(myModuleModel);
}
});
}
catch (Exception e) {
Messages.showErrorDialog(ProjectBundle.message("module.add.error.message", e.getMessage()),
ProjectBundle.message("module.add.error.title"));
return null;
}
}
@Nullable
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
ProjectBuilder runModuleWizard(Component dialogParent, boolean anImport) {
AbstractProjectWizard wizard;
if (anImport) {
wizard = ImportModuleAction.selectFileAndCreateWizard(myProject, dialogParent);
if (wizard == null) return null;
if (wizard.getStepCount() == 0) return wizard.getProjectBuilder();
}
else {
wizard = new NewProjectWizard(myProject, dialogParent, this);
}
wizard.show();
if (wizard.isOK()) {
final ProjectBuilder builder = wizard.getProjectBuilder();
if (builder instanceof ModuleBuilder) {
final ModuleBuilder moduleBuilder = (ModuleBuilder)builder;
if (moduleBuilder.getName() == null) {
moduleBuilder.setName(wizard.getProjectName());
}
if (moduleBuilder.getModuleFilePath() == null) {
moduleBuilder.setModuleFilePath(wizard.getModuleFilePath());
}
}
if (!builder.validate(myProject, myProject)) {
return null;
}
return wizard.getProjectBuilder();
}
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.moduleCountChanged();
}
}
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 || myFacetsConfigurator.isModified();
}
public static boolean showArtifactSettings(@NotNull Project project, @Nullable final Artifact artifact) {
final ProjectStructureConfigurable configurable = ProjectStructureConfigurable.getInstance(project);
return ShowSettingsUtil.getInstance().editConfigurable(project, configurable, new Runnable() {
@Override
public void run() {
configurable.select(artifact, true);
}
});
}
public static boolean showFacetSettingsDialog(@NotNull final Facet facet,
@Nullable final String tabNameToSelect) {
final Project project = facet.getModule().getProject();
final ProjectStructureConfigurable config = ProjectStructureConfigurable.getInstance(project);
return ShowSettingsUtil.getInstance().editConfigurable(project, config, new Runnable() {
@Override
public void run() {
final ModuleStructureConfigurable modulesConfig = config.getModulesConfig();
config.select(facet, true).doWhenDone(new Runnable() {
@Override
public void run() {
if (tabNameToSelect != null) {
FacetEditorImpl facetEditor = modulesConfig.getFacetConfigurator().getOrCreateEditor(facet);
facetEditor.setSelectedTabName(tabNameToSelect);
}
}
});
}
});
}
public static boolean showDialog(Project project, @Nullable final String moduleToSelect, @Nullable final String editorNameToSelect) {
final ProjectStructureConfigurable config = ProjectStructureConfigurable.getInstance(project);
return ShowSettingsUtil.getInstance().editConfigurable(project, config, new Runnable() {
@Override
public void run() {
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).getProjectConfig().getCompilerOutputUrl(), name);
myContext.getDaemonAnalyzer().queueUpdate(new ModuleProjectStructureElement(myContext, module));
return;
}
}
}
public StructureConfigurableContext getContext() {
return myContext;
}
}