IDEA-50121
[fedora-idea.git] / java / java-impl / src / com / intellij / slicer / SliceLeafAnalyzer.java
blob6352ae660572a360f2bb952ff19da4d7c386f231
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.slicer;
18 import com.intellij.codeInsight.PsiEquivalenceUtil;
19 import com.intellij.ide.util.treeView.AbstractTreeNode;
20 import com.intellij.ide.util.treeView.AbstractTreeStructure;
21 import com.intellij.openapi.application.ApplicationManager;
22 import com.intellij.openapi.progress.ProgressIndicator;
23 import com.intellij.openapi.progress.ProgressManager;
24 import com.intellij.openapi.progress.Task;
25 import com.intellij.openapi.ui.Messages;
26 import com.intellij.openapi.util.Comparing;
27 import com.intellij.openapi.util.Computable;
28 import com.intellij.openapi.util.Ref;
29 import com.intellij.psi.PsiElement;
30 import com.intellij.psi.PsiJavaReference;
31 import com.intellij.psi.PsiNamedElement;
32 import com.intellij.psi.WalkingState;
33 import com.intellij.psi.impl.source.tree.SourceUtil;
34 import com.intellij.util.NullableFunction;
35 import com.intellij.util.PairProcessor;
36 import com.intellij.util.containers.ContainerUtil;
37 import com.intellij.util.containers.FactoryMap;
38 import gnu.trove.THashMap;
39 import gnu.trove.THashSet;
40 import gnu.trove.TObjectHashingStrategy;
41 import org.jetbrains.annotations.NotNull;
43 import java.util.*;
45 /**
46 * @author cdr
48 public class SliceLeafAnalyzer {
49 public static final TObjectHashingStrategy<PsiElement> LEAF_ELEMENT_EQUALITY = new TObjectHashingStrategy<PsiElement>() {
50 public int computeHashCode(final PsiElement element) {
51 if (element == null) return 0;
52 String text = ApplicationManager.getApplication().runReadAction(new Computable<String>() {
53 public String compute() {
54 PsiElement elementToCompare = element;
55 if (element instanceof PsiJavaReference) {
56 PsiElement resolved = ((PsiJavaReference)element).resolve();
57 if (resolved != null) {
58 elementToCompare = resolved;
61 return elementToCompare instanceof PsiNamedElement ?
62 ((PsiNamedElement)elementToCompare).getName() : SourceUtil.getTextSkipWhiteSpaceAndComments(elementToCompare.getNode());
64 });
65 return Comparing.hashcode(text);
68 public boolean equals(final PsiElement o1, final PsiElement o2) {
69 return ApplicationManager.getApplication().runReadAction(new Computable<Boolean>() {
70 public Boolean compute() {
71 return o1 != null && o2 != null && PsiEquivalenceUtil.areElementsEquivalent(o1, o2);
73 });
77 static SliceNode filterTree(SliceNode oldRoot, NullableFunction<SliceNode, SliceNode> filter, PairProcessor<SliceNode, List<SliceNode>> postProcessor){
78 SliceNode filtered = filter.fun(oldRoot);
79 if (filtered == null) return null;
81 List<SliceNode> childrenFiltered = new ArrayList<SliceNode>();
82 if (oldRoot.myCachedChildren != null) {
83 for (SliceNode child : oldRoot.myCachedChildren) {
84 SliceNode childFiltered = filterTree(child, filter,postProcessor);
85 if (childFiltered != null) {
86 childrenFiltered.add(childFiltered);
90 boolean success = postProcessor == null || postProcessor.process(filtered, childrenFiltered);
91 if (!success) return null;
92 filtered.myCachedChildren = new ArrayList<SliceNode>(childrenFiltered);
93 return filtered;
96 private static void groupByValues(Collection<PsiElement> leaves, SliceRootNode oldRoot, final Map<SliceNode, Collection<PsiElement>> map) {
97 assert oldRoot.myCachedChildren.size() == 1;
98 SliceRootNode root = createTreeGroupedByValues(leaves, oldRoot, map);
100 SliceNode oldRootStart = oldRoot.myCachedChildren.get(0);
101 SliceUsage rootUsage = oldRootStart.getValue();
102 SliceManager.getInstance(root.getProject()).createToolWindow(true, root, true, SliceManager.getElementDescription(null, rootUsage.getElement(), " Grouped by Value") );
105 public static SliceRootNode createTreeGroupedByValues(Collection<PsiElement> leaves, SliceRootNode oldRoot, final Map<SliceNode, Collection<PsiElement>> map) {
106 SliceNode oldRootStart = oldRoot.myCachedChildren.get(0);
107 SliceRootNode root = oldRoot.copy();
108 root.setChanged();
109 root.targetEqualUsages.clear();
110 root.myCachedChildren = new ArrayList<SliceNode>(leaves.size());
112 for (final PsiElement leafExpression : leaves) {
113 SliceNode newNode = filterTree(oldRootStart, new NullableFunction<SliceNode, SliceNode>() {
114 public SliceNode fun(SliceNode oldNode) {
115 if (oldNode.getDuplicate() != null) return null;
116 if (!node(oldNode, map).contains(leafExpression)) return null;
118 return oldNode.copy();
120 }, new PairProcessor<SliceNode, List<SliceNode>>() {
121 public boolean process(SliceNode node, List<SliceNode> children) {
122 if (!children.isEmpty()) return true;
123 PsiElement element = node.getValue().getElement();
124 if (element == null) return false;
125 return element.getManager().areElementsEquivalent(element, leafExpression); // leaf can be there only if it's filtering expression
129 SliceLeafValueRootNode lvNode = new SliceLeafValueRootNode(root.getProject(), leafExpression, root, Collections.singletonList(newNode),
130 oldRoot.getValue().params);
131 root.myCachedChildren.add(lvNode);
133 return root;
136 public static void startAnalyzeValues(final AbstractTreeStructure treeStructure, final Runnable finish) {
137 final SliceRootNode root = (SliceRootNode)treeStructure.getRootElement();
138 final Ref<Collection<PsiElement>> leafExpressions = Ref.create(null);
140 final Map<SliceNode, Collection<PsiElement>> map = createMap();
142 ProgressManager.getInstance().run(new Task.Backgroundable(root.getProject(), "Expanding all nodes... (may very well take the whole day)", true) {
143 public void run(@NotNull final ProgressIndicator indicator) {
144 Collection<PsiElement> l = calcLeafExpressions(root, treeStructure, map);
145 leafExpressions.set(l);
148 @Override
149 public void onCancel() {
150 finish.run();
153 @Override
154 public void onSuccess() {
155 try {
156 Collection<PsiElement> leaves = leafExpressions.get();
157 if (leaves == null) return; //cancelled
159 if (leaves.isEmpty()) {
160 Messages.showErrorDialog("Unable to find leaf expressions to group by", "Cannot group");
161 return;
164 groupByValues(leaves, root, map);
166 finally {
167 finish.run();
174 public static Map<SliceNode, Collection<PsiElement>> createMap() {
175 final Map<SliceNode, Collection<PsiElement>> map = new FactoryMap<SliceNode, Collection<PsiElement>>() {
176 @Override
177 protected Map<SliceNode, Collection<PsiElement>> createMap() {
178 return new THashMap<SliceNode, Collection<PsiElement>>(TObjectHashingStrategy.IDENTITY);
181 @Override
182 protected Collection<PsiElement> create(SliceNode key) {
183 return new THashSet<PsiElement>(SliceLeafAnalyzer.LEAF_ELEMENT_EQUALITY);
186 return map;
189 static class SliceNodeGuide implements WalkingState.TreeGuide<SliceNode> {
190 private final AbstractTreeStructure myTreeStructure;
191 // use tree strucutre because it's setting 'parent' fields in the process
193 SliceNodeGuide(@NotNull AbstractTreeStructure treeStructure) {
194 myTreeStructure = treeStructure;
197 public SliceNode getNextSibling(@NotNull SliceNode element) {
198 AbstractTreeNode parent = element.getParent();
199 if (parent == null) return null;
201 return element.getNext((List)parent.getChildren());
204 public SliceNode getPrevSibling(@NotNull SliceNode element) {
205 AbstractTreeNode parent = element.getParent();
206 if (parent == null) return null;
207 return element.getPrev((List)parent.getChildren());
210 public SliceNode getFirstChild(@NotNull SliceNode element) {
211 Object[] children = myTreeStructure.getChildElements(element);
212 return children.length == 0 ? null : (SliceNode)children[0];
215 public SliceNode getParent(@NotNull SliceNode element) {
216 AbstractTreeNode parent = element.getParent();
217 return parent instanceof SliceNode ? (SliceNode)parent : null;
221 private static Collection<PsiElement> node(SliceNode node, Map<SliceNode, Collection<PsiElement>> map) {
222 return map.get(node);
225 @NotNull
226 public static Collection<PsiElement> calcLeafExpressions(@NotNull final SliceNode root, AbstractTreeStructure treeStructure,
227 final Map<SliceNode, Collection<PsiElement>> map) {
228 final SliceNodeGuide guide = new SliceNodeGuide(treeStructure);
229 WalkingState<SliceNode> walkingState = new WalkingState<SliceNode>(guide) {
230 @Override
231 public void visit(@NotNull SliceNode element) {
232 element.update(null);
233 node(element, map).clear();
234 SliceNode duplicate = element.getDuplicate();
235 if (duplicate != null) {
236 node(element, map).addAll(node(duplicate, map));
238 else {
239 SliceUsage sliceUsage = element.getValue();
241 Collection<? extends AbstractTreeNode> children = element.getChildren();
242 if (children.isEmpty()) {
243 PsiElement value = sliceUsage.getElement();
244 node(element, map).addAll(ContainerUtil.singleton(value, LEAF_ELEMENT_EQUALITY));
246 super.visit(element);
250 @Override
251 public void elementFinished(@NotNull SliceNode element) {
252 SliceNode parent = guide.getParent(element);
253 if (parent != null) {
254 node(parent, map).addAll(node(element, map));
258 walkingState.visit(root);
260 return node(root, map);