/* * Copyright 2000-2015 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.usages.impl; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.util.text.StringUtil; import com.intellij.pom.Navigatable; import com.intellij.usages.Usage; import com.intellij.usages.UsageGroup; import com.intellij.usages.UsageView; import com.intellij.usages.rules.MergeableUsage; import com.intellij.util.Consumer; import com.intellij.util.ObjectUtils; import com.intellij.util.SmartList; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreeNode; import java.util.*; /** * @author max */ public class GroupNode extends Node implements Navigatable, Comparable<GroupNode> { private static final NodeComparator COMPARATOR = new NodeComparator(); private final int myRuleIndex; private int myRecursiveUsageCount; // EDT only access private final List<Node> myChildren = new SmartList<>(); // guarded by this GroupNode(Node parent, @Nullable UsageGroup group, int ruleIndex) { setUserObject(group); setParent(parent); myRuleIndex = ruleIndex; } @Override protected void updateNotify() { if (getGroup() != null) { getGroup().update(); } } public String toString() { String result = getGroup() == null ? "" : getGroup().getText(null); synchronized (this) { List<Node> children = myChildren; return result + children.subList(0, Math.min(10, children.size())); } } @NotNull List<Node> getChildren() { return myChildren; } @NotNull List<Node> getSwingChildren() { return ObjectUtils.notNull(children, Collections.emptyList()); } @NotNull GroupNode addOrGetGroup(@NotNull UsageGroup group, int ruleIndex, @NotNull Consumer<Node> edtInsertedUnderQueue) { GroupNode newNode = new GroupNode(this, group, ruleIndex); synchronized (this) { int insertionIndex = getNodeIndex(newNode, myChildren); if (insertionIndex >= 0) return (GroupNode)myChildren.get(insertionIndex); int i = -insertionIndex - 1; myChildren.add(i, newNode); } edtInsertedUnderQueue.consume(this); return newNode; } // >= 0 if found, < 0 if not found private static int getNodeIndex(@NotNull Node newNode, @NotNull List<Node> children) { int low = 0; int high = children.size() - 1; while (low <= high) { int mid = (low + high) / 2; Node child = children.get(mid); int cmp = COMPARATOR.compare(child, newNode); if (cmp < 0) { low = mid + 1; } else if (cmp > 0) { high = mid - 1; } else { return mid; } } return -(low + 1); } // always >= 0 private static int getNodeInsertionIndex(@NotNull Node node, @NotNull List<Node> children) { int i = getNodeIndex(node, children); return i >= 0 ? i : -i-1; } void addTargetsNode(@NotNull Node node, @NotNull DefaultTreeModel treeModel) { ApplicationManager.getApplication().assertIsDispatchThread(); int index; synchronized (this) { index = getNodeInsertionIndex(node, getSwingChildren()); myChildren.add(index, node); } treeModel.insertNodeInto(node, this, index); } @Override public void removeAllChildren() { ApplicationManager.getApplication().assertIsDispatchThread(); super.removeAllChildren(); synchronized (this) { myChildren.clear(); } myRecursiveUsageCount = 0; } @Nullable private UsageNode tryMerge(@NotNull Usage usage) { if (!(usage instanceof MergeableUsage)) return null; MergeableUsage mergeableUsage = (MergeableUsage)usage; for (UsageNode node : getUsageNodes()) { Usage original = node.getUsage(); if (original == mergeableUsage) { // search returned duplicate usage, ignore return node; } if (original instanceof MergeableUsage) { if (((MergeableUsage)original).merge(mergeableUsage)) return node; } } return null; } void removeUsage(@NotNull UsageNode usage, @NotNull DefaultTreeModel treeModel) { removeUsagesBulk(Collections.singleton(usage), treeModel); } boolean removeUsagesBulk(@NotNull Set<UsageNode> usages, @NotNull DefaultTreeModel treeModel) { ApplicationManager.getApplication().assertIsDispatchThread(); boolean removed; synchronized (this) { removed = myChildren.removeAll(usages); if (!removed) { for (GroupNode groupNode : getSubGroups()) { if (groupNode.removeUsagesBulk(usages, treeModel)) { if (groupNode.getRecursiveUsageCount() == 0) { treeModel.removeNodeFromParent(groupNode); myChildren.remove(groupNode); } removed = true; break; } } } } if (removed) { wasRemoved(treeModel); } return removed; } private void wasRemoved(@NotNull DefaultTreeModel treeModel) { ApplicationManager.getApplication().assertIsDispatchThread(); myRecursiveUsageCount--; treeModel.nodeChanged(this); } @NotNull UsageNode addUsage(@NotNull Usage usage, @NotNull Consumer<Node> edtInsertedUnderQueue, boolean filterDuplicateLines) { final UsageNode newNode; synchronized (this) { if (filterDuplicateLines) { UsageNode mergedWith = tryMerge(usage); if (mergedWith != null) { return mergedWith; } } newNode = new UsageNode(this, usage); int index = getNodeInsertionIndex(newNode, myChildren); myChildren.add(index, newNode); } edtInsertedUnderQueue.consume(this); return newNode; } void incrementUsageCount() { ApplicationManager.getApplication().assertIsDispatchThread(); GroupNode groupNode = this; while (true) { groupNode.myRecursiveUsageCount++; TreeNode parent = groupNode.getParent(); if (!(parent instanceof GroupNode)) return; groupNode = (GroupNode)parent; } } @Override public String tree2string(int indent, String lineSeparator) { StringBuffer result = new StringBuffer(); StringUtil.repeatSymbol(result, ' ', indent); if (getGroup() != null) result.append(getGroup()); result.append("["); result.append(lineSeparator); for (Node node : myChildren) { result.append(node.tree2string(indent + 4, lineSeparator)); result.append(lineSeparator); } StringUtil.repeatSymbol(result, ' ', indent); result.append("]"); result.append(lineSeparator); return result.toString(); } @Override protected boolean isDataValid() { return getGroup() == null || getGroup().isValid(); } @Override protected boolean isDataReadOnly() { Enumeration enumeration = children(); while (enumeration.hasMoreElements()) { Object element = enumeration.nextElement(); if (element instanceof Node && ((Node)element).isReadOnly()) return true; } return false; } private static class NodeComparator implements Comparator<DefaultMutableTreeNode> { enum ClassIndex { UNKNOWN, USAGE_TARGET, GROUP, USAGE } private static ClassIndex getClassIndex(DefaultMutableTreeNode node) { if (node instanceof UsageNode) return ClassIndex.USAGE; if (node instanceof GroupNode) return ClassIndex.GROUP; if (node instanceof UsageTargetNode) return ClassIndex.USAGE_TARGET; return ClassIndex.UNKNOWN; } @Override public int compare(DefaultMutableTreeNode n1, DefaultMutableTreeNode n2) { ClassIndex classIdx1 = getClassIndex(n1); ClassIndex classIdx2 = getClassIndex(n2); if (classIdx1 != classIdx2) return classIdx1.compareTo(classIdx2); if (classIdx1 == ClassIndex.GROUP) return ((GroupNode)n1).compareTo((GroupNode)n2); if (classIdx1 == ClassIndex.USAGE) return ((UsageNode)n1).compareTo((UsageNode)n2); return 0; } } @Override public int compareTo(@NotNull GroupNode groupNode) { if (myRuleIndex == groupNode.myRuleIndex) { return getGroup().compareTo(groupNode.getGroup()); } return Integer.compare(myRuleIndex, groupNode.myRuleIndex); } public synchronized UsageGroup getGroup() { return (UsageGroup)getUserObject(); } int getRecursiveUsageCount() { ApplicationManager.getApplication().assertIsDispatchThread(); return myRecursiveUsageCount; } @Override public void navigate(boolean requestFocus) { if (getGroup() != null) { getGroup().navigate(requestFocus); } } @Override public boolean canNavigate() { return getGroup() != null && getGroup().canNavigate(); } @Override public boolean canNavigateToSource() { return getGroup() != null && getGroup().canNavigateToSource(); } @Override protected boolean isDataExcluded() { for (Node node : myChildren) { if (!node.isExcluded()) return false; } return true; } @NotNull @Override protected String getText(@NotNull UsageView view) { return getGroup().getText(view); } @NotNull public synchronized Collection<GroupNode> getSubGroups() { List<GroupNode> list = new ArrayList<>(); for (Node n : myChildren) { if (n instanceof GroupNode) { list.add((GroupNode)n); } } return list; } @NotNull public synchronized Collection<UsageNode> getUsageNodes() { List<UsageNode> list = new ArrayList<>(); for (Node n : myChildren) { if (n instanceof UsageNode) { list.add((UsageNode)n); } } return list; } }