infer nullity: preview with ability to exclude some usages(IDEA-123110), remember "annotate local" checkbox

This commit is contained in:
Anna Kozlova
2014-04-10 20:10:27 +02:00
parent 0b4a7dfb2b
commit b8249580db
6 changed files with 262 additions and 98 deletions
@@ -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();
}
@@ -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<UsageInfo[]>() {
@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<UsageInfo> usages = new ArrayList<UsageInfo>();
inferrer.collect(usages);
return usages.toArray(new UsageInfo[usages.size()]);
}
private static Runnable applyRunnable(final Project project, final Computable<UsageInfo[]> 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<Usage[]> convertUsagesRef = new Ref<Usage[]>();
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<UsageInfo[]>() {
@Override
public UsageInfo[] compute() {
final Set<UsageInfo> 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<UsageSearcher> rerunFactory(final Project project, final AnalysisScope scope) {
return new Factory<UsageSearcher>() {
@Override
public UsageSearcher create() {
return new UsageInfoSearcherAdapter() {
@Override
protected UsageInfo[] findUsages() {
return InferNullityAnnotationsAction.this.findUsages(project, scope);
}
@Override
public void generate(@NotNull Processor<Usage> 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;
}
@@ -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<? extends PsiModifierListOwner> pointer : myNullableSet) {
annotateNullable(project, manager, pointer);
annotateNullable(project, manager, pointer.getElement());
}
for (SmartPsiElementPointer<? extends PsiModifierListOwner> 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<? extends PsiModifierListOwner> 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<? extends PsiModifierListOwner> 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<UsageInfo> usages) {
collect(usages, true);
collect(usages, false);
}
private void collect(List<UsageInfo> usages, boolean nullable) {
final List<SmartPsiElementPointer<? extends PsiModifierListOwner>> set = nullable ? myNullableSet : myNotNullSet;
for (SmartPsiElementPointer<? extends PsiModifierListOwner> 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;
@@ -237,7 +237,7 @@ public abstract class BaseRefactoringProcessor implements Runnable {
Factory<UsageSearcher> factory = new Factory<UsageSearcher>() {
@Override
public UsageSearcher create() {
return new UsageSearcher() {
return new UsageInfoSearcherAdapter() {
@Override
public void generate(@NotNull final Processor<Usage> processor) {
ApplicationManager.getApplication().runReadAction(new Runnable() {
@@ -249,38 +249,12 @@ public abstract class BaseRefactoringProcessor implements Runnable {
refreshElements(elements);
}
});
final Ref<UsageInfo[]> refUsages = new Ref<UsageInfo[]>();
final Ref<Boolean> dumbModeOccurred = new Ref<Boolean>();
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<Usage[]>() {
@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<UsageInfo> usagesToRefactor = getUsageInfosToRefactor(usageView);
Set<UsageInfo> 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<UsageInfo> getUsageInfosToRefactor(final UsageView usageView) {
Set<Usage> excludedUsages = usageView.getExcludedUsages();
Set<UsageInfo> usageInfos = new LinkedHashSet<UsageInfo>();
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<UsageInfo> usageInfoSet) {
for (Iterator<UsageInfo> iterator = usageInfoSet.iterator(); iterator.hasNext();) {
UsageInfo usageInfo = iterator.next();
@@ -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<UsageInfo> getNotExcludedUsageInfos(final UsageView usageView) {
Set<Usage> excludedUsages = usageView.getExcludedUsages();
Set<UsageInfo> usageInfos = new LinkedHashSet<UsageInfo>();
for (Usage usage : usageView.getUsages()) {
if (usage instanceof UsageInfo2UsageAdapter && !excludedUsages.contains(usage)) {
UsageInfo usageInfo = ((UsageInfo2UsageAdapter)usage).getUsageInfo();
usageInfos.add(usageInfo);
}
}
return usageInfos;
}
}
@@ -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<Usage> processor, Project project) {
final Ref<UsageInfo[]> refUsages = new Ref<UsageInfo[]>();
final Ref<Boolean> dumbModeOccurred = new Ref<Boolean>();
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<Usage[]>() {
@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();
}