2 * Copyright 2000-2009 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.intellij.codeInspection.ex;
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;
52 public class QuickFixAction extends AnAction {
53 public static final QuickFixAction[] EMPTY = new QuickFixAction[0];
54 protected final InspectionToolWrapper myToolWrapper;
56 public static InspectionResultsView getInvoker(AnActionEvent e) {
57 return InspectionResultsView.DATA_KEY.getData(e.getDataContext());
60 protected QuickFixAction(String text, @NotNull InspectionToolWrapper toolWrapper) {
61 this(text, AllIcons.Actions.CreateFromUsage, null, toolWrapper);
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);
73 public void update(AnActionEvent e) {
74 final InspectionResultsView view = getInvoker(e);
76 e.getPresentation().setEnabled(false);
80 e.getPresentation().setVisible(false);
81 e.getPresentation().setEnabled(false);
83 final InspectionTree tree = view.getTree();
84 final InspectionToolWrapper toolWrapper = tree.getSelectedToolWrapper();
85 if (!view.isSingleToolInSelection() || toolWrapper != myToolWrapper) {
89 if (!isProblemDescriptorsAcceptable() && tree.getSelectedElements().length > 0 ||
90 isProblemDescriptorsAcceptable() && tree.getSelectedDescriptors().length > 0) {
91 e.getPresentation().setVisible(true);
92 e.getPresentation().setEnabled(true);
96 protected boolean isProblemDescriptorsAcceptable() {
100 public String getText(RefEntity where) {
101 return getTemplatePresentation().getText();
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());
116 doApplyFix(getSelectedElements(e), view);
120 protected void applyFix(@NotNull Project project,
121 @NotNull GlobalInspectionContextImpl context,
122 @NotNull CommonProblemDescriptor[] descriptors,
123 @NotNull Set<PsiElement> ignoredElements) {
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());
137 if (!FileModificationService.getInstance().prepareVirtualFilesForWrite(project, readOnlyFiles)) return;
139 final RefManagerImpl refManager = (RefManagerImpl)context.getRefManager();
141 final boolean initial = refManager.isInProcess();
143 refManager.inspectionReadActionFinished();
146 final Set<PsiElement> ignoredElements = new HashSet<PsiElement>();
148 final String templatePresentationText = getTemplatePresentation().getText();
149 assert templatePresentationText != null;
150 CommandProcessor.getInstance().executeCommand(project, new Runnable() {
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);
160 }, templatePresentationText, null);
162 refreshViews(project, ignoredElements, myToolWrapper);
164 finally { //to make offline view lazy
165 if (initial) refManager.inspectionReadActionStarted();
169 public void doApplyFix(@NotNull final RefEntity[] refElements, @NotNull InspectionResultsView view) {
170 final RefManagerImpl refManager = (RefManagerImpl)view.getGlobalInspectionContext().getRefManager();
172 final boolean initial = refManager.isInProcess();
174 refManager.inspectionReadActionFinished();
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() {
183 CommandProcessor.getInstance().markCurrentCommandAsGlobal(project);
184 ApplicationManager.getApplication().runWriteAction(new Runnable() {
187 refreshNeeded[0] = applyFix(refElements);
191 }, getTemplatePresentation().getText(), null);
193 if (refreshNeeded[0]) {
194 refreshViews(view.getProject(), refElements, myToolWrapper);
197 finally { //to make offline view lazy
198 if (initial) refManager.inspectionReadActionStarted();
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);
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());
218 return readOnlyFiles;
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>() {
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();
246 return containingFile1.getName().compareTo(containingFile2.getName());
248 if (o1 instanceof RefElement) {
251 if (o2 instanceof RefElement) {
254 return o1.getName().compareTo(o2.getName());
258 return selection.toArray(new RefEntity[selection.size()]);
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);
268 context.refreshViews();
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);
280 refreshViews(project, ignoredElements, toolWrapper);
284 * @return true if immediate UI update needed.
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;
297 private class PerformFixesTask extends PerformFixesModalTask {
298 @NotNull private final GlobalInspectionContextImpl myContext;
300 private final Set<PsiElement> myIgnoredElements;
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);
309 myIgnoredElements = ignoredElements;
313 protected void applyFix(Project project, CommonProblemDescriptor descriptor) {
314 QuickFixAction.this.applyFix(myProject, myContext, new CommonProblemDescriptor[]{descriptor}, myIgnoredElements);