[Gradle] Dependencies graph: show file dependencies IDEA-218166
[idea/community.git] / platform / external-system-impl / src / com / intellij / openapi / externalSystem / view / ExternalSystemViewDefaultContributor.java
1 // Copyright 2000-2019 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.externalSystem.view;
3
4 import com.intellij.icons.AllIcons;
5 import com.intellij.ide.projectView.PresentationData;
6 import com.intellij.openapi.externalSystem.model.DataNode;
7 import com.intellij.openapi.externalSystem.model.Key;
8 import com.intellij.openapi.externalSystem.model.ProjectKeys;
9 import com.intellij.openapi.externalSystem.model.ProjectSystemId;
10 import com.intellij.openapi.externalSystem.model.project.*;
11 import com.intellij.openapi.externalSystem.model.project.dependencies.*;
12 import com.intellij.openapi.externalSystem.model.task.TaskData;
13 import com.intellij.openapi.externalSystem.service.project.IdeModelsProviderImpl;
14 import com.intellij.openapi.externalSystem.settings.AbstractExternalSystemSettings;
15 import com.intellij.openapi.externalSystem.settings.ExternalProjectSettings;
16 import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil;
17 import com.intellij.openapi.externalSystem.util.ExternalSystemBundle;
18 import com.intellij.openapi.externalSystem.util.Order;
19 import com.intellij.openapi.project.Project;
20 import com.intellij.openapi.roots.OrderEntry;
21 import com.intellij.openapi.roots.ui.configuration.ProjectSettingsService;
22 import com.intellij.openapi.util.io.FileUtil;
23 import com.intellij.openapi.util.text.StringUtil;
24 import com.intellij.pom.Navigatable;
25 import com.intellij.ui.treeStructure.SimpleTree;
26 import com.intellij.util.ObjectUtils;
27 import com.intellij.util.SmartList;
28 import com.intellij.util.containers.ContainerUtil;
29 import com.intellij.util.containers.MultiMap;
30 import org.apache.commons.lang.StringEscapeUtils;
31 import org.jetbrains.annotations.NotNull;
32 import org.jetbrains.annotations.Nullable;
33
34 import java.awt.event.InputEvent;
35 import java.io.File;
36 import java.text.MessageFormat;
37 import java.util.*;
38 import java.util.function.Supplier;
39
40 import static com.intellij.openapi.externalSystem.model.ProjectKeys.PROJECT;
41
42 /**
43  * @author Vladislav.Soroka
44  */
45 public class ExternalSystemViewDefaultContributor extends ExternalSystemViewContributor {
46
47   private static final Key<?>[] KEYS = new Key[]{
48     ProjectKeys.MODULE,
49     ProjectKeys.DEPENDENCIES_GRAPH,
50     ProjectKeys.MODULE_DEPENDENCY,
51     ProjectKeys.LIBRARY_DEPENDENCY,
52     ProjectKeys.TASK
53   };
54
55   @NotNull
56   @Override
57   public ProjectSystemId getSystemId() {
58     return ProjectSystemId.IDE;
59   }
60
61   @NotNull
62   @Override
63   public List<Key<?>> getKeys() {
64     return Arrays.asList(KEYS);
65   }
66
67   @Override
68   @NotNull
69   public List<ExternalSystemNode<?>> createNodes(final ExternalProjectsView externalProjectsView,
70                                                  final MultiMap<Key<?>, DataNode<?>> dataNodes) {
71     final List<ExternalSystemNode<?>> result = new SmartList<>();
72
73     addModuleNodes(externalProjectsView, dataNodes, result);
74     // add tasks
75     Collection<DataNode<?>> tasksNodes = dataNodes.get(ProjectKeys.TASK);
76     if (!tasksNodes.isEmpty()) {
77       TasksNode tasksNode = new TasksNode(externalProjectsView, tasksNodes);
78       if (externalProjectsView.useTasksNode()) {
79         result.add(tasksNode);
80       }
81       else {
82         ContainerUtil.addAll(result, tasksNode.getChildren());
83       }
84     }
85
86     addDependenciesNode(externalProjectsView, dataNodes, result);
87
88     return result;
89   }
90
91   @Nullable
92   @Override
93   public String getDisplayName(@NotNull DataNode node) {
94     return getNodeDisplayName(node);
95   }
96
97   private static void addDependenciesNode(@NotNull ExternalProjectsView externalProjectsView,
98                                           @NotNull MultiMap<Key<?>, DataNode<?>> dataNodes,
99                                           @NotNull List<? super ExternalSystemNode<?>> result) {
100     final Collection<DataNode<?>> depsGraph = dataNodes.get(ProjectKeys.DEPENDENCIES_GRAPH);
101     if (!depsGraph.isEmpty()) {
102       final ExternalSystemNode<?> depNode = new MyDependenciesNode(externalProjectsView);
103       for (DataNode<?> dataNode : depsGraph) {
104         if (!(dataNode.getData() instanceof ProjectDependencies)) continue;
105
106         ProjectDependencies projectDependencies = (ProjectDependencies)dataNode.getData();
107         for (ComponentDependencies componentDependencies : projectDependencies.getComponentsDependencies()) {
108           depNode.add(new DependencyScopeExternalSystemNode(externalProjectsView, componentDependencies.getCompileDependenciesGraph()));
109           depNode.add(new DependencyScopeExternalSystemNode(externalProjectsView, componentDependencies.getRuntimeDependenciesGraph()));
110         }
111       }
112
113       if (depNode.hasChildren()) {
114         result.add(depNode);
115       }
116       return;
117     }
118
119     final Collection<DataNode<?>> moduleDeps = dataNodes.get(ProjectKeys.MODULE_DEPENDENCY);
120     final Collection<DataNode<?>> libDeps = dataNodes.get(ProjectKeys.LIBRARY_DEPENDENCY);
121
122     if (!moduleDeps.isEmpty() || !libDeps.isEmpty()) {
123       final ExternalSystemNode<?> depNode = new MyDependenciesNode(externalProjectsView);
124       boolean addDepNode = false;
125
126       for (DataNode<?> dataNode : moduleDeps) {
127         if (!(dataNode.getData() instanceof ModuleDependencyData)) continue;
128         //noinspection unchecked
129         ModuleDependencyDataExternalSystemNode moduleDependencyDataExternalSystemNode =
130           new ModuleDependencyDataExternalSystemNode(externalProjectsView, (DataNode<ModuleDependencyData>)dataNode);
131         if (dataNode.getParent() != null && dataNode.getParent().getData() instanceof AbstractDependencyData) {
132           result.add(moduleDependencyDataExternalSystemNode);
133         }
134         else {
135           depNode.add(moduleDependencyDataExternalSystemNode);
136           addDepNode = true;
137         }
138       }
139
140       for (DataNode<?> dataNode : libDeps) {
141         if (!(dataNode.getData() instanceof LibraryDependencyData)) continue;
142         //noinspection unchecked
143         final ExternalSystemNode<LibraryDependencyData> libraryDependencyDataExternalSystemNode =
144           new LibraryDependencyDataExternalSystemNode(externalProjectsView, (DataNode<LibraryDependencyData>)dataNode);
145         if (((LibraryDependencyData)dataNode.getData()).getTarget().isUnresolved()) {
146           libraryDependencyDataExternalSystemNode.setErrorLevel(
147             ExternalProjectsStructure.ErrorLevel.ERROR,
148             "Unable to resolve " + ((LibraryDependencyData)dataNode.getData()).getTarget().getExternalName());
149         }
150         else {
151           libraryDependencyDataExternalSystemNode.setErrorLevel(ExternalProjectsStructure.ErrorLevel.NONE);
152         }
153         if (dataNode.getParent() != null && dataNode.getParent().getData() instanceof ModuleData) {
154           depNode.add(libraryDependencyDataExternalSystemNode);
155           addDepNode = true;
156         }
157         else {
158           result.add(libraryDependencyDataExternalSystemNode);
159         }
160       }
161
162       if (addDepNode) {
163         result.add(depNode);
164       }
165     }
166   }
167
168   private static void addModuleNodes(@NotNull ExternalProjectsView externalProjectsView,
169                                      @NotNull MultiMap<Key<?>, DataNode<?>> dataNodes,
170                                      @NotNull List<? super ExternalSystemNode<?>> result) {
171     final Collection<DataNode<?>> moduleDataNodes = dataNodes.get(ProjectKeys.MODULE);
172     if (!moduleDataNodes.isEmpty()) {
173       final AbstractExternalSystemSettings systemSettings =
174         ExternalSystemApiUtil.getSettings(externalProjectsView.getProject(), externalProjectsView.getSystemId());
175
176       final Map<String, ModuleNode> groupToModule = new HashMap<>(moduleDataNodes.size());
177
178       List<ModuleNode> moduleNodes = new ArrayList<>();
179
180       for (DataNode<?> dataNode : moduleDataNodes) {
181         final ModuleData data = (ModuleData)dataNode.getData();
182
183         final ExternalProjectSettings projectSettings = systemSettings.getLinkedProjectSettings(data.getLinkedExternalProjectPath());
184         DataNode<ProjectData> projectDataNode = ExternalSystemApiUtil.findParent(dataNode, PROJECT);
185         final boolean isRoot =
186           projectSettings != null && data.getLinkedExternalProjectPath().equals(projectSettings.getExternalProjectPath()) &&
187           projectDataNode != null && projectDataNode.getData().getInternalName().equals(data.getInternalName());
188         //noinspection unchecked
189         final ModuleNode moduleNode = new ModuleNode(externalProjectsView, (DataNode<ModuleData>)dataNode, null, isRoot);
190         moduleNodes.add(moduleNode);
191
192         String group = moduleNode.getIdeGrouping();
193         if (group != null) {
194           groupToModule.put(group, moduleNode);
195         }
196       }
197
198       for (ModuleNode moduleNode : moduleNodes) {
199         moduleNode.setAllModules(moduleNodes);
200         String parentGroup = moduleNode.getIdeParentGrouping();
201         ModuleNode parent = parentGroup != null ? groupToModule.get(parentGroup) : null;
202         if (parent == null) {
203           continue;
204         }
205         moduleNode.setParent(parent);
206       }
207
208       result.addAll(moduleNodes);
209     }
210   }
211
212   @Order(ExternalSystemNode.BUILTIN_DEPENDENCIES_DATA_NODE_ORDER)
213   private static class MyDependenciesNode extends ExternalSystemNode<Object> {
214     MyDependenciesNode(ExternalProjectsView externalProjectsView) {
215       super(externalProjectsView, null, null);
216     }
217
218     @Override
219     protected void update(@NotNull PresentationData presentation) {
220       super.update(presentation);
221       presentation.setIcon(AllIcons.Nodes.PpLibFolder);
222     }
223
224     @Override
225     public String getName() {
226       return "Dependencies";
227     }
228   }
229
230   private static class DependencyScopeExternalSystemNode extends ExternalSystemNode<Object> {
231     private final DependencyScopeNode myDependenciesGraph;
232     private final Map<Long, DependencyNode> myDependencyNodeMap = new HashMap<>();
233
234     DependencyScopeExternalSystemNode(@NotNull ExternalProjectsView externalProjectsView,
235                                       @NotNull DependencyScopeNode dependenciesGraph) {
236       super(externalProjectsView, null);
237       myDependenciesGraph = dependenciesGraph;
238     }
239
240     @Override
241     public String getName() {
242       return myDependenciesGraph.getScope();
243     }
244
245     @Override
246     protected void update(@NotNull PresentationData presentation) {
247       super.update(presentation);
248       presentation.setIcon(AllIcons.Nodes.PpLibFolder);
249       String description = myDependenciesGraph.getDescription();
250       if (description != null) {
251         setNameAndTooltip(getName(), description, (String)null);
252       }
253     }
254
255     @NotNull
256     @Override
257     protected List<ExternalSystemNode<?>> doBuildChildren() {
258       buildNodesMap(myDependencyNodeMap, myDependenciesGraph);
259       List<ExternalSystemNode<?>> myChildNodes = new ArrayList<>();
260       for (DependencyNode dependency : myDependenciesGraph.getDependencies()) {
261         myChildNodes.add(new DependencyExternalSystemNode(getExternalProjectsView(), dependency, myDependencyNodeMap));
262       }
263       return myChildNodes;
264     }
265
266     private static void buildNodesMap(@NotNull Map<Long, DependencyNode> dependencyNodeMap, @NotNull DependencyNode node) {
267       for (DependencyNode child : node.getDependencies()) {
268         if (child instanceof ReferenceNode) continue;
269         dependencyNodeMap.put(child.getId(), child);
270         buildNodesMap(dependencyNodeMap, child);
271       }
272     }
273   }
274
275   private static class DependencyExternalSystemNode extends ExternalSystemNode<Object> {
276     @NotNull
277     private final DependencyNode myDependencyNode;
278     @NotNull
279     private final Map<Long, DependencyNode> myDependencyNodeMap;
280     @Nullable
281     private DependencyNode myReferencedNode;
282     private final String myName;
283
284     DependencyExternalSystemNode(@NotNull ExternalProjectsView externalProjectsView,
285                                  @NotNull DependencyNode dependencyNode,
286                                  @NotNull Map<Long, DependencyNode> dependencyNodeMap) {
287       super(externalProjectsView, null);
288       myDependencyNode = dependencyNode;
289       myDependencyNodeMap = dependencyNodeMap;
290       if (myDependencyNode instanceof ReferenceNode) {
291         myReferencedNode = myDependencyNodeMap.get(myDependencyNode.getId());
292       }
293       if (myReferencedNode != null) {
294         myName = MessageFormat.format("{0}{1}",
295                                       myReferencedNode.getDisplayName(),
296                                       myReferencedNode.getDependencies().isEmpty() ? "" : " (*)");
297       }
298       else {
299         myName = myDependencyNode.getDisplayName();
300       }
301     }
302
303     @Override
304     public String getName() {
305       return myName;
306     }
307
308     @Override
309     protected void update(@NotNull PresentationData presentation) {
310       super.update(presentation);
311       boolean isProjectDependency = myDependencyNode instanceof ProjectDependencyNode || myReferencedNode instanceof ProjectDependencyNode;
312       presentation.setIcon(isProjectDependency ? getUiAware().getProjectIcon() : AllIcons.Nodes.PpLib);
313       String tooltip;
314       if (myReferencedNode != null) {
315         tooltip = ExternalSystemBundle.message("external.system.view.nodes.dependency_reference_node_tooltip");
316       }
317       else {
318         if (myDependencyNode instanceof FileCollectionDependencyNode) {
319           String path = ((FileCollectionDependencyNode)myDependencyNode).getPath();
320           tooltip = StringUtil.join(path.split(File.pathSeparator), s -> StringEscapeUtils.escapeHtml(s) + "<br/>" , "");
321         }
322         else {
323           tooltip = null;
324         }
325       }
326       setNameAndTooltip(getName(), tooltip, (String)null);
327     }
328
329     @NotNull
330     @Override
331     protected List<ExternalSystemNode<?>> doBuildChildren() {
332       if (myReferencedNode != null) {
333         return Collections.emptyList();
334       }
335       List<ExternalSystemNode<?>> myChildNodes = new ArrayList<>();
336       for (DependencyNode dependency : myDependencyNode.getDependencies()) {
337         myChildNodes.add(new DependencyExternalSystemNode(getExternalProjectsView(), dependency, myDependencyNodeMap));
338       }
339       return myChildNodes;
340     }
341
342     @Override
343     public void handleDoubleClickOrEnter(SimpleTree tree, InputEvent inputEvent) {
344       if (myReferencedNode == null) {
345         super.handleDoubleClickOrEnter(tree, inputEvent);
346       }
347       else {
348         selectAndExpandReferencedNode();
349       }
350     }
351
352     private void selectAndExpandReferencedNode() {
353       ExternalProjectsStructure structure = getStructure();
354       if (structure == null) return;
355       DependencyScopeExternalSystemNode scopeNode = findParent(DependencyScopeExternalSystemNode.class);
356       if (scopeNode == null) return;
357
358       DependencyExternalSystemNode referencedNode = findReferencedNode(scopeNode);
359       if (referencedNode != null) {
360         structure.select(referencedNode);
361         structure.expand(referencedNode);
362       }
363     }
364
365     @Nullable
366     private DependencyExternalSystemNode findReferencedNode(ExternalSystemNode<?> node) {
367       for (ExternalSystemNode<?> child : node.getChildren()) {
368         if (child instanceof DependencyExternalSystemNode &&
369             ((DependencyExternalSystemNode)child).myDependencyNode == myReferencedNode) {
370           return (DependencyExternalSystemNode)child;
371         }
372         DependencyExternalSystemNode referencedNode = findReferencedNode(child);
373         if (referencedNode != null) return referencedNode;
374       }
375       return null;
376     }
377   }
378
379   private static abstract class DependencyDataExternalSystemNode<T extends DependencyData<?>> extends ExternalSystemNode<T> {
380
381     private final Navigatable myNavigatable;
382
383     DependencyDataExternalSystemNode(@NotNull ExternalProjectsView externalProjectsView,
384                                      @Nullable ExternalSystemNode parent,
385                                      @Nullable DataNode<T> dataNode) {
386       super(externalProjectsView, parent, dataNode);
387       myNavigatable = new OrderEntryNavigatable(getProject(), () -> getOrderEntry());
388     }
389
390     @Nullable
391     @Override
392     public Navigatable getNavigatable() {
393       return myNavigatable;
394     }
395
396     @Nullable
397     private OrderEntry getOrderEntry() {
398       final T data = getData();
399       if (data == null) return null;
400       final Project project = getProject();
401       if (project == null) return null;
402       return new IdeModelsProviderImpl(project).findIdeModuleOrderEntry(data);
403     }
404
405     @Override
406     public int compareTo(@NotNull ExternalSystemNode node) {
407       final T myData = getData();
408       final Object thatData = node.getData();
409       if (myData instanceof OrderAware && thatData instanceof OrderAware) {
410         int order1 = ((OrderAware)myData).getOrder();
411         int order2 = ((OrderAware)thatData).getOrder();
412         if (order1 != order2) {
413           return order1 < order2 ? -1 : 1;
414         }
415       }
416
417       String dependencyName = getDependencySimpleName(this);
418       String thatDependencyName = getDependencySimpleName(node);
419       return StringUtil.compare(dependencyName, thatDependencyName, true);
420     }
421
422     @NotNull
423     private static String getDependencySimpleName(@NotNull ExternalSystemNode<?> node) {
424       Object thatData = node.getData();
425       if (thatData instanceof LibraryDependencyData) {
426         LibraryDependencyData dependencyData = (LibraryDependencyData)thatData;
427         String externalName = dependencyData.getExternalName();
428         if (StringUtil.isEmpty(externalName)) {
429           Set<String> paths = dependencyData.getTarget().getPaths(LibraryPathType.BINARY);
430           if (paths.size() == 1) {
431             return new File(paths.iterator().next()).getName();
432           }
433         }
434       }
435       return node.getName();
436     }
437
438     private static class OrderEntryNavigatable implements Navigatable {
439       @NotNull private final Supplier<OrderEntry> myProvider;
440       @Nullable private final Project myProject;
441       @Nullable private OrderEntry myOrderEntry;
442
443       OrderEntryNavigatable(@Nullable Project project,
444                             @NotNull Supplier<OrderEntry> provider) {
445         myProject = project;
446         myProvider = provider;
447       }
448
449       @Override
450       public void navigate(boolean requestFocus) {
451         if (myOrderEntry != null && myProject != null) {
452           ProjectSettingsService.getInstance(myProject).openModuleDependenciesSettings(myOrderEntry.getOwnerModule(), myOrderEntry);
453         }
454       }
455
456       @Override
457       public boolean canNavigate() {
458         myOrderEntry = myProvider.get();
459         return myOrderEntry != null;
460       }
461
462       @Override
463       public boolean canNavigateToSource() {
464         return true;
465       }
466     }
467   }
468
469   private static class ModuleDependencyDataExternalSystemNode extends DependencyDataExternalSystemNode<ModuleDependencyData> {
470
471     ModuleDependencyDataExternalSystemNode(ExternalProjectsView externalProjectsView, DataNode<ModuleDependencyData> dataNode) {
472       super(externalProjectsView, null, dataNode);
473     }
474
475     @Override
476     protected void update(@NotNull PresentationData presentation) {
477       super.update(presentation);
478       presentation.setIcon(getUiAware().getProjectIcon());
479
480       final ModuleDependencyData data = getData();
481       if (data != null) {
482         setNameAndTooltip(getName(), null, data.getScope().getDisplayName());
483       }
484     }
485
486     @NotNull
487     @Override
488     protected List<? extends ExternalSystemNode<?>> doBuildChildren() {
489       return Collections.emptyList();
490     }
491
492     @Override
493     public ExternalProjectsStructure.ErrorLevel getChildrenErrorLevel() {
494       return ExternalProjectsStructure.ErrorLevel.NONE;
495     }
496   }
497
498   private static class LibraryDependencyDataExternalSystemNode extends DependencyDataExternalSystemNode<LibraryDependencyData> {
499
500     LibraryDependencyDataExternalSystemNode(ExternalProjectsView externalProjectsView, DataNode<LibraryDependencyData> dataNode) {
501       super(externalProjectsView, null, dataNode);
502     }
503
504     @Override
505     protected void update(@NotNull PresentationData presentation) {
506       super.update(presentation);
507       presentation.setIcon(AllIcons.Nodes.PpLib);
508
509       final LibraryDependencyData data = getData();
510       if (data != null) {
511         setNameAndTooltip(getName(), null, data.getScope().getDisplayName());
512       }
513     }
514   }
515
516   @NotNull
517   private static String getNodeDisplayName(@NotNull DataNode<?> node) {
518     Object data = node.getData();
519     if (data instanceof LibraryDependencyData) {
520       LibraryDependencyData libraryDependencyData = (LibraryDependencyData)data;
521       String externalName = libraryDependencyData.getExternalName();
522       if (StringUtil.isEmpty(externalName)) {
523         Set<String> paths = libraryDependencyData.getTarget().getPaths(LibraryPathType.BINARY);
524         if (paths.size() == 1) {
525           String relativePathToRoot = null;
526           String path = ExternalSystemApiUtil.toCanonicalPath(paths.iterator().next());
527           DataNode<ProjectData> projectDataDataNode = ExternalSystemApiUtil.findParent(node, PROJECT);
528           if (projectDataDataNode != null) {
529             relativePathToRoot = FileUtil.getRelativePath(projectDataDataNode.getData().getLinkedExternalProjectPath(), path, '/');
530             relativePathToRoot = relativePathToRoot != null && StringUtil.startsWith(relativePathToRoot, "../../")
531                                  ? new File(relativePathToRoot).getName()
532                                  : relativePathToRoot;
533           }
534           return ObjectUtils.notNull(relativePathToRoot, path);
535         }
536         else {
537           return "<file set>";
538         }
539       }
540       return externalName;
541     }
542     if (data instanceof Named) {
543       return ((Named)data).getExternalName();
544     }
545     if (data instanceof TaskData) {
546       return ((TaskData)data).getName();
547     }
548     return StringUtil.notNullize(node.toString());
549   }
550
551   @Override
552   public ExternalProjectsStructure.ErrorLevel getErrorLevel(DataNode<?> dataNode) {
553     if (ProjectKeys.LIBRARY_DEPENDENCY.equals(dataNode.getKey())) {
554       if (((LibraryDependencyData)dataNode.getData()).getTarget().isUnresolved()) {
555         return ExternalProjectsStructure.ErrorLevel.ERROR;
556       }
557     }
558     return super.getErrorLevel(dataNode);
559   }
560 }