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