project structure error highlighting: show warnings with quickfixes for unused project libraries

This commit is contained in:
nik
2011-10-07 10:22:58 +04:00
parent d14cc80149
commit f23f0b718f
21 changed files with 154 additions and 72 deletions
@@ -86,11 +86,6 @@ public class GeneralProjectSettingsElement extends ProjectStructureElement {
return Collections.emptyList();
}
@Override
public boolean highlightIfUnused() {
return false;
}
@Override
public String getId() {
return "project:general";
@@ -552,7 +552,7 @@ public class ModulesConfigurator implements ModulesProvider, ModuleEditor.Change
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), true, false);
myContext.getDaemonAnalyzer().queueUpdate(new ModuleProjectStructureElement(myContext, module));
return;
}
}
@@ -37,7 +37,7 @@ public class ArtifactErrorPanel {
private JPanel myMainPanel;
private JButton myFixButton;
private JLabel myErrorLabel;
private List<ConfigurationErrorQuickFix> myCurrentQuickFixes;
private List<? extends ConfigurationErrorQuickFix> myCurrentQuickFixes;
private String myErrorText;
public ArtifactErrorPanel(final ArtifactEditorImpl artifactEditor) {
@@ -83,7 +83,7 @@ public class ArtifactErrorPanel {
artifactEditor.queueValidation();
}
public void showError(@NotNull String message, @NotNull List<ConfigurationErrorQuickFix> quickFixes) {
public void showError(@NotNull String message, @NotNull List<? extends ConfigurationErrorQuickFix> quickFixes) {
myErrorLabel.setVisible(true);
final String errorText = "<html>" + message + "</html>";
if (myErrorLabel.isShowing()) {
@@ -128,11 +128,6 @@ public class ArtifactProjectStructureElement extends ProjectStructureElement {
return myOriginalArtifact.hashCode();
}
@Override
public boolean highlightIfUnused() {
return false;
}
@Override
public String getPresentableName() {
return "Artifact '" + getActualArtifactName() + "'";
@@ -73,7 +73,7 @@ public class ArtifactValidationManagerImpl implements Disposable {
if (problemDescriptions != null) {
for (ProjectStructureProblemDescription description : problemDescriptions) {
final String message = description.getMessage();
List<ConfigurationErrorQuickFix> quickFixes = Collections.emptyList();
List<? extends ConfigurationErrorQuickFix> quickFixes = Collections.emptyList();
if (description instanceof ArtifactProblemDescription) {
final ArtifactProblemDescription artifactProblem = (ArtifactProblemDescription)description;
quickFixes = artifactProblem.getFixes();
@@ -82,7 +82,7 @@ public class ArtifactsStructureConfigurable extends BaseStructureConfigurable {
context.getModulesConfigurator().addAllModuleChangeListener(new ModuleEditor.ChangeListener() {
public void moduleStateChanged(ModifiableRootModel moduleRootModel) {
for (ProjectStructureElement element : getProjectStructureElements()) {
myContext.getDaemonAnalyzer().queueUpdate(element, true, false);
myContext.getDaemonAnalyzer().queueUpdate(element);
}
}
});
@@ -110,7 +110,7 @@ public class ArtifactsStructureConfigurableContextImpl implements ArtifactsStruc
}
public void queueValidation(Artifact artifact) {
myContext.getDaemonAnalyzer().queueUpdate(getOrCreateArtifactElement(artifact), true, false);
myContext.getDaemonAnalyzer().queueUpdate(getOrCreateArtifactElement(artifact));
}
public CompositePackagingElement<?> getRootElement(@NotNull Artifact artifact) {
@@ -32,6 +32,7 @@ import com.intellij.openapi.roots.libraries.*;
import com.intellij.openapi.roots.libraries.ui.OrderRoot;
import com.intellij.openapi.roots.ui.configuration.classpath.ClasspathPanel;
import com.intellij.openapi.roots.ui.configuration.projectRoot.LibrariesModifiableModel;
import com.intellij.openapi.roots.ui.configuration.projectRoot.ModuleStructureConfigurable;
import com.intellij.openapi.ui.popup.PopupStep;
import com.intellij.openapi.ui.popup.util.BaseListPopupStep;
import com.intellij.openapi.util.io.FileUtil;
@@ -41,6 +42,7 @@ import com.intellij.util.ParameterizedRunnable;
import com.intellij.util.PathUtil;
import com.intellij.util.PlatformIcons;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.*;
@@ -199,4 +201,14 @@ public class LibraryEditingUtil {
}
};
}
public static List<Module> getSuitableModules(@NotNull ModuleStructureConfigurable rootConfigurable, final @Nullable LibraryType type) {
final List<Module> modules = new ArrayList<Module>();
for (Module module : rootConfigurable.getModules()) {
if (type == null || type.isSuitableModule(module, rootConfigurable.getFacetConfigurator())) {
modules.add(module);
}
}
return modules;
}
}
@@ -21,7 +21,6 @@ import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.fileChooser.FileChooser;
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.libraries.Library;
@@ -111,23 +110,13 @@ public class CreateNewLibraryAction extends DumbAwareAction {
rootConfigurable.selectNodeInTree(libraryNode);
}
private static List<Module> getSuitableModules(@NotNull ModuleStructureConfigurable rootConfigurable, final @Nullable LibraryType type) {
final List<Module> modules = new ArrayList<Module>();
for (Module module : rootConfigurable.getModules()) {
if (type == null || type.isSuitableModule(module, rootConfigurable.getFacetConfigurator())) {
modules.add(module);
}
}
return modules;
}
public static AnAction[] createActionOrGroup(@NotNull String text, @NotNull BaseLibrariesConfigurable librariesConfigurable, final @NotNull Project project) {
final LibraryType<?>[] extensions = LibraryType.EP_NAME.getExtensions();
List<LibraryType<?>> suitableTypes = new ArrayList<LibraryType<?>>();
if (librariesConfigurable instanceof ProjectLibrariesConfigurable) {
final ModuleStructureConfigurable configurable = ModuleStructureConfigurable.getInstance(project);
for (LibraryType<?> extension : extensions) {
if (!getSuitableModules(configurable, extension).isEmpty()) {
if (!LibraryEditingUtil.getSuitableModules(configurable, extension).isEmpty()) {
suitableTypes.add(extension);
}
}
@@ -26,6 +26,7 @@ import com.intellij.openapi.roots.impl.libraries.LibraryImpl;
import com.intellij.openapi.roots.libraries.Library;
import com.intellij.openapi.roots.libraries.LibraryTable;
import com.intellij.openapi.roots.libraries.LibraryTablePresentation;
import com.intellij.openapi.roots.libraries.LibraryTablesRegistrar;
import com.intellij.openapi.roots.ui.configuration.artifacts.UsageInArtifact;
import com.intellij.openapi.roots.ui.configuration.libraries.LibraryEditingUtil;
import com.intellij.openapi.roots.ui.configuration.libraryEditor.CreateNewLibraryAction;
@@ -51,6 +52,14 @@ public abstract class BaseLibrariesConfigurable extends BaseStructureConfigurabl
super(project);
}
public static BaseLibrariesConfigurable getInstance(@NotNull Project project, @NotNull String tableLevel) {
if (tableLevel.equals(LibraryTablesRegistrar.PROJECT_LEVEL)) {
return ProjectLibrariesConfigurable.getInstance(project);
}
else {
return GlobalLibrariesConfigurable.getInstance(project);
}
}
public abstract LibraryTablePresentation getLibraryTablePresentation();
@@ -208,8 +217,10 @@ public abstract class BaseLibrariesConfigurable extends BaseStructureConfigurabl
}
}
public void removeLibraryNode(@NotNull Library library) {
final MyNode node = findNodeByObject(myRoot, library);
public void removeLibrary(@NotNull LibraryProjectStructureElement element) {
getModelProvider().getModifiableModel().removeLibrary(element.getLibrary());
myContext.getDaemonAnalyzer().removeElement(element);
final MyNode node = findNodeByObject(myRoot, element.getLibrary());
if (node != null) {
removePaths(TreeUtil.getPathFromRoot(node));
}
@@ -61,14 +61,14 @@ public class ChangeLibraryLevelAction extends ChangeLibraryLevelActionBase {
final ProjectStructureElement selectedElement = mySourceConfigurable.getSelectedElement();
if (!(selectedElement instanceof LibraryProjectStructureElement)) return;
final StructureConfigurableContext context = mySourceConfigurable.myContext;
final Library originalLibrary = ((LibraryProjectStructureElement)selectedElement).getLibrary();
final LibraryEx oldLibrary = (LibraryEx)context.getLibrary(originalLibrary.getName(), mySourceConfigurable.getLevel());
final LibraryProjectStructureElement libraryElement = (LibraryProjectStructureElement)selectedElement;
final LibraryEx oldLibrary = (LibraryEx)context.getLibrary(libraryElement.getLibrary().getName(), mySourceConfigurable.getLevel());
LOG.assertTrue(oldLibrary != null);
final Library newLibrary = doCopy(oldLibrary);
if (newLibrary == null) return;
final ModulesConfigurator configurator = context.getModulesConfigurator();
final Collection<ProjectStructureElementUsage> usages = context.getDaemonAnalyzer().getUsages(selectedElement);
final Collection<ProjectStructureElementUsage> usages = context.getDaemonAnalyzer().getUsages(libraryElement);
for (ProjectStructureElementUsage usage : usages) {
if (usage instanceof UsageInModuleClasspath) {
final Module module = ((UsageInModuleClasspath)usage).getModule();
@@ -80,16 +80,14 @@ public class ChangeLibraryLevelAction extends ChangeLibraryLevelActionBase {
}
}
else if (usage instanceof UsageInArtifact) {
final PackagingElement<?> libraryElement = PackagingElementFactory.getInstance().createLibraryFiles(newLibrary.getName(),
newLibrary.getTable().getTableLevel(), null);
((UsageInArtifact)usage).replaceElement(libraryElement);
final PackagingElement<?> newLibraryElement = PackagingElementFactory.getInstance().createLibraryFiles(newLibrary.getName(),
newLibrary.getTable().getTableLevel(), null);
((UsageInArtifact)usage).replaceElement(newLibraryElement);
}
}
if (!myCopy) {
mySourceConfigurable.getModelProvider().getModifiableModel().removeLibrary(originalLibrary);
context.getDaemonAnalyzer().removeElement(selectedElement);
mySourceConfigurable.removeLibraryNode(originalLibrary);
mySourceConfigurable.removeLibrary(libraryElement);
}
ProjectStructureConfigurable.getInstance(myProject).selectProjectOrGlobalLibrary(newLibrary, true);
}
@@ -323,7 +323,6 @@ public class ModuleStructureConfigurable extends BaseStructureConfigurable imple
((DefaultTreeModel)myTree.getModel()).reload(parent);
}
myContext.getDaemonAnalyzer().removeElement(new LibraryProjectStructureElement(myContext, library));
// TODO: myContext.invalidateModules(myContext.myLibraryDependencyCache.get(library.getName()));
}
}
@@ -469,14 +468,6 @@ public class ModuleStructureConfigurable extends BaseStructureConfigurable imple
}
modelProxy.addLibraryEntry(library);
myContext.getDaemonAnalyzer().queueUpdate(new ModuleProjectStructureElement(myContext, module));
/* TODO
Set<String> modules = myContext.myLibraryDependencyCache.get(library.getName());
if (modules == null) {
modules = new HashSet<String>();
myContext.myLibraryDependencyCache.put(library.getName(), modules);
}
modules.add(module.getName());
*/
myTree.repaint();
}
@@ -43,11 +43,6 @@ public class FacetProjectStructureElement extends ProjectStructureElement {
return Collections.emptyList();
}
@Override
public boolean highlightIfUnused() {
return false;
}
@Override
public String getPresentableName() {
return "Facet '" + myFacet.getName() + "' in module '" + myFacet.getModule().getName() + "'";
@@ -1,6 +1,8 @@
package com.intellij.openapi.roots.ui.configuration.projectRoot.daemon;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectBundle;
import com.intellij.openapi.roots.JavadocOrderRootType;
import com.intellij.openapi.roots.OrderRootType;
import com.intellij.openapi.roots.impl.libraries.LibraryEx;
@@ -8,18 +10,19 @@ import com.intellij.openapi.roots.impl.libraries.LibraryImpl;
import com.intellij.openapi.roots.libraries.Library;
import com.intellij.openapi.roots.libraries.LibraryTable;
import com.intellij.openapi.roots.libraries.LibraryTablesRegistrar;
import com.intellij.openapi.roots.ui.configuration.ChooseModulesDialog;
import com.intellij.openapi.roots.ui.configuration.ModuleEditor;
import com.intellij.openapi.roots.ui.configuration.ProjectStructureConfigurable;
import com.intellij.openapi.roots.ui.configuration.libraries.LibraryEditingUtil;
import com.intellij.openapi.roots.ui.configuration.libraryEditor.ExistingLibraryEditor;
import com.intellij.openapi.roots.ui.configuration.projectRoot.LibrariesModifiableModel;
import com.intellij.openapi.roots.ui.configuration.projectRoot.LibraryConfigurable;
import com.intellij.openapi.roots.ui.configuration.projectRoot.StructureConfigurableContext;
import com.intellij.openapi.roots.ui.configuration.projectRoot.*;
import com.intellij.openapi.ui.NamedConfigurable;
import com.intellij.openapi.vfs.VfsUtil;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -126,11 +129,18 @@ public class LibraryProjectStructureElement extends ProjectStructureElement {
}
@Override
public boolean highlightIfUnused() {
public boolean shouldShowWarningIfUnused() {
final LibraryTable libraryTable = myLibrary.getTable();
return libraryTable != null && LibraryTablesRegistrar.PROJECT_LEVEL.equals(libraryTable.getTableLevel());
}
@Override
public ProjectStructureProblemDescription createUnusedElementWarning() {
final List<ConfigurationErrorQuickFix> fixes = Arrays.asList(new AddLibraryToDependenciesFix(), new RemoveLibraryFix());
return new ProjectStructureProblemDescription(getPresentableName() + " is not used", null, createPlace(), fixes,
ProjectStructureProblemType.warning("unused-library"));
}
@Override
public String getPresentableName() {
return "Library '" + myLibrary.getName() + "'";
@@ -175,4 +185,39 @@ public class LibraryProjectStructureElement extends ProjectStructureElement {
}
}
}
private class AddLibraryToDependenciesFix extends ConfigurationErrorQuickFix {
private AddLibraryToDependenciesFix() {
super("Add to Dependencies...");
}
@Override
public void performFix() {
final Project project = myContext.getProject();
final ModuleStructureConfigurable moduleStructureConfigurable = ModuleStructureConfigurable.getInstance(project);
final List<Module> modules = LibraryEditingUtil.getSuitableModules(moduleStructureConfigurable, ((LibraryEx)myLibrary).getType());
if (modules.isEmpty()) return;
final ChooseModulesDialog dlg = new ChooseModulesDialog(project, modules, ProjectBundle.message("choose.modules.dialog.title"),
ProjectBundle
.message("choose.modules.dialog.description", myLibrary.getName()));
dlg.show();
if (dlg.isOK()) {
final List<Module> chosenModules = dlg.getChosenElements();
for (Module module : chosenModules) {
moduleStructureConfigurable.addLibraryOrderEntry(module, myLibrary);
}
}
}
}
private class RemoveLibraryFix extends ConfigurationErrorQuickFix {
private RemoveLibraryFix() {
super("Remove Library");
}
@Override
public void performFix() {
BaseLibrariesConfigurable.getInstance(myContext.getProject(), myLibrary.getTable().getTableLevel()).removeLibrary(LibraryProjectStructureElement.this);
}
}
}
@@ -129,11 +129,6 @@ public class ModuleProjectStructureElement extends ProjectStructureElement {
return myModule.hashCode();
}
@Override
public boolean highlightIfUnused() {
return false;
}
@Override
public String getPresentableName() {
return "Module '" + myModule.getName() + "'";
@@ -26,6 +26,8 @@ public class ProjectStructureDaemonAnalyzer implements Disposable {
private final MultiValuesMap<ProjectStructureElement, ProjectStructureElementUsage> mySourceElement2Usages = new MultiValuesMap<ProjectStructureElement, ProjectStructureElementUsage>();
private final MultiValuesMap<ProjectStructureElement, ProjectStructureElementUsage> myContainingElement2Usages = new MultiValuesMap<ProjectStructureElement, ProjectStructureElementUsage>();
private final Set<ProjectStructureElement> myElementWithNotCalculatedUsages = new HashSet<ProjectStructureElement>();
private final Set<ProjectStructureElement> myElementsToShowWarningIfUnused = new HashSet<ProjectStructureElement>();
private final Map<ProjectStructureElement, ProjectStructureProblemDescription> myWarningsAboutUnused = new HashMap<ProjectStructureElement, ProjectStructureProblemDescription>();
private final MergingUpdateQueue myAnalyzerQueue;
private final EventDispatcher<ProjectStructureDaemonAnalyzerListener> myDispatcher = EventDispatcher.create(ProjectStructureDaemonAnalyzerListener.class);
private final AtomicBoolean myStopped = new AtomicBoolean(false);
@@ -71,6 +73,9 @@ public class ProjectStructureDaemonAnalyzer implements Disposable {
if (LOG.isDebugEnabled()) {
LOG.debug("updating problems for " + element);
}
final ProjectStructureProblemDescription warning = myWarningsAboutUnused.get(element);
if (warning != null)
problemsHolder.registerProblem(warning);
myProblemHolders.put(element, problemsHolder);
myDispatcher.getMulticaster().problemsChanged(element);
}
@@ -108,6 +113,7 @@ public class ProjectStructureDaemonAnalyzer implements Disposable {
addUsage(usage);
}
myElementWithNotCalculatedUsages.remove(element);
reportUnusedElements();
}
private static void invokeLater(Runnable runnable) {
@@ -125,11 +131,16 @@ public class ProjectStructureDaemonAnalyzer implements Disposable {
if (collectUsages) {
myElementWithNotCalculatedUsages.add(element);
}
if (element.shouldShowWarningIfUnused()) {
myElementsToShowWarningIfUnused.add(element);
}
myAnalyzerQueue.queue(new AnalyzeElementUpdate(element, check, collectUsages));
}
public void removeElement(ProjectStructureElement element) {
myElementWithNotCalculatedUsages.remove(element);
myElementsToShowWarningIfUnused.remove(element);
myWarningsAboutUnused.remove(element);
myProblemHolders.remove(element);
final Collection<ProjectStructureElementUsage> usages = mySourceElement2Usages.removeAll(element);
if (usages != null) {
@@ -139,10 +150,42 @@ public class ProjectStructureDaemonAnalyzer implements Disposable {
}
removeUsagesInElement(element);
myDispatcher.getMulticaster().problemsChanged(element);
reportUnusedElements();
}
private void reportUnusedElements() {
if (!myElementWithNotCalculatedUsages.isEmpty()) return;
for (ProjectStructureElement element : myElementsToShowWarningIfUnused) {
final ProjectStructureProblemDescription warning;
final Collection<ProjectStructureElementUsage> usages = mySourceElement2Usages.get(element);
if (usages == null || usages.isEmpty()) {
warning = element.createUnusedElementWarning();
}
else {
warning = null;
}
final ProjectStructureProblemDescription old = myWarningsAboutUnused.put(element, warning);
ProjectStructureProblemsHolderImpl holder = myProblemHolders.get(element);
if (holder == null) {
holder = new ProjectStructureProblemsHolderImpl();
myProblemHolders.put(element, holder);
}
if (old != null) {
holder.removeProblem(old);
}
if (warning != null) {
holder.registerProblem(warning);
}
if (old != null || warning != null) {
myDispatcher.getMulticaster().problemsChanged(element);
}
}
}
public boolean isUnused(ProjectStructureElement element) {
if (!element.highlightIfUnused()) {
if (!element.shouldShowWarningIfUnused()) {
return false;
}
if (!myElementWithNotCalculatedUsages.isEmpty()) {
@@ -2,6 +2,7 @@ package com.intellij.openapi.roots.ui.configuration.projectRoot.daemon;
import com.intellij.openapi.roots.ui.configuration.projectRoot.StructureConfigurableContext;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
@@ -23,7 +24,16 @@ public abstract class ProjectStructureElement {
public abstract List<ProjectStructureElementUsage> getUsagesInElement();
public abstract boolean highlightIfUnused();
public boolean shouldShowWarningIfUnused() {
return false;
}
@Nullable
public ProjectStructureProblemDescription createUnusedElementWarning() {
return null;
}
@Override
public abstract boolean equals(Object obj);
@@ -27,13 +27,13 @@ public class ProjectStructureProblemDescription {
private final String myMessage;
private final String myDescription;
private final PlaceInProjectStructure myPlace;
private final List<ConfigurationErrorQuickFix> myFixes;
private final List<? extends ConfigurationErrorQuickFix> myFixes;
private final ProjectStructureProblemType myProblemType;
public ProjectStructureProblemDescription(@NotNull String message,
@Nullable String description,
@NotNull PlaceInProjectStructure place,
@NotNull List<ConfigurationErrorQuickFix> fixes,
@NotNull List<? extends ConfigurationErrorQuickFix> fixes,
@NotNull ProjectStructureProblemType problemType) {
myMessage = message;
myDescription = description;
@@ -51,7 +51,7 @@ public class ProjectStructureProblemDescription {
return myDescription;
}
public List<ConfigurationErrorQuickFix> getFixes() {
public List<? extends ConfigurationErrorQuickFix> getFixes() {
return myFixes;
}
@@ -70,6 +70,12 @@ public class ProjectStructureProblemsHolderImpl implements ProjectStructureProbl
return myProblemDescriptions != null && !myProblemDescriptions.isEmpty();
}
public void removeProblem(@NotNull ProjectStructureProblemDescription description) {
if (myProblemDescriptions != null) {
myProblemDescriptions.remove(description);
}
}
@Nullable
public List<ProjectStructureProblemDescription> getProblemDescriptions() {
return myProblemDescriptions;
@@ -43,11 +43,6 @@ public class SdkProjectStructureElement extends ProjectStructureElement {
return mySdk.hashCode();
}
@Override
public boolean highlightIfUnused() {
return false;
}
@Override
public String getPresentableName() {
return "SDK '" + mySdk.getName() + "'";
@@ -312,6 +312,8 @@ project.roots.plain.mode.action.text.enabled=Show Module Groups
project.roots.tooltip.library.misconfigured=Library ''{0}'' has broken paths.
project.roots.error.message.invalid.classes.roots=invalid classes {0, choice, 1#root|2#roots}
project.roots.error.message.invalid.source.javadoc.roots=invalid source/javadoc {0, choice, 1#root|2#roots}
choose.modules.dialog.title=Choose modules
choose.modules.dialog.description=Library '' {0}'' will be added to the selected modules.
project.roots.tooltip.unused=''{0}'' is unused.
project.roots.javadoc.tab.description=Manage external JavaDocs attached to this module. External JavaDoc override JavaDoc annotations you might have in your module.
project.roots.output.compiler.title=Compiler output