bc6dfabe7edb0941c816f04040dcf349dab32801
[idea/community.git] / java / idea-ui / src / com / intellij / openapi / roots / ui / configuration / classpath / AnalyzeModuleDependencyAction.java
1 // Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.openapi.roots.ui.configuration.classpath;
3
4 import com.intellij.CommonBundle;
5 import com.intellij.analysis.AnalysisScope;
6 import com.intellij.codeInsight.CodeInsightBundle;
7 import com.intellij.ide.JavaUiBundle;
8 import com.intellij.openapi.actionSystem.AnAction;
9 import com.intellij.openapi.actionSystem.AnActionEvent;
10 import com.intellij.openapi.diagnostic.Logger;
11 import com.intellij.openapi.module.Module;
12 import com.intellij.openapi.module.impl.scopes.LibraryScope;
13 import com.intellij.openapi.roots.*;
14 import com.intellij.openapi.roots.libraries.Library;
15 import com.intellij.openapi.roots.ui.configuration.ModulesProvider;
16 import com.intellij.openapi.ui.Messages;
17 import com.intellij.openapi.util.text.StringUtil;
18 import com.intellij.openapi.vfs.VirtualFile;
19 import com.intellij.openapi.wm.ToolWindowId;
20 import com.intellij.packageDependencies.DependenciesBuilder;
21 import com.intellij.packageDependencies.DependencyVisitorFactory;
22 import com.intellij.packageDependencies.actions.AnalyzeDependenciesOnSpecifiedTargetHandler;
23 import com.intellij.psi.PsiFile;
24 import com.intellij.psi.search.GlobalSearchScope;
25 import org.jetbrains.annotations.Contract;
26 import org.jetbrains.annotations.NotNull;
27
28 import java.util.*;
29 import java.util.stream.Collectors;
30
31 class AnalyzeModuleDependencyAction extends AnAction {
32   private static final Logger LOG = Logger.getInstance(AnalyzeModuleDependencyAction.class);
33   private final ClasspathPanel myPanel;
34
35   AnalyzeModuleDependencyAction(final ClasspathPanel panel) {
36     super(JavaUiBundle.message("action.text.analyze.this.dependency"));
37     myPanel = panel;
38   }
39
40   @Override
41   public void actionPerformed(@NotNull AnActionEvent e) {
42     final OrderEntry selectedEntry = myPanel.getSelectedEntry();
43     GlobalSearchScope mainScope = getScopeForOrderEntry(selectedEntry);
44     LOG.assertTrue(mainScope != null);
45     Map<GlobalSearchScope, OrderEntry> additionalScopes;
46     ModulesProvider modulesProvider = myPanel.getModuleConfigurationState().getModulesProvider();
47     if (selectedEntry instanceof ModuleOrderEntry) {
48       Module depModule = ((ModuleOrderEntry)selectedEntry).getModule();
49       LOG.assertTrue(depModule != null);
50       Map<OrderEntry, OrderEntry> additionalDependencies = JavaProjectRootsUtil
51         .findExportedDependenciesReachableViaThisDependencyOnly(myPanel.getRootModel().getModule(),
52                                                                 depModule, modulesProvider);
53       additionalScopes = new LinkedHashMap<>();
54       for (Map.Entry<OrderEntry, OrderEntry> entry : additionalDependencies.entrySet()) {
55         additionalScopes.put(getScopeForOrderEntry(entry.getKey()), entry.getValue());
56       }
57     }
58     else {
59       additionalScopes = Collections.emptyMap();
60     }
61
62     List<GlobalSearchScope> scopes = new ArrayList<>(additionalScopes.keySet());
63     scopes.add(mainScope);
64     new AnalyzeDependenciesOnSpecifiedTargetHandler(myPanel.getProject(), new AnalysisScope(myPanel.getModuleConfigurationState().getRootModel().getModule()),
65                                                     GlobalSearchScope.union(scopes.toArray(GlobalSearchScope.EMPTY_ARRAY))) {
66       @Override
67       protected boolean shouldShowDependenciesPanel(List<? extends DependenciesBuilder> builders) {
68         Set<GlobalSearchScope> usedScopes = findUsedScopes(builders, scopes);
69         if (usedScopes.contains(mainScope)) {
70           Messages.showInfoMessage(myProject,
71                                    JavaUiBundle
72                                      .message("message.text.dependencies.were.successfully.collected.in.0.toolwindow", ToolWindowId.DEPENDENCIES),
73                                    getTemplateText());
74           return true;
75         }
76
77         List<OrderEntry> usedEntries = usedScopes.stream().map(additionalScopes::get).filter(Objects::nonNull).distinct().collect(Collectors.toList());
78         if (usedEntries.isEmpty()) {
79           String message = "No code dependencies were found." + generateSkipImportsWarning() + " Would you like to remove the dependency?";
80           if (Messages.showOkCancelDialog(myProject, message, getTemplateText(), CommonBundle.message("button.remove"),
81                                           Messages.getCancelButton(),
82                                           Messages.getWarningIcon()) == Messages.OK) {
83             myPanel.getRootModel().removeOrderEntry(selectedEntry);
84           }
85           return false;
86         }
87
88         String usedExportedEntriesText = "'" + usedEntries.get(0).getPresentableName() + "'";
89         if (usedEntries.size() == 2) {
90           usedExportedEntriesText += " and '" + usedEntries.get(1).getPresentableName() + "'";
91         }
92         else if (usedEntries.size() > 2) {
93           usedExportedEntriesText += " and " + (usedEntries.size() - 1) + " more dependencies";
94         }
95
96         String replacementText = usedEntries.size() == 1 ? "a direct dependency on '" + usedEntries.get(0).getPresentableName() + "'" : "direct dependencies";
97         String message = "No direct code dependencies were found." + generateSkipImportsWarning() + "\nHowever " + usedExportedEntriesText + " exported by '"
98                          + StringUtil.decapitalize(selectedEntry.getPresentableName()) + "' " + (usedEntries.size() > 1 ? "are" : "is") + " used in code.\n" +
99                          "Do you want to replace dependency on '" + selectedEntry.getPresentableName() + "' by " + replacementText + "?";
100         String[] options = {"Replace", "Show Dependencies", Messages.getCancelButton()};
101         switch (Messages.showDialog(myProject, message, getTemplateText(), options, 0, Messages.getWarningIcon())) {
102           case 0:
103             InlineModuleDependencyAction.inlineEntry(myPanel, selectedEntry, usedEntries::contains);
104             return false;
105           case 1:
106             return true;
107           default:
108             return false;
109         }
110       }
111
112       @Override
113       protected boolean canStartInBackground() {
114         return false;
115       }
116     }.analyze();
117   }
118
119   private String generateSkipImportsWarning() {
120     if (DependencyVisitorFactory.VisitorOptions.fromSettings(myPanel.getProject()).skipImports()) {
121       return " " + CodeInsightBundle.message("dependencies.in.imports.message");
122     }
123     return "";
124   }
125
126   private static Set<GlobalSearchScope> findUsedScopes(List<? extends DependenciesBuilder> builders, List<? extends GlobalSearchScope> scopes) {
127     Set<GlobalSearchScope> usedScopes = new LinkedHashSet<>();
128     for (DependenciesBuilder builder : builders) {
129       for (Set<PsiFile> files : builder.getDependencies().values()) {
130         for (PsiFile file : files) {
131           VirtualFile virtualFile = file.getVirtualFile();
132           if (virtualFile != null) {
133             for (GlobalSearchScope scope : scopes) {
134               if (scope.contains(virtualFile)) {
135                 usedScopes.add(scope);
136               }
137             }
138           }
139         }
140       }
141     }
142     return usedScopes;
143   }
144
145   @Contract("null -> null")
146   private GlobalSearchScope getScopeForOrderEntry(OrderEntry selectedEntry) {
147     if (selectedEntry instanceof ModuleSourceOrderEntry) {
148       return GlobalSearchScope.moduleScope(selectedEntry.getOwnerModule());
149     }
150     if (selectedEntry instanceof ModuleOrderEntry) {
151       Module module = ((ModuleOrderEntry)selectedEntry).getModule();
152       return module != null ? GlobalSearchScope.moduleScope(module) : null;
153     }
154     if (selectedEntry instanceof LibraryOrderEntry) {
155       Library library = ((LibraryOrderEntry)selectedEntry).getLibrary();
156       return library != null ? new LibraryScope(myPanel.getProject(), library) : null;
157     }
158     return null;
159   }
160
161   @Override
162   public void update(@NotNull AnActionEvent e) {
163     final OrderEntry entry = myPanel.getSelectedEntry();
164     e.getPresentation().setVisible(entry instanceof ModuleOrderEntry && ((ModuleOrderEntry)entry).getModule() != null
165                                  || entry instanceof LibraryOrderEntry && ((LibraryOrderEntry)entry).getLibrary() != null);
166   }
167 }