[i18n] idea-ui
[idea/community.git] / java / idea-ui / src / com / intellij / openapi / roots / ui / configuration / projectRoot / daemon / LibraryProjectStructureElement.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.daemon;
3
4 import com.intellij.ide.JavaUiBundle;
5 import com.intellij.openapi.project.Project;
6 import com.intellij.openapi.roots.JavadocOrderRootType;
7 import com.intellij.openapi.roots.OrderRootType;
8 import com.intellij.openapi.roots.impl.libraries.LibraryEx;
9 import com.intellij.openapi.roots.libraries.Library;
10 import com.intellij.openapi.roots.libraries.LibraryTable;
11 import com.intellij.openapi.roots.libraries.LibraryTablesRegistrar;
12 import com.intellij.openapi.roots.ui.configuration.ModuleEditor;
13 import com.intellij.openapi.roots.ui.configuration.ProjectStructureConfigurable;
14 import com.intellij.openapi.roots.ui.configuration.libraries.LibraryEditingUtil;
15 import com.intellij.openapi.roots.ui.configuration.libraryEditor.ExistingLibraryEditor;
16 import com.intellij.openapi.roots.ui.configuration.projectRoot.BaseLibrariesConfigurable;
17 import com.intellij.openapi.roots.ui.configuration.projectRoot.LibrariesModifiableModel;
18 import com.intellij.openapi.roots.ui.configuration.projectRoot.LibraryConfigurable;
19 import com.intellij.openapi.roots.ui.configuration.projectRoot.StructureConfigurableContext;
20 import com.intellij.openapi.ui.NamedConfigurable;
21 import com.intellij.openapi.util.ActionCallback;
22 import com.intellij.openapi.util.text.HtmlBuilder;
23 import com.intellij.openapi.util.text.HtmlChunk;
24 import com.intellij.openapi.util.text.StringUtil;
25 import com.intellij.util.PathUtil;
26 import com.intellij.xml.util.XmlStringUtil;
27 import org.jetbrains.annotations.Nls;
28 import org.jetbrains.annotations.NotNull;
29
30 import java.lang.reflect.InvocationHandler;
31 import java.lang.reflect.Proxy;
32 import java.util.*;
33
34 public class LibraryProjectStructureElement extends ProjectStructureElement {
35   private final Library myLibrary;
36
37   public LibraryProjectStructureElement(@NotNull StructureConfigurableContext context, @NotNull Library library) {
38     super(context);
39     myLibrary = library;
40   }
41
42   public Library getLibrary() {
43     return myLibrary;
44   }
45
46   @Override
47   public void check(ProjectStructureProblemsHolder problemsHolder) {
48     if (((LibraryEx)myLibrary).isDisposed()) return;
49     final LibraryEx library = (LibraryEx)myContext.getLibraryModel(myLibrary);
50     if (library == null || library.isDisposed()) return;
51
52     reportInvalidRoots(problemsHolder, library, OrderRootType.CLASSES, "classes", ProjectStructureProblemType.error("library-invalid-classes-path"));
53     final String libraryName = library.getName();
54     if (libraryName == null || !libraryName.startsWith("Maven: ")) {
55       reportInvalidRoots(problemsHolder, library, OrderRootType.SOURCES, "sources",
56                          ProjectStructureProblemType.warning("library-invalid-source-javadoc-path"));
57       reportInvalidRoots(problemsHolder, library, JavadocOrderRootType.getInstance(), "javadoc",
58                          ProjectStructureProblemType.warning("library-invalid-source-javadoc-path"));
59     }
60   }
61
62   private void reportInvalidRoots(ProjectStructureProblemsHolder problemsHolder, LibraryEx library,
63                                   @NotNull OrderRootType type, String rootName, final ProjectStructureProblemType problemType) {
64     final List<String> invalidUrls = library.getInvalidRootUrls(type);
65     if (!invalidUrls.isEmpty()) {
66       final String description = createInvalidRootsDescription(invalidUrls, rootName, library.getName());
67       final PlaceInProjectStructure place = createPlace();
68       final String message = JavaUiBundle.message("project.roots.error.message.invalid.roots", rootName, invalidUrls.size());
69       ProjectStructureProblemDescription.ProblemLevel level = library.getTable().getTableLevel().equals(LibraryTablesRegistrar.PROJECT_LEVEL)
70                                                               ? ProjectStructureProblemDescription.ProblemLevel.PROJECT : ProjectStructureProblemDescription.ProblemLevel.GLOBAL;
71       problemsHolder.registerProblem(new ProjectStructureProblemDescription(message, description, place,
72                                                                             problemType, level,
73                                                                             Collections.singletonList(new RemoveInvalidRootsQuickFix(library, type, invalidUrls)),
74                                                                             true));
75     }
76   }
77
78   private static String createInvalidRootsDescription(List<String> invalidClasses, String rootName, String libraryName) {
79     HtmlBuilder buffer = new HtmlBuilder();
80     final String name = StringUtil.escapeXmlEntities(libraryName);
81     final HtmlChunk.Element link = HtmlChunk.link("http://library/" + name, name);
82     buffer.append(
83       JavaUiBundle.message("library.project.structure.invalid.roots.description",
84                            link,
85                            rootName,
86                            invalidClasses.size()
87       )
88     );
89     for (String url : invalidClasses) {
90       buffer.br().nbsp(2);
91       buffer.append(PathUtil.toPresentableUrl(url));
92     }
93     return buffer.wrapWith("html").toString();
94   }
95
96   @NotNull
97   private PlaceInProjectStructure createPlace() {
98     final Project project = myContext.getProject();
99     return new PlaceInProjectStructureBase(project, ProjectStructureConfigurable.getInstance(project).createProjectOrGlobalLibraryPlace(myLibrary), this);
100   }
101
102   @Override
103   public List<ProjectStructureElementUsage> getUsagesInElement() {
104     return Collections.emptyList();
105   }
106
107   @Override
108   public boolean equals(Object o) {
109     if (this == o) return true;
110     if (!(o instanceof LibraryProjectStructureElement)) return false;
111
112     return getSourceOrThis() == (((LibraryProjectStructureElement)o).getSourceOrThis());
113   }
114
115   public ActionCallback navigate() {
116     return createPlace().navigate();
117   }
118
119   @NotNull
120   private Library getSourceOrThis() {
121     final InvocationHandler invocationHandler = Proxy.isProxyClass(myLibrary.getClass()) ? Proxy.getInvocationHandler(myLibrary) : null;
122     final Library realLibrary = invocationHandler instanceof ModuleEditor.ProxyDelegateAccessor ?
123                                 (Library)((ModuleEditor.ProxyDelegateAccessor)invocationHandler).getDelegate() : myLibrary;
124     final Library source = realLibrary instanceof LibraryEx? ((LibraryEx)realLibrary).getSource() : null;
125     return source != null ? source : myLibrary;
126   }
127
128   @Override
129   public int hashCode() {
130     return System.identityHashCode(getSourceOrThis());
131   }
132
133   @Override
134   public boolean shouldShowWarningIfUnused() {
135     final LibraryTable libraryTable = myLibrary.getTable();
136     if (libraryTable == null) return false;
137     return LibraryTablesRegistrar.PROJECT_LEVEL.equals(libraryTable.getTableLevel());
138   }
139
140   @Override
141   public ProjectStructureProblemDescription createUnusedElementWarning() {
142     final List<ConfigurationErrorQuickFix> fixes = Arrays.asList(new AddLibraryToDependenciesFix(), new RemoveLibraryFix(), new RemoveAllUnusedLibrariesFix());
143     final String name = Objects.toString(myLibrary.getName());
144     final String libraryName = HtmlChunk.link("http://library/" + name, name).toString();
145
146     @Nls final String result = JavaUiBundle.message("library.0.is.not.used", libraryName);
147     return new ProjectStructureProblemDescription(XmlStringUtil.wrapInHtml(result),
148                                                   null,
149                                                   createPlace(),
150                                                   ProjectStructureProblemType.unused("unused-library"),
151                                                   ProjectStructureProblemDescription.ProblemLevel.PROJECT,
152                                                   fixes,
153                                                   false);
154   }
155
156   @Override
157   public @Nls(capitalization = Nls.Capitalization.Sentence) String getPresentableName() {
158     return myLibrary.getName();
159   }
160
161   @Override
162   public @Nls(capitalization = Nls.Capitalization.Sentence) String getTypeName() {
163     return JavaUiBundle.message("configurable.library.prefix");
164   }
165
166   @Override
167   public String getId() {
168     return "library:" + myLibrary.getTable().getTableLevel() + ":" + myLibrary.getName();
169   }
170
171   private class RemoveInvalidRootsQuickFix extends ConfigurationErrorQuickFix {
172     private final Library myLibrary;
173     private final OrderRootType myType;
174     private final List<String> myInvalidUrls;
175
176     RemoveInvalidRootsQuickFix(Library library, OrderRootType type, List<String> invalidUrls) {
177       super(JavaUiBundle.message("label.remove.invalid.roots", invalidUrls.size()));
178       myLibrary = library;
179       myType = type;
180       myInvalidUrls = invalidUrls;
181     }
182
183     @Override
184     public void performFix() {
185       final LibraryTable.ModifiableModel libraryTable = myContext.getModifiableLibraryTable(myLibrary.getTable());
186       if (libraryTable instanceof LibrariesModifiableModel) {
187         for (String invalidRoot : myInvalidUrls) {
188           final ExistingLibraryEditor libraryEditor = ((LibrariesModifiableModel)libraryTable).getLibraryEditor(myLibrary);
189           libraryEditor.removeRoot(invalidRoot, myType);
190         }
191         myContext.getDaemonAnalyzer().queueUpdate(LibraryProjectStructureElement.this);
192         final ProjectStructureConfigurable structureConfigurable = ProjectStructureConfigurable.getInstance(myContext.getProject());
193         navigate().doWhenDone(() -> {
194           final NamedConfigurable configurable = structureConfigurable.getConfigurableFor(myLibrary).getSelectedConfigurable();
195           if (configurable instanceof LibraryConfigurable) {
196             ((LibraryConfigurable)configurable).updateComponent();
197           }
198         });
199       }
200     }
201   }
202
203   private final class AddLibraryToDependenciesFix extends ConfigurationErrorQuickFix {
204     private AddLibraryToDependenciesFix() {
205       super(JavaUiBundle.message("label.add.to.dependencies"));
206     }
207
208     @Override
209     public void performFix() {
210       LibraryEditingUtil.showDialogAndAddLibraryToDependencies(myLibrary, myContext.getProject(), false);
211     }
212   }
213
214   private final class RemoveLibraryFix extends ConfigurationErrorQuickFix {
215     private RemoveLibraryFix() {
216       super(JavaUiBundle.message("label.remove.library"));
217     }
218
219     @Override
220     public void performFix() {
221       BaseLibrariesConfigurable.getInstance(myContext.getProject(), myLibrary.getTable().getTableLevel()).removeLibrary(LibraryProjectStructureElement.this);
222     }
223   }
224
225   private final class RemoveAllUnusedLibrariesFix extends ConfigurationErrorQuickFix {
226     private RemoveAllUnusedLibrariesFix() {
227       super(JavaUiBundle.message("label.remove.all.unused.libraries"));
228     }
229
230     @Override
231     public void performFix() {
232       BaseLibrariesConfigurable configurable = BaseLibrariesConfigurable.getInstance(myContext.getProject(), LibraryTablesRegistrar.PROJECT_LEVEL);
233       Library[] libraries = configurable.getModelProvider().getModifiableModel().getLibraries();
234       List<LibraryProjectStructureElement> toRemove = new ArrayList<>();
235       for (Library library : libraries) {
236         LibraryProjectStructureElement libraryElement = new LibraryProjectStructureElement(myContext, library);
237         if (myContext.getDaemonAnalyzer().getUsages(libraryElement).isEmpty()) {
238           toRemove.add(libraryElement);
239         }
240       }
241       configurable.removeLibraries(toRemove);
242     }
243   }
244 }