blob: f08fb99cf4bf64458e727991fd129c4838b7f3ab [file] [log] [blame]
/*
* 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;
}
}