inspection tool window: update nodes when fix action is invoked (IDEA-152226)
[idea/community.git] / platform / lang-impl / src / com / intellij / codeInspection / ex / QuickFixAction.java
1 /*
2  * Copyright 2000-2009 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 package com.intellij.codeInspection.ex;
18
19 import com.intellij.codeInsight.FileModificationService;
20 import com.intellij.codeInspection.CommonProblemDescriptor;
21 import com.intellij.codeInspection.InspectionManager;
22 import com.intellij.codeInspection.ProblemDescriptor;
23 import com.intellij.codeInspection.reference.RefElement;
24 import com.intellij.codeInspection.reference.RefEntity;
25 import com.intellij.codeInspection.reference.RefManagerImpl;
26 import com.intellij.codeInspection.ui.InspectionResultsView;
27 import com.intellij.codeInspection.ui.InspectionTree;
28 import com.intellij.icons.AllIcons;
29 import com.intellij.openapi.actionSystem.AnAction;
30 import com.intellij.openapi.actionSystem.AnActionEvent;
31 import com.intellij.openapi.actionSystem.CustomShortcutSet;
32 import com.intellij.openapi.application.ApplicationManager;
33 import com.intellij.openapi.command.CommandProcessor;
34 import com.intellij.openapi.diagnostic.Logger;
35 import com.intellij.openapi.progress.ProgressManager;
36 import com.intellij.openapi.project.Project;
37 import com.intellij.openapi.vfs.ReadonlyStatusHandler;
38 import com.intellij.openapi.vfs.VfsUtilCore;
39 import com.intellij.openapi.vfs.VirtualFile;
40 import com.intellij.psi.PsiDocumentManager;
41 import com.intellij.psi.PsiElement;
42 import com.intellij.psi.PsiFile;
43 import com.intellij.psi.util.PsiUtilCore;
44 import com.intellij.util.SequentialModalProgressTask;
45 import gnu.trove.THashSet;
46 import org.jetbrains.annotations.NotNull;
47
48 import javax.swing.*;
49 import javax.swing.tree.DefaultTreeModel;
50 import javax.swing.tree.TreeNode;
51 import javax.swing.tree.TreePath;
52 import java.util.*;
53
54 /**
55  * @author max
56  */
57 public class QuickFixAction extends AnAction {
58   private static final Logger LOG = Logger.getInstance("#" + QuickFixAction.class.getName());
59
60   public static final QuickFixAction[] EMPTY = new QuickFixAction[0];
61   protected final InspectionToolWrapper myToolWrapper;
62
63   public static InspectionResultsView getInvoker(AnActionEvent e) {
64     return InspectionResultsView.DATA_KEY.getData(e.getDataContext());
65   }
66
67   protected QuickFixAction(String text, @NotNull InspectionToolWrapper toolWrapper) {
68     this(text, AllIcons.Actions.CreateFromUsage, null, toolWrapper);
69   }
70
71   protected QuickFixAction(String text, Icon icon, KeyStroke keyStroke, @NotNull InspectionToolWrapper toolWrapper) {
72     super(text, null, icon);
73     myToolWrapper = toolWrapper;
74     if (keyStroke != null) {
75       registerCustomShortcutSet(new CustomShortcutSet(keyStroke), null);
76     }
77   }
78
79   @Override
80   public void update(AnActionEvent e) {
81     final InspectionResultsView view = getInvoker(e);
82     if (view == null) {
83       e.getPresentation().setEnabled(false);
84       return;
85     }
86
87     e.getPresentation().setVisible(false);
88     e.getPresentation().setEnabled(false);
89
90     final InspectionTree tree = view.getTree();
91     final InspectionToolWrapper toolWrapper = tree.getSelectedToolWrapper();
92     if (!view.isSingleToolInSelection() || toolWrapper != myToolWrapper) {
93       return;
94     }
95
96     if (!isProblemDescriptorsAcceptable() && tree.getSelectedElements().length > 0 ||
97         isProblemDescriptorsAcceptable() && tree.getSelectedDescriptors().length > 0) {
98       e.getPresentation().setVisible(true);
99       e.getPresentation().setEnabled(true);
100     }
101   }
102
103   protected boolean isProblemDescriptorsAcceptable() {
104     return false;
105   }
106
107   public String getText() {
108     return getTemplatePresentation().getText();
109   }
110
111   @Override
112   public void actionPerformed(final AnActionEvent e) {
113     final InspectionResultsView view = getInvoker(e);
114     final InspectionTree tree = view.getTree();
115     final CommonProblemDescriptor[] descriptors;
116     if (isProblemDescriptorsAcceptable() && (descriptors = tree.getSelectedDescriptors()).length > 0) {
117       doApplyFix(view.getProject(), descriptors, tree.getContext());
118     } else {
119       doApplyFix(getSelectedElements(e), view);
120     }
121     view.updateRightPanel();
122   }
123
124
125   protected void applyFix(@NotNull Project project,
126                           @NotNull GlobalInspectionContextImpl context,
127                           @NotNull CommonProblemDescriptor[] descriptors,
128                           @NotNull Set<PsiElement> ignoredElements) {
129   }
130
131   private void doApplyFix(@NotNull final Project project,
132                           @NotNull final CommonProblemDescriptor[] descriptors,
133                           @NotNull final GlobalInspectionContextImpl context) {
134     final Set<VirtualFile> readOnlyFiles = new THashSet<VirtualFile>();
135     for (CommonProblemDescriptor descriptor : descriptors) {
136       final PsiElement psiElement = descriptor instanceof ProblemDescriptor ? ((ProblemDescriptor)descriptor).getPsiElement() : null;
137       if (psiElement != null && !psiElement.isWritable()) {
138         readOnlyFiles.add(psiElement.getContainingFile().getVirtualFile());
139       }
140     }
141
142     if (!FileModificationService.getInstance().prepareVirtualFilesForWrite(project, readOnlyFiles)) return;
143     
144     Arrays.sort(descriptors, (c1, c2) -> {
145       if (c1 instanceof ProblemDescriptor && c2 instanceof ProblemDescriptor) {
146         return PsiUtilCore.compareElementsByPosition(((ProblemDescriptor)c2).getPsiElement(), 
147                                                      ((ProblemDescriptor)c1).getPsiElement());
148       }
149       return c1.getDescriptionTemplate().compareTo(c2.getDescriptionTemplate()); 
150     });
151
152     final RefManagerImpl refManager = (RefManagerImpl)context.getRefManager();
153
154     final boolean initial = refManager.isInProcess();
155
156     refManager.inspectionReadActionFinished();
157
158     try {
159       final Set<PsiElement> ignoredElements = new HashSet<PsiElement>();
160
161       final String templatePresentationText = getTemplatePresentation().getText();
162       assert templatePresentationText != null;
163       CommandProcessor.getInstance().executeCommand(project, new Runnable() {
164         @Override
165         public void run() {
166           CommandProcessor.getInstance().markCurrentCommandAsGlobal(project);
167           final SequentialModalProgressTask progressTask =
168             new SequentialModalProgressTask(project, templatePresentationText, true);
169           progressTask.setMinIterationTime(200);
170           progressTask.setTask(new PerformFixesTask(project, descriptors, ignoredElements, progressTask, context));
171           ProgressManager.getInstance().run(progressTask);
172         }
173       }, templatePresentationText, null);
174
175       refreshViews(project, ignoredElements, myToolWrapper);
176       final InspectionTree tree = context.getView().getTree();
177       final TreePath[] selected = tree.getSelectionPaths();
178       if (selected != null) {
179         for (TreePath path : selected) {
180           path.getLastPathComponent();
181           ((DefaultTreeModel) tree.getModel()).reload((TreeNode)path.getLastPathComponent());
182         }
183       }
184       tree.restoreExpansionAndSelection();
185     }
186     finally { //to make offline view lazy
187       if (initial) refManager.inspectionReadActionStarted();
188     }
189   }
190
191   public void doApplyFix(@NotNull final RefEntity[] refElements, @NotNull InspectionResultsView view) {
192     final RefManagerImpl refManager = (RefManagerImpl)view.getGlobalInspectionContext().getRefManager();
193
194     final boolean initial = refManager.isInProcess();
195
196     refManager.inspectionReadActionFinished();
197
198     try {
199       final boolean[] refreshNeeded = {false};
200       if (refElements.length > 0) {
201         final Project project = refElements[0].getRefManager().getProject();
202         CommandProcessor.getInstance().executeCommand(project, new Runnable() {
203           @Override
204           public void run() {
205             CommandProcessor.getInstance().markCurrentCommandAsGlobal(project);
206             ApplicationManager.getApplication().runWriteAction(new Runnable() {
207               @Override
208               public void run() {
209                 refreshNeeded[0] = applyFix(refElements);
210               }
211             });
212           }
213         }, getTemplatePresentation().getText(), null);
214       }
215       if (refreshNeeded[0]) {
216         refreshViews(view.getProject(), refElements, myToolWrapper);
217       }
218     }
219     finally {  //to make offline view lazy
220       if (initial) refManager.inspectionReadActionStarted();
221     }
222   }
223
224   public static void removeElements(@NotNull RefEntity[] refElements, @NotNull Project project, @NotNull InspectionToolWrapper toolWrapper) {
225     refreshViews(project, refElements, toolWrapper);
226     final ArrayList<RefElement> deletedRefs = new ArrayList<RefElement>(1);
227     for (RefEntity refElement : refElements) {
228       if (!(refElement instanceof RefElement)) continue;
229       refElement.getRefManager().removeRefElement((RefElement)refElement, deletedRefs);
230     }
231   }
232
233   private static Set<VirtualFile> getReadOnlyFiles(@NotNull RefEntity[] refElements) {
234     Set<VirtualFile> readOnlyFiles = new THashSet<VirtualFile>();
235     for (RefEntity refElement : refElements) {
236       PsiElement psiElement = refElement instanceof RefElement ? ((RefElement)refElement).getElement() : null;
237       if (psiElement == null || psiElement.getContainingFile() == null) continue;
238       readOnlyFiles.add(psiElement.getContainingFile().getVirtualFile());
239     }
240     return readOnlyFiles;
241   }
242
243   private static RefEntity[] getSelectedElements(AnActionEvent e) {
244     final InspectionResultsView invoker = getInvoker(e);
245     if (invoker == null) return new RefElement[0];
246     List<RefEntity> selection = new ArrayList<RefEntity>(Arrays.asList(invoker.getTree().getSelectedElements()));
247     PsiDocumentManager.getInstance(invoker.getProject()).commitAllDocuments();
248     Collections.sort(selection, new Comparator<RefEntity>() {
249       @Override
250       public int compare(RefEntity o1, RefEntity o2) {
251         if (o1 instanceof RefElement && o2 instanceof RefElement) {
252           RefElement r1 = (RefElement)o1;
253           RefElement r2 = (RefElement)o2;
254           final PsiElement element1 = r1.getElement();
255           final PsiElement element2 = r2.getElement();
256           final PsiFile containingFile1 = element1.getContainingFile();
257           final PsiFile containingFile2 = element2.getContainingFile();
258           if (containingFile1 == containingFile2) {
259             int i1 = element1.getTextOffset();
260             int i2 = element2.getTextOffset();
261             if (i1 < i2) {
262               return 1;
263             } else if (i1 > i2){
264               return -1;
265             }
266             return 0;
267           }
268           return containingFile1.getName().compareTo(containingFile2.getName());
269         }
270         if (o1 instanceof RefElement) {
271           return 1;
272         }
273         if (o2 instanceof RefElement) {
274           return -1;
275         }
276         return o1.getName().compareTo(o2.getName());
277       }
278     });
279
280     return selection.toArray(new RefEntity[selection.size()]);
281   }
282
283   private static void refreshViews(@NotNull Project project, @NotNull Set<PsiElement> selectedElements, @NotNull InspectionToolWrapper toolWrapper) {
284     InspectionManagerEx managerEx = (InspectionManagerEx)InspectionManager.getInstance(project);
285     final Set<GlobalInspectionContextImpl> runningContexts = managerEx.getRunningContexts();
286     for (GlobalInspectionContextImpl context : runningContexts) {
287       for (PsiElement element : selectedElements) {
288         context.ignoreElement(toolWrapper.getTool(), element);
289       }
290       context.refreshViews();
291     }
292   }
293
294   private static void refreshViews(@NotNull Project project, @NotNull RefEntity[] refElements, @NotNull InspectionToolWrapper toolWrapper) {
295     final Set<PsiElement> ignoredElements = new HashSet<PsiElement>();
296     for (RefEntity element : refElements) {
297       final PsiElement psiElement = element instanceof RefElement ? ((RefElement)element).getElement() : null;
298       if (psiElement != null && psiElement.isValid()) {
299         ignoredElements.add(psiElement);
300       }
301     }
302     refreshViews(project, ignoredElements, toolWrapper);
303   }
304
305   /**
306    * @return true if immediate UI update needed.
307    */
308   protected boolean applyFix(@NotNull RefEntity[] refElements) {
309     Set<VirtualFile> readOnlyFiles = getReadOnlyFiles(refElements);
310     if (!readOnlyFiles.isEmpty()) {
311       final Project project = refElements[0].getRefManager().getProject();
312       final ReadonlyStatusHandler.OperationStatus operationStatus = ReadonlyStatusHandler.getInstance(project).ensureFilesWritable(
313         VfsUtilCore.toVirtualFileArray(readOnlyFiles));
314       if (operationStatus.hasReadonlyFiles()) return false;
315     }
316     return true;
317   }
318
319   private class PerformFixesTask extends PerformFixesModalTask {
320     @NotNull private final GlobalInspectionContextImpl myContext;
321     @NotNull
322     private final Set<PsiElement> myIgnoredElements;
323
324     public PerformFixesTask(@NotNull Project project,
325                             @NotNull CommonProblemDescriptor[] descriptors,
326                             @NotNull Set<PsiElement> ignoredElements,
327                             @NotNull SequentialModalProgressTask task,
328                             @NotNull GlobalInspectionContextImpl context) {
329       super(project, descriptors, task);
330       myContext = context;
331       myIgnoredElements = ignoredElements;
332     }
333
334     @Override
335     protected void applyFix(Project project, CommonProblemDescriptor descriptor) {
336       if (descriptor instanceof ProblemDescriptor && 
337           ((ProblemDescriptor)descriptor).getStartElement() == null &&
338           ((ProblemDescriptor)descriptor).getEndElement() == null) {
339         if (LOG.isDebugEnabled()) {
340           LOG.debug("Invalidated psi for " + descriptor);
341         }
342         return;
343       }
344       QuickFixAction.this.applyFix(myProject, myContext, new CommonProblemDescriptor[]{descriptor}, myIgnoredElements);
345     }
346
347     @Override
348     public void stop() {
349     }
350   }
351 }