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.

ContainerHierarchicalWrapper.java 24KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722
  1. /*
  2. @ITMillApache2LicenseForJavaFiles@
  3. */
  4. package com.vaadin.data.util;
  5. import java.util.Arrays;
  6. import java.util.Collection;
  7. import java.util.Collections;
  8. import java.util.Comparator;
  9. import java.util.HashSet;
  10. import java.util.Hashtable;
  11. import java.util.Iterator;
  12. import java.util.LinkedHashSet;
  13. import java.util.LinkedList;
  14. import com.vaadin.data.Container;
  15. import com.vaadin.data.Item;
  16. import com.vaadin.data.Property;
  17. /**
  18. * <p>
  19. * A wrapper class for adding external hierarchy to containers not implementing
  20. * the {@link com.vaadin.data.Container.Hierarchical} interface.
  21. * </p>
  22. *
  23. * <p>
  24. * If the wrapped container is changed directly (that is, not through the
  25. * wrapper), and does not implement Container.ItemSetChangeNotifier and/or
  26. * Container.PropertySetChangeNotifier the hierarchy information must be updated
  27. * with the {@link #updateHierarchicalWrapper()} method.
  28. * </p>
  29. *
  30. * @author IT Mill Ltd.
  31. * @version
  32. * @VERSION@
  33. * @since 3.0
  34. */
  35. @SuppressWarnings("serial")
  36. public class ContainerHierarchicalWrapper implements Container.Hierarchical,
  37. Container.ItemSetChangeNotifier, Container.PropertySetChangeNotifier {
  38. /** The wrapped container */
  39. private final Container container;
  40. /** Set of IDs of those contained Items that can't have children. */
  41. private HashSet<Object> noChildrenAllowed = null;
  42. /** Mapping from Item ID to parent Item ID */
  43. private Hashtable<Object, Object> parent = null;
  44. /** Mapping from Item ID to a list of child IDs */
  45. private Hashtable<Object, LinkedList<Object>> children = null;
  46. /** List that contains all root elements of the container. */
  47. private LinkedHashSet<Object> roots = null;
  48. /** Is the wrapped container hierarchical by itself ? */
  49. private boolean hierarchical;
  50. /**
  51. * Constructs a new hierarchical wrapper for an existing Container. Works
  52. * even if the to-be-wrapped container already implements the
  53. * <code>Container.Hierarchical</code> interface.
  54. *
  55. * @param toBeWrapped
  56. * the container that needs to be accessed hierarchically
  57. * @see #updateHierarchicalWrapper()
  58. */
  59. public ContainerHierarchicalWrapper(Container toBeWrapped) {
  60. container = toBeWrapped;
  61. hierarchical = container instanceof Container.Hierarchical;
  62. // Check arguments
  63. if (container == null) {
  64. throw new NullPointerException("Null can not be wrapped");
  65. }
  66. // Create initial order if needed
  67. if (!hierarchical) {
  68. noChildrenAllowed = new HashSet<Object>();
  69. parent = new Hashtable<Object, Object>();
  70. children = new Hashtable<Object, LinkedList<Object>>();
  71. roots = new LinkedHashSet<Object>(container.getItemIds());
  72. }
  73. updateHierarchicalWrapper();
  74. }
  75. /**
  76. * Updates the wrapper's internal hierarchy data to include all Items in the
  77. * underlying container. If the contents of the wrapped container change
  78. * without the wrapper's knowledge, this method needs to be called to update
  79. * the hierarchy information of the Items.
  80. */
  81. public void updateHierarchicalWrapper() {
  82. if (!hierarchical) {
  83. // Recreate hierarchy and data structures if missing
  84. if (noChildrenAllowed == null || parent == null || children == null
  85. || roots == null) {
  86. noChildrenAllowed = new HashSet<Object>();
  87. parent = new Hashtable<Object, Object>();
  88. children = new Hashtable<Object, LinkedList<Object>>();
  89. roots = new LinkedHashSet<Object>(container.getItemIds());
  90. }
  91. // Check that the hierarchy is up-to-date
  92. else {
  93. // ensure order of root and child lists is same as in wrapped
  94. // container
  95. final Collection<?> itemIds = container.getItemIds();
  96. Comparator<Object> basedOnOrderFromWrappedContainer = new Comparator<Object>() {
  97. public int compare(Object o1, Object o2) {
  98. if (o1.equals(o2)) {
  99. return 0;
  100. }
  101. for (Object id : itemIds) {
  102. if (id == o1) {
  103. return -1;
  104. } else if (id == o2) {
  105. return 1;
  106. }
  107. }
  108. return 0;
  109. }
  110. };
  111. // Calculate the set of all items in the hierarchy
  112. final HashSet<Object> s = new HashSet<Object>();
  113. s.addAll(parent.keySet());
  114. s.addAll(children.keySet());
  115. s.addAll(roots);
  116. // Remove unnecessary items
  117. for (final Iterator<Object> i = s.iterator(); i.hasNext();) {
  118. final Object id = i.next();
  119. if (!container.containsId(id)) {
  120. removeFromHierarchyWrapper(id);
  121. }
  122. }
  123. // Add all the missing items
  124. final Collection<?> ids = container.getItemIds();
  125. for (final Iterator<?> i = ids.iterator(); i.hasNext();) {
  126. final Object id = i.next();
  127. if (!s.contains(id)) {
  128. addToHierarchyWrapper(id);
  129. s.add(id);
  130. }
  131. }
  132. Object[] array = roots.toArray();
  133. Arrays.sort(array, basedOnOrderFromWrappedContainer);
  134. roots = new LinkedHashSet<Object>();
  135. for (int i = 0; i < array.length; i++) {
  136. roots.add(array[i]);
  137. }
  138. for (Object object : children.keySet()) {
  139. LinkedList<Object> object2 = children.get(object);
  140. Collections.sort(object2, basedOnOrderFromWrappedContainer);
  141. }
  142. }
  143. }
  144. }
  145. /**
  146. * Removes the specified Item from the wrapper's internal hierarchy
  147. * structure.
  148. * <p>
  149. * Note : The Item is not removed from the underlying Container.
  150. * </p>
  151. *
  152. * @param itemId
  153. * the ID of the item to remove from the hierarchy.
  154. */
  155. private void removeFromHierarchyWrapper(Object itemId) {
  156. if (isRoot(itemId)) {
  157. roots.remove(itemId);
  158. }
  159. final Object p = parent.get(itemId);
  160. if (p != null) {
  161. final LinkedList<Object> c = children.get(p);
  162. if (c != null) {
  163. c.remove(itemId);
  164. }
  165. }
  166. parent.remove(itemId);
  167. children.remove(itemId);
  168. noChildrenAllowed.remove(itemId);
  169. }
  170. /**
  171. * Adds the specified Item specified to the internal hierarchy structure.
  172. * The new item is added as a root Item. The underlying container is not
  173. * modified.
  174. *
  175. * @param itemId
  176. * the ID of the item to add to the hierarchy.
  177. */
  178. private void addToHierarchyWrapper(Object itemId) {
  179. roots.add(itemId);
  180. }
  181. /*
  182. * Can the specified Item have any children? Don't add a JavaDoc comment
  183. * here, we use the default documentation from implemented interface.
  184. */
  185. public boolean areChildrenAllowed(Object itemId) {
  186. // If the wrapped container implements the method directly, use it
  187. if (hierarchical) {
  188. return ((Container.Hierarchical) container)
  189. .areChildrenAllowed(itemId);
  190. }
  191. return !noChildrenAllowed.contains(itemId);
  192. }
  193. /*
  194. * Gets the IDs of the children of the specified Item. Don't add a JavaDoc
  195. * comment here, we use the default documentation from implemented
  196. * interface.
  197. */
  198. public Collection getChildren(Object itemId) {
  199. // If the wrapped container implements the method directly, use it
  200. if (hierarchical) {
  201. return ((Container.Hierarchical) container).getChildren(itemId);
  202. }
  203. final Collection c = children.get(itemId);
  204. if (c == null) {
  205. return null;
  206. }
  207. return Collections.unmodifiableCollection(c);
  208. }
  209. /*
  210. * Gets the ID of the parent of the specified Item. Don't add a JavaDoc
  211. * comment here, we use the default documentation from implemented
  212. * interface.
  213. */
  214. public Object getParent(Object itemId) {
  215. // If the wrapped container implements the method directly, use it
  216. if (hierarchical) {
  217. return ((Container.Hierarchical) container).getParent(itemId);
  218. }
  219. return parent.get(itemId);
  220. }
  221. /*
  222. * Is the Item corresponding to the given ID a leaf node? Don't add a
  223. * JavaDoc comment here, we use the default documentation from implemented
  224. * interface.
  225. */
  226. public boolean hasChildren(Object itemId) {
  227. // If the wrapped container implements the method directly, use it
  228. if (hierarchical) {
  229. return ((Container.Hierarchical) container).hasChildren(itemId);
  230. }
  231. return children.get(itemId) != null;
  232. }
  233. /*
  234. * Is the Item corresponding to the given ID a root node? Don't add a
  235. * JavaDoc comment here, we use the default documentation from implemented
  236. * interface.
  237. */
  238. public boolean isRoot(Object itemId) {
  239. // If the wrapped container implements the method directly, use it
  240. if (hierarchical) {
  241. return ((Container.Hierarchical) container).isRoot(itemId);
  242. }
  243. return parent.get(itemId) == null;
  244. }
  245. /*
  246. * Gets the IDs of the root elements in the container. Don't add a JavaDoc
  247. * comment here, we use the default documentation from implemented
  248. * interface.
  249. */
  250. public Collection rootItemIds() {
  251. // If the wrapped container implements the method directly, use it
  252. if (hierarchical) {
  253. return ((Container.Hierarchical) container).rootItemIds();
  254. }
  255. return Collections.unmodifiableCollection(roots);
  256. }
  257. /**
  258. * <p>
  259. * Sets the given Item's capability to have children. If the Item identified
  260. * with the itemId already has children and the areChildrenAllowed is false
  261. * this method fails and <code>false</code> is returned; the children must
  262. * be first explicitly removed with
  263. * {@link #setParent(Object itemId, Object newParentId)} or
  264. * {@link com.vaadin.data.Container#removeItem(Object itemId)}.
  265. * </p>
  266. *
  267. * @param itemId
  268. * the ID of the Item in the container whose child capability is
  269. * to be set.
  270. * @param childrenAllowed
  271. * the boolean value specifying if the Item can have children or
  272. * not.
  273. * @return <code>true</code> if the operation succeeded, <code>false</code>
  274. * if not
  275. */
  276. public boolean setChildrenAllowed(Object itemId, boolean childrenAllowed) {
  277. // If the wrapped container implements the method directly, use it
  278. if (hierarchical) {
  279. return ((Container.Hierarchical) container).setChildrenAllowed(
  280. itemId, childrenAllowed);
  281. }
  282. // Check that the item is in the container
  283. if (!containsId(itemId)) {
  284. return false;
  285. }
  286. // Update status
  287. if (childrenAllowed) {
  288. noChildrenAllowed.remove(itemId);
  289. } else {
  290. noChildrenAllowed.add(itemId);
  291. }
  292. return true;
  293. }
  294. /**
  295. * <p>
  296. * Sets the parent of an Item. The new parent item must exist and be able to
  297. * have children. (<code>canHaveChildren(newParentId) == true</code>). It is
  298. * also possible to detach a node from the hierarchy (and thus make it root)
  299. * by setting the parent <code>null</code>.
  300. * </p>
  301. *
  302. * @param itemId
  303. * the ID of the item to be set as the child of the Item
  304. * identified with newParentId.
  305. * @param newParentId
  306. * the ID of the Item that's to be the new parent of the Item
  307. * identified with itemId.
  308. * @return <code>true</code> if the operation succeeded, <code>false</code>
  309. * if not
  310. */
  311. public boolean setParent(Object itemId, Object newParentId) {
  312. // If the wrapped container implements the method directly, use it
  313. if (hierarchical) {
  314. return ((Container.Hierarchical) container).setParent(itemId,
  315. newParentId);
  316. }
  317. // Check that the item is in the container
  318. if (!containsId(itemId)) {
  319. return false;
  320. }
  321. // Get the old parent
  322. final Object oldParentId = parent.get(itemId);
  323. // Check if no change is necessary
  324. if ((newParentId == null && oldParentId == null)
  325. || (newParentId != null && newParentId.equals(oldParentId))) {
  326. return true;
  327. }
  328. // Making root
  329. if (newParentId == null) {
  330. // Remove from old parents children list
  331. final LinkedList<Object> l = children.get(oldParentId);
  332. if (l != null) {
  333. l.remove(itemId);
  334. if (l.isEmpty()) {
  335. children.remove(itemId);
  336. }
  337. }
  338. // Add to be a root
  339. roots.add(itemId);
  340. // Update parent
  341. parent.remove(itemId);
  342. return true;
  343. }
  344. // Check that the new parent exists in container and can have
  345. // children
  346. if (!containsId(newParentId) || noChildrenAllowed.contains(newParentId)) {
  347. return false;
  348. }
  349. // Check that setting parent doesn't result to a loop
  350. Object o = newParentId;
  351. while (o != null && !o.equals(itemId)) {
  352. o = parent.get(o);
  353. }
  354. if (o != null) {
  355. return false;
  356. }
  357. // Update parent
  358. parent.put(itemId, newParentId);
  359. LinkedList<Object> pcl = children.get(newParentId);
  360. if (pcl == null) {
  361. pcl = new LinkedList<Object>();
  362. children.put(newParentId, pcl);
  363. }
  364. pcl.add(itemId);
  365. // Remove from old parent or root
  366. if (oldParentId == null) {
  367. roots.remove(itemId);
  368. } else {
  369. final LinkedList<Object> l = children.get(oldParentId);
  370. if (l != null) {
  371. l.remove(itemId);
  372. if (l.isEmpty()) {
  373. children.remove(oldParentId);
  374. }
  375. }
  376. }
  377. return true;
  378. }
  379. /**
  380. * Creates a new Item into the Container, assigns it an automatic ID, and
  381. * adds it to the hierarchy.
  382. *
  383. * @return the autogenerated ID of the new Item or <code>null</code> if the
  384. * operation failed
  385. * @throws UnsupportedOperationException
  386. * if the addItem is not supported.
  387. */
  388. public Object addItem() throws UnsupportedOperationException {
  389. final Object id = container.addItem();
  390. if (!hierarchical && id != null) {
  391. addToHierarchyWrapper(id);
  392. }
  393. return id;
  394. }
  395. /**
  396. * Adds a new Item by its ID to the underlying container and to the
  397. * hierarchy.
  398. *
  399. * @param itemId
  400. * the ID of the Item to be created.
  401. * @return the added Item or <code>null</code> if the operation failed.
  402. * @throws UnsupportedOperationException
  403. * if the addItem is not supported.
  404. */
  405. public Item addItem(Object itemId) throws UnsupportedOperationException {
  406. // Null ids are not accepted
  407. if (itemId == null) {
  408. throw new NullPointerException("Container item id can not be null");
  409. }
  410. final Item item = container.addItem(itemId);
  411. if (!hierarchical && item != null) {
  412. addToHierarchyWrapper(itemId);
  413. }
  414. return item;
  415. }
  416. /**
  417. * Removes all items from the underlying container and from the hierarcy.
  418. *
  419. * @return <code>true</code> if the operation succeeded, <code>false</code>
  420. * if not
  421. * @throws UnsupportedOperationException
  422. * if the removeAllItems is not supported.
  423. */
  424. public boolean removeAllItems() throws UnsupportedOperationException {
  425. final boolean success = container.removeAllItems();
  426. if (!hierarchical && success) {
  427. roots.clear();
  428. parent.clear();
  429. children.clear();
  430. noChildrenAllowed.clear();
  431. }
  432. return success;
  433. }
  434. /**
  435. * Removes an Item specified by the itemId from the underlying container and
  436. * from the hierarcy.
  437. *
  438. * @param itemId
  439. * the ID of the Item to be removed.
  440. * @return <code>true</code> if the operation succeeded, <code>false</code>
  441. * if not
  442. * @throws UnsupportedOperationException
  443. * if the removeItem is not supported.
  444. */
  445. public boolean removeItem(Object itemId)
  446. throws UnsupportedOperationException {
  447. final boolean success = container.removeItem(itemId);
  448. if (!hierarchical && success) {
  449. removeFromHierarchyWrapper(itemId);
  450. }
  451. return success;
  452. }
  453. /**
  454. * Adds a new Property to all Items in the Container.
  455. *
  456. * @param propertyId
  457. * the ID of the new Property.
  458. * @param type
  459. * the Data type of the new Property.
  460. * @param defaultValue
  461. * the value all created Properties are initialized to.
  462. * @return <code>true</code> if the operation succeeded, <code>false</code>
  463. * if not
  464. * @throws UnsupportedOperationException
  465. * if the addContainerProperty is not supported.
  466. */
  467. public boolean addContainerProperty(Object propertyId, Class<?> type,
  468. Object defaultValue) throws UnsupportedOperationException {
  469. return container.addContainerProperty(propertyId, type, defaultValue);
  470. }
  471. /**
  472. * Removes the specified Property from the underlying container and from the
  473. * hierarchy.
  474. * <p>
  475. * Note : The Property will be removed from all Items in the Container.
  476. * </p>
  477. *
  478. * @param propertyId
  479. * the ID of the Property to remove.
  480. * @return <code>true</code> if the operation succeeded, <code>false</code>
  481. * if not
  482. * @throws UnsupportedOperationException
  483. * if the removeContainerProperty is not supported.
  484. */
  485. public boolean removeContainerProperty(Object propertyId)
  486. throws UnsupportedOperationException {
  487. return container.removeContainerProperty(propertyId);
  488. }
  489. /*
  490. * Does the container contain the specified Item? Don't add a JavaDoc
  491. * comment here, we use the default documentation from implemented
  492. * interface.
  493. */
  494. public boolean containsId(Object itemId) {
  495. return container.containsId(itemId);
  496. }
  497. /*
  498. * Gets the specified Item from the container. Don't add a JavaDoc comment
  499. * here, we use the default documentation from implemented interface.
  500. */
  501. public Item getItem(Object itemId) {
  502. return container.getItem(itemId);
  503. }
  504. /*
  505. * Gets the ID's of all Items stored in the Container Don't add a JavaDoc
  506. * comment here, we use the default documentation from implemented
  507. * interface.
  508. */
  509. public Collection getItemIds() {
  510. return container.getItemIds();
  511. }
  512. /*
  513. * Gets the Property identified by the given itemId and propertyId from the
  514. * Container Don't add a JavaDoc comment here, we use the default
  515. * documentation from implemented interface.
  516. */
  517. public Property getContainerProperty(Object itemId, Object propertyId) {
  518. return container.getContainerProperty(itemId, propertyId);
  519. }
  520. /*
  521. * Gets the ID's of all Properties stored in the Container Don't add a
  522. * JavaDoc comment here, we use the default documentation from implemented
  523. * interface.
  524. */
  525. public Collection getContainerPropertyIds() {
  526. return container.getContainerPropertyIds();
  527. }
  528. /*
  529. * Gets the data type of all Properties identified by the given Property ID.
  530. * Don't add a JavaDoc comment here, we use the default documentation from
  531. * implemented interface.
  532. */
  533. public Class getType(Object propertyId) {
  534. return container.getType(propertyId);
  535. }
  536. /*
  537. * Gets the number of Items in the Container. Don't add a JavaDoc comment
  538. * here, we use the default documentation from implemented interface.
  539. */
  540. public int size() {
  541. return container.size();
  542. }
  543. /*
  544. * Registers a new Item set change listener for this Container. Don't add a
  545. * JavaDoc comment here, we use the default documentation from implemented
  546. * interface.
  547. */
  548. public void addListener(Container.ItemSetChangeListener listener) {
  549. if (container instanceof Container.ItemSetChangeNotifier) {
  550. ((Container.ItemSetChangeNotifier) container)
  551. .addListener(new PiggybackListener(listener));
  552. }
  553. }
  554. /*
  555. * Removes a Item set change listener from the object. Don't add a JavaDoc
  556. * comment here, we use the default documentation from implemented
  557. * interface.
  558. */
  559. public void removeListener(Container.ItemSetChangeListener listener) {
  560. if (container instanceof Container.ItemSetChangeNotifier) {
  561. ((Container.ItemSetChangeNotifier) container)
  562. .removeListener(new PiggybackListener(listener));
  563. }
  564. }
  565. /*
  566. * Registers a new Property set change listener for this Container. Don't
  567. * add a JavaDoc comment here, we use the default documentation from
  568. * implemented interface.
  569. */
  570. public void addListener(Container.PropertySetChangeListener listener) {
  571. if (container instanceof Container.PropertySetChangeNotifier) {
  572. ((Container.PropertySetChangeNotifier) container)
  573. .addListener(new PiggybackListener(listener));
  574. }
  575. }
  576. /*
  577. * Removes a Property set change listener from the object. Don't add a
  578. * JavaDoc comment here, we use the default documentation from implemented
  579. * interface.
  580. */
  581. public void removeListener(Container.PropertySetChangeListener listener) {
  582. if (container instanceof Container.PropertySetChangeNotifier) {
  583. ((Container.PropertySetChangeNotifier) container)
  584. .removeListener(new PiggybackListener(listener));
  585. }
  586. }
  587. /**
  588. * This listener 'piggybacks' on the real listener in order to update the
  589. * wrapper when needed. It proxies equals() and hashCode() to the real
  590. * listener so that the correct listener gets removed.
  591. *
  592. */
  593. private class PiggybackListener implements
  594. Container.PropertySetChangeListener,
  595. Container.ItemSetChangeListener {
  596. Object listener;
  597. public PiggybackListener(Object realListener) {
  598. listener = realListener;
  599. }
  600. public void containerItemSetChange(ItemSetChangeEvent event) {
  601. updateHierarchicalWrapper();
  602. ((Container.ItemSetChangeListener) listener)
  603. .containerItemSetChange(event);
  604. }
  605. public void containerPropertySetChange(PropertySetChangeEvent event) {
  606. updateHierarchicalWrapper();
  607. ((Container.PropertySetChangeListener) listener)
  608. .containerPropertySetChange(event);
  609. }
  610. @Override
  611. public boolean equals(Object obj) {
  612. return obj == listener || (obj != null && obj.equals(listener));
  613. }
  614. @Override
  615. public int hashCode() {
  616. return listener.hashCode();
  617. }
  618. }
  619. }