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.

AbstractRemoteDataSource.java 7.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. /*
  2. * Copyright 2000-2013 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.client.data;
  17. import java.util.HashMap;
  18. import java.util.List;
  19. import com.google.gwt.core.client.Scheduler;
  20. import com.google.gwt.core.client.Scheduler.ScheduledCommand;
  21. import com.vaadin.client.Profiler;
  22. import com.vaadin.client.ui.grid.Range;
  23. /**
  24. * Base implementation for data sources that fetch data from a remote system.
  25. * This class takes care of caching data and communicating with the data source
  26. * user. An implementation of this class should override
  27. * {@link #requestRows(int, int)} to trigger asynchronously loading of data.
  28. * When data is received from the server, new row data should be passed to
  29. * {@link #setRowData(int, List)}. {@link #setEstimatedSize(int)} should be used
  30. * based on estimations of how many rows are available.
  31. *
  32. * @since 7.2
  33. * @author Vaadin Ltd
  34. * @param <T>
  35. * the row type
  36. */
  37. public abstract class AbstractRemoteDataSource<T> implements DataSource<T> {
  38. private boolean requestPending = false;
  39. private boolean coverageCheckPending = false;
  40. private Range requestedAvailability = Range.between(0, 0);
  41. private Range cached = Range.between(0, 0);
  42. private final HashMap<Integer, T> rowCache = new HashMap<Integer, T>();
  43. private DataChangeHandler dataChangeHandler;
  44. private int estimatedSize;
  45. private final ScheduledCommand coverageChecker = new ScheduledCommand() {
  46. @Override
  47. public void execute() {
  48. coverageCheckPending = false;
  49. checkCacheCoverage();
  50. }
  51. };
  52. /**
  53. * Sets the estimated number of rows in the data source.
  54. *
  55. * @param estimatedSize
  56. * the estimated number of available rows
  57. */
  58. protected void setEstimatedSize(int estimatedSize) {
  59. // TODO update dataChangeHandler if size changes
  60. this.estimatedSize = estimatedSize;
  61. }
  62. private void ensureCoverageCheck() {
  63. if (!coverageCheckPending) {
  64. coverageCheckPending = true;
  65. Scheduler.get().scheduleDeferred(coverageChecker);
  66. }
  67. }
  68. @Override
  69. public void ensureAvailability(int firstRowIndex, int numberOfRows) {
  70. requestedAvailability = Range.withLength(firstRowIndex, numberOfRows);
  71. /*
  72. * Don't request any data right away since the data might be included in
  73. * a message that has been received but not yet fully processed.
  74. */
  75. ensureCoverageCheck();
  76. }
  77. private void checkCacheCoverage() {
  78. if (requestPending) {
  79. // Anyone clearing requestPending should run this method again
  80. return;
  81. }
  82. Profiler.enter("AbstractRemoteDataSource.checkCacheCoverage");
  83. if (!requestedAvailability.intersects(cached)) {
  84. /*
  85. * Simple case: no overlap between cached data and needed data.
  86. * Clear the cache and request new data
  87. */
  88. rowCache.clear();
  89. cached = Range.between(0, 0);
  90. handleMissingRows(requestedAvailability);
  91. } else {
  92. discardStaleCacheEntries();
  93. // Might need more rows -> request them
  94. Range[] availabilityPartition = requestedAvailability
  95. .partitionWith(cached);
  96. handleMissingRows(availabilityPartition[0]);
  97. handleMissingRows(availabilityPartition[2]);
  98. }
  99. Profiler.leave("AbstractRemoteDataSource.checkCacheCoverage");
  100. }
  101. private void discardStaleCacheEntries() {
  102. Range[] cacheParition = cached.partitionWith(requestedAvailability);
  103. dropFromCache(cacheParition[0]);
  104. cached = cacheParition[1];
  105. dropFromCache(cacheParition[2]);
  106. }
  107. private void dropFromCache(Range range) {
  108. for (int i = range.getStart(); i < range.getEnd(); i++) {
  109. rowCache.remove(Integer.valueOf(i));
  110. }
  111. }
  112. private void handleMissingRows(Range range) {
  113. if (range.isEmpty()) {
  114. return;
  115. }
  116. requestPending = true;
  117. requestRows(range.getStart(), range.length());
  118. }
  119. /**
  120. * Triggers fetching rows from the remote data source.
  121. * {@link #setRowData(int, List)} should be invoked with data for the
  122. * requested rows when they have been received.
  123. *
  124. * @param firstRowIndex
  125. * the index of the first row to fetch
  126. * @param numberOfRows
  127. * the number of rows to fetch
  128. */
  129. protected abstract void requestRows(int firstRowIndex, int numberOfRows);
  130. @Override
  131. public int getEstimatedSize() {
  132. return estimatedSize;
  133. }
  134. @Override
  135. public T getRow(int rowIndex) {
  136. return rowCache.get(Integer.valueOf(rowIndex));
  137. }
  138. @Override
  139. public void setDataChangeHandler(DataChangeHandler dataChangeHandler) {
  140. this.dataChangeHandler = dataChangeHandler;
  141. if (dataChangeHandler != null && !cached.isEmpty()) {
  142. // Push currently cached data to the implementation
  143. dataChangeHandler.dataUpdated(cached.getStart(), cached.length());
  144. }
  145. }
  146. /**
  147. * Informs this data source that updated data has been sent from the server.
  148. *
  149. * @param firstRowIndex
  150. * the index of the first received row
  151. * @param rowData
  152. * a list of rows, starting from <code>firstRowIndex</code>
  153. */
  154. protected void setRowData(int firstRowIndex, List<T> rowData) {
  155. requestPending = false;
  156. Profiler.enter("AbstractRemoteDataSource.setRowData");
  157. Range received = Range.withLength(firstRowIndex, rowData.size());
  158. Range[] partition = received.partitionWith(requestedAvailability);
  159. Range newUsefulData = partition[1];
  160. if (!newUsefulData.isEmpty()) {
  161. // Update the parts that are actually inside
  162. for (int i = newUsefulData.getStart(); i < newUsefulData.getEnd(); i++) {
  163. rowCache.put(Integer.valueOf(i), rowData.get(i - firstRowIndex));
  164. }
  165. if (dataChangeHandler != null) {
  166. Profiler.enter("AbstractRemoteDataSource.setRowData notify dataChangeHandler");
  167. dataChangeHandler.dataUpdated(newUsefulData.getStart(),
  168. newUsefulData.length());
  169. Profiler.leave("AbstractRemoteDataSource.setRowData notify dataChangeHandler");
  170. }
  171. // Potentially extend the range
  172. if (cached.isEmpty()) {
  173. cached = newUsefulData;
  174. } else {
  175. discardStaleCacheEntries();
  176. cached = cached.combineWith(newUsefulData);
  177. }
  178. }
  179. if (!partition[0].isEmpty() || !partition[2].isEmpty()) {
  180. /*
  181. * FIXME
  182. *
  183. * Got data that we might need in a moment if the container is
  184. * updated before the widget settings. Support for this will be
  185. * implemented later on.
  186. */
  187. }
  188. // Eventually check whether all needed rows are now available
  189. ensureCoverageCheck();
  190. Profiler.leave("AbstractRemoteDataSource.setRowData");
  191. }
  192. }