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