| /* |
| * 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.ide.util.treeView.smartTree; |
| |
| import com.intellij.ide.structureView.impl.StructureViewElementWrapper; |
| import com.intellij.ide.util.treeView.AbstractTreeNode; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.pom.Navigatable; |
| import gnu.trove.THashMap; |
| import org.jetbrains.annotations.NotNull; |
| |
| import java.util.*; |
| |
| public abstract class CachingChildrenTreeNode <Value> extends AbstractTreeNode<Value> { |
| private static final Logger LOG = Logger.getInstance("#com.intellij.ide.util.treeView.smartTree.CachingChildrenTreeNode"); |
| private List<CachingChildrenTreeNode> myChildren; |
| private List<CachingChildrenTreeNode> myOldChildren = null; |
| protected final TreeModel myTreeModel; |
| |
| public CachingChildrenTreeNode(Project project, Value value, TreeModel treeModel) { |
| super(project, |
| value instanceof StructureViewElementWrapper ? (Value) ((StructureViewElementWrapper) value).getWrappedElement() : value); |
| myTreeModel = treeModel; |
| } |
| |
| @Override |
| @NotNull |
| public Collection<AbstractTreeNode> getChildren() { |
| ensureChildrenAreInitialized(); |
| return new ArrayList<AbstractTreeNode>(myChildren); |
| } |
| |
| private void ensureChildrenAreInitialized() { |
| if (myChildren == null) { |
| myChildren = new ArrayList<CachingChildrenTreeNode>(); |
| rebuildSubtree(); |
| } |
| } |
| |
| protected void addSubElement(CachingChildrenTreeNode node) { |
| ensureChildrenAreInitialized(); |
| myChildren.add(node); |
| node.setParent(this); |
| } |
| |
| protected void setChildren(Collection<AbstractTreeNode> children) { |
| clearChildren(); |
| for (AbstractTreeNode node : children) { |
| myChildren.add((CachingChildrenTreeNode)node); |
| node.setParent(this); |
| } |
| } |
| |
| private static class CompositeComparator implements java.util.Comparator<CachingChildrenTreeNode> { |
| private final Sorter[] mySorters; |
| |
| public CompositeComparator(final Sorter[] sorters) { |
| mySorters = sorters; |
| } |
| |
| @Override |
| public int compare(final CachingChildrenTreeNode o1, final CachingChildrenTreeNode o2) { |
| final Object value1 = o1.getValue(); |
| final Object value2 = o2.getValue(); |
| for (Sorter sorter : mySorters) { |
| final int result = sorter.getComparator().compare(value1, value2); |
| if (result != 0) return result; |
| } |
| return 0; |
| } |
| } |
| |
| protected void sortChildren(Sorter[] sorters) { |
| if (myChildren == null) return; |
| |
| Collections.sort(myChildren, new CompositeComparator(sorters)); |
| |
| for (CachingChildrenTreeNode child : myChildren) { |
| if (child instanceof GroupWrapper) { |
| child.sortChildren(sorters); |
| } |
| } |
| } |
| |
| protected void filterChildren(@NotNull Filter[] filters) { |
| Collection<AbstractTreeNode> children = getChildren(); |
| for (Filter filter : filters) { |
| for (Iterator<AbstractTreeNode> eachNode = children.iterator(); eachNode.hasNext();) { |
| TreeElementWrapper eachChild = (TreeElementWrapper)eachNode.next(); |
| TreeElement value = eachChild.getValue(); |
| if (value == null || !filter.isVisible(value)) { |
| eachNode.remove(); |
| } |
| } |
| } |
| setChildren(children); |
| } |
| |
| protected void groupChildren(@NotNull Grouper[] groupers) { |
| for (Grouper grouper : groupers) { |
| groupElements(grouper); |
| } |
| Collection<AbstractTreeNode> children = getChildren(); |
| for (AbstractTreeNode child : children) { |
| if (child instanceof GroupWrapper) { |
| ((GroupWrapper)child).groupChildren(groupers); |
| } |
| } |
| } |
| |
| private void groupElements(Grouper grouper) { |
| ArrayList<AbstractTreeNode<TreeElement>> ungrouped = new ArrayList<AbstractTreeNode<TreeElement>>(); |
| Collection<AbstractTreeNode> children = getChildren(); |
| for (final AbstractTreeNode child : children) { |
| CachingChildrenTreeNode<TreeElement> node = (CachingChildrenTreeNode<TreeElement>)child; |
| if (node instanceof TreeElementWrapper) { |
| ungrouped.add(node); |
| } |
| } |
| |
| if (!ungrouped.isEmpty()) { |
| processUngrouped(ungrouped, grouper); |
| } |
| |
| Collection<AbstractTreeNode> result = new LinkedHashSet<AbstractTreeNode>(); |
| for (AbstractTreeNode child : children) { |
| AbstractTreeNode parent = child.getParent(); |
| if (parent != this) { |
| if (!result.contains(parent)) result.add(parent); |
| } |
| else { |
| result.add(child); |
| } |
| } |
| setChildren(result); |
| } |
| |
| private void processUngrouped(@NotNull List<AbstractTreeNode<TreeElement>> ungrouped, @NotNull Grouper grouper) { |
| Map<TreeElement,AbstractTreeNode> ungroupedObjects = collectValues(ungrouped); |
| Collection<Group> groups = grouper.group(this, ungroupedObjects.keySet()); |
| |
| Map<Group, GroupWrapper> groupNodes = createGroupNodes(groups); |
| |
| for (Group group : groups) { |
| if (group == null) { |
| LOG.error(grouper + " returned null group: "+groups); |
| } |
| GroupWrapper groupWrapper = groupNodes.get(group); |
| Collection<TreeElement> children = group.getChildren(); |
| for (TreeElement node : children) { |
| if (node == null) { |
| LOG.error(group + " returned null child: " + children); |
| } |
| CachingChildrenTreeNode child = createChildNode(node); |
| groupWrapper.addSubElement(child); |
| AbstractTreeNode abstractTreeNode = ungroupedObjects.get(node); |
| abstractTreeNode.setParent(groupWrapper); |
| } |
| } |
| } |
| |
| protected TreeElementWrapper createChildNode(@NotNull TreeElement child) { |
| return new TreeElementWrapper(getProject(), child, myTreeModel); |
| } |
| |
| private static Map<TreeElement, AbstractTreeNode> collectValues(List<AbstractTreeNode<TreeElement>> ungrouped) { |
| Map<TreeElement, AbstractTreeNode> objects = new LinkedHashMap<TreeElement, AbstractTreeNode>(); |
| for (final AbstractTreeNode<TreeElement> node : ungrouped) { |
| objects.put(node.getValue(), node); |
| } |
| return objects; |
| } |
| |
| private Map<Group, GroupWrapper> createGroupNodes(@NotNull Collection<Group> groups) { |
| Map<Group, GroupWrapper> result = new THashMap<Group, GroupWrapper>(); |
| for (Group group : groups) { |
| result.put(group, createGroupWrapper(getProject(), group, myTreeModel)); |
| } |
| return result; |
| } |
| |
| protected GroupWrapper createGroupWrapper(final Project project, @NotNull Group group, final TreeModel treeModel) { |
| return new GroupWrapper(project, group, treeModel); |
| } |
| |
| |
| private void rebuildSubtree() { |
| initChildren(); |
| performTreeActions(); |
| |
| synchronizeChildren(); |
| |
| } |
| |
| protected void synchronizeChildren() { |
| if (myOldChildren != null && myChildren != null) { |
| HashMap<Object, CachingChildrenTreeNode> oldValuesToChildrenMap = new HashMap<Object, CachingChildrenTreeNode>(); |
| for (CachingChildrenTreeNode oldChild : myOldChildren) { |
| final Object oldValue = oldChild instanceof TreeElementWrapper ? oldChild.getValue() : oldChild; |
| if (oldValue != null) { |
| oldValuesToChildrenMap.put(oldValue, oldChild); |
| } |
| } |
| |
| for (int i = 0; i < myChildren.size(); i++) { |
| CachingChildrenTreeNode newChild = myChildren.get(i); |
| final Object newValue = newChild instanceof TreeElementWrapper ? newChild.getValue() : newChild; |
| if (newValue != null) { |
| final CachingChildrenTreeNode oldChild = oldValuesToChildrenMap.get(newValue); |
| if (oldChild != null) { |
| oldChild.copyFromNewInstance(newChild); |
| oldChild.setValue(newChild.getValue()); |
| myChildren.set(i, oldChild); |
| } |
| } |
| } |
| |
| myOldChildren = null; |
| } |
| } |
| |
| protected abstract void copyFromNewInstance(@NotNull CachingChildrenTreeNode newInstance); |
| |
| protected abstract void performTreeActions(); |
| |
| protected abstract void initChildren(); |
| |
| @Override |
| public void navigate(final boolean requestFocus) { |
| ((Navigatable)getValue()).navigate(requestFocus); |
| } |
| |
| @Override |
| public boolean canNavigate() { |
| return getValue() instanceof Navigatable && ((Navigatable)getValue()).canNavigate(); |
| } |
| |
| @Override |
| public boolean canNavigateToSource() { |
| return getValue() instanceof Navigatable && ((Navigatable)getValue()).canNavigateToSource(); |
| } |
| |
| protected void clearChildren() { |
| if (myChildren != null) { |
| myChildren.clear(); |
| } else { |
| myChildren = new ArrayList<CachingChildrenTreeNode>(); |
| } |
| } |
| |
| public void rebuildChildren() { |
| if (myChildren != null) { |
| myOldChildren = myChildren; |
| for (final CachingChildrenTreeNode node : myChildren) { |
| node.rebuildChildren(); |
| } |
| myChildren = null; |
| } |
| } |
| |
| protected void resetChildren() { |
| myChildren = null; |
| } |
| } |