blob: ba6f8602ba46169300b6790cd08ac9004dea9dfe [file] [log] [blame]
/*
* 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.slicer;
import com.intellij.codeInsight.NullableNotNullManager;
import com.intellij.codeInsight.PsiEquivalenceUtil;
import com.intellij.codeInspection.dataFlow.DfaUtil;
import com.intellij.codeInspection.dataFlow.Nullness;
import com.intellij.ide.util.treeView.AbstractTreeNode;
import com.intellij.ide.util.treeView.AbstractTreeStructure;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Ref;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.NullableFunction;
import com.intellij.util.PairProcessor;
import com.intellij.util.WalkingState;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.FactoryMap;
import gnu.trove.THashSet;
import org.jetbrains.annotations.NotNull;
import java.util.*;
/**
* User: cdr
*/
public class SliceNullnessAnalyzer {
private static void groupByNullness(NullAnalysisResult result, SliceRootNode oldRoot, final Map<SliceNode, NullAnalysisResult> map) {
SliceRootNode root = createNewTree(result, oldRoot, map);
SliceUsage rootUsage = oldRoot.myCachedChildren.get(0).getValue();
SliceManager.getInstance(root.getProject()).createToolWindow(true, root, true, SliceManager.getElementDescription(null, rootUsage.getElement(), " Grouped by Nullness") );
}
@NotNull
public static SliceRootNode createNewTree(NullAnalysisResult result, SliceRootNode oldRoot, final Map<SliceNode, NullAnalysisResult> map) {
SliceRootNode root = oldRoot.copy();
assert oldRoot.myCachedChildren.size() == 1;
SliceNode oldRootStart = oldRoot.myCachedChildren.get(0);
root.setChanged();
root.targetEqualUsages.clear();
root.myCachedChildren = new ArrayList<SliceNode>();
createValueRootNode(result, oldRoot, map, root, oldRootStart, "Null Values", NullAnalysisResult.NULLS);
createValueRootNode(result, oldRoot, map, root, oldRootStart, "NotNull Values", NullAnalysisResult.NOT_NULLS);
createValueRootNode(result, oldRoot, map, root, oldRootStart, "Other Values", NullAnalysisResult.UNKNOWNS);
return root;
}
private static void createValueRootNode(NullAnalysisResult result,
SliceRootNode oldRoot,
final Map<SliceNode, NullAnalysisResult> map,
SliceRootNode root,
SliceNode oldRootStart,
String nodeName,
final int group) {
Collection<PsiElement> groupedByValue = result.groupedByValue[group];
if (groupedByValue.isEmpty()) {
return;
}
SliceLeafValueClassNode valueRoot = new SliceLeafValueClassNode(root.getProject(), root, nodeName);
root.myCachedChildren.add(valueRoot);
Set<PsiElement> uniqueValues = new THashSet<PsiElement>(groupedByValue, SliceLeafAnalyzer.LEAF_ELEMENT_EQUALITY);
for (final PsiElement expression : uniqueValues) {
SliceNode newRoot = SliceLeafAnalyzer.filterTree(oldRootStart, new NullableFunction<SliceNode, SliceNode>() {
@Override
public SliceNode fun(SliceNode oldNode) {
if (oldNode.getDuplicate() != null) {
return null;
}
for (PsiElement nullSuspect : group(oldNode, map, group)) {
if (PsiEquivalenceUtil.areElementsEquivalent(nullSuspect, expression)) {
return oldNode.copy();
}
}
return null;
}
},new PairProcessor<SliceNode, List<SliceNode>>() {
@Override
public boolean process(SliceNode node, List<SliceNode> children) {
if (!children.isEmpty()) return true;
PsiElement element = node.getValue().getElement();
if (element == null) return false;
return PsiEquivalenceUtil.areElementsEquivalent(element, expression); // leaf can be there only if it's filtering expression
}
});
valueRoot.myCachedChildren.add(new SliceLeafValueRootNode(root.getProject(), expression, valueRoot, Collections.singletonList(newRoot),
oldRoot.getValue().params));
}
}
public static void startAnalyzeNullness(final AbstractTreeStructure treeStructure, final Runnable finish) {
final SliceRootNode root = (SliceRootNode)treeStructure.getRootElement();
final Ref<NullAnalysisResult> leafExpressions = Ref.create(null);
final Map<SliceNode, NullAnalysisResult> map = createMap();
ProgressManager.getInstance().run(new Task.Backgroundable(root.getProject(), "Expanding all nodes... (may very well take the whole day)", true) {
@Override
public void run(@NotNull final ProgressIndicator indicator) {
NullAnalysisResult l = calcNullableLeaves(root, treeStructure, map);
leafExpressions.set(l);
}
@Override
public void onCancel() {
finish.run();
}
@Override
public void onSuccess() {
try {
NullAnalysisResult leaves = leafExpressions.get();
if (leaves == null) return; //cancelled
groupByNullness(leaves, root, map);
}
finally {
finish.run();
}
}
});
}
public static Map<SliceNode, NullAnalysisResult> createMap() {
return new FactoryMap<SliceNode, NullAnalysisResult>() {
@Override
protected NullAnalysisResult create(SliceNode key) {
return new NullAnalysisResult();
}
@Override
protected Map<SliceNode, NullAnalysisResult> createMap() {
return ContainerUtil.<SliceNode, NullAnalysisResult>newIdentityTroveMap();
}
};
}
private static NullAnalysisResult node(@NotNull SliceNode node, @NotNull Map<SliceNode, NullAnalysisResult> nulls) {
return nulls.get(node);
}
private static Collection<PsiElement> group(@NotNull SliceNode node, @NotNull Map<SliceNode, NullAnalysisResult> nulls, int group) {
return nulls.get(node).groupedByValue[group];
}
@NotNull
public static NullAnalysisResult calcNullableLeaves(@NotNull final SliceNode root,
@NotNull AbstractTreeStructure treeStructure,
@NotNull final Map<SliceNode, NullAnalysisResult> map) {
final SliceLeafAnalyzer.SliceNodeGuide guide = new SliceLeafAnalyzer.SliceNodeGuide(treeStructure);
WalkingState<SliceNode> walkingState = new WalkingState<SliceNode>(guide) {
@Override
public void visit(@NotNull final SliceNode element) {
element.calculateDupNode();
node(element, map).clear();
SliceNode duplicate = element.getDuplicate();
if (duplicate != null) {
node(element, map).add(node(duplicate, map));
}
else {
final PsiElement value = ApplicationManager.getApplication().runReadAction(new Computable<PsiElement>() {
@Override
public PsiElement compute() {
return element.getValue().getElement();
}
});
Nullness nullness = ApplicationManager.getApplication().runReadAction(new Computable<Nullness>() {
@Override
public Nullness compute() {
return checkNullness(value);
}
});
if (nullness == Nullness.NULLABLE) {
group(element, map, NullAnalysisResult.NULLS).add(value);
}
else if (nullness == Nullness.NOT_NULL) {
group(element, map, NullAnalysisResult.NOT_NULLS).add(value);
}
else {
Collection<? extends AbstractTreeNode> children = ApplicationManager.getApplication().runReadAction(
new Computable<Collection<? extends AbstractTreeNode>>() {
@Override
public Collection<? extends AbstractTreeNode> compute() {
return element.getChildren();
}
});
if (children.isEmpty()) {
group(element, map, NullAnalysisResult.UNKNOWNS).add(value);
}
super.visit(element);
}
}
}
@Override
public void elementFinished(@NotNull SliceNode element) {
SliceNode parent = guide.getParent(element);
if (parent != null) {
node(parent, map).add(node(element, map));
}
}
};
walkingState.visit(root);
return node(root, map);
}
@NotNull
private static Nullness checkNullness(final PsiElement element) {
// null
PsiElement value = element;
if (value instanceof PsiExpression) {
value = PsiUtil.deparenthesizeExpression((PsiExpression)value);
}
if (value instanceof PsiLiteralExpression) {
return ((PsiLiteralExpression)value).getValue() == null ? Nullness.NULLABLE : Nullness.NOT_NULL;
}
// not null
if (value instanceof PsiNewExpression) return Nullness.NOT_NULL;
if (value instanceof PsiThisExpression) return Nullness.NOT_NULL;
if (value instanceof PsiMethodCallExpression) {
PsiMethod method = ((PsiMethodCallExpression)value).resolveMethod();
if (method != null && NullableNotNullManager.isNotNull(method)) return Nullness.NOT_NULL;
if (method != null && NullableNotNullManager.isNullable(method)) return Nullness.NULLABLE;
}
if (value instanceof PsiPolyadicExpression && ((PsiPolyadicExpression)value).getOperationTokenType() == JavaTokenType.PLUS) {
return Nullness.NOT_NULL; // "xxx" + var
}
// unfortunately have to resolve here, since there can be no subnodes
PsiElement context = value;
if (value instanceof PsiReference) {
PsiElement resolved = ((PsiReference)value).resolve();
if (resolved instanceof PsiCompiledElement) {
resolved = resolved.getNavigationElement();
}
value = resolved;
}
if (value instanceof PsiParameter && ((PsiParameter)value).getDeclarationScope() instanceof PsiCatchSection) {
// exception thrown is always not null
return Nullness.NOT_NULL;
}
if (value instanceof PsiLocalVariable || value instanceof PsiParameter) {
Nullness result = DfaUtil.checkNullness((PsiVariable)value, context);
if (result != Nullness.UNKNOWN) {
return result;
}
}
if (value instanceof PsiModifierListOwner) {
if (NullableNotNullManager.isNotNull((PsiModifierListOwner)value)) return Nullness.NOT_NULL;
if (NullableNotNullManager.isNullable((PsiModifierListOwner)value)) return Nullness.NULLABLE;
}
if (value instanceof PsiEnumConstant) return Nullness.NOT_NULL;
return Nullness.UNKNOWN;
}
static class NullAnalysisResult {
public static int NULLS = 0;
public static int NOT_NULLS = 1;
public static int UNKNOWNS = 2;
public final Collection<PsiElement>[] groupedByValue = new Collection[] {new THashSet<PsiElement>(),new THashSet<PsiElement>(),new THashSet<PsiElement>()};
public void clear() {
for (Collection<PsiElement> elements : groupedByValue) {
elements.clear();
}
}
private void add(NullAnalysisResult duplicate) {
for (int i = 0; i < groupedByValue.length; i++) {
Collection<PsiElement> elements = groupedByValue[i];
Collection<PsiElement> other = duplicate.groupedByValue[i];
elements.addAll(other);
}
}
}
}