simplify GTDUCollector a bit more
[idea/community.git] / java / idea-ui / src / com / intellij / openapi / roots / ui / configuration / projectRoot / BaseLibrariesConfigurable.java
1 // Copyright 2000-2020 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.projectRoot;
3
4 import com.intellij.CommonBundle;
5 import com.intellij.ide.JavaUiBundle;
6 import com.intellij.openapi.actionSystem.AnAction;
7 import com.intellij.openapi.actionSystem.AnActionEvent;
8 import com.intellij.openapi.application.ApplicationManager;
9 import com.intellij.openapi.options.ConfigurationException;
10 import com.intellij.openapi.project.Project;
11 import com.intellij.openapi.roots.impl.libraries.LibraryEx;
12 import com.intellij.openapi.roots.libraries.Library;
13 import com.intellij.openapi.roots.libraries.LibraryTable;
14 import com.intellij.openapi.roots.libraries.LibraryTablePresentation;
15 import com.intellij.openapi.roots.libraries.LibraryTablesRegistrar;
16 import com.intellij.openapi.roots.ui.configuration.libraries.LibraryEditingUtil;
17 import com.intellij.openapi.roots.ui.configuration.libraryEditor.CreateNewLibraryAction;
18 import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.LibraryProjectStructureElement;
19 import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.ProjectStructureDaemonAnalyzer;
20 import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.ProjectStructureElement;
21 import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.ProjectStructureElementUsage;
22 import com.intellij.openapi.ui.Messages;
23 import com.intellij.openapi.ui.NamedConfigurable;
24 import com.intellij.openapi.ui.NonEmptyInputValidator;
25 import com.intellij.openapi.util.Comparing;
26 import com.intellij.openapi.util.Disposer;
27 import com.intellij.openapi.util.NlsActions;
28 import com.intellij.openapi.util.Pair;
29 import com.intellij.openapi.util.text.StringUtil;
30 import com.intellij.util.containers.MultiMap;
31 import com.intellij.util.ui.tree.TreeUtil;
32 import org.jetbrains.annotations.NonNls;
33 import org.jetbrains.annotations.NotNull;
34 import org.jetbrains.annotations.Nullable;
35
36 import javax.swing.tree.DefaultTreeModel;
37 import javax.swing.tree.TreeNode;
38 import javax.swing.tree.TreePath;
39 import java.util.ArrayList;
40 import java.util.Collection;
41 import java.util.Collections;
42 import java.util.List;
43
44 import static com.intellij.ui.tree.TreePathUtil.toTreePathArray;
45
46 public abstract class BaseLibrariesConfigurable extends BaseStructureConfigurable  {
47   @NotNull
48   protected final String myLevel;
49
50   protected BaseLibrariesConfigurable(final @NotNull Project project, @NotNull String libraryTableLevel) {
51     super(project);
52     myLevel = libraryTableLevel;
53   }
54
55   public static BaseLibrariesConfigurable getInstance(@NotNull Project project, @NotNull String tableLevel) {
56     if (tableLevel.equals(LibraryTablesRegistrar.PROJECT_LEVEL)) {
57       return ProjectLibrariesConfigurable.getInstance(project);
58     }
59     else {
60       return GlobalLibrariesConfigurable.getInstance(project);
61     }
62   }
63
64   public abstract LibraryTablePresentation getLibraryTablePresentation();
65
66   @Override
67   @Nullable
68   @NonNls
69   public String getHelpTopic() {
70     return "reference.settingsdialog.project.structure.library";
71   }
72
73   @Override
74   public boolean isModified() {
75     boolean isModified = false;
76     for (final LibrariesModifiableModel provider : myContext.myLevel2Providers.values()) {
77       isModified |= provider.isChanged();
78     }
79     return isModified || super.isModified();
80   }
81
82   @Override
83   public void checkCanApply() throws ConfigurationException {
84     super.checkCanApply();
85     checkForEmptyAndDuplicatedNames(JavaUiBundle.message("configurable.library.prefix"), CommonBundle.getErrorTitle(), LibraryConfigurable.class);
86     for (LibraryConfigurable configurable : getLibraryConfigurables()) {
87       if (configurable.getDisplayName().isEmpty()) {
88         ((LibraryProjectStructureElement)configurable.getProjectStructureElement()).navigate();
89         throw new ConfigurationException("Library name is not specified");
90       }
91     }
92   }
93
94   @Override
95   public void reset() {
96     super.reset();
97     myTree.setRootVisible(false);
98   }
99
100   @Override
101   protected void loadTree() {
102     createLibrariesNode(myContext.createModifiableModelProvider(myLevel));
103   }
104
105   @NotNull
106   @Override
107   protected Collection<? extends ProjectStructureElement> getProjectStructureElements() {
108     final List<ProjectStructureElement> result = new ArrayList<>();
109     for (LibraryConfigurable libraryConfigurable : getLibraryConfigurables()) {
110       result.add(new LibraryProjectStructureElement(myContext, libraryConfigurable.getEditableObject()));
111     }
112     return result;
113   }
114
115   private List<LibraryConfigurable> getLibraryConfigurables() {
116     //todo[nik] improve
117     List<LibraryConfigurable> libraryConfigurables = new ArrayList<>();
118     for (int i = 0; i < myRoot.getChildCount(); i++) {
119       final TreeNode node = myRoot.getChildAt(i);
120       if (node instanceof MyNode) {
121         final NamedConfigurable configurable = ((MyNode)node).getConfigurable();
122         if (configurable instanceof LibraryConfigurable) {
123           libraryConfigurables.add((LibraryConfigurable)configurable);
124         }
125       }
126     }
127     return libraryConfigurables;
128   }
129
130   private void createLibrariesNode(final StructureLibraryTableModifiableModelProvider modelProvider) {
131     final Library[] libraries = modelProvider.getModifiableModel().getLibraries();
132     for (Library library : libraries) {
133       myRoot.add(new MyNode(new LibraryConfigurable(modelProvider, library, myContext, TREE_UPDATER)));
134     }
135     TreeUtil.sortRecursively(myRoot, (o1, o2) -> o1.getDisplayName().compareToIgnoreCase(o2.getDisplayName()));
136     ((DefaultTreeModel)myTree.getModel()).reload(myRoot);
137   }
138
139   @Override
140   public void apply() throws ConfigurationException {
141     super.apply();
142     ApplicationManager.getApplication().runWriteAction(() -> {
143       for (final LibrariesModifiableModel provider : myContext.myLevel2Providers.values()) {
144         provider.deferredCommit();
145       }
146     });
147   }
148
149   @NotNull
150   public String getLevel() {
151     return myLevel;
152   }
153
154   public void createLibraryNode(Library library) {
155     final LibraryTable table = library.getTable();
156     if (table != null) {
157       final String level = table.getTableLevel();
158       final LibraryConfigurable configurable =
159         new LibraryConfigurable(myContext.createModifiableModelProvider(level), library, myContext, TREE_UPDATER);
160       final MyNode node = new MyNode(configurable);
161       addNode(node, myRoot);
162       final ProjectStructureDaemonAnalyzer daemonAnalyzer = myContext.getDaemonAnalyzer();
163       daemonAnalyzer.queueUpdate(new LibraryProjectStructureElement(myContext, library));
164       daemonAnalyzer.queueUpdateForAllElementsWithErrors();
165     }
166   }
167
168   public void removeLibraryNode(@NotNull final Library library) {
169     removeLibrary(new LibraryProjectStructureElement(myContext, library));
170   }
171
172   @Override
173   public void dispose() {
174     if (myContext != null) {
175       for (final LibrariesModifiableModel provider : myContext.myLevel2Providers.values()) {
176         Disposer.dispose(provider);
177       }
178     }
179   }
180
181   @Override
182   @NotNull
183   protected List<? extends AnAction> createCopyActions(boolean fromPopup) {
184     final ArrayList<AnAction> actions = new ArrayList<>();
185     actions.add(new CopyLibraryAction());
186     if (fromPopup) {
187       final BaseLibrariesConfigurable targetGroup = getOppositeGroup();
188       actions.add(new ChangeLibraryLevelAction(myProject, myTree, this, targetGroup));
189       actions.add(new AddLibraryToModuleDependenciesAction(myProject, this));
190     }
191     return actions;
192   }
193
194   @Override
195   protected AbstractAddGroup createAddAction() {
196     return new AbstractAddGroup(getAddText()) {
197       @Override
198       public AnAction @NotNull [] getChildren(@Nullable final AnActionEvent e) {
199         return CreateNewLibraryAction.createActionOrGroup(getAddText(), BaseLibrariesConfigurable.this, myProject);
200       }
201     };
202   }
203
204   protected abstract @NlsActions.ActionText String getAddText();
205
206   public abstract StructureLibraryTableModifiableModelProvider getModelProvider();
207
208   public abstract BaseLibrariesConfigurable getOppositeGroup();
209
210   @Override
211   protected void updateSelection(@Nullable NamedConfigurable configurable) {
212     boolean selectionChanged = !Comparing.equal(myCurrentConfigurable, configurable);
213     if (myCurrentConfigurable instanceof LibraryConfigurable && selectionChanged) {
214       ((LibraryConfigurable)myCurrentConfigurable).onUnselected();
215     }
216     super.updateSelection(configurable);
217     if (myCurrentConfigurable instanceof LibraryConfigurable && selectionChanged) {
218       ((LibraryConfigurable)myCurrentConfigurable).onSelected();
219     }
220   }
221
222   @Override
223   public void onStructureUnselected() {
224     if (myCurrentConfigurable instanceof LibraryConfigurable) {
225       ((LibraryConfigurable)myCurrentConfigurable).onUnselected();
226     }
227   }
228
229   @Override
230   public void onStructureSelected() {
231     if (myCurrentConfigurable instanceof LibraryConfigurable) {
232       ((LibraryConfigurable)myCurrentConfigurable).onSelected();
233     }
234   }
235
236   public void removeLibrary(@NotNull LibraryProjectStructureElement element) {
237     removeLibraries(Collections.singletonList(element));
238   }
239
240   public void removeLibraries(@NotNull List<? extends LibraryProjectStructureElement> libraries) {
241     List<TreePath> pathsToRemove = new ArrayList<>();
242     for (LibraryProjectStructureElement element : libraries) {
243       getModelProvider().getModifiableModel().removeLibrary(element.getLibrary());
244       MyNode node = findNodeByObject(myRoot, element.getLibrary());
245       if (node != null) {
246         pathsToRemove.add(TreeUtil.getPathFromRoot(node));
247       }
248     }
249     myContext.getDaemonAnalyzer().removeElements(libraries);
250     removePaths(toTreePathArray(pathsToRemove));
251   }
252
253   @Override
254   protected List<? extends RemoveConfigurableHandler<?>> getRemoveHandlers() {
255     return Collections.singletonList(new RemoveConfigurableHandler<Library>(LibraryConfigurable.class) {
256       @Override
257       public boolean remove(@NotNull Collection<? extends Library> libraries) {
258         List<Pair<LibraryProjectStructureElement, Collection<ProjectStructureElementUsage>>> toRemove = new ArrayList<>();
259
260         String firstLibraryUsageDescription = null;
261         String firstLibraryWithUsageName = null;
262         int librariesWithUsages = 0;
263         for (Library library : libraries) {
264           final LibraryTable table = library.getTable();
265           if (table == null) continue;
266
267           final LibraryProjectStructureElement libraryElement = new LibraryProjectStructureElement(myContext, library);
268           final Collection<ProjectStructureElementUsage> usages =
269             new ArrayList<>(myContext.getDaemonAnalyzer().getUsages(libraryElement));
270           if (usages.size() > 0) {
271             if (librariesWithUsages == 0) {
272               final MultiMap<String, ProjectStructureElementUsage> containerType2Usage = new MultiMap<>();
273               for (final ProjectStructureElementUsage usage : usages) {
274                 containerType2Usage.putValue(usage.getContainingElement().getTypeName(), usage);
275               }
276
277               List<String> types = new ArrayList<>(containerType2Usage.keySet());
278               Collections.sort(types);
279
280               final StringBuilder sb = new StringBuilder("Library '");
281               Library libraryModel = myContext.getLibraryModel(library);
282               sb.append(libraryModel != null ? libraryModel.getName() : library.getName()).append("' is used in ");
283               for (int i = 0; i < types.size(); i++) {
284                 if (i > 0 && i == types.size() - 1) {
285                   sb.append(" and in ");
286                 }
287                 else if (i > 0) {
288                   sb.append(", in ");
289                 }
290                 String type = types.get(i);
291                 Collection<ProjectStructureElementUsage> usagesOfType = containerType2Usage.get(type);
292                 if (usagesOfType.size() > 1) {
293                   sb.append(usagesOfType.size()).append(" ").append(StringUtil.decapitalize(StringUtil.pluralize(type)));
294                 }
295                 else {
296                   sb.append(StringUtil.decapitalize(usagesOfType.iterator().next().getContainingElement().getPresentableText()));
297                 }
298               }
299               firstLibraryWithUsageName = library.getName();
300               firstLibraryUsageDescription = sb.toString();
301             }
302             librariesWithUsages++;
303           }
304           toRemove.add(Pair.create(libraryElement, usages));
305         }
306         if (librariesWithUsages > 0) {
307           String message;
308           if (librariesWithUsages == 1) {
309             message = firstLibraryUsageDescription + ".\nAre you sure you want to delete this library?";
310           }
311           else {
312             message = JavaUiBundle.message("libraries.remove.confirmation.text", firstLibraryWithUsageName, librariesWithUsages-1);
313           }
314
315           if (Messages.OK != Messages.showOkCancelDialog(myProject, message,
316                                                          JavaUiBundle.message("libraries.remove.confirmation.title", librariesWithUsages), Messages.getQuestionIcon())) {
317             return false;
318           }
319         }
320
321         for (Pair<LibraryProjectStructureElement, Collection<ProjectStructureElementUsage>> pair : toRemove) {
322           for (ProjectStructureElementUsage usage : pair.getSecond()) {
323             usage.removeSourceElement();
324           }
325           getModelProvider().getModifiableModel().removeLibrary(pair.getFirst().getLibrary());
326           myContext.getDaemonAnalyzer().removeElement(pair.getFirst());
327         }
328         return true;
329       }
330
331       @Override
332       public boolean canBeRemoved(@NotNull Collection<? extends Library> libraries) {
333         for (Library library : libraries) {
334           LibraryTable table = library.getTable();
335           if (table != null && !table.isEditable()) {
336             return false;
337           }
338         }
339         return true;
340       }
341     });
342   }
343
344   @Override
345   @Nullable
346   protected String getEmptySelectionString() {
347     return JavaUiBundle.message("configurable.empty.text.select.library");
348   }
349
350   private final class CopyLibraryAction extends AnAction {
351    private CopyLibraryAction() {
352       super(CommonBundle.messagePointer("button.copy"), CommonBundle.messagePointer("button.copy"), COPY_ICON);
353     }
354
355     @Override
356     public void actionPerformed(@NotNull final AnActionEvent e) {
357       final Object o = getSelectedObject();
358       if (o instanceof LibraryEx) {
359         final LibraryEx selected = (LibraryEx)o;
360         final String newName = Messages.showInputDialog(JavaUiBundle.message("label.enter.library.name"), JavaUiBundle.message(
361           "dialog.title.copy.library"), null, selected.getName() + "2", new NonEmptyInputValidator());
362         if (newName == null) return;
363
364         BaseLibrariesConfigurable configurable = BaseLibrariesConfigurable.this;
365         final LibraryEx library = (LibraryEx)myContext.getLibrary(selected.getName(), myLevel);
366         LOG.assertTrue(library != null);
367
368         final LibrariesModifiableModel libsModel = configurable.getModelProvider().getModifiableModel();
369         final Library lib = libsModel.createLibrary(newName, library.getKind());
370         final LibraryEx.ModifiableModelEx model = libsModel.getLibraryEditor(lib).getModel();
371         LibraryEditingUtil.copyLibrary(library, Collections.emptyMap(), model);
372       }
373     }
374
375     @Override
376     public void update(@NotNull final AnActionEvent e) {
377       if (myTree.getSelectionPaths() == null || myTree.getSelectionPaths().length != 1) {
378         e.getPresentation().setEnabled(false);
379       } else {
380         e.getPresentation().setEnabled(getSelectedObject() instanceof LibraryEx);
381       }
382     }
383   }
384 }