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.

ContainerOrderedWrapper.java 19KB

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