| /* |
| * 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.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.impl.source.tree.AstBufferUtil; |
| import com.intellij.util.NullableFunction; |
| import com.intellij.util.PairProcessor; |
| import com.intellij.util.WalkingState; |
| import com.intellij.util.containers.ConcurrentHashMap; |
| import com.intellij.util.containers.ConcurrentHashSet; |
| import com.intellij.util.containers.ContainerUtil; |
| import com.intellij.util.containers.FactoryMap; |
| import gnu.trove.TObjectHashingStrategy; |
| import org.jetbrains.annotations.NotNull; |
| |
| import java.util.*; |
| |
| /** |
| * @author cdr |
| */ |
| public class SliceLeafAnalyzer { |
| public static final TObjectHashingStrategy<PsiElement> LEAF_ELEMENT_EQUALITY = new TObjectHashingStrategy<PsiElement>() { |
| @Override |
| public int computeHashCode(final PsiElement element) { |
| if (element == null) return 0; |
| String text = ApplicationManager.getApplication().runReadAction(new Computable<String>() { |
| @Override |
| 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() |
| : AstBufferUtil.getTextSkippingWhitespaceComments(elementToCompare.getNode()); |
| } |
| }); |
| return Comparing.hashcode(text); |
| } |
| |
| @Override |
| public boolean equals(final PsiElement o1, final PsiElement o2) { |
| return ApplicationManager.getApplication().runReadAction(new Computable<Boolean>() { |
| @Override |
| public Boolean compute() { |
| return o1 != null && o2 != null && PsiEquivalenceUtil.areElementsEquivalent(o1, o2); |
| } |
| }); |
| } |
| }; |
| |
| static SliceNode filterTree(SliceNode oldRoot, |
| NullableFunction<SliceNode, SliceNode> filter, |
| PairProcessor<SliceNode, List<SliceNode>> postProcessor) { |
| SliceNode filtered = filter.fun(oldRoot); |
| if (filtered == null) return null; |
| |
| List<SliceNode> childrenFiltered = new ArrayList<SliceNode>(); |
| if (oldRoot.myCachedChildren != null) { |
| for (SliceNode child : oldRoot.myCachedChildren) { |
| SliceNode childFiltered = filterTree(child, filter,postProcessor); |
| if (childFiltered != null) { |
| childrenFiltered.add(childFiltered); |
| } |
| } |
| } |
| boolean success = postProcessor == null || postProcessor.process(filtered, childrenFiltered); |
| if (!success) return null; |
| filtered.myCachedChildren = new ArrayList<SliceNode>(childrenFiltered); |
| return filtered; |
| } |
| |
| private static void groupByValues(@NotNull Collection<PsiElement> leaves, |
| @NotNull SliceRootNode oldRoot, |
| @NotNull Map<SliceNode, Collection<PsiElement>> map) { |
| assert oldRoot.myCachedChildren.size() == 1; |
| SliceRootNode root = createTreeGroupedByValues(leaves, oldRoot, map); |
| |
| SliceNode oldRootStart = oldRoot.myCachedChildren.get(0); |
| SliceUsage rootUsage = oldRootStart.getValue(); |
| String description = SliceManager.getElementDescription(null, rootUsage.getElement(), " (grouped by value)"); |
| SliceManager.getInstance(root.getProject()).createToolWindow(true, root, true, description); |
| } |
| |
| @NotNull |
| public static SliceRootNode createTreeGroupedByValues(Collection<PsiElement> leaves, SliceRootNode oldRoot, final Map<SliceNode, Collection<PsiElement>> map) { |
| SliceNode oldRootStart = oldRoot.myCachedChildren.get(0); |
| SliceRootNode root = oldRoot.copy(); |
| root.setChanged(); |
| root.targetEqualUsages.clear(); |
| root.myCachedChildren = new ArrayList<SliceNode>(leaves.size()); |
| |
| for (final PsiElement leafExpression : leaves) { |
| SliceNode newNode = filterTree(oldRootStart, new NullableFunction<SliceNode, SliceNode>() { |
| @Override |
| public SliceNode fun(SliceNode oldNode) { |
| if (oldNode.getDuplicate() != null) return null; |
| if (!node(oldNode, map).contains(leafExpression)) return null; |
| |
| return oldNode.copy(); |
| } |
| }, 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 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), |
| oldRoot.getValue().params); |
| root.myCachedChildren.add(lvNode); |
| } |
| return root; |
| } |
| |
| public static void startAnalyzeValues(@NotNull final AbstractTreeStructure treeStructure, @NotNull final Runnable finish) { |
| final SliceRootNode root = (SliceRootNode)treeStructure.getRootElement(); |
| final Ref<Collection<PsiElement>> leafExpressions = Ref.create(null); |
| |
| final Map<SliceNode, Collection<PsiElement>> 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) { |
| Collection<PsiElement> l = calcLeafExpressions(root, treeStructure, map); |
| leafExpressions.set(l); |
| } |
| |
| @Override |
| public void onCancel() { |
| finish.run(); |
| } |
| |
| @Override |
| public void onSuccess() { |
| try { |
| Collection<PsiElement> 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<SliceNode, Collection<PsiElement>> createMap() { |
| return new FactoryMap<SliceNode, Collection<PsiElement>>() { |
| @Override |
| protected Map<SliceNode, Collection<PsiElement>> createMap() { |
| return new ConcurrentHashMap<SliceNode, Collection<PsiElement>>(ContainerUtil.<SliceNode>identityStrategy()); |
| } |
| |
| @Override |
| protected Collection<PsiElement> create(SliceNode key) { |
| return new ConcurrentHashSet<PsiElement>(LEAF_ELEMENT_EQUALITY); |
| } |
| }; |
| } |
| |
| static class SliceNodeGuide implements WalkingState.TreeGuide<SliceNode> { |
| private final AbstractTreeStructure myTreeStructure; |
| // use tree structure because it's setting 'parent' fields in the process |
| |
| SliceNodeGuide(@NotNull AbstractTreeStructure treeStructure) { |
| myTreeStructure = treeStructure; |
| } |
| |
| @Override |
| public SliceNode getNextSibling(@NotNull SliceNode element) { |
| AbstractTreeNode parent = element.getParent(); |
| if (parent == null) return null; |
| |
| return element.getNext((List)parent.getChildren()); |
| } |
| |
| @Override |
| public SliceNode getPrevSibling(@NotNull SliceNode element) { |
| AbstractTreeNode parent = element.getParent(); |
| if (parent == null) return null; |
| return element.getPrev((List)parent.getChildren()); |
| } |
| |
| @Override |
| public SliceNode getFirstChild(@NotNull SliceNode element) { |
| Object[] children = myTreeStructure.getChildElements(element); |
| return children.length == 0 ? null : (SliceNode)children[0]; |
| } |
| |
| @Override |
| public SliceNode getParent(@NotNull SliceNode element) { |
| AbstractTreeNode parent = element.getParent(); |
| return parent instanceof SliceNode ? (SliceNode)parent : null; |
| } |
| } |
| |
| private static Collection<PsiElement> node(SliceNode node, Map<SliceNode, Collection<PsiElement>> map) { |
| return map.get(node); |
| } |
| |
| @NotNull |
| public static Collection<PsiElement> calcLeafExpressions(@NotNull final SliceNode root, |
| @NotNull AbstractTreeStructure treeStructure, |
| @NotNull final Map<SliceNode, Collection<PsiElement>> map) { |
| final SliceNodeGuide guide = new 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).addAll(node(duplicate, map)); |
| } |
| else { |
| ApplicationManager.getApplication().runReadAction(new Runnable() { |
| @Override |
| public void run() { |
| final SliceUsage sliceUsage = element.getValue(); |
| |
| Collection<? extends AbstractTreeNode> children = element.getChildren(); |
| if (children.isEmpty()) { |
| PsiElement value = sliceUsage.indexNesting == 0 ? sliceUsage.getElement() : null; |
| if (value != null) { |
| node(element, map).addAll(ContainerUtil.singleton(value, LEAF_ELEMENT_EQUALITY)); |
| } |
| } |
| } |
| }); |
| |
| super.visit(element); |
| } |
| } |
| |
| @Override |
| public void elementFinished(@NotNull SliceNode element) { |
| SliceNode parent = guide.getParent(element); |
| if (parent != null) { |
| node(parent, map).addAll(node(element, map)); |
| } |
| } |
| }; |
| walkingState.visit(root); |
| |
| return node(root, map); |
| } |
| } |