java & platform: write action is unnecessary in some quick fixes
[idea/community.git] / java / java-analysis-impl / src / com / intellij / codeInspection / unusedLibraries / UnusedLibrariesInspection.java
1 /*
2  * Copyright 2000-2015 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 /*
18  * User: anna
19  * Date: 18-Apr-2007
20  */
21 package com.intellij.codeInspection.unusedLibraries;
22
23 import com.intellij.analysis.AnalysisScope;
24 import com.intellij.codeInsight.daemon.GroupNames;
25 import com.intellij.codeInspection.*;
26 import com.intellij.codeInspection.reference.RefEntity;
27 import com.intellij.codeInspection.reference.RefGraphAnnotator;
28 import com.intellij.codeInspection.reference.RefManager;
29 import com.intellij.codeInspection.reference.RefModule;
30 import com.intellij.openapi.application.ApplicationManager;
31 import com.intellij.openapi.module.Module;
32 import com.intellij.openapi.module.ModuleUtilCore;
33 import com.intellij.openapi.project.Project;
34 import com.intellij.openapi.roots.*;
35 import com.intellij.openapi.roots.libraries.Library;
36 import com.intellij.openapi.util.Comparing;
37 import com.intellij.openapi.util.Key;
38 import com.intellij.openapi.util.text.StringUtil;
39 import com.intellij.openapi.vfs.VirtualFile;
40 import com.intellij.psi.PsiElement;
41 import com.intellij.psi.util.PsiUtilCore;
42 import org.jetbrains.annotations.Nls;
43 import org.jetbrains.annotations.NonNls;
44 import org.jetbrains.annotations.NotNull;
45 import org.jetbrains.annotations.Nullable;
46
47 import java.util.*;
48
49 public class UnusedLibrariesInspection extends GlobalInspectionTool {
50
51   @Nullable
52   @Override
53   public RefGraphAnnotator getAnnotator(@NotNull RefManager refManager) {
54     return new UnusedLibraryGraphAnnotator(refManager);
55   }
56
57   @Nullable
58   @Override
59   public CommonProblemDescriptor[] checkElement(@NotNull RefEntity refEntity,
60                                                 @NotNull AnalysisScope scope,
61                                                 @NotNull InspectionManager manager,
62                                                 @NotNull GlobalInspectionContext globalContext,
63                                                 @NotNull ProblemDescriptionsProcessor processor) {
64     if (refEntity instanceof RefModule) {
65       final RefModule refModule = (RefModule)refEntity;
66       final Module module = refModule.getModule();
67       if (module.isDisposed() || !scope.containsModule(module)) return CommonProblemDescriptor.EMPTY_ARRAY;
68       final ModuleRootManager moduleRootManager = ModuleRootManager.getInstance(module);
69       final Set<VirtualFile> usedRoots = refModule.getUserData(UnusedLibraryGraphAnnotator.USED_LIBRARY_ROOTS);
70
71       final List<CommonProblemDescriptor> result = new ArrayList<>();
72       for (OrderEntry entry : moduleRootManager.getOrderEntries()) {
73         if (entry instanceof LibraryOrderEntry && !((LibraryOrderEntry)entry).isExported()) {
74           if (usedRoots == null) {
75             String message = InspectionsBundle.message("unused.library.problem.descriptor", entry.getPresentableName());
76             result.add(manager.createProblemDescriptor(message, new RemoveUnusedLibrary(refModule, entry, null)));
77           } else {
78             final Set<VirtualFile> files = new HashSet<>(Arrays.asList(((LibraryOrderEntry)entry).getRootFiles(OrderRootType.CLASSES)));
79             files.removeAll(usedRoots);
80             if (!files.isEmpty()) {
81               final String unusedLibraryRoots = StringUtil.join(files, file -> file.getPresentableName(), ",");
82               String message =
83                 InspectionsBundle.message("unused.library.roots.problem.descriptor", unusedLibraryRoots, entry.getPresentableName());
84               processor.addProblemElement(refModule,
85                                           manager.createProblemDescriptor(message, new RemoveUnusedLibrary(refModule, entry, files)));
86             }
87           }
88         }
89       }
90
91       return result.isEmpty() ? null : result.toArray(new CommonProblemDescriptor[result.size()]);
92     }
93     return null;
94   }
95
96
97   @Override
98   public boolean isEnabledByDefault() {
99     return false;
100   }
101
102   @Override
103   @Nls
104   @NotNull
105   public String getGroupDisplayName() {
106     return GroupNames.DECLARATION_REDUNDANCY;
107   }
108
109   @Override
110   @NotNull
111   public String getDisplayName() {
112     return InspectionsBundle.message("unused.library.display.name");
113   }
114
115   @Override
116   @NonNls
117   @NotNull
118   public String getShortName() {
119     return "UnusedLibrary";
120   }
121
122   private static class RemoveUnusedLibrary implements QuickFix {
123     private final RefModule myRefModule;
124     private final OrderEntry myOrderEntry;
125     private final Set<VirtualFile> myFiles;
126
127     public RemoveUnusedLibrary(final RefModule refModule, final OrderEntry orderEntry, final Set<VirtualFile> files) {
128       myRefModule = refModule;
129       myOrderEntry = orderEntry;
130       myFiles = files;
131     }
132
133     @Override
134     public boolean startInWriteAction() {
135       return false;
136     }
137
138     @Override
139     @NotNull
140     public String getFamilyName() {
141       return myFiles == null ? InspectionsBundle.message("detach.library.quickfix.name") : InspectionsBundle.message("detach.library.roots.quickfix.name");
142     }
143
144     @Override
145     public void applyFix(@NotNull final Project project, @NotNull final CommonProblemDescriptor descriptor) {
146       final Module module = myRefModule.getModule();
147
148       ApplicationManager.getApplication().runWriteAction(() -> {
149         final ModifiableRootModel model = ModuleRootManager.getInstance(module).getModifiableModel();
150         for (OrderEntry entry : model.getOrderEntries()) {
151           if (entry instanceof LibraryOrderEntry && Comparing.strEqual(entry.getPresentableName(), myOrderEntry.getPresentableName())) {
152             if (myFiles == null) {
153               model.removeOrderEntry(entry);
154             }
155             else {
156               final Library library = ((LibraryOrderEntry)entry).getLibrary();
157               if (library != null) {
158                 final Library.ModifiableModel modifiableModel = library.getModifiableModel();
159                 for (VirtualFile file : myFiles) {
160                   modifiableModel.removeRoot(file.getUrl(), OrderRootType.CLASSES);
161                 }
162                 modifiableModel.commit();
163               }
164             }
165           }
166         }
167         model.commit();
168       });
169     }
170   }
171
172   private static class UnusedLibraryGraphAnnotator extends RefGraphAnnotator {
173     public static final Key<Set<VirtualFile>> USED_LIBRARY_ROOTS = Key.create("inspection.dependencies");
174     private final ProjectFileIndex myFileIndex;
175     private final RefManager myManager;
176
177     public UnusedLibraryGraphAnnotator(RefManager manager) {
178       myManager = manager;
179       myFileIndex = ProjectRootManager.getInstance(manager.getProject()).getFileIndex();
180     }
181
182     @Override
183     public void onMarkReferenced(PsiElement what, PsiElement from, boolean referencedFromClassInitializer) {
184       if (what != null && from != null){
185         final VirtualFile virtualFile = PsiUtilCore.getVirtualFile(what);
186         final VirtualFile containingDir = virtualFile != null ? virtualFile.getParent() : null;
187         if (containingDir != null) {
188           final VirtualFile libraryClassRoot = myFileIndex.getClassRootForFile(containingDir);
189           if (libraryClassRoot != null) {
190             final Module fromModule = ModuleUtilCore.findModuleForPsiElement(from);
191             if (fromModule != null){
192               final RefModule refModule = myManager.getRefModule(fromModule);
193               if (refModule != null) {
194                 Set<VirtualFile> modules = refModule.getUserData(USED_LIBRARY_ROOTS);
195                 if (modules == null){
196                   modules = new HashSet<>();
197                   refModule.putUserData(USED_LIBRARY_ROOTS, modules);
198                 }
199                 modules.add(libraryClassRoot);
200               }
201             }
202           }
203         }
204       }
205     }
206   }
207 }