From 1d2f2070d873fd53063a888416b2ac24b936a4de Mon Sep 17 00:00:00 2001 From: Alexey Kudravtsev Date: Mon, 25 Jan 2010 13:10:22 +0300 Subject: [PATCH] group by nullness --- .../com/intellij/slicer/AnalyzeLeavesAction.java | 27 ++- .../src/com/intellij/slicer/CanItBeNullAction.java | 64 +++++ .../src/com/intellij/slicer/DuplicateMap.java | 2 +- .../src/com/intellij/slicer/SliceLeafAnalyzer.java | 250 +++++++++++++------ .../intellij/slicer/SliceLeafValueClassNode.java | 68 ++++++ .../intellij/slicer/SliceLeafValueRootNode.java | 56 +---- .../src/com/intellij/slicer/SliceManager.java | 47 ++-- .../src/com/intellij/slicer/SliceNode.java | 107 ++------- .../com/intellij/slicer/SliceNullnessAnalyzer.java | 265 +++++++++++++++++++++ .../src/com/intellij/slicer/SlicePanel.java | 32 +-- .../src/com/intellij/slicer/SliceRootNode.java | 31 +-- .../src/com/intellij/slicer/SliceTreeBuilder.java | 72 ++---- .../src/com/intellij/slicer/SliceUsage.java | 23 +- .../src/com/intellij/slicer/SliceUtil.java | 38 ++- 14 files changed, 753 insertions(+), 329 deletions(-) create mode 100644 java/java-impl/src/com/intellij/slicer/CanItBeNullAction.java create mode 100644 java/java-impl/src/com/intellij/slicer/SliceLeafValueClassNode.java create mode 100644 java/java-impl/src/com/intellij/slicer/SliceNullnessAnalyzer.java diff --git a/java/java-impl/src/com/intellij/slicer/AnalyzeLeavesAction.java b/java/java-impl/src/com/intellij/slicer/AnalyzeLeavesAction.java index a46c68cc85..87bd42d86a 100644 --- a/java/java-impl/src/com/intellij/slicer/AnalyzeLeavesAction.java +++ b/java/java-impl/src/com/intellij/slicer/AnalyzeLeavesAction.java @@ -15,33 +15,36 @@ */ package com.intellij.slicer; +import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.ToggleAction; import com.intellij.util.Icons; /** * @author cdr */ -public class AnalyzeLeavesAction extends ToggleAction { +public class AnalyzeLeavesAction extends AnAction { private final SliceTreeBuilder myTreeBuilder; + private static final String TEXT = "Group by leaf expression"; public AnalyzeLeavesAction(SliceTreeBuilder treeBuilder) { - super("Group by leaf expression", "Show original expression values that might appear in this place", Icons.XML_TAG_ICON); + super(TEXT, "Show original expression values that might appear in this place", Icons.XML_TAG_ICON); myTreeBuilder = treeBuilder; } @Override - public boolean isSelected(AnActionEvent e) { - return myTreeBuilder.splitByLeafExpressions; + public void update(AnActionEvent e) { + e.getPresentation().setText(TEXT + (myTreeBuilder.analysisInProgress ? " (Analysis in progress)" : "")); + e.getPresentation().setEnabled(isAvailabale()); + } + + private boolean isAvailabale() { + if (myTreeBuilder.analysisInProgress) return false; + + return !myTreeBuilder.splitByLeafExpressions; } @Override - public void setSelected(AnActionEvent e, boolean state) { - if (state) { - myTreeBuilder.switchToSplittedNodes(); - } - else { - myTreeBuilder.switchToUnsplittedNodes(); - } + public void actionPerformed(AnActionEvent e) { + myTreeBuilder.switchToSplittedNodes(myTreeBuilder.getTreeStructure()); } } diff --git a/java/java-impl/src/com/intellij/slicer/CanItBeNullAction.java b/java/java-impl/src/com/intellij/slicer/CanItBeNullAction.java new file mode 100644 index 0000000000..4dddb17afc --- /dev/null +++ b/java/java-impl/src/com/intellij/slicer/CanItBeNullAction.java @@ -0,0 +1,64 @@ +/* + * Copyright 2000-2010 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.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.util.IconLoader; +import com.intellij.psi.*; + +/** + * User: cdr + */ +public class CanItBeNullAction extends AnAction { + private final SliceTreeBuilder myTreeBuilder; + private static final String TEXT = "Can it be null?"; + + public CanItBeNullAction(SliceTreeBuilder treeBuilder) { + super(TEXT, "Determine whether null can flow into this expression", IconLoader.getIcon("/debugger/db_disabled_breakpoint_process.png")); + myTreeBuilder = treeBuilder; + } + + @Override + public void update(AnActionEvent e) { + e.getPresentation().setText(TEXT + (myTreeBuilder.analysisInProgress ? " (Analysis in progress)" : "")); + e.getPresentation().setEnabled(isAvailable()); + } + + private boolean isAvailable() { + if (myTreeBuilder.analysisInProgress) return false; + if (!myTreeBuilder.dataFlowToThis) return false; + if (myTreeBuilder.splitByLeafExpressions) return false; + SliceRootNode rootNode = (SliceRootNode)myTreeBuilder.getRootNode().getUserObject(); + PsiElement element = rootNode == null ? null : rootNode.getRootUsage().getUsageInfo().getElement(); + PsiType type; + if (element instanceof PsiVariable) { + type = ((PsiVariable)element).getType(); + } + else if (element instanceof PsiExpression) { + type = ((PsiExpression)element).getType(); + } + else { + type = null; + } + return type instanceof PsiClassType; + } + + @Override + public void actionPerformed(AnActionEvent e) { + myTreeBuilder.switchToLeafNulls(); + } +} diff --git a/java/java-impl/src/com/intellij/slicer/DuplicateMap.java b/java/java-impl/src/com/intellij/slicer/DuplicateMap.java index 77b49f79f4..3d8c795003 100644 --- a/java/java-impl/src/com/intellij/slicer/DuplicateMap.java +++ b/java/java-impl/src/com/intellij/slicer/DuplicateMap.java @@ -24,7 +24,7 @@ import java.util.Map; /** * @author cdr */ // rehash map on each PSI modification since SmartPsiPointer's hashCode() and equals() are changed -class DuplicateMap { +public class DuplicateMap { private static final TObjectHashingStrategy USAGEINFO_EQUALITY = new TObjectHashingStrategy() { public int computeHashCode(SliceUsage object) { UsageInfo info = object.getUsageInfo(); diff --git a/java/java-impl/src/com/intellij/slicer/SliceLeafAnalyzer.java b/java/java-impl/src/com/intellij/slicer/SliceLeafAnalyzer.java index be59b9ad74..6b7787b17a 100644 --- a/java/java-impl/src/com/intellij/slicer/SliceLeafAnalyzer.java +++ b/java/java-impl/src/com/intellij/slicer/SliceLeafAnalyzer.java @@ -17,131 +17,245 @@ package com.intellij.slicer; import com.intellij.codeInsight.PsiEquivalenceUtil; 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.ui.Messages; import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.util.Computable; +import com.intellij.openapi.util.Ref; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiJavaReference; import com.intellij.psi.PsiNamedElement; import com.intellij.psi.WalkingState; import com.intellij.psi.impl.source.tree.SourceUtil; +import com.intellij.util.NullableFunction; +import com.intellij.util.PairProcessor; import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.FactoryMap; +import gnu.trove.THashMap; +import gnu.trove.THashSet; import gnu.trove.TObjectHashingStrategy; import org.jetbrains.annotations.NotNull; -import java.util.Collection; +import java.util.*; /** * @author cdr */ public class SliceLeafAnalyzer { public static final TObjectHashingStrategy LEAF_ELEMENT_EQUALITY = new TObjectHashingStrategy() { - public int computeHashCode(PsiElement element) { + public int computeHashCode(final PsiElement element) { if (element == null) return 0; - PsiElement o = elementToCompare(element); - String text = o instanceof PsiNamedElement ? ((PsiNamedElement)o).getName() : SourceUtil.getTextSkipWhiteSpaceAndComments(o.getNode()); + String text = ApplicationManager.getApplication().runReadAction(new Computable() { + public String compute() { + PsiElement elementToCompare = element; + if (element instanceof PsiJavaReference) { + PsiElement resolved = ((PsiJavaReference)element).resolve(); + if (resolved != null) { + elementToCompare = resolved; + } + } + return elementToCompare instanceof PsiNamedElement ? + ((PsiNamedElement)elementToCompare).getName() : SourceUtil.getTextSkipWhiteSpaceAndComments(elementToCompare.getNode()); + } + }); return Comparing.hashcode(text); } - @NotNull - private PsiElement elementToCompare(PsiElement element) { - if (element instanceof PsiJavaReference) { - PsiElement resolved = ((PsiJavaReference)element).resolve(); - if (resolved != null) return resolved; + public boolean equals(final PsiElement o1, final PsiElement o2) { + return ApplicationManager.getApplication().runReadAction(new Computable() { + public Boolean compute() { + return o1 != null && o2 != null && PsiEquivalenceUtil.areElementsEquivalent(o1, o2); + } + }); + } + }; + + static SliceNode filterTree(SliceNode oldRoot, NullableFunction filter, PairProcessor> postProcessor){ + SliceNode filtered = filter.fun(oldRoot); + if (filtered == null) return null; + + List childrenFiltered = new ArrayList(); + if (oldRoot.myCachedChildren != null) { + for (SliceNode child : oldRoot.myCachedChildren) { + SliceNode childFiltered = filterTree(child, filter,postProcessor); + if (childFiltered != null) { + childrenFiltered.add(childFiltered); + } } - return element; } + boolean success = postProcessor == null || postProcessor.process(filtered, childrenFiltered); + if (!success) return null; + filtered.myCachedChildren = new ArrayList(childrenFiltered); + return filtered; + } + + private static void groupByValues(Collection leaves, SliceRootNode oldRoot, final Map> map) { + assert oldRoot.myCachedChildren.size() == 1; + SliceRootNode root = createTreeGroupedByValues(leaves, oldRoot, map); + + SliceNode oldRootStart = oldRoot.myCachedChildren.get(0); + SliceUsage rootUsage = oldRootStart.getValue(); + SliceManager.getInstance(root.getProject()).createToolWindow(true, root, true, SliceManager.getElementDescription(null, rootUsage.getElement(), " Grouped by Value") ); + } + + public static SliceRootNode createTreeGroupedByValues(Collection leaves, SliceRootNode oldRoot, final Map> map) { + SliceNode oldRootStart = oldRoot.myCachedChildren.get(0); + SliceRootNode root = oldRoot.copy(); + root.setChanged(); + root.targetEqualUsages.clear(); + root.myCachedChildren = new ArrayList(leaves.size()); + + for (final PsiElement leafExpression : leaves) { + SliceNode newNode = filterTree(oldRootStart, new NullableFunction() { + public SliceNode fun(SliceNode oldNode) { + if (oldNode.getDuplicate() != null) return null; + if (!node(oldNode, map).contains(leafExpression)) return null; - public boolean equals(PsiElement o1, PsiElement o2) { - return o1 != null && o2 != null && PsiEquivalenceUtil.areElementsEquivalent(o1, o2); + return oldNode.copy(); + } + }, new PairProcessor>() { + public boolean process(SliceNode node, List children) { + if (!children.isEmpty()) return true; + PsiElement element = node.getValue().getElement(); + if (element == null) return false; + return element.getManager().areElementsEquivalent(element, leafExpression); // leaf can be there only if it's filtering expression + } + }); + + SliceLeafValueRootNode lvNode = new SliceLeafValueRootNode(root.getProject(), leafExpression, root, Collections.singletonList(newNode)); + root.myCachedChildren.add(lvNode); } - }; + return root; + } + + public static void startAnalyzeValues(final AbstractTreeStructure treeStructure, final Runnable finish) { + final SliceRootNode root = (SliceRootNode)treeStructure.getRootElement(); + final Ref> leafExpressions = Ref.create(null); + + final Map> map = createMap(); - private static class SliceNodeGuide implements WalkingState.TreeGuide { - public SliceNode getNextSibling(SliceNode element) { - return element.getNext(); + ProgressManager.getInstance().run(new Task.Backgroundable(root.getProject(), "Expanding all nodes... (may very well take the whole day)", true) { + public void run(@NotNull final ProgressIndicator indicator) { + Collection l = calcLeafExpressions(root, treeStructure, map); + leafExpressions.set(l); + } + + @Override + public void onCancel() { + finish.run(); + } + + @Override + public void onSuccess() { + try { + Collection leaves = leafExpressions.get(); + if (leaves == null) return; //cancelled + + if (leaves.isEmpty()) { + Messages.showErrorDialog("Unable to find leaf expressions to group by", "Cannot group"); + return; + } + + groupByValues(leaves, root, map); + } + finally { + finish.run(); + } + } + }); + + } + + public static Map> createMap() { + final Map> map = new FactoryMap>() { + @Override + protected Map> createMap() { + return new THashMap>(TObjectHashingStrategy.IDENTITY); + } + + @Override + protected Collection create(SliceNode key) { + return new THashSet(SliceLeafAnalyzer.LEAF_ELEMENT_EQUALITY); + } + }; + return map; + } + + static class SliceNodeGuide implements WalkingState.TreeGuide { + private final AbstractTreeStructure myTreeStructure; + // use tree strucutre because it's setting 'parent' fields in the process + + SliceNodeGuide(@NotNull AbstractTreeStructure treeStructure) { + myTreeStructure = treeStructure; } - public SliceNode getPrevSibling(SliceNode element) { - return element.getPrev(); + public SliceNode getNextSibling(@NotNull SliceNode element) { + AbstractTreeNode parent = element.getParent(); + if (parent == null) return null; + + return element.getNext((List)parent.getChildren()); } - public SliceNode getFirstChild(SliceNode element) { - Object[] children = element.getTreeBuilder().getTreeStructure().getChildElements(element); + public SliceNode getPrevSibling(@NotNull SliceNode element) { + AbstractTreeNode parent = element.getParent(); + if (parent == null) return null; + return element.getPrev((List)parent.getChildren()); + } + + public SliceNode getFirstChild(@NotNull SliceNode element) { + Object[] children = myTreeStructure.getChildElements(element); return children.length == 0 ? null : (SliceNode)children[0]; } - public SliceNode getParent(SliceNode element) { + public SliceNode getParent(@NotNull SliceNode element) { AbstractTreeNode parent = element.getParent(); return parent instanceof SliceNode ? (SliceNode)parent : null; } - private static final SliceNodeGuide instance = new SliceNodeGuide(); + } + + private static Collection node(SliceNode node, Map> map) { + return map.get(node); } @NotNull - public static Collection calcLeafExpressions(@NotNull final SliceNode root) { - WalkingState walkingState = new WalkingState(SliceNodeGuide.instance) { + public static Collection calcLeafExpressions(@NotNull final SliceNode root, AbstractTreeStructure treeStructure, + final Map> map) { + final SliceNodeGuide guide = new SliceNodeGuide(treeStructure); + WalkingState walkingState = new WalkingState(guide) { @Override - public void visit(SliceNode element) { + public void visit(@NotNull SliceNode element) { element.update(null); - element.getLeafExpressions().clear(); + node(element, map).clear(); SliceNode duplicate = element.getDuplicate(); if (duplicate != null) { - element.addLeafExpressions(duplicate.getLeafExpressions()); + node(element, map).addAll(node(duplicate, map)); } else { SliceUsage sliceUsage = element.getValue(); - Object[] children = element.getTreeBuilder().getTreeStructure().getChildElements(element); - if (children.length == 0) { + Collection children = element.getChildren(); + if (children.isEmpty()) { PsiElement value = sliceUsage.getElement(); - element.addLeafExpressions(ContainerUtil.singleton(value, LEAF_ELEMENT_EQUALITY)); + node(element, map).addAll(ContainerUtil.singleton(value, LEAF_ELEMENT_EQUALITY)); } super.visit(element); } } @Override - public void elementFinished(SliceNode element) { - SliceNode parent = SliceNodeGuide.instance.getParent(element); + public void elementFinished(@NotNull SliceNode element) { + SliceNode parent = guide.getParent(element); if (parent != null) { - parent.addLeafExpressions(element.getLeafExpressions()); + node(parent, map).addAll(node(element, map)); } } }; - walkingState.elementStarted(root); + walkingState.visit(root); - return root.getLeafExpressions(); + return node(root, map); } - - //@NotNull - //public static Collection calcLeafExpressionsSOE(@NotNull SliceNode root, @NotNull ProgressIndicator progress) { - // root.update(null); - // root.getLeafExpressions().clear(); - // Collection leaves; - // SliceNode duplicate = root.getDuplicate(); - // if (duplicate != null) { - // leaves = duplicate.getLeafExpressions(); - // //null means other - // //leaves = ContainerUtil.singleton(PsiUtilBase.NULL_PSI_ELEMENT, LEAF_ELEMENT_EQUALITY); - // //return leaves;//todo - // } - // else { - // SliceUsage sliceUsage = root.getValue(); - // - // Collection children = root.getChildrenUnderProgress(progress); - // if (children.isEmpty()) { - // PsiElement element = sliceUsage.getElement(); - // leaves = ContainerUtil.singleton(element, LEAF_ELEMENT_EQUALITY); - // } - // else { - // leaves = new THashSet(LEAF_ELEMENT_EQUALITY); - // for (AbstractTreeNode child : children) { - // Collection elements = calcLeafExpressions((SliceNode)child); - // leaves.addAll(elements); - // } - // } - // } - // - // root.addLeafExpressions(leaves); - // return leaves; - //} } diff --git a/java/java-impl/src/com/intellij/slicer/SliceLeafValueClassNode.java b/java/java-impl/src/com/intellij/slicer/SliceLeafValueClassNode.java new file mode 100644 index 0000000000..9005428a10 --- /dev/null +++ b/java/java-impl/src/com/intellij/slicer/SliceLeafValueClassNode.java @@ -0,0 +1,68 @@ +/* + * Copyright 2000-2010 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.openapi.project.Project; +import com.intellij.psi.PsiElement; +import com.intellij.psi.util.PsiUtilBase; +import com.intellij.ui.SimpleTextAttributes; +import com.intellij.usageView.UsageViewBundle; +import com.intellij.usages.Usage; +import com.intellij.usages.UsageInfo2UsageAdapter; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import java.util.ArrayList; +import java.util.List; + +/** + * User: cdr + */ +public class SliceLeafValueClassNode extends SliceLeafValueRootNode { + private final String myClassName; + + public SliceLeafValueClassNode(@NotNull Project project, SliceNode root, String className) { + super(project, root.getValue().getElement(), root, new ArrayList()); + myClassName = className; + } + + @Override + public boolean canNavigate() { + return false; + } + + @Override + public boolean canNavigateToSource() { + return false; + } + + @Override + public void customizeCellRenderer(SliceUsageCellRenderer renderer, + JTree tree, + Object value, + boolean selected, + boolean expanded, + boolean leaf, + int row, + boolean hasFocus) { + renderer.append(myClassName, SimpleTextAttributes.DARK_TEXT); + } + + @Override + public String toString() { + return myClassName; + } +} diff --git a/java/java-impl/src/com/intellij/slicer/SliceLeafValueRootNode.java b/java/java-impl/src/com/intellij/slicer/SliceLeafValueRootNode.java index 81b64dac26..57835824eb 100644 --- a/java/java-impl/src/com/intellij/slicer/SliceLeafValueRootNode.java +++ b/java/java-impl/src/com/intellij/slicer/SliceLeafValueRootNode.java @@ -15,67 +15,29 @@ */ package com.intellij.slicer; +import com.intellij.analysis.AnalysisScope; import com.intellij.ide.projectView.PresentationData; -import com.intellij.ide.util.treeView.AbstractTreeNode; import com.intellij.openapi.project.Project; import com.intellij.psi.PsiElement; -import com.intellij.psi.util.PsiUtilBase; import com.intellij.ui.SimpleTextAttributes; -import com.intellij.usageView.UsageInfo; import com.intellij.usageView.UsageViewBundle; import com.intellij.usages.Usage; import com.intellij.usages.UsageInfo2UsageAdapter; -import com.intellij.usages.impl.NullUsage; -import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import javax.swing.*; -import java.util.*; +import java.util.Collection; +import java.util.List; /** * @author cdr */ -public class SliceLeafValueRootNode extends AbstractTreeNode implements MyColoredTreeCellRenderer { - final List myCachedChildren; +public class SliceLeafValueRootNode extends SliceNode implements MyColoredTreeCellRenderer { + protected final List myCachedChildren; - protected SliceLeafValueRootNode(@NotNull Project project, PsiElement leafExpression, SliceNode root) { - super(project, leafExpression == PsiUtilBase.NULL_PSI_ELEMENT ? NullUsage.INSTANCE : new UsageInfo2UsageAdapter(new UsageInfo(leafExpression))); - - Set withLeaves = ContainerUtil.singleton(leafExpression, SliceLeafAnalyzer.LEAF_ELEMENT_EQUALITY); - SliceNode node = root.copy(withLeaves); - myCachedChildren = Collections.singletonList(node); - restructureChildrenByLeaf(node, root, leafExpression, withLeaves, null); - } - - private static void restructureChildrenByLeaf(SliceNode node, SliceNode oldRoot, @Nullable PsiElement leafExpression, Set withLeaves, - SliceNode parent) { - List children = new ArrayList(); - node.myCachedChildren = children; - assert oldRoot.getLeafExpressions().contains(leafExpression); - boolean iAmHereToStay = false; - for (AbstractTreeNode cachedChild : oldRoot.myCachedChildren) { - SliceNode cachedSliceNode = (SliceNode)cachedChild; - if (cachedSliceNode.getDuplicate() != null) { - // put entire (potentially unbounded) subtree here - //children.add(cachedSliceNode.copy(withLeaves)); - } - else if (cachedSliceNode.getLeafExpressions().contains(leafExpression)) { - SliceNode newNode = cachedSliceNode.copy(withLeaves); - children.add(newNode); - PsiElement element = newNode.getValue().getElement(); - if (element != null && element.getManager().areElementsEquivalent(element, leafExpression)) { - iAmHereToStay = true; - } - if (!cachedSliceNode.myCachedChildren.isEmpty()) { - restructureChildrenByLeaf(newNode, cachedSliceNode, leafExpression, withLeaves, node); - } - } - } - - if (!iAmHereToStay && children.isEmpty() && parent != null) { - parent.myCachedChildren.remove(node); - } + public SliceLeafValueRootNode(@NotNull Project project, PsiElement leafExpression, SliceNode root, List children) { + super(project, new SliceUsage(leafExpression, new AnalysisScope(project)), root.targetEqualUsages, true); + myCachedChildren = children; } @NotNull @@ -144,4 +106,4 @@ public class SliceLeafValueRootNode extends AbstractTreeNode implements M public boolean canNavigateToSource() { return getValue().canNavigateToSource(); } -} \ No newline at end of file +} diff --git a/java/java-impl/src/com/intellij/slicer/SliceManager.java b/java/java-impl/src/com/intellij/slicer/SliceManager.java index 22dac8d846..07f3126abb 100644 --- a/java/java-impl/src/com/intellij/slicer/SliceManager.java +++ b/java/java-impl/src/com/intellij/slicer/SliceManager.java @@ -19,7 +19,6 @@ import com.intellij.analysis.AnalysisScope; import com.intellij.analysis.AnalysisUIOptions; import com.intellij.analysis.BaseAnalysisActionDialog; import com.intellij.ide.impl.ContentManagerWatcher; -import com.intellij.openapi.actionSystem.ActionManager; import com.intellij.openapi.components.PersistentStateComponent; import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.components.State; @@ -41,6 +40,8 @@ import com.intellij.ui.content.Content; import com.intellij.ui.content.ContentManager; import org.jetbrains.annotations.NotNull; +import java.util.regex.Pattern; + @State( name = "SliceManager", storages = {@Storage(id = "other", file = "$WORKSPACE_FILE$")} @@ -55,7 +56,8 @@ public class SliceManager implements PersistentStateComponent private static final String FORTH_TOOLWINDOW_ID = "Analyze Dataflow from"; public static class Bean { - public boolean includeTestSources = true; // to show in dialog + //public boolean includeTestSources = true; // to show in dialog + public AnalysisUIOptions analysisUIOptions = new AnalysisUIOptions(); } public static SliceManager getInstance(@NotNull Project project) { @@ -110,31 +112,35 @@ public class SliceManager implements PersistentStateComponent } public void slice(@NotNull PsiElement element, boolean dataFlowToThis) { - String dialogTitle = ActionManager.getInstance().getAction(dataFlowToThis ? "SliceBackward" : "SliceForward").getTemplatePresentation().getText(); - doSlice(element, dialogTitle, dataFlowToThis, - dataFlowToThis ? myBackContentManager : myForthContentManager, - dataFlowToThis ? BACK_TOOLWINDOW_ID : FORTH_TOOLWINDOW_ID); + doSlice(element, dataFlowToThis); } - private void doSlice(@NotNull PsiElement element, @NotNull String dialogTitle, boolean dataFlowToThis, @NotNull final ContentManager contentManager, - @NotNull final String toolwindowId) { + private void doSlice(@NotNull PsiElement element, boolean dataFlowToThis) { Module module = ModuleUtil.findModuleForPsiElement(element); AnalysisUIOptions analysisUIOptions = new AnalysisUIOptions(); - analysisUIOptions.SCOPE_TYPE = AnalysisScope.PROJECT; - analysisUIOptions.ANALYZE_TEST_SOURCES = myStoredSettings.includeTestSources; + analysisUIOptions.save(myStoredSettings.analysisUIOptions); AnalysisScope analysisScope = new AnalysisScope(element.getContainingFile()); String name = module == null ? null : module.getName(); + String dialogTitle = getElementDescription((dataFlowToThis ? BACK_TOOLWINDOW_ID : FORTH_TOOLWINDOW_ID) + " ", element, null); + + dialogTitle = Pattern.compile("<[^<>]*>").matcher(dialogTitle).replaceAll(""); + BaseAnalysisActionDialog dialog = new BaseAnalysisActionDialog(dialogTitle, "Analyze scope", myProject, analysisScope, name, true, analysisUIOptions, element); dialog.show(); if (!dialog.isOK()) return; - AnalysisScope scope = dialog.getScope(analysisUIOptions, new AnalysisScope(myProject), myProject, module); - myStoredSettings.includeTestSources = scope.isIncludeTestSource(); + AnalysisScope scope = dialog.getScope(analysisUIOptions, analysisScope, myProject, module); + myStoredSettings.analysisUIOptions.save(analysisUIOptions); + + SliceRootNode rootNode = new SliceRootNode(myProject, new DuplicateMap(), scope, createRootUsage(element, scope), dataFlowToThis); + createToolWindow(dataFlowToThis, rootNode, false, getElementDescription(null, element, null)); + } + public void createToolWindow(final boolean dataFlowToThis, final SliceRootNode rootNode, boolean splitByLeafExpressions, String displayName) { final SliceToolwindowSettings sliceToolwindowSettings = SliceToolwindowSettings.getInstance(myProject); - SliceUsage usage = createRootUsage(element, scope); + final ContentManager contentManager = dataFlowToThis ? myBackContentManager : myForthContentManager; final Content[] myContent = new Content[1]; - final SlicePanel slicePanel = new SlicePanel(myProject, usage, scope, dataFlowToThis) { + final SlicePanel slicePanel = new SlicePanel(myProject, dataFlowToThis, rootNode, splitByLeafExpressions) { protected void close() { contentManager.removeContent(myContent[0], true); } @@ -156,20 +162,19 @@ public class SliceManager implements PersistentStateComponent } }; - myContent[0] = contentManager.getFactory().createContent(slicePanel, getElementDescription(element), true); + myContent[0] = contentManager.getFactory().createContent(slicePanel, displayName, true); contentManager.addContent(myContent[0]); contentManager.setSelectedContent(myContent[0]); - ToolWindowManager.getInstance(myProject).getToolWindow(toolwindowId).activate(null); + ToolWindowManager.getInstance(myProject).getToolWindow(dataFlowToThis ? BACK_TOOLWINDOW_ID : FORTH_TOOLWINDOW_ID).activate(null); } - public static String getElementDescription(PsiElement element) { + public static String getElementDescription(String prefix, PsiElement element, String suffix) { PsiElement elementToSlice = element; if (element instanceof PsiReferenceExpression) elementToSlice = ((PsiReferenceExpression)element).resolve(); if (elementToSlice == null) elementToSlice = element; - String title = ""+ ElementDescriptionUtil.getElementDescription(elementToSlice, RefactoringDescriptionLocation.WITHOUT_PARENT); - title = StringUtil.first(title, 100, true)+""; - return title; + String desc = ElementDescriptionUtil.getElementDescription(elementToSlice, RefactoringDescriptionLocation.WITHOUT_PARENT); + return ""+ (prefix == null ? "" : prefix) + StringUtil.first(desc, 100, true)+(suffix == null ? "" : suffix) + ""; } public static SliceUsage createRootUsage(@NotNull PsiElement element, @NotNull AnalysisScope scope) { @@ -202,6 +207,6 @@ public class SliceManager implements PersistentStateComponent } public void loadState(Bean state) { - myStoredSettings.includeTestSources = state.includeTestSources; + myStoredSettings.analysisUIOptions.save(state.analysisUIOptions); } } diff --git a/java/java-impl/src/com/intellij/slicer/SliceNode.java b/java/java-impl/src/com/intellij/slicer/SliceNode.java index 23e5ca528c..1a6059eb2b 100644 --- a/java/java-impl/src/com/intellij/slicer/SliceNode.java +++ b/java/java-impl/src/com/intellij/slicer/SliceNode.java @@ -22,16 +22,13 @@ import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.progress.impl.ProgressManagerImpl; import com.intellij.openapi.progress.util.ProgressIndicatorBase; import com.intellij.openapi.project.Project; -import com.intellij.psi.PsiElement; +import com.intellij.ui.DuplicateNodeRenderer; import com.intellij.usageView.UsageViewBundle; import com.intellij.util.ArrayUtil; import com.intellij.util.Processor; -import com.intellij.ui.DuplicateNodeRenderer; -import gnu.trove.THashSet; import org.jetbrains.annotations.NotNull; import javax.swing.*; -import javax.swing.tree.DefaultMutableTreeNode; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -41,35 +38,23 @@ import java.util.List; * @author cdr */ public class SliceNode extends AbstractTreeNode implements DuplicateNodeRenderer.DuplicatableNode, MyColoredTreeCellRenderer { - protected List myCachedChildren; - private boolean initialized; - private SliceNode duplicate; + protected List myCachedChildren; + protected boolean initialized; + protected SliceNode duplicate; protected final DuplicateMap targetEqualUsages; - private final SliceTreeBuilder myTreeBuilder; - private final Collection leafExpressions = new THashSet(SliceLeafAnalyzer.LEAF_ELEMENT_EQUALITY); protected boolean changed; private int index; // my index in parent's mycachedchildren + protected final boolean dataFlowToThis; - protected SliceNode(@NotNull Project project, SliceUsage sliceUsage, @NotNull DuplicateMap targetEqualUsages, - @NotNull Collection leafExpressions) { - this(project, sliceUsage, targetEqualUsages, null, leafExpressions); - } - - protected SliceNode(@NotNull Project project, SliceUsage sliceUsage, @NotNull DuplicateMap targetEqualUsages, - SliceTreeBuilder treeBuilder, @NotNull Collection leafExpressions) { + protected SliceNode(@NotNull Project project, SliceUsage sliceUsage, @NotNull DuplicateMap targetEqualUsages, boolean dataFlowToThis) { super(project, sliceUsage); this.targetEqualUsages = targetEqualUsages; - myTreeBuilder = treeBuilder; - this.leafExpressions.addAll(leafExpressions); - } - - protected SliceTreeBuilder getTreeBuilder() { - return myTreeBuilder; + this.dataFlowToThis = dataFlowToThis; } - SliceNode copy(Collection withLeaves) { + SliceNode copy() { SliceUsage newUsage = getValue().copy(); - SliceNode newNode = new SliceNode(getProject(), newUsage, targetEqualUsages, getTreeBuilder(), withLeaves); + SliceNode newNode = new SliceNode(getProject(), newUsage, targetEqualUsages, dataFlowToThis); newNode.initialized = initialized; newNode.duplicate = duplicate; return newNode; @@ -94,34 +79,24 @@ public class SliceNode extends AbstractTreeNode implements Duplicate return nodes[0]; } - SliceNode getNext() { - AbstractTreeNode parent = getParent(); - if (parent instanceof SliceNode) { - Object[] children = getTreeBuilder().getTreeStructure().getChildElements(parent); - return index == children.length - 1 ? null : (SliceNode)children[index + 1]; - } - return null; + SliceNode getNext(List parentChildren) { + return index == parentChildren.size() - 1 ? null : (SliceNode)parentChildren.get(index + 1); } - SliceNode getPrev() { - AbstractTreeNode parent = getParent(); - if (parent instanceof SliceNode) { - Object[] children = getTreeBuilder().getTreeStructure().getChildElements(parent); - return index == 0 ? null : (SliceNode)children[index - 1]; - } - return null; + SliceNode getPrev(List parentChildren) { + return index == 0 ? null : (SliceNode)parentChildren.get(index - 1); } protected List getChildrenUnderProgress(ProgressIndicator progress) { if (isUpToDate()) return myCachedChildren == null ? Collections.emptyList() : myCachedChildren; - final List children = new ArrayList(); + final List children = new ArrayList(); final SliceManager manager = SliceManager.getInstance(getProject()); manager.runInterruptibly(new Runnable() { public void run() { Processor processor = new Processor() { public boolean process(SliceUsage sliceUsage) { manager.checkCanceled(); - SliceNode node = new SliceNode(myProject, sliceUsage, targetEqualUsages, getTreeBuilder(), getLeafExpressions()); + SliceNode node = new SliceNode(myProject, sliceUsage, targetEqualUsages, dataFlowToThis); synchronized (children) { node.index = children.size(); children.add(node); @@ -130,20 +105,20 @@ public class SliceNode extends AbstractTreeNode implements Duplicate } }; - getValue().processChildren(processor, getTreeBuilder().dataFlowToThis); + getValue().processChildren(processor, dataFlowToThis); } }, new Runnable(){ public void run() { changed = true; - SwingUtilities.invokeLater(new Runnable() { - public void run() { - if (getTreeBuilder().isDisposed()) return; - DefaultMutableTreeNode node = getTreeBuilder().getNodeForElement(getValue()); - //myTreeBuilder.getUi().queueBackgroundUpdate(node, (NodeDescriptor)node.getUserObject(), new TreeUpdatePass(node)); - if (node == null) node = getTreeBuilder().getRootNode(); - getTreeBuilder().addSubtreeToUpdate(node); - } - }); + //SwingUtilities.invokeLater(new Runnable() { + // public void run() { + // if (getTreeBuilder().isDisposed()) return; + // DefaultMutableTreeNode node = getTreeBuilder().getNodeForElement(getValue()); + // //myTreeBuilder.getUi().queueBackgroundUpdate(node, (NodeDescriptor)node.getUserObject(), new TreeUpdatePass(node)); + // if (node == null) node = getTreeBuilder().getRootNode(); + // getTreeBuilder().addSubtreeToUpdate(node); + // } + //}); } }, progress); @@ -154,7 +129,7 @@ public class SliceNode extends AbstractTreeNode implements Duplicate } private boolean isUpToDate() { - if (myCachedChildren != null || !isValid() || getTreeBuilder().splitByLeafExpressions) { + if (myCachedChildren != null || !isValid()/* || getTreeBuilder().splitByLeafExpressions*/) { return true; } return false; @@ -210,15 +185,6 @@ public class SliceNode extends AbstractTreeNode implements Duplicate return false; } - public void addLeafExpressions(@NotNull Collection leafExpressions) { - this.leafExpressions.addAll(leafExpressions); - } - - @NotNull - public Collection getLeafExpressions() { - return leafExpressions; - } - public void customizeCellRenderer(SliceUsageCellRenderer renderer, JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { renderer.setIcon(getPresentation().getIcon(expanded)); if (isValid()) { @@ -232,12 +198,6 @@ public class SliceNode extends AbstractTreeNode implements Duplicate } public void setChanged() { - //storedModificationCount = -1; - //for (SliceNode cachedChild : myCachedChildren) { - // cachedChild.clearCaches(); - //} - //myCachedChildren = null; - //initialized = false; changed = true; } @@ -246,19 +206,4 @@ public class SliceNode extends AbstractTreeNode implements Duplicate return getValue()==null?"":getValue().toString(); } - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - if (!super.equals(o)) return false; - - SliceNode sliceNode = (SliceNode)o; - - return getValue().equals(sliceNode.getValue()); - } - - @Override - public int hashCode() { - return getValue().hashCode(); - } } diff --git a/java/java-impl/src/com/intellij/slicer/SliceNullnessAnalyzer.java b/java/java-impl/src/com/intellij/slicer/SliceNullnessAnalyzer.java new file mode 100644 index 0000000000..09b884e7b4 --- /dev/null +++ b/java/java-impl/src/com/intellij/slicer/SliceNullnessAnalyzer.java @@ -0,0 +1,265 @@ +/* + * Copyright 2000-2010 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.AnnotationUtil; +import com.intellij.codeInspection.dataFlow.DfaUtil; +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.containers.FactoryMap; +import gnu.trove.THashMap; +import gnu.trove.THashSet; +import gnu.trove.TObjectHashingStrategy; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +/** + * User: cdr + */ +public class SliceNullnessAnalyzer { + private static void groupByNullness(NullAnalysisResult result, SliceRootNode oldRoot, final Map 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") ); +} + + public static SliceRootNode createNewTree(NullAnalysisResult result, SliceRootNode oldRoot, final Map 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(); + + if (!result.nulls.isEmpty()) { + SliceLeafValueClassNode nullRoot = new SliceLeafValueClassNode(root.getProject(), root, "Null Values"); + root.myCachedChildren.add(nullRoot); + + for (final PsiElement nullExpression : result.nulls) { + SliceNode newRoot = SliceLeafAnalyzer.filterTree(oldRootStart, new NullableFunction() { + public SliceNode fun(SliceNode oldNode) { + return oldNode.getDuplicate() == null && node(oldNode, map).nulls.contains(nullExpression) ? oldNode.copy() : null; + } + },null); + nullRoot.myCachedChildren.add(new SliceLeafValueRootNode(root.getProject(), nullExpression, nullRoot, Collections.singletonList(newRoot))); + } + } + if (!result.notNulls.isEmpty()) { + SliceLeafValueClassNode valueRoot = new SliceLeafValueClassNode(root.getProject(), root, "NotNull Values"); + root.myCachedChildren.add(valueRoot); + + for (final PsiElement expression : result.notNulls) { + SliceNode newRoot = SliceLeafAnalyzer.filterTree(oldRootStart, new NullableFunction() { + public SliceNode fun(SliceNode oldNode) { + return oldNode.getDuplicate() == null && node(oldNode, map).notNulls.contains(expression) ? oldNode.copy() : null; + } + },null); + valueRoot.myCachedChildren.add(new SliceLeafValueRootNode(root.getProject(), expression, valueRoot, Collections.singletonList(newRoot))); + } + } + if (!result.unknown.isEmpty()) { + SliceLeafValueClassNode valueRoot = new SliceLeafValueClassNode(root.getProject(), root, "Other Values"); + root.myCachedChildren.add(valueRoot); + + for (final PsiElement expression : result.unknown) { + SliceNode newRoot = SliceLeafAnalyzer.filterTree(oldRootStart, new NullableFunction() { + public SliceNode fun(SliceNode oldNode) { + return oldNode.getDuplicate() == null && node(oldNode, map).unknown.contains(expression) ? oldNode.copy() : null; + } + },null); + valueRoot.myCachedChildren.add(new SliceLeafValueRootNode(root.getProject(), expression, valueRoot, Collections.singletonList(newRoot))); + } + } + return root; + } + + public static void startAnalyzeNullness(final AbstractTreeStructure treeStructure, final Runnable finish) { + final SliceRootNode root = (SliceRootNode)treeStructure.getRootElement(); + final Ref leafExpressions = Ref.create(null); + final Map map = createMap(); + + ProgressManager.getInstance().run(new Task.Backgroundable(root.getProject(), "Expanding all nodes... (may very well take the whole day)", true) { + 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 createMap() { + return new FactoryMap() { + @Override + protected NullAnalysisResult create(SliceNode key) { + return new NullAnalysisResult(); + } + + @Override + protected Map createMap() { + return new THashMap(TObjectHashingStrategy.IDENTITY); + } + }; + } + + private static NullAnalysisResult node(SliceNode node, Map nulls) { + return nulls.get(node); + } + + @NotNull + public static NullAnalysisResult calcNullableLeaves(@NotNull final SliceNode root, @NotNull AbstractTreeStructure treeStructure, + final Map map) { + final SliceLeafAnalyzer.SliceNodeGuide guide = new SliceLeafAnalyzer.SliceNodeGuide(treeStructure); + WalkingState walkingState = new WalkingState(guide) { + @Override + public void visit(@NotNull SliceNode element) { + element.update(null); + node(element, map).clear(); + SliceNode duplicate = element.getDuplicate(); + if (duplicate != null) { + node(element, map).add(node(duplicate, map)); + } + else { + SliceUsage sliceUsage = element.getValue(); + final PsiElement value = sliceUsage.getElement(); + DfaUtil.Nullness nullness = ApplicationManager.getApplication().runReadAction(new Computable() { + public DfaUtil.Nullness compute() { + return checkNullness(value); + } + }); + if (nullness == DfaUtil.Nullness.NULL) { + node(element, map).nulls.add(value); + } + else if (nullness == DfaUtil.Nullness.NOT_NULL) { + node(element, map).notNulls.add(value); + } + else { + Collection children = element.getChildren(); + if (children.isEmpty()) { + node(element, map).unknown.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); + } + + private static DfaUtil.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 ? DfaUtil.Nullness.NULL : DfaUtil.Nullness.NOT_NULL; + } + + // not null + if (value instanceof PsiNewExpression) return DfaUtil.Nullness.NOT_NULL; + if (value instanceof PsiThisExpression) return DfaUtil.Nullness.NOT_NULL; + if (value instanceof PsiMethodCallExpression) { + PsiMethod method = ((PsiMethodCallExpression)value).resolveMethod(); + if (method != null && AnnotationUtil.isNotNull(method)) return DfaUtil.Nullness.NOT_NULL; + if (method != null && AnnotationUtil.isNullable(method)) return DfaUtil.Nullness.NULL; + } + if (value instanceof PsiBinaryExpression && ((PsiBinaryExpression)value).getOperationTokenType() == JavaTokenType.PLUS) { + return DfaUtil.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 DfaUtil.Nullness.NOT_NULL; + } + if (value instanceof PsiModifierListOwner) { + if (AnnotationUtil.isNotNull((PsiModifierListOwner)value)) return DfaUtil.Nullness.NOT_NULL; + if (AnnotationUtil.isNullable((PsiModifierListOwner)value)) return DfaUtil.Nullness.NULL; + } + + if (value instanceof PsiLocalVariable || value instanceof PsiParameter) { + return DfaUtil.checkNullness((PsiVariable)value, context); + } + return null; + } + + public static class NullAnalysisResult { + public final Collection nulls = new THashSet(SliceLeafAnalyzer.LEAF_ELEMENT_EQUALITY); + public final Collection notNulls = new THashSet(SliceLeafAnalyzer.LEAF_ELEMENT_EQUALITY); + public final Collection unknown = new THashSet(SliceLeafAnalyzer.LEAF_ELEMENT_EQUALITY); + + public void clear() { + nulls.clear(); + notNulls.clear(); + unknown.clear(); + } + + public void add(NullAnalysisResult duplicate) { + nulls.addAll(duplicate.nulls); + notNulls.addAll(duplicate.notNulls); + unknown.addAll(duplicate.unknown); + } + } +} diff --git a/java/java-impl/src/com/intellij/slicer/SlicePanel.java b/java/java-impl/src/com/intellij/slicer/SlicePanel.java index e2396c6c67..5abdc99d33 100644 --- a/java/java-impl/src/com/intellij/slicer/SlicePanel.java +++ b/java/java-impl/src/com/intellij/slicer/SlicePanel.java @@ -15,7 +15,6 @@ */ package com.intellij.slicer; -import com.intellij.analysis.AnalysisScope; import com.intellij.ide.IdeBundle; import com.intellij.ide.actions.CloseTabToolbarAction; import com.intellij.ide.util.treeView.AbstractTreeNode; @@ -74,39 +73,29 @@ public abstract class SlicePanel extends JPanel implements TypeSafeDataProvider, private final Project myProject; private boolean isDisposed; - public SlicePanel(Project project, final SliceUsage root, AnalysisScope scope, boolean dataFlowToThis) { + public SlicePanel(Project project, boolean dataFlowToThis, SliceNode rootNode, boolean splitByLeafExpressions) { super(new BorderLayout()); myProject = project; myTree = createTree(); - DuplicateMap targetEqualUsages = new DuplicateMap(); + myBuilder = new SliceTreeBuilder(myTree, project, dataFlowToThis, rootNode, splitByLeafExpressions); + myBuilder.setCanYieldUpdate(!ApplicationManager.getApplication().isUnitTestMode()); - final SliceTreeBuilder[] builder = {null}; - final SliceNode rootNode = new SliceRootNode(project, targetEqualUsages, scope, root){ - @Override - protected SliceTreeBuilder getTreeBuilder() { - return builder[0]; - } - }; - - builder[0] = new SliceTreeBuilder(myTree, project, dataFlowToThis, rootNode); - builder[0].setCanYieldUpdate(!ApplicationManager.getApplication().isUnitTestMode()); - - Disposer.register(this, builder[0]); + Disposer.register(this, myBuilder); - builder[0].addSubtreeToUpdate((DefaultMutableTreeNode)myTree.getModel().getRoot(), new Runnable() { + myBuilder.addSubtreeToUpdate((DefaultMutableTreeNode)myTree.getModel().getRoot(), new Runnable() { public void run() { - if (isDisposed || builder[0].isDisposed() || myProject.isDisposed()) return; - builder[0].expand(rootNode, new Runnable() { + if (isDisposed || myBuilder.isDisposed() || myProject.isDisposed()) return; + final SliceNode rootNode = myBuilder.getRootSliceNode(); + myBuilder.expand(rootNode, new Runnable() { public void run() { - if (isDisposed || builder[0].isDisposed() || myProject.isDisposed()) return; - builder[0].select(rootNode.myCachedChildren.get(0)); //first there is ony one child + if (isDisposed || myBuilder.isDisposed() || myProject.isDisposed()) return; + myBuilder.select(rootNode.myCachedChildren.get(0)); //first there is ony one child } }); treeSelectionChanged(); } }); - myBuilder = builder[0]; layoutPanel(); } @@ -282,6 +271,7 @@ public abstract class SlicePanel extends JPanel implements TypeSafeDataProvider, if (myBuilder.dataFlowToThis) { actionGroup.add(new AnalyzeLeavesAction(myBuilder)); + actionGroup.add(new CanItBeNullAction(myBuilder)); } //actionGroup.add(new ContextHelpAction(HELP_ID)); diff --git a/java/java-impl/src/com/intellij/slicer/SliceRootNode.java b/java/java-impl/src/com/intellij/slicer/SliceRootNode.java index 9f810354f3..44d09c2f46 100644 --- a/java/java-impl/src/com/intellij/slicer/SliceRootNode.java +++ b/java/java-impl/src/com/intellij/slicer/SliceRootNode.java @@ -20,11 +20,9 @@ import com.intellij.ide.projectView.PresentationData; import com.intellij.ide.util.treeView.AbstractTreeNode; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.project.Project; -import com.intellij.psi.PsiElement; import org.jetbrains.annotations.NotNull; import javax.swing.*; -import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -32,20 +30,29 @@ import java.util.List; /** * @author cdr */ -public abstract class SliceRootNode extends SliceNode { +public class SliceRootNode extends SliceNode { private final SliceUsage myRootUsage; - protected SliceRootNode(@NotNull Project project, @NotNull DuplicateMap targetEqualUsages, - AnalysisScope scope, final SliceUsage rootUsage) { - super(project, new SliceUsage(rootUsage.getElement().getContainingFile(), scope), targetEqualUsages, Collections.emptyList()); + public SliceRootNode(@NotNull Project project, @NotNull DuplicateMap targetEqualUsages, + AnalysisScope scope, final SliceUsage rootUsage, boolean dataFlowToThis) { + super(project, new SliceUsage(rootUsage.getElement().getContainingFile(), scope), targetEqualUsages, dataFlowToThis); myRootUsage = rootUsage; } void switchToAllLeavesTogether(SliceUsage rootUsage) { - AbstractTreeNode node = new SliceNode(getProject(), rootUsage, targetEqualUsages, getTreeBuilder(), getLeafExpressions()); + SliceNode node = new SliceNode(getProject(), rootUsage, targetEqualUsages, dataFlowToThis); myCachedChildren = Collections.singletonList(node); } + @Override + SliceRootNode copy() { + SliceUsage newUsage = getValue().copy(); + SliceRootNode newNode = new SliceRootNode(getProject(), new DuplicateMap(), getValue().getScope(), newUsage, dataFlowToThis); + newNode.initialized = initialized; + newNode.duplicate = duplicate; + return newNode; + } + @NotNull public Collection getChildren() { if (myCachedChildren == null) { @@ -84,13 +91,7 @@ public abstract class SliceRootNode extends SliceNode { boolean hasFocus) { } - public void restructureByLeaves(Collection leafExpressions) { - assert myCachedChildren.size() == 1; - SliceNode root = (SliceNode)myCachedChildren.get(0); - myCachedChildren = new ArrayList(leafExpressions.size()); - for (PsiElement leaf : leafExpressions) { - SliceLeafValueRootNode node = new SliceLeafValueRootNode(getProject(), leaf, root); - myCachedChildren.add(node); - } + public SliceUsage getRootUsage() { + return myRootUsage; } } \ No newline at end of file diff --git a/java/java-impl/src/com/intellij/slicer/SliceTreeBuilder.java b/java/java-impl/src/com/intellij/slicer/SliceTreeBuilder.java index 05c25231ea..ad26e256ed 100644 --- a/java/java-impl/src/com/intellij/slicer/SliceTreeBuilder.java +++ b/java/java-impl/src/com/intellij/slicer/SliceTreeBuilder.java @@ -16,28 +16,26 @@ package com.intellij.slicer; import com.intellij.ide.util.treeView.AbstractTreeBuilder; +import com.intellij.ide.util.treeView.AbstractTreeStructure; import com.intellij.ide.util.treeView.AlphaComparator; import com.intellij.ide.util.treeView.NodeDescriptor; -import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.Comparing; -import com.intellij.openapi.util.Ref; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; -import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.swing.tree.DefaultTreeModel; -import java.util.Collection; import java.util.Comparator; /** * @author cdr */ public class SliceTreeBuilder extends AbstractTreeBuilder { - public boolean splitByLeafExpressions; + public final boolean splitByLeafExpressions; public final boolean dataFlowToThis; + public volatile boolean analysisInProgress; + private static final Comparator SLICE_NODE_COMPARATOR = new Comparator() { public int compare(NodeDescriptor o1, NodeDescriptor o2) { if (!(o1 instanceof SliceNode) || !(o2 instanceof SliceNode)) { @@ -65,63 +63,37 @@ public class SliceTreeBuilder extends AbstractTreeBuilder { } }; - public SliceTreeBuilder(JTree tree, Project project, boolean dataFlowToThis, final SliceNode rootNode) { + public SliceTreeBuilder(JTree tree, Project project, boolean dataFlowToThis, final SliceNode rootNode, boolean splitByLeafExpressions) { super(tree, (DefaultTreeModel)tree.getModel(), new SliceTreeStructure(project, rootNode), SLICE_NODE_COMPARATOR, false); this.dataFlowToThis = dataFlowToThis; + this.splitByLeafExpressions = splitByLeafExpressions; initRootNode(); } - protected boolean isAutoExpandNode(NodeDescriptor nodeDescriptor) { - return false; + public SliceNode getRootSliceNode() { + return (SliceNode)getTreeStructure().getRootElement(); } - public void switchToSplittedNodes() { - final SliceRootNode root = (SliceRootNode)getRootNode().getUserObject(); - - Collection leaves = calcLeafExpressions(root); - if (leaves == null) return; //cancelled - - if (leaves.isEmpty()) { - Messages.showErrorDialog("Unable to find leaf expressions to group by", "Cannot group"); - return; - } - - root.setChanged(); - root.restructureByLeaves(leaves); - root.setChanged(); - splitByLeafExpressions = true; - root.targetEqualUsages.clear(); - - getUpdater().cancelAllRequests(); - getUpdater().addSubtreeToUpdateByElement(root); + protected boolean isAutoExpandNode(NodeDescriptor nodeDescriptor) { + return false; } - @Nullable("null means canceled") - public static Collection calcLeafExpressions(final SliceRootNode root) { - final Ref> leafExpressions = Ref.create(null); - boolean b = ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() { + public void switchToSplittedNodes(final AbstractTreeStructure treeStructure) { + analysisInProgress = true; + SliceLeafAnalyzer.startAnalyzeValues(getTreeStructure(), new Runnable(){ public void run() { - Collection l = SliceLeafAnalyzer.calcLeafExpressions(root); - leafExpressions.set(l); + analysisInProgress = false; } - }, "Expanding all nodes... (may very well take the whole day)", true, root.getProject()); - if (!b) return null; - - Collection leaves = leafExpressions.get(); - return leaves; + }); } - public void switchToUnsplittedNodes() { - SliceRootNode root = (SliceRootNode)getRootNode().getUserObject(); - SliceLeafValueRootNode valueNode = (SliceLeafValueRootNode)root.myCachedChildren.get(0); - SliceNode rootNode = valueNode.myCachedChildren.get(0); - root.switchToAllLeavesTogether(rootNode.getValue()); - root.setChanged(); - splitByLeafExpressions = false; - root.targetEqualUsages.clear(); - - getUpdater().cancelAllRequests(); - getUpdater().addSubtreeToUpdateByElement(root); + public void switchToLeafNulls() { + analysisInProgress = true; + SliceNullnessAnalyzer.startAnalyzeNullness(getTreeStructure(), new Runnable(){ + public void run() { + analysisInProgress = false; + } + }); } } diff --git a/java/java-impl/src/com/intellij/slicer/SliceUsage.java b/java/java-impl/src/com/intellij/slicer/SliceUsage.java index e006806976..e0aae8973b 100644 --- a/java/java-impl/src/com/intellij/slicer/SliceUsage.java +++ b/java/java-impl/src/com/intellij/slicer/SliceUsage.java @@ -16,6 +16,7 @@ package com.intellij.slicer; import com.intellij.analysis.AnalysisScope; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; import com.intellij.psi.PsiElement; @@ -50,13 +51,13 @@ public class SliceUsage extends UsageInfo2UsageAdapter { mySubstitutor = PsiSubstitutor.EMPTY; } - public void processChildren(Processor processor, boolean dataFlowToThis) { - PsiElement element = getElement(); + public void processChildren(Processor processor, final boolean dataFlowToThis) { + final PsiElement element = getElement(); ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); //indicator.setText2("Searching for usages of "+ StringUtil.trimStart(SliceManager.getElementDescription(element),"")+""); indicator.checkCanceled(); - Processor uniqueProcessor = + final Processor uniqueProcessor = new CommonProcessors.UniqueProcessor(processor, new TObjectHashingStrategy() { public int computeHashCode(final SliceUsage object) { return object.getUsageInfo().hashCode(); @@ -67,12 +68,16 @@ public class SliceUsage extends UsageInfo2UsageAdapter { } }); - if (dataFlowToThis) { - SliceUtil.processUsagesFlownDownTo(element, uniqueProcessor, this, mySubstitutor); - } - else { - SliceFUtil.processUsagesFlownFromThe(element, uniqueProcessor, this); - } + ApplicationManager.getApplication().runReadAction(new Runnable() { + public void run() { + if (dataFlowToThis) { + SliceUtil.processUsagesFlownDownTo(element, uniqueProcessor, SliceUsage.this, mySubstitutor); + } + else { + SliceFUtil.processUsagesFlownFromThe(element, uniqueProcessor, SliceUsage.this); + } + } + }); } public SliceUsage getParent() { diff --git a/java/java-impl/src/com/intellij/slicer/SliceUtil.java b/java/java-impl/src/com/intellij/slicer/SliceUtil.java index d7e51d6d70..2185ac0a5d 100644 --- a/java/java-impl/src/com/intellij/slicer/SliceUtil.java +++ b/java/java-impl/src/com/intellij/slicer/SliceUtil.java @@ -21,9 +21,11 @@ import com.intellij.openapi.util.Comparing; import com.intellij.psi.*; import com.intellij.psi.impl.PsiSubstitutorImpl; import com.intellij.psi.impl.source.DummyHolder; +import com.intellij.psi.search.SearchScope; import com.intellij.psi.search.searches.MethodReferencesSearch; import com.intellij.psi.search.searches.OverridingMethodsSearch; import com.intellij.psi.search.searches.ReferencesSearch; +import com.intellij.psi.tree.IElementType; import com.intellij.psi.util.MethodSignatureUtil; import com.intellij.psi.util.PsiUtil; import com.intellij.psi.util.TypeConversionUtil; @@ -60,6 +62,9 @@ public class SliceUtil { JavaResolveResult result = ref.advancedResolve(false); parentSubstitutor = result.getSubstitutor().putAll(parentSubstitutor); PsiElement resolved = result.getElement(); + if (resolved instanceof PsiCompiledElement) { + resolved = resolved.getNavigationElement(); + } if (resolved instanceof PsiMethod && expression.getParent() instanceof PsiMethodCallExpression) { return processUsagesFlownDownTo(expression.getParent(), processor, parent, parentSubstitutor); } @@ -92,6 +97,15 @@ public class SliceUtil { if (thenE != null && !handToProcessor(thenE, processor, parent, parentSubstitutor)) return false; if (elseE != null && !handToProcessor(elseE, processor, parent, parentSubstitutor)) return false; } + if (expression instanceof PsiAssignmentExpression) { + PsiAssignmentExpression assignment = (PsiAssignmentExpression)expression; + IElementType tokenType = assignment.getOperationTokenType(); + PsiExpression rExpression = assignment.getRExpression(); + + if (tokenType == JavaTokenType.EQ && rExpression != null) { + return processUsagesFlownDownTo(rExpression, processor, parent, parentSubstitutor); + } + } return true; } @@ -124,7 +138,10 @@ public class SliceUtil { @NotNull final SliceUsage parent, @NotNull final PsiSubstitutor parentSubstitutor) { final JavaResolveResult resolved = methodCallExpr.resolveMethodGenerics(); - final PsiElement r = resolved.getElement(); + PsiElement r = resolved.getElement(); + if (r instanceof PsiCompiledElement) { + r = r.getNavigationElement(); + } if (!(r instanceof PsiMethod)) return true; PsiMethod methodCalled = (PsiMethod)r; @@ -139,6 +156,11 @@ public class SliceUtil { final boolean[] result = {true}; for (PsiMethod override : overrides) { if (!result[0]) break; + if (override instanceof PsiCompiledElement) { + override = (PsiMethod)override.getNavigationElement(); + } + if (!parent.getScope().contains(override)) continue; + final PsiCodeBlock body = override.getBody(); if (body == null) continue; @@ -174,12 +196,16 @@ public class SliceUtil { if (!handToProcessor(initializer, processor, parent, parentSubstitutor)) return false; } } - return ReferencesSearch.search(field, parent.getScope().toSearchScope()).forEach(new Processor() { + SearchScope searchScope = parent.getScope().toSearchScope(); + return ReferencesSearch.search(field, searchScope).forEach(new Processor() { public boolean process(final PsiReference reference) { SliceManager.getInstance(field.getProject()).checkCanceled(); PsiElement element = reference.getElement(); if (!(element instanceof PsiReferenceExpression)) return true; - if (element instanceof PsiCompiledElement) return true; + if (element instanceof PsiCompiledElement) { + element = element.getNavigationElement(); + if (!parent.getScope().contains(element)) return true; + } final PsiReferenceExpression referenceExpression = (PsiReferenceExpression)element; PsiElement parentExpr = referenceExpression.getParent(); if (PsiUtil.isOnAssignmentLeftHand(referenceExpression)) { @@ -265,6 +291,10 @@ public class SliceUtil { Project project = argumentList.getProject(); PsiElement element = result.getElement(); + if (element instanceof PsiCompiledElement) { + element = element.getNavigationElement(); + } + // for erased method calls for which we cannot determine target substitutor, // rely on call argument types. I.e. new Pair(1,2) -> Pair if (element instanceof PsiTypeParameterListOwner && PsiUtil.isRawSubstitutor((PsiTypeParameterListOwner)element, substitutor)) { @@ -279,7 +309,7 @@ public class SliceUtil { PsiSubstitutor combined = unify(substitutor, parentSubstitutor, project); if (combined == null) return true; PsiType substitited = combined.substitute(passExpression.getType()); - if (!TypeConversionUtil.areTypesConvertible(substitited, actualType)) return true; + if (substitited == null || !TypeConversionUtil.areTypesConvertible(substitited, actualType)) return true; return handToProcessor(passExpression, processor, parent, combined); } -- 2.11.4.GIT