project structure dialog: circular dependency checking implemented via daemon

This commit is contained in:
nik
2011-09-29 15:16:25 +04:00
parent 7e0d2e1cc9
commit 8766282b8e
4 changed files with 162 additions and 88 deletions
@@ -0,0 +1,88 @@
/*
* Copyright 2000-2011 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.openapi.project.Project;
import com.intellij.openapi.project.ProjectBundle;
import com.intellij.openapi.roots.ModifiableRootModel;
import com.intellij.openapi.roots.ui.configuration.projectRoot.StructureConfigurableContext;
import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.*;
import com.intellij.util.Chunk;
import com.intellij.util.graph.Graph;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
/**
* @author nik
*/
public class GeneralProjectSettingsElement extends ProjectStructureElement {
public GeneralProjectSettingsElement(@NotNull StructureConfigurableContext context) {
super(context);
}
@Override
public void check(ProjectStructureProblemsHolder problemsHolder) {
final Graph<Chunk<ModifiableRootModel>> graph = ModuleCompilerUtil.toChunkGraph(myContext.getModulesConfigurator().createGraphGenerator());
final Collection<Chunk<ModifiableRootModel>> chunks = graph.getNodes();
String cycles = "";
int count = 0;
for (Chunk<ModifiableRootModel> chunk : chunks) {
final Set<ModifiableRootModel> modules = chunk.getNodes();
String cycle = "";
for (ModifiableRootModel model : modules) {
cycle += ", " + model.getModule().getName();
}
if (modules.size() > 1) {
@NonNls final String br = "<br>&nbsp;&nbsp;&nbsp;&nbsp;";
cycles += br + (++count) + ". " + cycle.substring(2);
}
}
if (count > 0) {
@NonNls final String leftBrace = "<html>";
@NonNls final String rightBrace = "</html>";
final String warningMessage = leftBrace + ProjectBundle.message("module.circular.dependency.warning", cycles, count) + rightBrace;
final Project project = myContext.getProject();
final PlaceInProjectStructureBase place = new PlaceInProjectStructureBase(project, ProjectStructureConfigurable.getInstance(project).createModulesPlace());
problemsHolder.registerWarning("Circular dependencies", warningMessage, place, null);
}
}
@Override
public List<ProjectStructureElementUsage> getUsagesInElement() {
return Collections.emptyList();
}
@Override
public boolean highlightIfUnused() {
return false;
}
@Override
public boolean equals(Object obj) {
return obj instanceof GeneralProjectSettingsElement;
}
@Override
public int hashCode() {
return 0;
}
}
@@ -68,7 +68,6 @@ public class ModulesConfigurator implements ModulesProvider, ModuleEditor.Change
private static final Logger LOG = Logger.getInstance("#" + ModulesConfigurator.class.getName());
private final Project myProject;
private final ProjectConfigurable myProjectConfigurable;
private final List<ModuleEditor> myModuleEditors = new ArrayList<ModuleEditor>();
private final Comparator<ModuleEditor> myModuleEditorComparator = new Comparator<ModuleEditor>() {
public int compare(ModuleEditor editor1, ModuleEditor editor2) {
@@ -91,7 +90,6 @@ public class ModulesConfigurator implements ModulesProvider, ModuleEditor.Change
public ModulesConfigurator(Project project, ProjectSdksModel projectJdksModel) {
myProject = project;
myModuleModel = ModuleManager.getInstance(myProject).getModifiableModel();
myProjectConfigurable = new ProjectConfigurable(project, this, projectJdksModel);
}
public void setContext(final StructureConfigurableContext context) {
@@ -202,7 +200,6 @@ public class ModulesConfigurator implements ModulesProvider, ModuleEditor.Change
}
public void moduleStateChanged(final ModifiableRootModel moduleRootModel) {
myProjectConfigurable.updateCircularDependencyWarning();
for (ModuleEditor.ChangeListener listener : myAllModulesChangeListeners) {
listener.moduleStateChanged(moduleRootModel);
}
@@ -16,7 +16,6 @@
package com.intellij.openapi.roots.ui.configuration;
import com.intellij.compiler.ModuleCompilerUtil;
import com.intellij.ide.util.BrowseFilesListener;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.StorageScheme;
@@ -31,10 +30,11 @@ import com.intellij.openapi.roots.CompilerProjectExtension;
import com.intellij.openapi.roots.LanguageLevelProjectExtension;
import com.intellij.openapi.roots.ModifiableRootModel;
import com.intellij.openapi.roots.ui.configuration.projectRoot.ProjectSdksModel;
import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.ProjectConfigurationProblems;
import com.intellij.openapi.roots.ui.configuration.projectRoot.ProjectStructureElementConfigurable;
import com.intellij.openapi.roots.ui.configuration.projectRoot.StructureConfigurableContext;
import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.*;
import com.intellij.openapi.ui.DetailsComponent;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.ui.NamedConfigurable;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.EmptyRunnable;
import com.intellij.openapi.util.IconLoader;
@@ -44,30 +44,26 @@ import com.intellij.pom.java.LanguageLevel;
import com.intellij.ui.DocumentAdapter;
import com.intellij.ui.FieldPanel;
import com.intellij.ui.InsertPathAction;
import com.intellij.util.Alarm;
import com.intellij.util.Chunk;
import com.intellij.util.graph.Graph;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import java.awt.*;
import java.io.IOException;
import java.util.Collection;
import java.util.Set;
/**
* @author Eugene Zhuravlev
* Date: Dec 15, 2003
*/
public class ProjectConfigurable extends NamedConfigurable<Project> implements DetailsComponent.Facade {
public class ProjectConfigurable extends ProjectStructureElementConfigurable<Project> implements DetailsComponent.Facade {
private final Project myProject;
private static final Icon PROJECT_ICON = IconLoader.getIcon("/nodes/project.png");
private boolean myStartModuleWizardOnShow;
private LanguageLevelCombo myLanguageLevelCombo;
private ProjectJdkConfigurable myProjectJdkConfigurable;
@@ -77,22 +73,69 @@ public class ProjectConfigurable extends NamedConfigurable<Project> implements D
private JPanel myPanel;
private final Alarm myUpdateWarningAlarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD);
private final JLabel myWarningLabel = new JLabel("");
private final StructureConfigurableContext myContext;
private final ModulesConfigurator myModulesConfigurator;
private JPanel myWholePanel;
private boolean myFreeze = false;
private DetailsComponent myDetailsComponent;
private CircularDependencyWarning myCircularDependencyWarning;
private final GeneralProjectSettingsElement mySettingsElement;
public ProjectConfigurable(Project project, ModulesConfigurator configurator, ProjectSdksModel model) {
public ProjectConfigurable(Project project,
final StructureConfigurableContext context,
ModulesConfigurator configurator,
ProjectSdksModel model) {
myProject = project;
myContext = context;
myModulesConfigurator = configurator;
mySettingsElement = new GeneralProjectSettingsElement(context);
final ProjectStructureDaemonAnalyzer daemonAnalyzer = context.getDaemonAnalyzer();
myModulesConfigurator.addAllModuleChangeListener(new ModuleEditor.ChangeListener() {
@Override
public void moduleStateChanged(ModifiableRootModel moduleRootModel) {
daemonAnalyzer.queueUpdate(mySettingsElement);
}
});
daemonAnalyzer.addListener(new ProjectStructureDaemonAnalyzerListener() {
@Override
public void usagesCollected(@NotNull ProjectStructureElement containingElement) {
}
@Override
public void problemsChanged(@NotNull ProjectStructureElement element) {
if (element instanceof GeneralProjectSettingsElement) {
updateCircularDependencyWarning();
}
}
@Override
public void allProblemsChanged() {
updateCircularDependencyWarning();
}
});
init(model);
}
private void updateCircularDependencyWarning() {
ProjectStructureProblemsHolderImpl holder = myContext.getDaemonAnalyzer().getProblemsHolder(mySettingsElement);
final ProjectStructureProblemDescription item = holder != null ? ContainerUtil.getFirstItem(holder.getProblemDescriptions()) : null;
if (item != null) {
myWarningLabel.setIcon(Messages.getWarningIcon());
myWarningLabel.setText(item.getDescription());
}
else {
myWarningLabel.setIcon(null);
myWarningLabel.setText("");
}
myWarningLabel.repaint();
}
@Override
public ProjectStructureElement getProjectStructureElement() {
return mySettingsElement;
}
public DetailsComponent getDetailsComponent() {
return myDetailsComponent;
}
@@ -155,7 +198,6 @@ public class ProjectConfigurable extends NamedConfigurable<Project> implements D
}
public void disposeUIResources() {
myUpdateWarningAlarm.cancelAllRequests();
if (myProjectJdkConfigurable != null) {
myProjectJdkConfigurable.disposeUIResources();
}
@@ -165,12 +207,11 @@ public class ProjectConfigurable extends NamedConfigurable<Project> implements D
myFreeze = true;
try {
myProjectJdkConfigurable.reset();
final String compilerOutput = CompilerProjectExtension.getInstance(myProject).getCompilerOutputUrl();
final String compilerOutput = getOriginalCompilerOutputUrl();
if (compilerOutput != null) {
myProjectCompilerOutput.setText(FileUtil.toSystemDependentName(VfsUtil.urlToPath(compilerOutput)));
}
myLanguageLevelCombo.reset(myProject);
updateCircularDependencyWarning();
if (myProjectName != null) {
myProjectName.setText(myProject.getName());
@@ -179,55 +220,8 @@ public class ProjectConfigurable extends NamedConfigurable<Project> implements D
finally {
myFreeze = false;
}
}
void updateCircularDependencyWarning() {
myUpdateWarningAlarm.cancelAllRequests();
myUpdateWarningAlarm.addRequest(new Runnable() {
public void run() {
ApplicationManager.getApplication().runReadAction(new Runnable(){
public void run() {
final Graph<Chunk<ModifiableRootModel>> graph = ModuleCompilerUtil.toChunkGraph(myModulesConfigurator.createGraphGenerator());
final Collection<Chunk<ModifiableRootModel>> chunks = graph.getNodes();
String cycles = "";
int count = 0;
for (Chunk<ModifiableRootModel> chunk : chunks) {
final Set<ModifiableRootModel> modules = chunk.getNodes();
String cycle = "";
for (ModifiableRootModel model : modules) {
cycle += ", " + model.getModule().getName();
}
if (modules.size() > 1) {
@NonNls final String br = "<br>&nbsp;&nbsp;&nbsp;&nbsp;";
cycles += br + (++count) + ". " + cycle.substring(2);
}
}
@NonNls final String leftBrace = "<html>";
@NonNls final String rightBrace = "</html>";
final String warningMessage =
leftBrace + (count > 0 ? ProjectBundle.message("module.circular.dependency.warning", cycles, count) : "") + rightBrace;
if (ProjectConfigurationProblems.isVisible()) {
if (myCircularDependencyWarning != null) {
ConfigurationErrors.Bus.removeError(myCircularDependencyWarning, myProject);
}
if (count > 0) {
myCircularDependencyWarning = new CircularDependencyWarning("Circular dependencies", warningMessage);
ConfigurationErrors.Bus.addError(myCircularDependencyWarning, myProject);
}
}
final Icon icon = count > 0 ? Messages.getWarningIcon() : null;
//noinspection SSBasedInspection
SwingUtilities.invokeLater(new Runnable() {
public void run() {
myWarningLabel.setIcon(icon);
myWarningLabel.setText(warningMessage);
myWarningLabel.repaint();
}
});
}
});
}
}, 300);
myContext.getDaemonAnalyzer().queueUpdate(mySettingsElement);
}
@@ -307,8 +301,7 @@ public class ProjectConfigurable extends NamedConfigurable<Project> implements D
if (!LanguageLevelProjectExtension.getInstance(myProject).getLanguageLevel().equals(myLanguageLevelCombo.getSelectedItem())) {
return true;
}
final CompilerProjectExtension compilerProjectExtension = CompilerProjectExtension.getInstance(myProject);
final String compilerOutput = compilerProjectExtension.getCompilerOutputUrl();
final String compilerOutput = getOriginalCompilerOutputUrl();
if (!Comparing.strEqual(FileUtil.toSystemIndependentName(VfsUtil.urlToPath(compilerOutput)),
FileUtil.toSystemIndependentName(myProjectCompilerOutput.getText()))) return true;
if (myProjectJdkConfigurable.isModified()) return true;
@@ -319,6 +312,12 @@ public class ProjectConfigurable extends NamedConfigurable<Project> implements D
return false;
}
@Nullable
private String getOriginalCompilerOutputUrl() {
final CompilerProjectExtension extension = CompilerProjectExtension.getInstance(myProject);
return extension != null ? extension.getCompilerOutputUrl() : null;
}
private void createUIComponents() {
myLanguageLevelCombo = new LanguageLevelCombo();
final JTextField textField = new JTextField();
@@ -333,18 +332,4 @@ public class ProjectConfigurable extends NamedConfigurable<Project> implements D
public String getCompilerOutputUrl() {
return VfsUtil.pathToUrl(myProjectCompilerOutput.getText().trim());
}
private static class CircularDependencyWarning extends ConfigurationError {
private CircularDependencyWarning(String plainTextTitle, String description) {
super(plainTextTitle, description);
}
@Override
public void fix(JComponent contextComponent) {
}
@Override
public void navigate() {
}
}
}
@@ -248,7 +248,7 @@ public class ProjectStructureConfigurable extends BaseConfigurable implements Se
}
private void addProjectConfig() {
myProjectConfig = new ProjectConfigurable(myProject, myModuleConfigurator, myProjectJdksModel);
myProjectConfig = new ProjectConfigurable(myProject, myContext, myModuleConfigurator, myProjectJdksModel);
addConfigurable(myProjectConfig);
}
@@ -373,7 +373,7 @@ public class ProjectStructureConfigurable extends BaseConfigurable implements Se
}
public ActionCallback select(@Nullable final String moduleToSelect, @Nullable String editorNameToSelect, final boolean requestFocus) {
Place place = createPlaceFor(myModulesConfig);
Place place = createModulesPlace();
if (moduleToSelect != null) {
final Module module = ModuleManager.getInstance(myProject).findModuleByName(moduleToSelect);
assert module != null;
@@ -382,12 +382,16 @@ public class ProjectStructureConfigurable extends BaseConfigurable implements Se
return navigateTo(place, requestFocus);
}
public Place createModulesPlace() {
return createPlaceFor(myModulesConfig);
}
public Place createModulePlace(@NotNull Module module) {
return createPlaceFor(myModulesConfig).putPath(ModuleStructureConfigurable.TREE_OBJECT, module);
return createModulesPlace().putPath(ModuleStructureConfigurable.TREE_OBJECT, module);
}
public ActionCallback select(@Nullable final Facet facetToSelect, final boolean requestFocus) {
Place place = createPlaceFor(myModulesConfig);
Place place = createModulesPlace();
if (facetToSelect != null) {
place = place.putPath(ModuleStructureConfigurable.TREE_OBJECT, facetToSelect);
}