IDEA-108938 External system: Provide ability to detach linked external project
[idea/community.git] / platform / external-system-impl / src / com / intellij / openapi / externalSystem / service / task / ui / ExternalSystemTasksTreeModel.java
1 /*
2  * Copyright 2000-2013 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.openapi.externalSystem.service.task.ui;
17
18 import com.intellij.openapi.diagnostic.Logger;
19 import com.intellij.openapi.externalSystem.ExternalSystemUiAware;
20 import com.intellij.openapi.externalSystem.model.ProjectSystemId;
21 import com.intellij.openapi.externalSystem.model.execution.ExternalSystemTaskExecutionSettings;
22 import com.intellij.openapi.externalSystem.model.execution.ExternalTaskExecutionInfo;
23 import com.intellij.openapi.externalSystem.model.execution.ExternalTaskPojo;
24 import com.intellij.openapi.externalSystem.model.project.ExternalProjectPojo;
25 import com.intellij.openapi.externalSystem.util.ExternalSystemUiUtil;
26 import com.intellij.openapi.wm.ToolWindowId;
27 import com.intellij.util.containers.ContainerUtilRt;
28 import gnu.trove.TObjectIntHashMap;
29 import org.jetbrains.annotations.NotNull;
30 import org.jetbrains.annotations.Nullable;
31
32 import javax.swing.*;
33 import javax.swing.tree.DefaultTreeModel;
34 import javax.swing.tree.TreeNode;
35 import java.util.*;
36
37 /**
38  * @author Denis Zhdanov
39  * @since 5/12/13 10:28 PM
40  */
41 public class ExternalSystemTasksTreeModel extends DefaultTreeModel {
42
43   private static final Logger LOG = Logger.getInstance("#" + ExternalSystemTasksTreeModel.class.getName());
44
45   @NotNull private static final Comparator<TreeNode> NODE_COMPARATOR = new Comparator<TreeNode>() {
46     @Override
47     public int compare(TreeNode t1, TreeNode t2) {
48       Object e1 = ((ExternalSystemNode<?>)t1).getDescriptor().getElement();
49       Object e2 = ((ExternalSystemNode<?>)t2).getDescriptor().getElement();
50       if (e1 instanceof ExternalProjectPojo) {
51         if (e2 instanceof ExternalTaskExecutionInfo) {
52           return 1;
53         }
54         else {
55           return ((ExternalProjectPojo)e1).getName().compareTo(((ExternalProjectPojo)e2).getName());
56         }
57       }
58       else {
59         if (e2 instanceof ExternalProjectPojo) {
60           return -1;
61         }
62         else {
63           return getTaskName((ExternalTaskExecutionInfo)e1).compareTo(getTaskName((ExternalTaskExecutionInfo)e2));
64         }
65       }
66     }
67   };
68
69   @NotNull private final TreeNode[] myNodeHolder  = new TreeNode[1];
70   @NotNull private final int[]      myIndexHolder = new int[1];
71
72   @NotNull private final ExternalSystemUiAware myUiAware;
73   @NotNull private final ProjectSystemId       myExternalSystemId;
74
75   public ExternalSystemTasksTreeModel(@NotNull ProjectSystemId externalSystemId) {
76     super(new ExternalSystemNode<String>(new ExternalSystemNodeDescriptor<String>("", "", null)));
77     myExternalSystemId = externalSystemId;
78     myUiAware = ExternalSystemUiUtil.getUiAware(externalSystemId);
79   }
80
81   private static String getTaskName(@NotNull ExternalTaskExecutionInfo taskInfo) {
82     return taskInfo.getSettings().getTaskNames().get(0);
83   }
84
85   /**
86    * Ensures that current model has a top-level node which corresponds to the given external project info holder
87    *
88    * @param project  target external project info holder
89    */
90   @SuppressWarnings("unchecked")
91   @NotNull
92   public ExternalSystemNode<ExternalProjectPojo> ensureProjectNodeExists(@NotNull ExternalProjectPojo project) {
93     ExternalSystemNode<?> root = getRoot();
94
95     // Remove outdated projects.
96     for (int i = root.getChildCount() - 1; i >= 0; i--) {
97       ExternalSystemNode<?> child = root.getChildAt(i);
98       Object element = child.getDescriptor().getElement();
99       if (element instanceof ExternalProjectPojo
100           && ((ExternalProjectPojo)element).getPath().equals(project.getPath()))
101       {
102         return (ExternalSystemNode<ExternalProjectPojo>)child;
103       }
104     }
105     ExternalProjectPojo element = new ExternalProjectPojo(project.getName(), project.getPath());
106     ExternalSystemNodeDescriptor<ExternalProjectPojo> descriptor = descriptor(element, myUiAware.getProjectIcon());
107     myIndexHolder[0] = root.getChildCount();
108     ExternalSystemNode<ExternalProjectPojo> result = new ExternalSystemNode<ExternalProjectPojo>(descriptor);
109     root.add(result);
110     nodesWereInserted(root, myIndexHolder);
111     return result;
112   }
113
114   /**
115    * Asks current model to remove all nodes which have given data as a {@link ExternalSystemNodeDescriptor#getElement() payload}.
116    *
117    * @param payload  target payload
118    */
119   public void pruneNodes(@NotNull Object payload) {
120     Deque<ExternalSystemNode<?>> toProcess = new ArrayDeque<ExternalSystemNode<?>>();
121     toProcess.addFirst(getRoot());
122     while (!toProcess.isEmpty()) {
123       ExternalSystemNode<?> node = toProcess.removeLast();
124       if (payload.equals(node.getDescriptor().getElement())) {
125         removeNodeFromParent(node);
126       }
127       else {
128         for (int i = 0; i < node.getChildCount(); i++) {
129           toProcess.addFirst(node.getChildAt(i));
130         }
131       }
132     }
133   }
134
135   public void ensureSubProjectsStructure(@NotNull ExternalProjectPojo topLevelProject,
136                                          @NotNull Collection<ExternalProjectPojo> subProjects)
137   {
138     ExternalSystemNode<ExternalProjectPojo> topLevelProjectNode = ensureProjectNodeExists(topLevelProject);
139     Map<String/*config path*/, ExternalProjectPojo> toAdd = ContainerUtilRt.newHashMap();
140     for (ExternalProjectPojo subProject : subProjects) {
141       toAdd.put(subProject.getPath(), subProject);
142     }
143     toAdd.remove(topLevelProject.getPath());
144
145     final TObjectIntHashMap<Object> taskWeights = new TObjectIntHashMap<Object>();
146     for (int i = 0; i < topLevelProjectNode.getChildCount(); i++) {
147       ExternalSystemNode<?> child = topLevelProjectNode.getChildAt(i);
148       Object childElement = child.getDescriptor().getElement();
149       if (childElement instanceof ExternalTaskExecutionInfo) {
150         taskWeights.put(childElement, subProjects.size() + i);
151         continue;
152       }
153       
154       if (toAdd.remove(((ExternalProjectPojo)childElement).getPath()) == null) {
155         topLevelProjectNode.remove(child);
156         myIndexHolder[0] = i;
157         myNodeHolder[0] = child;
158         nodesWereRemoved(topLevelProjectNode, myIndexHolder, myNodeHolder);
159         //noinspection AssignmentToForLoopParameter
160         i--;
161       }
162     }
163     if (!toAdd.isEmpty()) {
164       for (Map.Entry<String, ExternalProjectPojo> entry : toAdd.entrySet()) {
165         ExternalProjectPojo
166           element = new ExternalProjectPojo(entry.getValue().getName(), entry.getValue().getPath());
167         topLevelProjectNode.add(new ExternalSystemNode<ExternalProjectPojo>(descriptor(element, myUiAware.getProjectIcon())));
168         myIndexHolder[0] = topLevelProjectNode.getChildCount() - 1;
169         nodesWereInserted(topLevelProjectNode, myIndexHolder);
170       }
171     }
172
173     ExternalSystemUiUtil.sort(topLevelProjectNode, this, NODE_COMPARATOR);
174   }
175
176   public void ensureTasks(@NotNull String externalProjectConfigPath, @NotNull Collection<ExternalTaskPojo> tasks) {
177     if (tasks.isEmpty()) {
178       return;
179     }
180     ExternalSystemNode<ExternalProjectPojo> moduleNode = findProjectNode(externalProjectConfigPath);
181     if (moduleNode == null) {
182       LOG.warn(String.format(
183         "Can't proceed tasks for module which external config path is '%s'. Reason: no such module node is found. Tasks: %s",
184         externalProjectConfigPath, tasks
185       ));
186       return;
187     }
188     Set<ExternalTaskExecutionInfo> toAdd = ContainerUtilRt.newHashSet();
189     for (ExternalTaskPojo task : tasks) {
190       toAdd.add(buildTaskInfo(task));
191     }
192     for (int i = 0; i < moduleNode.getChildCount(); i++) {
193       ExternalSystemNode<?> childNode = moduleNode.getChildAt(i);
194       Object element = childNode.getDescriptor().getElement();
195       if (element instanceof ExternalTaskExecutionInfo) {
196         if (!toAdd.remove(element)) {
197           moduleNode.remove(childNode);
198           myIndexHolder[0] = i;
199           myNodeHolder[0] = childNode;
200           nodesWereRemoved(moduleNode, myIndexHolder, myNodeHolder);
201         }
202       }
203     }
204
205     if (!toAdd.isEmpty()) {
206       for (ExternalTaskExecutionInfo taskInfo : toAdd) {
207         moduleNode.add(new ExternalSystemNode<ExternalTaskExecutionInfo>(descriptor(taskInfo, myUiAware.getTaskIcon())));
208         myIndexHolder[0] = moduleNode.getChildCount() - 1;
209         nodesWereInserted(moduleNode, myIndexHolder);
210       }
211     }
212     ExternalSystemUiUtil.sort(moduleNode, this, NODE_COMPARATOR);
213   }
214
215   @NotNull
216   private ExternalTaskExecutionInfo buildTaskInfo(@NotNull ExternalTaskPojo task) {
217     ExternalSystemTaskExecutionSettings settings = new ExternalSystemTaskExecutionSettings();
218     settings.setExternalProjectPath(task.getLinkedExternalProjectPath());
219     settings.setTaskNames(Collections.singletonList(task.getName()));
220     settings.setExternalSystemIdString(myExternalSystemId.toString());
221     return new ExternalTaskExecutionInfo(settings, ToolWindowId.RUN);
222   }
223
224   @SuppressWarnings("unchecked")
225   @Nullable
226   private ExternalSystemNode<ExternalProjectPojo> findProjectNode(@NotNull String configPath) {
227     for (int i = getRoot().getChildCount() - 1; i >= 0; i--) {
228       ExternalSystemNode<?> child = getRoot().getChildAt(i);
229       Object childElement = child.getDescriptor().getElement();
230       if (childElement instanceof ExternalProjectPojo && ((ExternalProjectPojo)childElement).getPath().equals(configPath)) {
231         return (ExternalSystemNode<ExternalProjectPojo>)child;
232       }
233       for (int j = child.getChildCount() - 1; j >= 0; j--) {
234         ExternalSystemNode<?> grandChild = child.getChildAt(j);
235         Object grandChildElement = grandChild.getDescriptor().getElement();
236         if (grandChildElement instanceof ExternalProjectPojo
237             && ((ExternalProjectPojo)grandChildElement).getPath().equals(configPath))
238         {
239           return (ExternalSystemNode<ExternalProjectPojo>)grandChild;
240         }
241       }
242     }
243     return null;
244   }
245
246   @NotNull
247   private static <T> ExternalSystemNodeDescriptor<T> descriptor(@NotNull T element, @Nullable Icon icon) {
248     return new ExternalSystemNodeDescriptor<T>(element, element.toString(), icon);
249   }
250   
251   @NotNull
252   public ExternalSystemNode<?> getRoot() {
253     return (ExternalSystemNode<?>)super.getRoot();
254   }
255 }