You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

TreeData.java 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481
  1. /*
  2. * Copyright 2000-2016 Vaadin Ltd.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.vaadin.data;
  17. import java.io.Serializable;
  18. import java.util.ArrayList;
  19. import java.util.Arrays;
  20. import java.util.Collection;
  21. import java.util.Collections;
  22. import java.util.LinkedHashMap;
  23. import java.util.List;
  24. import java.util.Map;
  25. import java.util.Objects;
  26. import java.util.stream.Collectors;
  27. import java.util.stream.Stream;
  28. import com.vaadin.data.provider.TreeDataProvider;
  29. /**
  30. * Class for representing hierarchical data.
  31. * <p>
  32. * Typically used as a backing data source for {@link TreeDataProvider}.
  33. *
  34. * @author Vaadin Ltd
  35. * @since 8.1
  36. *
  37. * @param <T>
  38. * data type
  39. */
  40. public class TreeData<T> implements Serializable {
  41. private static class HierarchyWrapper<T> implements Serializable {
  42. private T item;
  43. private T parent;
  44. private List<T> children;
  45. public HierarchyWrapper(T item, T parent) {
  46. this.item = item;
  47. this.parent = parent;
  48. children = new ArrayList<>();
  49. }
  50. public T getItem() {
  51. return item;
  52. }
  53. public void setItem(T item) {
  54. this.item = item;
  55. }
  56. public T getParent() {
  57. return parent;
  58. }
  59. public void setParent(T parent) {
  60. this.parent = parent;
  61. }
  62. public List<T> getChildren() {
  63. return children;
  64. }
  65. public void setChildren(List<T> children) {
  66. this.children = children;
  67. }
  68. public void addChild(T child) {
  69. children.add(child);
  70. }
  71. public void removeChild(T child) {
  72. children.remove(child);
  73. }
  74. }
  75. private final Map<T, HierarchyWrapper<T>> itemToWrapperMap;
  76. /**
  77. * Creates an initially empty hierarchical data representation to which
  78. * items can be added or removed.
  79. */
  80. public TreeData() {
  81. itemToWrapperMap = new LinkedHashMap<>();
  82. itemToWrapperMap.put(null, new HierarchyWrapper<>(null, null));
  83. }
  84. /**
  85. * Adds the items as root items to this structure.
  86. *
  87. * @param items
  88. * the items to add
  89. * @return this
  90. *
  91. * @throws IllegalArgumentException
  92. * if any of the given items have already been added to this
  93. * structure
  94. * @throws NullPointerException
  95. * if any of the items are {code null}
  96. */
  97. public TreeData<T> addRootItems(T... items) {
  98. addItems(null, items);
  99. return this;
  100. }
  101. /**
  102. * Adds the items of the given collection as root items to this structure.
  103. *
  104. * @param items
  105. * the collection of items to add
  106. * @return this
  107. *
  108. * @throws IllegalArgumentException
  109. * if any of the given items have already been added to this
  110. * structure
  111. * @throws NullPointerException
  112. * if any of the items are {code null}
  113. */
  114. public TreeData<T> addRootItems(Collection<T> items) {
  115. addItems(null, items);
  116. return this;
  117. }
  118. /**
  119. * Adds the items of the given stream as root items to this structure.
  120. *
  121. * @param items
  122. * the stream of root items to add
  123. * @return this
  124. *
  125. * @throws IllegalArgumentException
  126. * if any of the given items have already been added to this
  127. * structure
  128. * @throws NullPointerException
  129. * if any of the items are {code null}
  130. */
  131. public TreeData<T> addRootItems(Stream<T> items) {
  132. addItems(null, items);
  133. return this;
  134. }
  135. /**
  136. * Adds a data item as a child of {@code parent}. Call with {@code null} as
  137. * parent to add a root level item. The given parent item must already exist
  138. * in this structure, and an item can only be added to this structure once.
  139. *
  140. * @param parent
  141. * the parent item for which the items are added as children
  142. * @param item
  143. * the item to add
  144. * @return this
  145. *
  146. * @throws IllegalArgumentException
  147. * if parent is not null and not already added to this structure
  148. * @throws IllegalArgumentException
  149. * if the item has already been added to this structure
  150. * @throws NullPointerException
  151. * if item is null
  152. */
  153. public TreeData<T> addItem(T parent, T item) {
  154. Objects.requireNonNull(item, "Item cannot be null");
  155. if (parent != null && !contains(parent)) {
  156. throw new IllegalArgumentException(
  157. "Parent needs to be added before children. "
  158. + "To add root items, call with parent as null");
  159. }
  160. if (contains(item)) {
  161. throw new IllegalArgumentException(
  162. "Cannot add the same item multiple times: " + item);
  163. }
  164. putItem(item, parent);
  165. return this;
  166. }
  167. /**
  168. * Adds a list of data items as children of {@code parent}. Call with
  169. * {@code null} as parent to add root level items. The given parent item
  170. * must already exist in this structure, and an item can only be added to
  171. * this structure once.
  172. *
  173. * @param parent
  174. * the parent item for which the items are added as children
  175. * @param items
  176. * the list of items to add
  177. * @return this
  178. *
  179. * @throws IllegalArgumentException
  180. * if parent is not null and not already added to this structure
  181. * @throws IllegalArgumentException
  182. * if any of the given items have already been added to this
  183. * structure
  184. * @throws NullPointerException
  185. * if any of the items are null
  186. */
  187. public TreeData<T> addItems(T parent,
  188. @SuppressWarnings("unchecked") T... items) {
  189. Arrays.asList(items).stream().forEach(item -> addItem(parent, item));
  190. return this;
  191. }
  192. /**
  193. * Adds a list of data items as children of {@code parent}. Call with
  194. * {@code null} as parent to add root level items. The given parent item
  195. * must already exist in this structure, and an item can only be added to
  196. * this structure once.
  197. *
  198. * @param parent
  199. * the parent item for which the items are added as children
  200. * @param items
  201. * the collection of items to add
  202. * @return this
  203. *
  204. * @throws IllegalArgumentException
  205. * if parent is not null and not already added to this structure
  206. * @throws IllegalArgumentException
  207. * if any of the given items have already been added to this
  208. * structure
  209. * @throws NullPointerException
  210. * if any of the items are null
  211. */
  212. public TreeData<T> addItems(T parent, Collection<T> items) {
  213. items.stream().forEach(item -> addItem(parent, item));
  214. return this;
  215. }
  216. /**
  217. * Adds data items contained in a stream as children of {@code parent}. Call
  218. * with {@code null} as parent to add root level items. The given parent
  219. * item must already exist in this structure, and an item can only be added
  220. * to this structure once.
  221. *
  222. * @param parent
  223. * the parent item for which the items are added as children
  224. * @param items
  225. * stream of items to add
  226. * @return this
  227. *
  228. * @throws IllegalArgumentException
  229. * if parent is not null and not already added to this structure
  230. * @throws IllegalArgumentException
  231. * if any of the given items have already been added to this
  232. * structure
  233. * @throws NullPointerException
  234. * if any of the items are null
  235. */
  236. public TreeData<T> addItems(T parent, Stream<T> items) {
  237. items.forEach(item -> addItem(parent, item));
  238. return this;
  239. }
  240. /**
  241. * Adds the given items as root items and uses the given value provider to
  242. * recursively populate children of the root items.
  243. *
  244. * @param rootItems
  245. * the root items to add
  246. * @param childItemProvider
  247. * the value provider used to recursively populate this TreeData
  248. * from the given root items
  249. * @return this
  250. */
  251. public TreeData<T> addItems(Collection<T> rootItems,
  252. ValueProvider<T, Collection<T>> childItemProvider) {
  253. rootItems.forEach(item -> {
  254. addItem(null, item);
  255. Collection<T> childItems = childItemProvider.apply(item);
  256. addItems(item, childItems);
  257. addItemsRecursively(childItems, childItemProvider);
  258. });
  259. return this;
  260. }
  261. /**
  262. * Adds the given items as root items and uses the given value provider to
  263. * recursively populate children of the root items.
  264. *
  265. * @param rootItems
  266. * the root items to add
  267. * @param childItemProvider
  268. * the value provider used to recursively populate this TreeData
  269. * from the given root items
  270. * @return this
  271. */
  272. public TreeData<T> addItems(Stream<T> rootItems,
  273. ValueProvider<T, Stream<T>> childItemProvider) {
  274. // Must collect to lists since the algorithm iterates multiple times
  275. return addItems(rootItems.collect(Collectors.toList()),
  276. item -> childItemProvider.apply(item)
  277. .collect(Collectors.toList()));
  278. }
  279. /**
  280. * Remove a given item from this structure. Additionally, this will
  281. * recursively remove any descendants of the item.
  282. *
  283. * @param item
  284. * the item to remove, or null to clear all data
  285. * @return this
  286. *
  287. * @throws IllegalArgumentException
  288. * if the item does not exist in this structure
  289. */
  290. public TreeData<T> removeItem(T item) {
  291. if (!contains(item)) {
  292. throw new IllegalArgumentException(
  293. "Item '" + item + "' not in the hierarchy");
  294. }
  295. new ArrayList<>(getChildren(item)).forEach(child -> removeItem(child));
  296. itemToWrapperMap.get(itemToWrapperMap.get(item).getParent())
  297. .removeChild(item);
  298. if (item != null) {
  299. // remove non root item from backing map
  300. itemToWrapperMap.remove(item);
  301. }
  302. return this;
  303. }
  304. /**
  305. * Clear all items from this structure. Shorthand for calling
  306. * {@link #removeItem(Object)} with null.
  307. *
  308. * @return this
  309. */
  310. public TreeData<T> clear() {
  311. removeItem(null);
  312. return this;
  313. }
  314. /**
  315. * Gets the root items of this structure.
  316. *
  317. * @return an unmodifiable list of root items of this structure
  318. */
  319. public List<T> getRootItems() {
  320. return getChildren(null);
  321. }
  322. /**
  323. * Get the immediate child items for the given item.
  324. *
  325. * @param item
  326. * the item for which to retrieve child items for, null to
  327. * retrieve all root items
  328. * @return an unmodifiable list of child items for the given item
  329. *
  330. * @throws IllegalArgumentException
  331. * if the item does not exist in this structure
  332. */
  333. public List<T> getChildren(T item) {
  334. if (!contains(item)) {
  335. throw new IllegalArgumentException(
  336. "Item '" + item + "' not in the hierarchy");
  337. }
  338. return Collections
  339. .unmodifiableList(itemToWrapperMap.get(item).getChildren());
  340. }
  341. /**
  342. * Moves an item to become a child of the given parent item. The new parent
  343. * item must exist in the hierarchy. Setting the parent to {@code null}
  344. * makes the item a root item. After making changes to the tree data, {@link
  345. * TreeDataProvider#refreshAll()} should be called.
  346. *
  347. * @param item
  348. * the item to be set as the child of {@code parent}
  349. * @param parent
  350. * the item to be set as parent or {@code null} to set the item as
  351. * root
  352. * @since 8.1
  353. */
  354. public void setParent(T item, T parent) {
  355. if (!contains(item)) {
  356. throw new IllegalArgumentException(
  357. "Item '" + item + "' not in the hierarchy");
  358. }
  359. if (parent != null && !contains(parent)) {
  360. throw new IllegalArgumentException(
  361. "Parent needs to be added before children. "
  362. + "To set as root item, call with parent as null");
  363. }
  364. if (item.equals(parent)) {
  365. throw new IllegalArgumentException(
  366. "Item cannot be the parent of itself");
  367. }
  368. T oldParent = itemToWrapperMap.get(item).getParent();
  369. if (!Objects.equals(oldParent, parent)) {
  370. // Remove item from old parent's children
  371. itemToWrapperMap.get(oldParent).removeChild(item);
  372. // Add item to parent's children
  373. itemToWrapperMap.get(parent).addChild(item);
  374. // Set item's new parent
  375. itemToWrapperMap.get(item).setParent(parent);
  376. }
  377. }
  378. /**
  379. * Moves an item to the position immediately after a sibling item. The two
  380. * items must have the same parent. After making changes to the tree data,
  381. * {@link TreeDataProvider#refreshAll()} should be called.
  382. *
  383. * @param item
  384. * the item to be moved
  385. * @param sibling
  386. * the item after which the moved item will be located
  387. * @since 8.1
  388. */
  389. public void moveAfterSibling(T item, T sibling) {
  390. if (!contains(item)) {
  391. throw new IllegalArgumentException(
  392. "Item '" + item + "' not in the hierarchy");
  393. }
  394. if (!contains(sibling)) {
  395. throw new IllegalArgumentException(
  396. "Item '" + sibling + "' not in the hierarchy");
  397. }
  398. T parent = itemToWrapperMap.get(item).getParent();
  399. if (!Objects
  400. .equals(parent, itemToWrapperMap.get(sibling).getParent())) {
  401. throw new IllegalArgumentException(
  402. "Items '" + item + "' and '" + sibling
  403. + "' don't have the same parent");
  404. }
  405. List<T> children = itemToWrapperMap.get(parent).getChildren();
  406. // Move item to the position after the sibling
  407. // If sibling is null, item is moved to the first position
  408. children.remove(item);
  409. children.add(children.indexOf(sibling) + 1, item);
  410. }
  411. /**
  412. * Check whether the given item is in this hierarchy.
  413. *
  414. * @param item
  415. * the item to check
  416. * @return {@code true} if the item is in this hierarchy, {@code false} if
  417. * not
  418. */
  419. public boolean contains(T item) {
  420. return itemToWrapperMap.containsKey(item);
  421. }
  422. private void putItem(T item, T parent) {
  423. HierarchyWrapper<T> wrappedItem = new HierarchyWrapper<>(item, parent);
  424. if (itemToWrapperMap.containsKey(parent)) {
  425. itemToWrapperMap.get(parent).addChild(item);
  426. }
  427. itemToWrapperMap.put(item, wrappedItem);
  428. }
  429. private void addItemsRecursively(Collection<T> items,
  430. ValueProvider<T, Collection<T>> childItemProvider) {
  431. items.forEach(item -> {
  432. Collection<T> childItems = childItemProvider.apply(item);
  433. addItems(item, childItems);
  434. addItemsRecursively(childItems, childItemProvider);
  435. });
  436. }
  437. }