Cleanup: NotNull/Nullable
[idea/community.git] / java / java-impl / src / com / intellij / ide / projectView / impl / PackageViewPane.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
3 package com.intellij.ide.projectView.impl;
4
5 import com.intellij.history.LocalHistory;
6 import com.intellij.history.LocalHistoryAction;
7 import com.intellij.icons.AllIcons;
8 import com.intellij.ide.DeleteProvider;
9 import com.intellij.ide.IdeBundle;
10 import com.intellij.ide.SelectInTarget;
11 import com.intellij.ide.impl.PackagesPaneSelectInTarget;
12 import com.intellij.ide.projectView.BaseProjectTreeBuilder;
13 import com.intellij.ide.projectView.ProjectView;
14 import com.intellij.ide.projectView.ViewSettings;
15 import com.intellij.ide.projectView.impl.nodes.PackageElement;
16 import com.intellij.ide.projectView.impl.nodes.PackageElementNode;
17 import com.intellij.ide.projectView.impl.nodes.PackageUtil;
18 import com.intellij.ide.projectView.impl.nodes.PackageViewProjectNode;
19 import com.intellij.ide.util.DeleteHandler;
20 import com.intellij.ide.util.treeView.AbstractTreeBuilder;
21 import com.intellij.ide.util.treeView.AbstractTreeNode;
22 import com.intellij.ide.util.treeView.AbstractTreeUpdater;
23 import com.intellij.openapi.actionSystem.*;
24 import com.intellij.openapi.module.Module;
25 import com.intellij.openapi.project.Project;
26 import com.intellij.openapi.roots.OrderEntry;
27 import com.intellij.openapi.roots.ProjectFileIndex;
28 import com.intellij.openapi.roots.ProjectRootManager;
29 import com.intellij.openapi.util.registry.Registry;
30 import com.intellij.openapi.vfs.VirtualFile;
31 import com.intellij.psi.JavaDirectoryService;
32 import com.intellij.psi.PsiDirectory;
33 import com.intellij.psi.PsiElement;
34 import com.intellij.psi.PsiPackage;
35 import com.intellij.psi.search.GlobalSearchScope;
36 import com.intellij.psi.util.PsiUtilCore;
37 import com.intellij.util.containers.ContainerUtil;
38 import com.intellij.util.ui.tree.TreeUtil;
39 import org.jetbrains.annotations.NonNls;
40 import org.jetbrains.annotations.NotNull;
41 import org.jetbrains.annotations.Nullable;
42
43 import javax.swing.*;
44 import javax.swing.tree.DefaultTreeModel;
45 import java.util.*;
46
47 public class PackageViewPane extends AbstractProjectViewPSIPane {
48   @NonNls public static final String ID = "PackagesPane";
49   private final MyDeletePSIElementProvider myDeletePSIElementProvider = new MyDeletePSIElementProvider();
50
51   public PackageViewPane(Project project) {
52     super(project);
53   }
54
55   @NotNull
56   @Override
57   public String getTitle() {
58     return IdeBundle.message("title.packages");
59   }
60
61   @NotNull
62   @Override
63   public Icon getIcon() {
64     return AllIcons.Nodes.CopyOfFolder;
65   }
66
67   @Override
68   @NotNull
69   public String getId() {
70     return ID;
71   }
72
73   @NotNull
74   @Override
75   public List<PsiElement> getElementsFromNode(@Nullable Object node) {
76     Object o = getValueFromNode(node);
77     if (o instanceof PackageElement) {
78       PsiPackage aPackage = ((PackageElement)o).getPackage();
79       return ContainerUtil.createMaybeSingletonList(aPackage.isValid() ? aPackage : null);
80     }
81     return super.getElementsFromNode(node);
82   }
83
84   @Override
85   protected Module getNodeModule(@Nullable Object element) {
86     if (element instanceof PackageElement) {
87       return ((PackageElement)element).getModule();
88     }
89     return super.getNodeModule(element);
90   }
91
92   @Override
93   public Object getData(@NotNull final String dataId) {
94     if (PlatformDataKeys.DELETE_ELEMENT_PROVIDER.is(dataId)) {
95       final PackageElement selectedPackageElement = getSelectedPackageElement();
96       if (selectedPackageElement != null) {
97         return myDeletePSIElementProvider;
98       }
99     }
100     if (PackageElement.DATA_KEY.is(dataId)) {
101       final PackageElement packageElement = getSelectedPackageElement();
102     }
103     if (LangDataKeys.MODULE.is(dataId)) {
104       final PackageElement packageElement = getSelectedPackageElement();
105       if (packageElement != null) {
106         return packageElement.getModule();
107       }
108     }
109     return super.getData(dataId);
110   }
111
112   @Nullable
113   private PackageElement getSelectedPackageElement() {
114     AbstractTreeNode node = TreeUtil.getLastUserObject(AbstractTreeNode.class, getSelectedPath());
115     Object selected = node == null ? null : node.getValue();
116     return selected instanceof PackageElement ? (PackageElement)selected : null;
117   }
118
119   @NotNull
120   @Override
121   public PsiDirectory[] getSelectedDirectories() {
122     List<PsiDirectory> directories = ContainerUtil.newArrayList();
123     for (PackageElementNode node : getSelectedNodes(PackageElementNode.class)) {
124       PackageElement packageElement = node.getValue();
125       PsiPackage aPackage = packageElement != null ? packageElement.getPackage() : null;
126       final Module module = packageElement != null ? packageElement.getModule() : null;
127       if (aPackage != null && module != null) {
128         GlobalSearchScope scope = GlobalSearchScope.moduleScope(module);
129         Collections.addAll(directories, aPackage.getDirectories(scope));
130
131         if (Registry.is("projectView.choose.directory.on.compacted.middle.packages")) {
132           Object parentValue = node.getParent().getValue();
133           PsiPackage parentNodePackage = parentValue instanceof PackageElement ? ((PackageElement)parentValue).getPackage() : null;
134           while (true) {
135             aPackage = aPackage.getParentPackage();
136             if (aPackage == null || aPackage.getQualifiedName().isEmpty() || aPackage.equals(parentNodePackage)) {
137               break;
138             }
139             Collections.addAll(directories, aPackage.getDirectories(scope));
140           }
141         }
142       }
143     }
144     if (!directories.isEmpty()) {
145       return directories.toArray(PsiDirectory.EMPTY_ARRAY);
146     }
147
148     return super.getSelectedDirectories();
149   }
150
151   private final class ShowLibraryContentsAction extends ToggleAction {
152     private ShowLibraryContentsAction() {
153       super(IdeBundle.message("action.show.libraries.contents"), IdeBundle.message("action.show.hide.library.contents"),
154             AllIcons.ObjectBrowser.ShowLibraryContents);
155     }
156
157     @Override
158     public boolean isSelected(@NotNull AnActionEvent event) {
159       return ProjectView.getInstance(myProject).isShowLibraryContents(getId());
160     }
161
162     @Override
163     public void setSelected(@NotNull AnActionEvent event, boolean flag) {
164       final ProjectViewImpl projectView = (ProjectViewImpl)ProjectView.getInstance(myProject);
165       projectView.setShowLibraryContents(getId(), flag);
166     }
167
168     @Override
169     public void update(@NotNull AnActionEvent e) {
170       super.update(e);
171       final Presentation presentation = e.getPresentation();
172       final ProjectViewImpl projectView = (ProjectViewImpl)ProjectView.getInstance(myProject);
173       presentation.setVisible(projectView.getCurrentProjectViewPane() == PackageViewPane.this);
174     }
175   }
176
177   @Override
178   public void addToolbarActions(@NotNull DefaultActionGroup actionGroup) {
179     actionGroup.addAction(new ShowModulesAction(myProject, ID)).setAsSecondary(true);
180     actionGroup.addAction(createFlattenModulesAction(() -> true)).setAsSecondary(true);
181     actionGroup.addAction(new ShowLibraryContentsAction()).setAsSecondary(true);
182     AnAction editScopesAction = ActionManager.getInstance().getAction("ScopeView.EditScopes");
183     if (editScopesAction != null) actionGroup.addAction(editScopesAction).setAsSecondary(true);
184   }
185
186   @NotNull
187   @Override
188   protected AbstractTreeUpdater createTreeUpdater(@NotNull AbstractTreeBuilder treeBuilder) {
189     return new PackageViewTreeUpdater(treeBuilder);
190   }
191
192   @NotNull
193   @Override
194   public SelectInTarget createSelectInTarget() {
195     return new PackagesPaneSelectInTarget(myProject);
196   }
197
198   @NotNull
199   @Override
200   protected ProjectAbstractTreeStructureBase createStructure() {
201     return new ProjectTreeStructure(myProject, ID){
202       @Override
203       protected AbstractTreeNode createRoot(@NotNull final Project project, @NotNull ViewSettings settings) {
204         return new PackageViewProjectNode(project, settings);
205       }
206
207       @Override
208       public boolean isToBuildChildrenInBackground(@NotNull Object element) {
209         return Registry.is("ide.projectView.PackageViewTreeStructure.BuildChildrenInBackground");
210       }
211     };
212   }
213
214   @NotNull
215   @Override
216   protected ProjectViewTree createTree(@NotNull DefaultTreeModel treeModel) {
217     return new ProjectViewTree(treeModel) {
218       public String toString() {
219         return getTitle() + " " + super.toString();
220       }
221     };
222   }
223
224   @NotNull
225   public String getComponentName() {
226     return "PackagesPane";
227   }
228
229   @Override
230   public int getWeight() {
231     return 1;
232   }
233
234   private final class PackageViewTreeUpdater extends AbstractTreeUpdater {
235     private PackageViewTreeUpdater(final AbstractTreeBuilder treeBuilder) {
236       super(treeBuilder);
237     }
238
239     @Override
240     public boolean addSubtreeToUpdateByElement(@NotNull Object element) {
241       // should convert PsiDirectories into PackageElements
242       if (element instanceof PsiDirectory) {
243         PsiDirectory dir = (PsiDirectory)element;
244         final PsiPackage aPackage = JavaDirectoryService.getInstance().getPackage(dir);
245         if (ProjectView.getInstance(myProject).isShowModules(getId())) {
246           Module[] modules = getModulesFor(dir);
247           boolean rv = false;
248           for (Module module : modules) {
249             rv |= addPackageElementToUpdate(aPackage, module);
250           }
251           return rv;
252         }
253         else {
254           return addPackageElementToUpdate(aPackage, null);
255         }
256       }
257
258       return super.addSubtreeToUpdateByElement(element);
259     }
260
261     private boolean addPackageElementToUpdate(final PsiPackage aPackage, Module module) {
262       final ProjectTreeStructure packageTreeStructure = (ProjectTreeStructure)myTreeStructure;
263       PsiPackage packageToUpdateFrom = aPackage;
264       if (!packageTreeStructure.isFlattenPackages() && packageTreeStructure.isHideEmptyMiddlePackages()) {
265         // optimization: this check makes sense only if flattenPackages == false && HideEmptyMiddle == true
266         while (packageToUpdateFrom != null && packageToUpdateFrom.isValid() && PackageUtil.isPackageEmpty(packageToUpdateFrom, module, true, false)) {
267           packageToUpdateFrom = packageToUpdateFrom.getParentPackage();
268         }
269       }
270       boolean addedOk;
271       while (!(addedOk = super.addSubtreeToUpdateByElement(getTreeElementToUpdateFrom(packageToUpdateFrom, module)))) {
272         if (packageToUpdateFrom == null) {
273           break;
274         }
275         packageToUpdateFrom = packageToUpdateFrom.getParentPackage();
276       }
277       return addedOk;
278     }
279
280     @NotNull
281     private Object getTreeElementToUpdateFrom(PsiPackage packageToUpdateFrom, Module module) {
282       if (packageToUpdateFrom == null || !packageToUpdateFrom.isValid() || "".equals(packageToUpdateFrom.getQualifiedName())) {
283         return module == null ? myTreeStructure.getRootElement() : module;
284       }
285       else {
286         return new PackageElement(module, packageToUpdateFrom, false);
287       }
288     }
289
290     private Module[] getModulesFor(PsiDirectory dir) {
291       final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(myProject).getFileIndex();
292       final VirtualFile vFile = dir.getVirtualFile();
293       final Set<Module> modules = new HashSet<>();
294       final Module module = fileIndex.getModuleForFile(vFile);
295       if (module != null) {
296         modules.add(module);
297       }
298       if (fileIndex.isInLibrary(vFile)) {
299         final List<OrderEntry> orderEntries = fileIndex.getOrderEntriesForFile(vFile);
300         if (orderEntries.isEmpty()) {
301           return Module.EMPTY_ARRAY;
302         }
303         for (OrderEntry entry : orderEntries) {
304           modules.add(entry.getOwnerModule());
305         }
306       }
307       return modules.toArray(Module.EMPTY_ARRAY);
308     }
309   }
310
311   private final class MyDeletePSIElementProvider implements DeleteProvider {
312     @Override
313     public boolean canDeleteElement(@NotNull DataContext dataContext) {
314       for (PsiDirectory directory : getSelectedDirectories()) {
315         if (!directory.getManager().isInProject(directory)) return false;
316       }
317       return true;
318     }
319
320     @Override
321     public void deleteElement(@NotNull DataContext dataContext) {
322       PsiDirectory[] allElements = getSelectedDirectories();
323       List<PsiElement> validElements = new ArrayList<>();
324       for (PsiElement psiElement : allElements) {
325         if (psiElement != null && psiElement.isValid()) validElements.add(psiElement);
326       }
327       final PsiElement[] elements = PsiUtilCore.toPsiElementArray(validElements);
328
329       LocalHistoryAction a = LocalHistory.getInstance().startAction(IdeBundle.message("progress.deleting"));
330       try {
331         DeleteHandler.deletePsiElement(elements, myProject);
332       }
333       finally {
334         a.finish();
335       }
336     }
337   }
338
339   @Override
340   protected BaseProjectTreeBuilder createBuilder(@NotNull DefaultTreeModel model) {
341     return null;
342   }
343 }