diff --git a/java/java-impl/src/com/intellij/codeInspection/inferNullity/AnnotateTask.java b/java/java-impl/src/com/intellij/codeInspection/inferNullity/AnnotateTask.java index 3cc23f4140ff..a7fd8ff16632 100644 --- a/java/java-impl/src/com/intellij/codeInspection/inferNullity/AnnotateTask.java +++ b/java/java-impl/src/com/intellij/codeInspection/inferNullity/AnnotateTask.java @@ -18,23 +18,24 @@ package com.intellij.codeInspection.inferNullity; import com.intellij.codeInsight.NullableNotNullManager; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.project.Project; +import com.intellij.usageView.UsageInfo; import com.intellij.util.SequentialModalProgressTask; import com.intellij.util.SequentialTask; class AnnotateTask implements SequentialTask { private final Project myProject; - private final NullityInferrer myInferrer; + private UsageInfo[] myInfos; private final SequentialModalProgressTask myTask; private int myCount = 0; private final int myTotal; private final NullableNotNullManager myNotNullManager; - public AnnotateTask(Project project, NullityInferrer inferrer, SequentialModalProgressTask progressTask) { + public AnnotateTask(Project project, SequentialModalProgressTask progressTask, UsageInfo[] infos) { myProject = project; + myInfos = infos; myNotNullManager = NullableNotNullManager.getInstance(myProject); - myInferrer = inferrer; myTask = progressTask; - myTotal = myInferrer.getCount(); + myTotal = infos.length; } @Override @@ -53,7 +54,7 @@ class AnnotateTask implements SequentialTask { indicator.setFraction(((double)myCount) / myTotal); } - myInferrer.apply(myCount++, myProject, myNotNullManager); + NullityInferrer.apply(myProject, myNotNullManager, myInfos[myCount++]); return isDone(); } diff --git a/java/java-impl/src/com/intellij/codeInspection/inferNullity/InferNullityAnnotationsAction.java b/java/java-impl/src/com/intellij/codeInspection/inferNullity/InferNullityAnnotationsAction.java index 7affcf4c8b04..c3d61021c0d0 100644 --- a/java/java-impl/src/com/intellij/codeInspection/inferNullity/InferNullityAnnotationsAction.java +++ b/java/java-impl/src/com/intellij/codeInspection/inferNullity/InferNullityAnnotationsAction.java @@ -25,13 +25,14 @@ import com.intellij.codeInsight.daemon.impl.quickfix.LocateLibraryDialog; import com.intellij.codeInsight.daemon.impl.quickfix.OrderEntryFix; import com.intellij.history.LocalHistory; import com.intellij.history.LocalHistoryAction; +import com.intellij.ide.util.PropertiesComponent; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.PathManager; import com.intellij.openapi.application.Result; import com.intellij.openapi.command.WriteCommandAction; import com.intellij.openapi.editor.Document; import com.intellij.openapi.module.Module; -import com.intellij.openapi.module.ModuleUtil; +import com.intellij.openapi.module.ModuleUtilCore; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; @@ -41,24 +42,35 @@ import com.intellij.openapi.roots.libraries.Library; import com.intellij.openapi.roots.libraries.LibraryUtil; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.ui.VerticalFlowLayout; +import com.intellij.openapi.util.Computable; +import com.intellij.openapi.util.Factory; +import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.pom.java.LanguageLevel; import com.intellij.psi.*; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.util.PsiUtil; +import com.intellij.refactoring.RefactoringBundle; import com.intellij.ui.TitledSeparator; +import com.intellij.usageView.UsageInfo; +import com.intellij.usageView.UsageViewUtil; +import com.intellij.usages.*; import com.intellij.util.Function; +import com.intellij.util.Processor; import com.intellij.util.SequentialModalProgressTask; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import javax.swing.*; +import java.util.ArrayList; import java.util.HashSet; +import java.util.List; import java.util.Set; public class InferNullityAnnotationsAction extends BaseAnalysisAction { @NonNls private static final String INFER_NULLITY_ANNOTATIONS = "Infer Nullity Annotations"; + @NonNls private static final String ANNOTATE_LOCAL_VARIABLES = "annotate.local.variables"; private JCheckBox myAnnotateLocalVariablesCb; public InferNullityAnnotationsAction() { @@ -67,6 +79,9 @@ public class InferNullityAnnotationsAction extends BaseAnalysisAction { @Override protected void analyze(@NotNull final Project project, @NotNull final AnalysisScope scope) { + + PropertiesComponent.getInstance().setValue(ANNOTATE_LOCAL_VARIABLES, String.valueOf(myAnnotateLocalVariablesCb.isSelected())); + final ProgressManager progressManager = ProgressManager.getInstance(); final int totalFiles = scope.getFileCount(); @@ -91,7 +106,7 @@ public class InferNullityAnnotationsAction extends BaseAnalysisAction { } progressIndicator.setFraction(((double)myFileCount) / totalFiles); } - final Module module = ModuleUtil.findModuleForPsiElement(file); + final Module module = ModuleUtilCore.findModuleForPsiElement(file); if (module != null && !processed.contains(module)) { processed.add(module); if (JavaPsiFacade.getInstance(project) @@ -173,12 +188,32 @@ public class InferNullityAnnotationsAction extends BaseAnalysisAction { return; } if (scope.checkScopeWritable(project)) return; + PsiDocumentManager.getInstance(project).commitAllDocuments(); + final UsageInfo[] usageInfos = findUsages(project, scope); + if (usageInfos == null) return; + + if (usageInfos.length < 5) { + SwingUtilities.invokeLater(applyRunnable(project, new Computable() { + @Override + public UsageInfo[] compute() { + return usageInfos; + } + })); + } + else { + showUsageView(project, usageInfos, scope); + } + } + + private UsageInfo[] findUsages(final Project project, + final AnalysisScope scope) { final NullityInferrer inferrer = new NullityInferrer(myAnnotateLocalVariablesCb.isSelected(), project); final PsiManager psiManager = PsiManager.getInstance(project); - if (!progressManager.runProcessWithProgressSynchronously(new Runnable() { + final Runnable searchForUsages = new Runnable() { @Override public void run() { + final int totalFiles = scope.getFileCount(); scope.accept(new PsiElementVisitor() { int myFileCount = 0; @@ -191,9 +226,7 @@ public class InferNullityAnnotationsAction extends BaseAnalysisAction { if (document == null || virtualFile.getFileType().isBinary()) return; //do not inspect binary files final ProgressIndicator progressIndicator = ProgressManager.getInstance().getProgressIndicator(); if (progressIndicator != null) { - if (virtualFile != null) { - progressIndicator.setText2(ProjectUtil.calcRelativeToProjectPath(virtualFile, project)); - } + progressIndicator.setText2(ProjectUtil.calcRelativeToProjectPath(virtualFile, project)); progressIndicator.setFraction(((double)myFileCount) / totalFiles); } if (file instanceof PsiJavaFile) { @@ -202,11 +235,22 @@ public class InferNullityAnnotationsAction extends BaseAnalysisAction { } }); } - }, INFER_NULLITY_ANNOTATIONS, true, project)) { - return; + }; + if (ApplicationManager.getApplication().isDispatchThread()) { + if (!ProgressManager.getInstance().runProcessWithProgressSynchronously(searchForUsages, INFER_NULLITY_ANNOTATIONS, true, project)) { + return null; + } + } else { + searchForUsages.run(); } - final Runnable applyRunnable = new Runnable() { + final List usages = new ArrayList(); + inferrer.collect(usages); + return usages.toArray(new UsageInfo[usages.size()]); + } + + private static Runnable applyRunnable(final Project project, final Computable computable) { + return new Runnable() { @Override public void run() { final LocalHistoryAction action = LocalHistory.getInstance().startAction(INFER_NULLITY_ANNOTATIONS); @@ -214,11 +258,14 @@ public class InferNullityAnnotationsAction extends BaseAnalysisAction { new WriteCommandAction(project, INFER_NULLITY_ANNOTATIONS) { @Override protected void run(Result result) throws Throwable { - if (!inferrer.nothingFoundMessage(project)) { + final UsageInfo[] infos = computable.compute(); + if (infos.length > 0) { final SequentialModalProgressTask progressTask = new SequentialModalProgressTask(project, INFER_NULLITY_ANNOTATIONS, false); progressTask.setMinIterationTime(200); - progressTask.setTask(new AnnotateTask(project, inferrer, progressTask)); + progressTask.setTask(new AnnotateTask(project, progressTask, infos)); ProgressManager.getInstance().run(progressTask); + } else { + NullityInferrer.nothingFoundMessage(project); } } }.execute(); @@ -228,7 +275,6 @@ public class InferNullityAnnotationsAction extends BaseAnalysisAction { } } }; - SwingUtilities.invokeLater(applyRunnable); } private void restartAnalysis(final Project project, final AnalysisScope scope) { @@ -241,11 +287,69 @@ public class InferNullityAnnotationsAction extends BaseAnalysisAction { } + private void showUsageView(final Project project, final UsageInfo[] usageInfos, AnalysisScope scope) { + final UsageTarget[] targets = UsageTarget.EMPTY_ARRAY; + final Ref convertUsagesRef = new Ref(); + if (!ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() { + @Override + public void run() { + ApplicationManager.getApplication().runReadAction(new Runnable() { + @Override + public void run() { + convertUsagesRef.set(UsageInfo2UsageAdapter.convert(usageInfos)); + } + }); + } + }, "Preprocess usages", true, project)) return; + + if (convertUsagesRef.isNull()) return; + final Usage[] usages = convertUsagesRef.get(); + + final UsageViewPresentation presentation = new UsageViewPresentation(); + presentation.setTabText("Infer Nullity Preview"); + presentation.setShowReadOnlyStatusAsRed(true); + presentation.setShowCancelButton(true); + presentation.setUsagesString(RefactoringBundle.message("usageView.usagesText")); + + final UsageView usageView = UsageViewManager.getInstance(project).showUsages(targets, usages, presentation, rerunFactory(project, scope)); + + final Runnable refactoringRunnable = applyRunnable(project, new Computable() { + @Override + public UsageInfo[] compute() { + final Set infos = UsageViewUtil.getNotExcludedUsageInfos(usageView); + return infos.toArray(new UsageInfo[infos.size()]); + } + }); + + String canNotMakeString = "Cannot perform operation.\nThere were changes in code after usages have been found.\nPlease perform operation search again."; + + usageView.addPerformOperationAction(refactoringRunnable, INFER_NULLITY_ANNOTATIONS, canNotMakeString, INFER_NULLITY_ANNOTATIONS, false); + } + + private Factory rerunFactory(final Project project, final AnalysisScope scope) { + return new Factory() { + @Override + public UsageSearcher create() { + return new UsageInfoSearcherAdapter() { + @Override + protected UsageInfo[] findUsages() { + return InferNullityAnnotationsAction.this.findUsages(project, scope); + } + + @Override + public void generate(@NotNull Processor processor) { + processUsages(processor, project); + } + }; + } + }; + } + @Override protected JComponent getAdditionalActionSettings(Project project, BaseAnalysisActionDialog dialog) { final JPanel panel = new JPanel(new VerticalFlowLayout()); panel.add(new TitledSeparator()); - myAnnotateLocalVariablesCb = new JCheckBox("Annotate local variables", false); + myAnnotateLocalVariablesCb = new JCheckBox("Annotate local variables", PropertiesComponent.getInstance().getBoolean(ANNOTATE_LOCAL_VARIABLES, false)); panel.add(myAnnotateLocalVariablesCb); return panel; } diff --git a/java/java-impl/src/com/intellij/codeInspection/inferNullity/NullityInferrer.java b/java/java-impl/src/com/intellij/codeInspection/inferNullity/NullityInferrer.java index 649634903574..7ce5e3f36183 100644 --- a/java/java-impl/src/com/intellij/codeInspection/inferNullity/NullityInferrer.java +++ b/java/java-impl/src/com/intellij/codeInspection/inferNullity/NullityInferrer.java @@ -17,7 +17,6 @@ package com.intellij.codeInspection.inferNullity; import com.intellij.codeInsight.NullableNotNullManager; import com.intellij.codeInsight.intention.AddAnnotationFix; -import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.command.WriteCommandAction; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Messages; @@ -27,6 +26,7 @@ import com.intellij.psi.search.searches.OverridingMethodsSearch; import com.intellij.psi.search.searches.ReferencesSearch; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiUtil; +import com.intellij.usageView.UsageInfo; import com.intellij.util.ArrayUtil; import com.intellij.util.Query; import org.jetbrains.annotations.NotNull; @@ -145,49 +145,40 @@ public class NullityInferrer { public void apply(final Project project) { final NullableNotNullManager manager = NullableNotNullManager.getInstance(project); for (SmartPsiElementPointer pointer : myNullableSet) { - annotateNullable(project, manager, pointer); + annotateNullable(project, manager, pointer.getElement()); } for (SmartPsiElementPointer pointer : myNotNullSet) { - annotateNotNull(project, manager, pointer); + annotateNotNull(project, manager, pointer.getElement()); } - nothingFoundMessage(project); - } - - public boolean nothingFoundMessage(final Project project) { if (myNullableSet.isEmpty() && myNotNullSet.isEmpty()) { - if (ApplicationManager.getApplication().isUnitTestMode()) { - throw new RuntimeException(NOTHING_FOUND_TO_INFER); - } - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - Messages.showInfoMessage(project, "No places found to infer @Nullable/@NotNull", "Infer Nullity Results"); - } - }); - return true; + throw new RuntimeException(NOTHING_FOUND_TO_INFER); } - return false; } - private void annotateNotNull(Project project, - NullableNotNullManager manager, - SmartPsiElementPointer pointer) { - final PsiModifierListOwner element = pointer.getElement(); + public static void nothingFoundMessage(final Project project) { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + Messages.showInfoMessage(project, "No places found to infer @Nullable/@NotNull", "Infer Nullity Results"); + } + }); + } + + private static void annotateNotNull(Project project, + NullableNotNullManager manager, + final PsiModifierListOwner element) { if (element != null) { - if (shouldIgnore(element)) return; if (element instanceof PsiField && ((PsiField)element).hasInitializer() && element.hasModifierProperty(PsiModifier.FINAL)) return; invoke(project, element, manager.getDefaultNotNull(), manager.getDefaultNullable()); } } - private void annotateNullable(Project project, - NullableNotNullManager manager, - SmartPsiElementPointer pointer) { - final PsiModifierListOwner element = pointer.getElement(); + private static void annotateNullable(Project project, + NullableNotNullManager manager, + final PsiModifierListOwner element) { if (element != null) { - if (shouldIgnore(element)) return; invoke(project, element, manager.getDefaultNullable(), manager.getDefaultNotNull()); } } @@ -207,12 +198,11 @@ public class NullityInferrer { return myNotNullSet.size() + myNullableSet.size(); } - public void apply(int i, Project project, NullableNotNullManager manager) { - if (i < myNullableSet.size()) { - annotateNullable(project, manager, myNullableSet.get(i)); - } else { - i -= myNullableSet.size(); - annotateNotNull(project, manager, myNotNullSet.get(i)); + public static void apply(Project project, NullableNotNullManager manager, UsageInfo info) { + if (info instanceof NullableUsageInfo) { + annotateNullable(project, manager, (PsiModifierListOwner)info.getElement()); + } else if (info instanceof NotNullUsageInfo) { + annotateNotNull(project, manager, (PsiModifierListOwner)info.getElement()); } } @@ -243,6 +233,33 @@ public class NullityInferrer { numAnnotationsAdded++; } + private static class NullableUsageInfo extends UsageInfo { + public NullableUsageInfo(@NotNull PsiElement element) { + super(element); + } + } + + private static class NotNullUsageInfo extends UsageInfo { + private NotNullUsageInfo(@NotNull PsiElement element) { + super(element); + } + } + + public void collect(List usages) { + collect(usages, true); + collect(usages, false); + } + + private void collect(List usages, boolean nullable) { + final List> set = nullable ? myNullableSet : myNotNullSet; + for (SmartPsiElementPointer elementPointer : set) { + final PsiModifierListOwner element = elementPointer.getElement(); + if (element != null && !shouldIgnore(element)) { + usages.add(nullable ? new NullableUsageInfo(element) : new NotNullUsageInfo(element)); + } + } + } + private class ExpressionIsNeverNullVisitor extends JavaElementVisitor { private boolean neverNull = true; diff --git a/platform/lang-impl/src/com/intellij/refactoring/BaseRefactoringProcessor.java b/platform/lang-impl/src/com/intellij/refactoring/BaseRefactoringProcessor.java index 2532a9207298..27f65c87ccc6 100644 --- a/platform/lang-impl/src/com/intellij/refactoring/BaseRefactoringProcessor.java +++ b/platform/lang-impl/src/com/intellij/refactoring/BaseRefactoringProcessor.java @@ -237,7 +237,7 @@ public abstract class BaseRefactoringProcessor implements Runnable { Factory factory = new Factory() { @Override public UsageSearcher create() { - return new UsageSearcher() { + return new UsageInfoSearcherAdapter() { @Override public void generate(@NotNull final Processor processor) { ApplicationManager.getApplication().runReadAction(new Runnable() { @@ -249,38 +249,12 @@ public abstract class BaseRefactoringProcessor implements Runnable { refreshElements(elements); } }); - final Ref refUsages = new Ref(); - final Ref dumbModeOccurred = new Ref(); - ApplicationManager.getApplication().runReadAction(new Runnable() { - @Override - public void run() { - try { - refUsages.set(findUsages()); - } - catch (IndexNotReadyException e) { - dumbModeOccurred.set(true); - } - } - }); - if (!dumbModeOccurred.isNull()) { - DumbService.getInstance(myProject).showDumbModeNotification("Usage search is not available until indices are ready"); - return; - } - final Usage[] usages = ApplicationManager.getApplication().runReadAction(new Computable() { - @Override - public Usage[] compute() { - return UsageInfo2UsageAdapter.convert(refUsages.get()); - } - }); + processUsages(processor, myProject); + } - for (final Usage usage : usages) { - ApplicationManager.getApplication().runReadAction(new Runnable() { - @Override - public void run() { - processor.process(usage); - } - }); - } + @Override + protected UsageInfo[] findUsages() { + return BaseRefactoringProcessor.this.findUsages(); } }; } @@ -418,7 +392,7 @@ public abstract class BaseRefactoringProcessor implements Runnable { final Runnable refactoringRunnable = new Runnable() { @Override public void run() { - Set usagesToRefactor = getUsageInfosToRefactor(usageView); + Set usagesToRefactor = UsageViewUtil.getNotExcludedUsageInfos(usageView); final UsageInfo[] infos = usagesToRefactor.toArray(new UsageInfo[usagesToRefactor.size()]); if (ensureElementsWritable(infos, viewDescriptor)) { execute(infos); @@ -436,19 +410,6 @@ public abstract class BaseRefactoringProcessor implements Runnable { RefactoringBundle.message("usageView.doAction"), false); } - private static Set getUsageInfosToRefactor(final UsageView usageView) { - Set excludedUsages = usageView.getExcludedUsages(); - - Set usageInfos = new LinkedHashSet(); - for (Usage usage : usageView.getUsages()) { - if (usage instanceof UsageInfo2UsageAdapter && !excludedUsages.contains(usage)) { - UsageInfo usageInfo = ((UsageInfo2UsageAdapter)usage).getUsageInfo(); - usageInfos.add(usageInfo); - } - } - return usageInfos; - } - private void doRefactoring(@NotNull final Collection usageInfoSet) { for (Iterator iterator = usageInfoSet.iterator(); iterator.hasNext();) { UsageInfo usageInfo = iterator.next(); diff --git a/platform/lang-impl/src/com/intellij/usageView/UsageViewUtil.java b/platform/lang-impl/src/com/intellij/usageView/UsageViewUtil.java index 82b4ffec1ebe..ca230c790e65 100644 --- a/platform/lang-impl/src/com/intellij/usageView/UsageViewUtil.java +++ b/platform/lang-impl/src/com/intellij/usageView/UsageViewUtil.java @@ -30,6 +30,9 @@ import com.intellij.psi.PsiFile; import com.intellij.psi.PsiReference; import com.intellij.refactoring.util.MoveRenameUsageInfo; import com.intellij.refactoring.util.NonCodeUsageInfo; +import com.intellij.usages.Usage; +import com.intellij.usages.UsageInfo2UsageAdapter; +import com.intellij.usages.UsageView; import com.intellij.util.Function; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; @@ -163,4 +166,17 @@ public class UsageViewUtil { Project project = info.getProject(); FileEditorManager.getInstance(project).openTextEditor(new OpenFileDescriptor(project, file, offset), requestFocus); } + + public static Set getNotExcludedUsageInfos(final UsageView usageView) { + Set excludedUsages = usageView.getExcludedUsages(); + + Set usageInfos = new LinkedHashSet(); + for (Usage usage : usageView.getUsages()) { + if (usage instanceof UsageInfo2UsageAdapter && !excludedUsages.contains(usage)) { + UsageInfo usageInfo = ((UsageInfo2UsageAdapter)usage).getUsageInfo(); + usageInfos.add(usageInfo); + } + } + return usageInfos; + } } diff --git a/platform/usageView/src/com/intellij/usages/UsageInfoSearcherAdapter.java b/platform/usageView/src/com/intellij/usages/UsageInfoSearcherAdapter.java new file mode 100644 index 000000000000..d448cbde9b46 --- /dev/null +++ b/platform/usageView/src/com/intellij/usages/UsageInfoSearcherAdapter.java @@ -0,0 +1,65 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.usages; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.project.DumbService; +import com.intellij.openapi.project.IndexNotReadyException; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Computable; +import com.intellij.openapi.util.Ref; +import com.intellij.usageView.UsageInfo; +import com.intellij.util.Processor; +import org.jetbrains.annotations.NotNull; + +public abstract class UsageInfoSearcherAdapter implements UsageSearcher { + protected void processUsages(final @NotNull Processor processor, Project project) { + final Ref refUsages = new Ref(); + final Ref dumbModeOccurred = new Ref(); + ApplicationManager.getApplication().runReadAction(new Runnable() { + @Override + public void run() { + try { + refUsages.set(findUsages()); + } + catch (IndexNotReadyException e) { + dumbModeOccurred.set(true); + } + } + }); + if (!dumbModeOccurred.isNull()) { + DumbService.getInstance(project).showDumbModeNotification("Usage search is not available until indices are ready"); + return; + } + final Usage[] usages = ApplicationManager.getApplication().runReadAction(new Computable() { + @Override + public Usage[] compute() { + return UsageInfo2UsageAdapter.convert(refUsages.get()); + } + }); + + for (final Usage usage : usages) { + ApplicationManager.getApplication().runReadAction(new Runnable() { + @Override + public void run() { + processor.process(usage); + } + }); + } + } + + protected abstract UsageInfo[] findUsages(); +}