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

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