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