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 20KB

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