/* * Copyright 2000-2016 Vaadin Ltd. * * 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.vaadin.data; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.stream.Stream; /** * Class for representing hierarchical data. * * @author Vaadin Ltd * @since 8.1 * * @param <T> * data type */ public class HierarchyData<T> implements Serializable { private static class HierarchyWrapper<T> implements Serializable { private T item; private T parent; private List<T> children; public HierarchyWrapper(T item, T parent) { this.item = item; this.parent = parent; children = new ArrayList<>(); } public T getItem() { return item; } public void setItem(T item) { this.item = item; } public T getParent() { return parent; } public void setParent(T parent) { this.parent = parent; } public List<T> getChildren() { return children; } public void setChildren(List<T> children) { this.children = children; } public void addChild(T child) { children.add(child); } public void removeChild(T child) { children.remove(child); } } private final Map<T, HierarchyWrapper<T>> itemToWrapperMap; /** * Creates an initially empty hierarchical data representation to which * items can be added or removed. */ public HierarchyData() { itemToWrapperMap = new LinkedHashMap<>(); itemToWrapperMap.put(null, new HierarchyWrapper<>(null, null)); } /** * Adds a data item as a child of {@code parent}. Call with {@code null} as * parent to add a root level item. The given parent item must already exist * in this structure, and an item can only be added to this structure once. * * @param parent * the parent item for which the items are added as children * @param item * the item to add * @return this * * @throws IllegalArgumentException * if parent is not null and not already added to this structure * @throws IllegalArgumentException * if the item has already been added to this structure * @throws NullPointerException * if item is null */ public HierarchyData<T> addItem(T parent, T item) { Objects.requireNonNull(item, "Item cannot be null"); if (parent != null && !contains(parent)) { throw new IllegalArgumentException( "Parent needs to be added before children. " + "To add root items, call with parent as null"); } if (contains(item)) { throw new IllegalArgumentException( "Cannot add the same item multiple times: " + item); } putItem(item, parent); return this; } /** * Adds a list of data items as children of {@code parent}. Call with * {@code null} as parent to add root level items. The given parent item * must already exist in this structure, and an item can only be added to * this structure once. * * @param parent * the parent item for which the items are added as children * @param items * the list of items to add * @return this * * @throws IllegalArgumentException * if parent is not null and not already added to this structure * @throws IllegalArgumentException * if any of the given items have already been added to this * structure * @throws NullPointerException * if any of the items are null */ public HierarchyData<T> addItems(T parent, @SuppressWarnings("unchecked") T... items) { Arrays.asList(items).stream().forEach(item -> addItem(parent, item)); return this; } /** * Adds a list of data items as children of {@code parent}. Call with * {@code null} as parent to add root level items. The given parent item * must already exist in this structure, and an item can only be added to * this structure once. * * @param parent * the parent item for which the items are added as children * @param items * the collection of items to add * @return this * * @throws IllegalArgumentException * if parent is not null and not already added to this structure * @throws IllegalArgumentException * if any of the given items have already been added to this * structure * @throws NullPointerException * if any of the items are null */ public HierarchyData<T> addItems(T parent, Collection<T> items) { items.stream().forEach(item -> addItem(parent, item)); return this; } /** * Adds data items contained in a stream as children of {@code parent}. Call * with {@code null} as parent to add root level items. The given parent * item must already exist in this structure, and an item can only be added * to this structure once. * * @param parent * the parent item for which the items are added as children * @param items * stream of items to add * @return this * * @throws IllegalArgumentException * if parent is not null and not already added to this structure * @throws IllegalArgumentException * if any of the given items have already been added to this * structure * @throws NullPointerException * if any of the items are null */ public HierarchyData<T> addItems(T parent, Stream<T> items) { items.forEach(item -> addItem(parent, item)); return this; } /** * Remove a given item from this structure. Additionally, this will * recursively remove any descendants of the item. * * @param item * the item to remove, or null to clear all data * @return this * * @throws IllegalArgumentException * if the item does not exist in this structure */ public HierarchyData<T> removeItem(T item) { if (!contains(item)) { throw new IllegalArgumentException( "Item '" + item + "' not in the hierarchy"); } new ArrayList<>(getChildren(item)).forEach(child -> removeItem(child)); itemToWrapperMap.get(itemToWrapperMap.get(item).getParent()) .removeChild(item); if (item != null) { // remove non root item from backing map itemToWrapperMap.remove(item); } return this; } /** * Clear all items from this structure. Shorthand for calling * {@link #removeItem(Object)} with null. * * @return this */ public HierarchyData<T> clear() { removeItem(null); return this; } /** * Get the immediate child items for the given item. * * @param item * the item for which to retrieve child items for, null to * retrieve all root items * @return a list of child items for the given item * * @throws IllegalArgumentException * if the item does not exist in this structure */ public List<T> getChildren(T item) { if (!contains(item)) { throw new IllegalArgumentException( "Item '" + item + "' not in the hierarchy"); } return itemToWrapperMap.get(item).getChildren(); } /** * Check whether the given item is in this hierarchy. * * @param item * the item to check * @return {@code true} if the item is in this hierarchy, {@code false} if * not */ public boolean contains(T item) { return itemToWrapperMap.containsKey(item); } private void putItem(T item, T parent) { HierarchyWrapper<T> wrappedItem = new HierarchyWrapper<>(item, parent); if (itemToWrapperMap.containsKey(parent)) { itemToWrapperMap.get(parent).addChild(item); } itemToWrapperMap.put(item, wrappedItem); } }