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
;
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());
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
);
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
);
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();
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
);
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
);
149 public void onCancel() {
154 public void onSuccess() {
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");
164 groupByValues(leaves
, root
, map
);
174 public static Map
<SliceNode
, Collection
<PsiElement
>> createMap() {
175 final Map
<SliceNode
, Collection
<PsiElement
>> map
= new FactoryMap
<SliceNode
, Collection
<PsiElement
>>() {
177 protected Map
<SliceNode
, Collection
<PsiElement
>> createMap() {
178 return new THashMap
<SliceNode
, Collection
<PsiElement
>>(TObjectHashingStrategy
.IDENTITY
);
182 protected Collection
<PsiElement
> create(SliceNode key
) {
183 return new THashSet
<PsiElement
>(SliceLeafAnalyzer
.LEAF_ELEMENT_EQUALITY
);
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
);
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
) {
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
));
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
);
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
);