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