/******************************************************************************* * Copyright (c) 2012-2017 Codenvy, S.A. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ package org.eclipse.che.ide.ui.smartTree; import com.google.common.base.Predicate; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.gwt.event.shared.GwtEvent; import com.google.gwt.event.shared.HandlerManager; import com.google.gwt.event.shared.HandlerRegistration; import org.eclipse.che.ide.api.data.tree.Node; import org.eclipse.che.ide.ui.smartTree.handler.GroupingHandlerRegistration; import org.eclipse.che.ide.ui.smartTree.event.StoreAddEvent; import org.eclipse.che.ide.ui.smartTree.event.StoreClearEvent; import org.eclipse.che.ide.ui.smartTree.event.StoreDataChangeEvent; import org.eclipse.che.ide.ui.smartTree.event.StoreHandlers; import org.eclipse.che.ide.ui.smartTree.event.StoreRecordChangeEvent; import org.eclipse.che.ide.ui.smartTree.event.StoreRemoveEvent; import org.eclipse.che.ide.ui.smartTree.event.StoreSortEvent; import org.eclipse.che.ide.ui.smartTree.event.StoreUpdateEvent; import javax.validation.constraints.NotNull; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; /** * Hierarchical type storage. Based on Parent-Child relationship, * which is uses internally by tree. To obtain parent or children * uses specified methods such as {@link #getParent(Node)} or * {@link #getChildren(Node)}. Modifications performs by calling * such methods like {@link #add(Node)} or {@link #remove(Node)}, * etc. * * @author Vlad Zhukovskiy */ public class NodeStorage implements StoreHandlers.HasStoreHandlers { private final NodeDescriptor roots = new NodeDescriptor(this, null); private final Map<String, NodeDescriptor> idToNodeMap = new HashMap<>(); private HandlerManager handlerManager; private UniqueKeyProvider<Node> keyProvider; private List<StoreSortInfo> comparators = new ArrayList<>(); public NodeStorage() { this(new NodeUniqueKeyProvider() { @Override public String getKey(@NotNull Node item) { return String.valueOf(item.hashCode()); } }); } public NodeStorage(UniqueKeyProvider<Node> keyProvider) { this.keyProvider = keyProvider; } protected boolean isSorted() { return comparators.size() != 0; } /** * Adds the data models as roots of the tree. * * @param nodes */ public void add(List<Node> nodes) { if (nodes.size() == 1) { insert(roots, roots.getChildren().size(), nodes.get(0)); } else { insert(roots, roots.getChildren().size(), nodes); } } /** * Adds the given data model as a root of the tree. * * @param node */ public void add(Node node) { insert(roots, roots.getChildren().size(), node); } /** * Adds the list of children to the end of the visible children of the given * parent model * * @param parent * @param children */ public void add(Node parent, List<Node> children) { NodeDescriptor nodeDescriptor = getWrapper(parent); if (children.size() == 1) { insert(nodeDescriptor, nodeDescriptor.getChildren().size(), children.get(0)); } else { insert(nodeDescriptor, nodeDescriptor.getChildren().size(), children); } } /** * Adds the child to the end of the visible children of the parent model * * @param parent * @param child */ public void add(Node parent, Node child) { NodeDescriptor nodeDescriptor = getWrapper(parent); insert(nodeDescriptor, nodeDescriptor.getChildren().size(), child); } /** * Imports a list of subtrees at the given position in the root of the tree. * * @param index * @param children */ public void addSubTree(int index, List<Node> children) { List<NodeDescriptor> nodeDescriptors = convertTreeNodesHelper(children); roots.addChildren(index, nodeDescriptors); List<Node> nodes = new ArrayList<>(); for (NodeDescriptor child : nodeDescriptors) { nodes.add(child.getNode()); } if (!nodes.isEmpty()) { fireEvent(new StoreAddEvent(index, nodes)); } } /** * Imports a list of subtrees to append to the given parent object already * present in the tree. * * @param parent * @param index * @param children */ public void addSubTree(Node parent, int index, List<Node> children) { List<NodeDescriptor> nodeDescriptors = convertTreeNodesHelper(children); getWrapper(parent).addChildren(index, nodeDescriptors); List<Node> nodes = new ArrayList<>(); for (NodeDescriptor child : nodeDescriptors) { nodes.add(child.getNode()); } if (!nodes.isEmpty()) { fireEvent(new StoreAddEvent(index, nodes)); } } public void clear() { idToNodeMap.clear(); roots.clear(); fireEvent(new StoreClearEvent()); } /** * Gets all visible items in the tree * * @return */ public List<Node> getAll() { List<NodeDescriptor> allChildren = new LinkedList<>(roots.getChildren()); for (int i = 0; i < allChildren.size(); i++) { allChildren.addAll(allChildren.get(i).getChildren()); } return unwrap(allChildren); } /** * Recursively builds a list of all of the visible elements below the given * one in the tree * * @param parent * @return */ public List<Node> getAllChildren(Node parent) { List<NodeDescriptor> allChildren = new LinkedList<>(getWrapper(parent).getChildren()); for (int i = 0; i < allChildren.size(); i++) { allChildren.addAll(allChildren.get(i).getChildren()); } return unwrap(allChildren); } /** * Gets the total count of all visible items in the tree * * @return */ public int getAllItemsCount() { List<NodeDescriptor> allChildren = new LinkedList<>(roots.getChildren()); for (int i = 0; i < allChildren.size(); i++) { allChildren.addAll(allChildren.get(i).getChildren()); } return allChildren.size(); } /** * Returns the root level child. * * @param index * @return */ public Node getChild(int index) { return getRootItems().get(index); } /** * Gets the number of visible children in the given node * * @param parent * @return */ public int getChildCount(Node parent) { return getWrapper(parent).getChildren().size(); } /** * Gets the list of visible children attached to the given element * * @param parent * @return */ public List<Node> getChildren(Node parent) { return unwrap(getWrapper(parent).getChildren()); } /** * Gets the depth of the given element in the tree, where 1 indicates that it * is a root element of the tree. * * @param child * @return */ public int getDepth(Node child) { int depth = 0; while (child != null) { depth++; child = getParent(child); } return depth; } /** * Returns the fist child of the parent. * * @param parent * @return */ public Node getFirstChild(Node parent) { NodeDescriptor nodeDescriptor = parent == null ? roots : getWrapper(parent); if (nodeDescriptor.getChildren().size() != 0) { return nodeDescriptor.getChildren().get(0).getNode(); } return null; } /** * Returns the last child of the parent. * * @param parent * @return */ public Node getLastChild(Node parent) { NodeDescriptor nodeDescriptor = parent == null ? roots : getWrapper(parent); List<NodeDescriptor> children = nodeDescriptor.getChildren(); if (children.size() != 0) { return children.get(children.size() - 1).getNode(); } return null; } /** * Returns the next sibling of the model. * * @param item * @return */ public Node getNextSibling(Node item) { Node parent = getParent(item); List<Node> children = parent == null ? getRootItems() : getChildren(parent); int index = children.indexOf(item); if (children.size() > (index + 1)) { return children.get(index + 1); } return null; } /** * Returns the item's previous sibling. * * @param item * @return */ public Node getPreviousSibling(Node item) { Node parent = getParent(item); List<Node> children = parent == null ? getRootItems() : getChildren(parent); int index = children.indexOf(item); if (index > 0) { return children.get(index - 1); } return null; } /** * Gets the number of items at the root of the tree, that is, the number of * visible items that have been added without specifying a parent. * * @return */ public int getRootCount() { return roots.getChildren().size(); } /** * Gets the visible items at the root of the tree. * * @return */ public List<Node> getRootItems() { return unwrap(roots.getChildren()); } /** * Returns true if the given node has visible children. * * @param item * @return */ public boolean hasChildren(Node item) { return getChildCount(item) != 0; } /** * Returns the item's index in it's parent including root level items. * * @param item * @return */ public int indexOf(Node item) { Node parent = getParent(item); if (parent == null) { return getRootItems().indexOf(item); } else { return getChildren(parent).indexOf(item); } } /** * Inserts the given models at the given index in the list of root nodes * * @param index * @param rootNodes */ public void insert(int index, List<Node> rootNodes) { insert(roots, index, rootNodes); } /** * Inserts the given model at the given index in the list of root nodes * * @param index * @param root */ public void insert(int index, Node root) { insert(roots, index, root); } public void insert(Node node, int index, List<Node> children) { insert(getWrapper(node), index, children); } public void insert(Node parent, int index, Node child) { insert(getWrapper(parent), index, child); } /** * Inserts the child models at the given position in the parent's list of * visible children. * * @param parent * @param index * @param children */ public void insert(NodeDescriptor parent, int index, List<Node> children) { int initialCount = parent.getChildren().size(); parent.addChildren(index, wrap(children)); if (initialCount != parent.getChildren().size()) { List<Node> addedChildren = new ArrayList<>(); List<NodeDescriptor> currentChildren = parent.getChildren(); if (isSorted()) { int currentChildrenSize = currentChildren.size(); for (int i = 0; i < currentChildrenSize; i++) { int childrenSize = children.size(); for (int j = 0; j < childrenSize; j++) { Node currentData = currentChildren.get(i).getNode(); Node child = children.get(j); if (child == currentData) { addedChildren.add(child); break; } } } } else { for (NodeDescriptor currentChild : currentChildren) { if (children.contains(currentChild.getNode())) { addedChildren.add(currentChild.getNode()); } } } if (addedChildren.size() != 0) { fireEvent(new StoreAddEvent(index, addedChildren)); } } } /** * Inserts the child model at the given position in the parent's list of * visible children * * @param parent * @param index * @param child */ public void insert(NodeDescriptor parent, int index, Node child) { int initialCount = parent.getChildren().size(); parent.addChild(index, wrap(child)); if (parent.getChildren().size() != initialCount) { int addedIndex = -1; if (isSorted()) { List<NodeDescriptor> childrenModels = parent.getChildren(); for (int i = 0; i < childrenModels.size(); i++) { if (childrenModels.get(i).getNode().equals(child)) { addedIndex = i; break; } } } else { addedIndex = index; } // if the change actually occurred, fire an event fireEvent(new StoreAddEvent(addedIndex, child)); } } public boolean remove(Node node) { NodeDescriptor nodeDescriptor = idToNodeMap.get(getKeyProvider().getKey(node)); if (nodeDescriptor != null) { Node parent = getParent(node); List<Node> children = getAllChildren(node); int visibleIndex = nodeDescriptor.getParent().getChildren().indexOf(nodeDescriptor); nodeDescriptor.getParent().remove(nodeDescriptor); if (visibleIndex != -1) { fireEvent(new StoreRemoveEvent(visibleIndex, node, parent, children)); } else { List<NodeDescriptor> descriptors = new LinkedList<>(); descriptors.add(nodeDescriptor); for (int i = 0; i < descriptors.size(); i++) { nodeDescriptor = descriptors.get(i); descriptors.addAll(nodeDescriptor.getChildren()); idToNodeMap.remove(getKeyProvider().getKey(nodeDescriptor.getNode())); } } return true; } return false; } public void removeChildren(Node parent) { removeChildren(getWrapper(parent)); } private void removeChildren(NodeDescriptor parent) { if (parent.getChildren().size() != 0) { List<NodeDescriptor> models = new LinkedList<>(); models.addAll(parent.getChildren()); parent.clear(); for (int i = 0; i < models.size(); i++) { NodeDescriptor wrapper = models.get(i); models.addAll(wrapper.getChildren()); List<Node> children = getAllChildren(wrapper.getNode()); idToNodeMap.remove(getKeyProvider().getKey(wrapper.getNode())); if (wrapper.getParent() == parent) { fireEvent(new StoreRemoveEvent(0, wrapper.getNode(), parent.getNode(), children)); } } } } public void replaceChildren(Node parent, List<Node> children) { if (parent == null) { roots.clear(); idToNodeMap.clear(); roots.addChildren(0, wrap(children)); } else { NodeDescriptor parentNodeDescriptor = getWrapper(parent); List<NodeDescriptor> models = new LinkedList<>(); models.addAll(parentNodeDescriptor.getChildren()); for (int i = 0; i < models.size(); i++) { NodeDescriptor wrapper = models.get(i); models.addAll(wrapper.getChildren()); idToNodeMap.remove(getKeyProvider().getKey(wrapper.getNode())); remove(wrapper.getNode()); } parentNodeDescriptor.clear(); parentNodeDescriptor.addChildren(0, wrap(children)); } fireEvent(new StoreDataChangeEvent(parent)); } private List<NodeDescriptor> findRemovedNodes(NodeDescriptor parent, final List<Node> loadedChildren) { List<NodeDescriptor> existed = parent.getChildren(); if (existed == null || existed.isEmpty()) { return Collections.emptyList(); } Iterable<NodeDescriptor> removedItems = Iterables.filter(existed, new Predicate<NodeDescriptor>() { @Override public boolean apply(NodeDescriptor existedChild) { return !loadedChildren.contains(existedChild.getNode()); } }); return Lists.newArrayList(removedItems); } private List<Node> findNewNodes(NodeDescriptor parent, final List<Node> loadedChildren) { final List<NodeDescriptor> existed = parent.getChildren(); if (existed == null || existed.isEmpty()) { return loadedChildren; } Iterable<Node> newItems = Iterables.filter(loadedChildren, new Predicate<Node>() { @Override public boolean apply(Node loadedChild) { for (NodeDescriptor nodeDescriptor : existed) { if (nodeDescriptor.getNode().equals(loadedChild)) { return false; } } return true; } }); return Lists.newArrayList(newItems); } private List<NodeDescriptor> convertTreeNodesHelper(List<Node> children) { List<NodeDescriptor> nodeDescriptors = new ArrayList<>(); if (children != null) { for (Node child : children) { NodeDescriptor nodeDescriptor = new NodeDescriptor(this, child); idToNodeMap.put(keyProvider.getKey(child), nodeDescriptor); nodeDescriptors.add(nodeDescriptor); } } return nodeDescriptors; } protected Map<String, NodeDescriptor> getNodeMap() { return idToNodeMap; } public Collection<NodeDescriptor> getStoredNodes() { return idToNodeMap.values(); } public Node getParent(Node child) { final NodeDescriptor wrapper = getWrapper(child); if (wrapper == null) { return null; } NodeDescriptor nodeDescriptor = wrapper.getParent(); return (nodeDescriptor != null && !nodeDescriptor.isRoot()) ? nodeDescriptor.getNode() : null; } public void fireEvent(GwtEvent<?> event) { if (handlerManager != null) { handlerManager.fireEvent(event); } } protected HandlerManager ensureHandlers() { if (handlerManager == null) { handlerManager = new HandlerManager(this); } return handlerManager; } public UniqueKeyProvider<Node> getKeyProvider() { return keyProvider; } public boolean hasMatchingKey(Node model1, Node model2) { return keyProvider.getKey(model1).equals(keyProvider.getKey(model2)); } public Node findNode(Node node) { return findNodeWithKey(getKeyProvider().getKey(node)); } public Node findNodeWithKey(String key) { NodeDescriptor nodeDescriptor = idToNodeMap.get(key); if (nodeDescriptor == null) { return null; } return nodeDescriptor.getNode(); } @Override public HandlerRegistration addStoreHandlers(StoreHandlers handlers) { GroupingHandlerRegistration reg = new GroupingHandlerRegistration(); reg.add(addStoreAddHandler(handlers)); reg.add(addStoreRemoveHandler(handlers)); reg.add(addStoreClearHandler(handlers)); reg.add(addStoreDataChangeHandler(handlers)); reg.add(addStoreUpdateHandler(handlers)); reg.add(addStoreRecordChangeHandler(handlers)); reg.add(addStoreSortHandler(handlers)); return reg; } @Override public HandlerRegistration addStoreAddHandler(StoreAddEvent.StoreAddHandler handler) { return ensureHandlers().addHandler(StoreAddEvent.getType(), handler); } @Override public HandlerRegistration addStoreClearHandler(StoreClearEvent.StoreClearHandler handler) { return ensureHandlers().addHandler(StoreClearEvent.getType(), handler); } @Override public HandlerRegistration addStoreDataChangeHandler(StoreDataChangeEvent.StoreDataChangeHandler handler) { return ensureHandlers().addHandler(StoreDataChangeEvent.getType(), handler); } @Override public HandlerRegistration addStoreRecordChangeHandler(StoreRecordChangeEvent.StoreRecordChangeHandler handler) { return ensureHandlers().addHandler(StoreRecordChangeEvent.getType(), handler); } @Override public HandlerRegistration addStoreRemoveHandler(StoreRemoveEvent.StoreRemoveHandler handler) { return ensureHandlers().addHandler(StoreRemoveEvent.getType(), handler); } @Override public HandlerRegistration addStoreSortHandler(StoreSortEvent.StoreSortHandler handler) { return ensureHandlers().addHandler(StoreSortEvent.getType(), handler); } @Override public HandlerRegistration addStoreUpdateHandler(StoreUpdateEvent.StoreUpdateHandler handler) { return ensureHandlers().addHandler(StoreUpdateEvent.getType(), handler); } public NodeDescriptor getWrapper(Node node) { return idToNodeMap.get(getKeyProvider().getKey(node)); } public NodeDescriptor wrap(Node node) { NodeDescriptor nodeDescriptor = new NodeDescriptor(this, node); idToNodeMap.put(getKeyProvider().getKey(node), nodeDescriptor); return nodeDescriptor; } protected List<NodeDescriptor> wrap(List<Node> nodes) { List<NodeDescriptor> nodeDescriptors = new ArrayList<>(); for (Node node : nodes) { nodeDescriptors.add(wrap(node)); } return nodeDescriptors; } protected List<Node> unwrap(List<NodeDescriptor> nodeDescriptors) { List<Node> nodes = new ArrayList<>(); for (NodeDescriptor nodeDescriptor : nodeDescriptors) { nodes.add(nodeDescriptor.getNode()); } return Collections.unmodifiableList(nodes); } public void update(Node node) { fireEvent(new StoreUpdateEvent(Collections.singletonList(node))); } public List<StoreSortInfo> getSortInfo() { return comparators; } public void clearSortInfo() { comparators.clear(); } public void addSortInfo(int index, StoreSortInfo info) { comparators.add(index, info); applySort(false); } public void addSortInfo(StoreSortInfo info) { comparators.add(info); applySort(false); } protected Comparator<NodeDescriptor> buildFullComparator() { return new Comparator<NodeDescriptor>() { public int compare(NodeDescriptor o1, NodeDescriptor o2) { for (StoreSortInfo comparator : comparators) { int val = comparator.compare(o1.getNode(), o2.getNode()); if (val != 0) { return val; } } return 0; } }; } public void applySort(boolean suppressEvent) { Comparator<NodeDescriptor> comparator = buildFullComparator(); Collections.sort(roots.getChildren(), comparator); for (NodeDescriptor descriptor : idToNodeMap.values()) { Collections.sort(descriptor.getChildren(), comparator); } if (!suppressEvent) { fireEvent(new StoreSortEvent()); } } public static class StoreSortInfo implements Comparator<Node> { private SortDir direction; private final Comparator<Node> comparator; /** * Creates a sort info object based on the given comparator to act on the * item itself. Complex comparators can easily be built in this way, instead * of adding multiple StoreSortInfo objects, or using one of the other * constructors. * * @param itemComparator * the comparator to use to sort the items * @param direction * the sort direction */ public StoreSortInfo(Comparator<Node> itemComparator, SortDir direction) { this.comparator = itemComparator; this.direction = direction; } public Comparator<Node> getComparator() { return comparator; } @Override public int compare(Node o1, Node o2) { int val = comparator.compare(o1, o2); return direction == SortDir.ASC ? val : -val; } public SortDir getDirection() { return direction; } public void setDirection(SortDir direction) { this.direction = direction; } } public boolean reIndexNode(String oldId, Node node) { if (!idToNodeMap.containsKey(oldId)) { return false; } NodeDescriptor descriptor = idToNodeMap.remove(oldId); idToNodeMap.put(getKeyProvider().getKey(node), descriptor); return true; } }