選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

AbstractRemoteDataSource.java 33KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977
  1. /*
  2. * Copyright 2000-2018 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.LinkedHashSet;
  19. import java.util.List;
  20. import java.util.Map;
  21. import java.util.Objects;
  22. import java.util.Set;
  23. import java.util.stream.Stream;
  24. import com.google.gwt.core.client.Duration;
  25. import com.google.gwt.core.client.Scheduler;
  26. import com.google.gwt.core.client.Scheduler.ScheduledCommand;
  27. import com.vaadin.client.Profiler;
  28. import com.vaadin.shared.Range;
  29. import com.vaadin.shared.Registration;
  30. /**
  31. * Base implementation for data sources that fetch data from a remote system.
  32. * This class takes care of caching data and communicating with the data source
  33. * user. An implementation of this class should override
  34. * {@link #requestRows(int, int, RequestRowsCallback)} to trigger asynchronously
  35. * loading of data and then pass the loaded data into the provided callback.
  36. *
  37. * @since 7.4
  38. * @author Vaadin Ltd
  39. * @param <T>
  40. * the row type
  41. */
  42. public abstract class AbstractRemoteDataSource<T> implements DataSource<T> {
  43. /**
  44. * Callback used by
  45. * {@link AbstractRemoteDataSource#requestRows(int, int, RequestRowsCallback)}
  46. * to pass data to the underlying implementation when data has been fetched.
  47. *
  48. * @param <T>
  49. * the row type
  50. */
  51. public static class RequestRowsCallback<T> {
  52. private final Range requestedRange;
  53. private final double requestStart;
  54. private final AbstractRemoteDataSource<T> source;
  55. /**
  56. * Creates a new callback.
  57. *
  58. * @param source
  59. * the data source for which the request is made
  60. * @param requestedRange
  61. * the requested row range
  62. */
  63. protected RequestRowsCallback(AbstractRemoteDataSource<T> source,
  64. Range requestedRange) {
  65. this.source = source;
  66. this.requestedRange = requestedRange;
  67. requestStart = Duration.currentTimeMillis();
  68. }
  69. /**
  70. * Called by the
  71. * {@link AbstractRemoteDataSource#requestRows(int, int, RequestRowsCallback)}
  72. * implementation when data has been received.
  73. *
  74. * @param rowData
  75. * a list of row objects starting at the requested offset
  76. * @param totalSize
  77. * the total number of rows available at the remote end
  78. */
  79. public void onResponse(List<T> rowData, int totalSize) {
  80. if (source.size != totalSize) {
  81. source.resetDataAndSize(totalSize);
  82. }
  83. source.setRowData(requestedRange.getStart(), rowData);
  84. }
  85. /**
  86. * Gets the range of rows that was requested.
  87. *
  88. * @return the requsted row range
  89. */
  90. public Range getRequestedRange() {
  91. return requestedRange;
  92. }
  93. }
  94. protected class RowHandleImpl extends RowHandle<T> {
  95. private T row;
  96. private final Object key;
  97. public RowHandleImpl(final T row, final Object key) {
  98. this.row = row;
  99. this.key = key;
  100. }
  101. /**
  102. * A method for the data source to update the row data.
  103. *
  104. * @param row
  105. * the updated row object
  106. */
  107. public void setRow(final T row) {
  108. this.row = row;
  109. assert getRowKey(row).equals(key) : "The old key does not "
  110. + "equal the new key for the given row (old: " + key
  111. + ", new :" + getRowKey(row) + ")";
  112. }
  113. @Override
  114. public T getRow() throws IllegalStateException {
  115. return row;
  116. }
  117. public boolean isPinned() {
  118. return pinnedRows.containsKey(key);
  119. }
  120. @Override
  121. public void pin() {
  122. pinHandle(this);
  123. }
  124. @Override
  125. public void unpin() throws IllegalStateException {
  126. unpinHandle(this);
  127. }
  128. @Override
  129. protected boolean equalsExplicit(final Object obj) {
  130. if (obj instanceof AbstractRemoteDataSource.RowHandleImpl) {
  131. /*
  132. * Java prefers AbstractRemoteDataSource<?>.RowHandleImpl. I
  133. * like the @SuppressWarnings more (keeps the line length in
  134. * check.)
  135. */
  136. @SuppressWarnings("unchecked")
  137. final RowHandleImpl rhi = (RowHandleImpl) obj;
  138. return key.equals(rhi.key);
  139. } else {
  140. return false;
  141. }
  142. }
  143. @Override
  144. protected int hashCodeExplicit() {
  145. return key.hashCode();
  146. }
  147. @Override
  148. public void updateRow() {
  149. int index = indexOf(row);
  150. if (index >= 0) {
  151. getHandlers().forEach(dch -> dch.dataUpdated(index, 1));
  152. }
  153. }
  154. }
  155. private RequestRowsCallback<T> currentRequestCallback;
  156. private boolean coverageCheckPending = false;
  157. private Range requestedAvailability = Range.between(0, 0);
  158. private Range cached = Range.between(0, 0);
  159. private final Map<Integer, T> indexToRowMap = new HashMap<>();
  160. private final Map<Object, Integer> keyToIndexMap = new HashMap<>();
  161. /**
  162. * Map used to temporarily store rows invalidated by
  163. * {@link #insertRowData(int, int)}. All invalidated rows are stored with
  164. * their indices matching the state after row insertion. If the backend has
  165. * pre-emptively pushed the row data for the added rows, these rows will be
  166. * used again to fill the cache.
  167. * <p>
  168. * To avoid this cache invalidation issue from getting too big, this class
  169. * does not attempt to track these rows indefinitely. Multiple row
  170. * manipulations without filling the gaps in between will remove all
  171. * invalidated rows and prevent any attempts to restore them. This is
  172. * indicated by having the map empty, not {@code null}.
  173. * <p>
  174. * The map is set to {@code null} upon requesting the rows from the backend
  175. * to indicate that future row additions can attempt to restore the cache.
  176. */
  177. private Map<Integer, T> invalidatedRows;
  178. /**
  179. * Tracking the invalidated rows inside {{@link #insertRowData(int, int)}}
  180. * and then filling cache from those invalidated rows is a feature
  181. * introduced to improve caching in hierarchical data in V8, but as this
  182. * interface is also shared with V7 compatibility package, this change
  183. * causes this issue: issue https://github.com/vaadin/framework/issues/11477
  184. *
  185. * By having {#AbstractRemoteDataSource} define whether or not to track and
  186. * then fill cache with the invalidated rows, allows different
  187. * implementation of {#AbstractRemoteDataSource} to enable/disable this
  188. * feature, as a consequence it is possible for V7 compatibility-package of
  189. * this class to disabled it and fix the above issue.
  190. */
  191. private boolean trackInvalidatedRows = true;
  192. private Set<DataChangeHandler> dataChangeHandlers = new LinkedHashSet<>();
  193. private CacheStrategy cacheStrategy = new CacheStrategy.DefaultCacheStrategy();
  194. private final ScheduledCommand coverageChecker = () -> {
  195. coverageCheckPending = false;
  196. checkCacheCoverage();
  197. };
  198. private Map<Object, Integer> pinnedCounts = new HashMap<>();
  199. private Map<Object, RowHandleImpl> pinnedRows = new HashMap<>();
  200. // Size not yet known
  201. private int size = -1;
  202. private void ensureCoverageCheck() {
  203. if (!coverageCheckPending) {
  204. coverageCheckPending = true;
  205. Scheduler.get().scheduleDeferred(coverageChecker);
  206. }
  207. }
  208. /**
  209. * Pins a row with given handle. This function can be overridden to do
  210. * specific logic related to pinning rows.
  211. *
  212. * @param handle
  213. * row handle to pin
  214. */
  215. protected void pinHandle(RowHandleImpl handle) {
  216. Object key = handle.key;
  217. Integer count = pinnedCounts.get(key);
  218. if (count == null) {
  219. count = Integer.valueOf(0);
  220. pinnedRows.put(key, handle);
  221. }
  222. pinnedCounts.put(key, Integer.valueOf(count.intValue() + 1));
  223. }
  224. /**
  225. * Unpins a previously pinned row with given handle. This function can be
  226. * overridden to do specific logic related to unpinning rows.
  227. *
  228. * @param handle
  229. * row handle to unpin
  230. *
  231. * @throws IllegalStateException
  232. * if given row handle has not been pinned before
  233. */
  234. protected void unpinHandle(RowHandleImpl handle)
  235. throws IllegalStateException {
  236. Object key = handle.key;
  237. final Integer count = pinnedCounts.get(key);
  238. if (count == null) {
  239. throw new IllegalStateException("Row " + handle.getRow()
  240. + " with key " + key + " was not pinned to begin with");
  241. } else if (count.equals(Integer.valueOf(1))) {
  242. pinnedRows.remove(key);
  243. pinnedCounts.remove(key);
  244. } else {
  245. pinnedCounts.put(key, Integer.valueOf(count.intValue() - 1));
  246. }
  247. }
  248. @Override
  249. public void ensureAvailability(int firstRowIndex, int numberOfRows) {
  250. requestedAvailability = Range.withLength(firstRowIndex, numberOfRows);
  251. /*
  252. * Don't request any data right away since the data might be included in
  253. * a message that has been received but not yet fully processed.
  254. */
  255. ensureCoverageCheck();
  256. }
  257. /**
  258. * Gets the row index range that was requested by the previous call to
  259. * {@link #ensureAvailability(int, int)}.
  260. *
  261. * @return the requested availability range
  262. */
  263. public Range getRequestedAvailability() {
  264. return requestedAvailability;
  265. }
  266. private void checkCacheCoverage() {
  267. if (isWaitingForData()) {
  268. // Anyone clearing the waiting status should run this method again
  269. return;
  270. }
  271. Profiler.enter("AbstractRemoteDataSource.checkCacheCoverage");
  272. // Clean up invalidated data
  273. invalidatedRows = null;
  274. Range minCacheRange = getMinCacheRange();
  275. if (!minCacheRange.intersects(cached) || cached.isEmpty()) {
  276. /*
  277. * Simple case: no overlap between cached data and needed data.
  278. * Clear the cache and request new data
  279. */
  280. dropFromCache(cached);
  281. cached = Range.between(0, 0);
  282. Range maxCacheRange = getMaxCacheRange();
  283. if (!maxCacheRange.isEmpty()) {
  284. handleMissingRows(maxCacheRange);
  285. } else {
  286. // There is nothing to fetch. We're done here.
  287. getHandlers().forEach(dch -> dch
  288. .dataAvailable(cached.getStart(), cached.length()));
  289. }
  290. } else {
  291. discardStaleCacheEntries();
  292. // Might need more rows -> request them
  293. if (!minCacheRange.isSubsetOf(cached)) {
  294. Range[] missingCachePartition = getMaxCacheRange()
  295. .partitionWith(cached);
  296. handleMissingRows(missingCachePartition[0]);
  297. handleMissingRows(missingCachePartition[2]);
  298. } else {
  299. getHandlers().forEach(dch -> dch
  300. .dataAvailable(cached.getStart(), cached.length()));
  301. }
  302. }
  303. Profiler.leave("AbstractRemoteDataSource.checkCacheCoverage");
  304. }
  305. /**
  306. * Checks whether this data source is currently waiting for more rows to
  307. * become available.
  308. *
  309. * @return <code>true</code> if waiting for data; otherwise
  310. * <code>false</code>
  311. */
  312. @Override
  313. public boolean isWaitingForData() {
  314. return currentRequestCallback != null;
  315. }
  316. private void discardStaleCacheEntries() {
  317. Range[] cacheParition = cached.partitionWith(getMaxCacheRange());
  318. dropFromCache(cacheParition[0]);
  319. cached = cacheParition[1];
  320. dropFromCache(cacheParition[2]);
  321. }
  322. /**
  323. * Drop the given range of rows from this data source's cache.
  324. *
  325. * @param range
  326. * the range of rows to drop
  327. */
  328. protected void dropFromCache(Range range) {
  329. for (int i = range.getStart(); i < range.getEnd(); i++) {
  330. // Called after dropping from cache. Dropped row is passed as a
  331. // parameter, but is no longer present in the DataSource
  332. T removed = indexToRowMap.remove(Integer.valueOf(i));
  333. if (removed != null) {
  334. onDropFromCache(i, removed);
  335. keyToIndexMap.remove(getRowKey(removed));
  336. }
  337. }
  338. }
  339. /**
  340. * A hook that can be overridden to do something whenever a row has been
  341. * dropped from the cache. DataSource no longer has anything in the given
  342. * index.
  343. * <p>
  344. * NOTE: This method has been replaced. Override
  345. * {@link #onDropFromCache(int, Object)} instead of this method.
  346. *
  347. * @since 7.5.0
  348. * @param rowIndex
  349. * the index of the dropped row
  350. * @deprecated replaced by {@link #onDropFromCache(int, Object)}
  351. */
  352. @Deprecated
  353. protected void onDropFromCache(int rowIndex) {
  354. // noop
  355. }
  356. /**
  357. * A hook that can be overridden to do something whenever a row has been
  358. * dropped from the cache. DataSource no longer has anything in the given
  359. * index.
  360. *
  361. * @since 7.6
  362. * @param rowIndex
  363. * the index of the dropped row
  364. * @param removed
  365. * the removed row object
  366. */
  367. protected void onDropFromCache(int rowIndex, T removed) {
  368. // Call old version as a fallback (someone might have used it)
  369. onDropFromCache(rowIndex);
  370. }
  371. private void handleMissingRows(Range range) {
  372. if (range.isEmpty() || !canFetchData()) {
  373. return;
  374. }
  375. currentRequestCallback = new RequestRowsCallback<>(this, range);
  376. requestRows(range.getStart(), range.length(), currentRequestCallback);
  377. }
  378. /**
  379. * Triggers fetching rows from the remote data source. The provided callback
  380. * should be informed when the requested rows have been received.
  381. *
  382. * @param firstRowIndex
  383. * the index of the first row to fetch
  384. * @param numberOfRows
  385. * the number of rows to fetch
  386. * @param callback
  387. * callback to inform when the requested rows are available
  388. */
  389. protected abstract void requestRows(int firstRowIndex, int numberOfRows,
  390. RequestRowsCallback<T> callback);
  391. @Override
  392. public T getRow(int rowIndex) {
  393. return indexToRowMap.get(Integer.valueOf(rowIndex));
  394. }
  395. /**
  396. * Retrieves the index for given row object.
  397. * <p>
  398. * <em>Note:</em> This method does not verify that the given row object
  399. * exists at all in this DataSource.
  400. *
  401. * @param row
  402. * the row object
  403. * @return index of the row; or <code>-1</code> if row is not available
  404. */
  405. public int indexOf(T row) {
  406. Object key = getRowKey(row);
  407. if (keyToIndexMap.containsKey(key)) {
  408. return keyToIndexMap.get(key);
  409. }
  410. return -1;
  411. }
  412. @Override
  413. public Registration addDataChangeHandler(
  414. final DataChangeHandler dataChangeHandler) {
  415. Objects.requireNonNull(dataChangeHandler,
  416. "DataChangeHandler can't be null");
  417. dataChangeHandlers.add(dataChangeHandler);
  418. if (!cached.isEmpty()) {
  419. // Push currently cached data to the implementation
  420. dataChangeHandler.dataUpdated(cached.getStart(), cached.length());
  421. dataChangeHandler.dataAvailable(cached.getStart(), cached.length());
  422. }
  423. return () -> dataChangeHandlers.remove(dataChangeHandler);
  424. }
  425. /**
  426. * Informs this data source that updated data has been sent from the server.
  427. *
  428. * @param firstRowIndex
  429. * the index of the first received row
  430. * @param rowData
  431. * a list of rows, starting from <code>firstRowIndex</code>
  432. */
  433. protected void setRowData(int firstRowIndex, List<T> rowData) {
  434. assert firstRowIndex + rowData.size() <= size();
  435. Profiler.enter("AbstractRemoteDataSource.setRowData");
  436. Range received = Range.withLength(firstRowIndex, rowData.size());
  437. if (isWaitingForData()) {
  438. cacheStrategy.onDataArrive(
  439. Duration.currentTimeMillis()
  440. - currentRequestCallback.requestStart,
  441. received.length());
  442. currentRequestCallback = null;
  443. }
  444. Range maxCacheRange = getMaxCacheRange(received);
  445. Range[] partition = received.partitionWith(maxCacheRange);
  446. Range newUsefulData = partition[1];
  447. if (!newUsefulData.isEmpty()) {
  448. if (!cached.isEmpty())
  449. discardStaleCacheEntries();
  450. // Update the parts that are actually inside
  451. int start = newUsefulData.getStart();
  452. for (int i = start; i < newUsefulData.getEnd(); i++) {
  453. final T row = rowData.get(i - firstRowIndex);
  454. indexToRowMap.put(Integer.valueOf(i), row);
  455. keyToIndexMap.put(getRowKey(row), Integer.valueOf(i));
  456. }
  457. Profiler.enter(
  458. "AbstractRemoteDataSource.setRowData notify dataChangeHandler");
  459. int length = newUsefulData.length();
  460. getHandlers().forEach(dch -> dch.dataUpdated(start, length));
  461. Profiler.leave(
  462. "AbstractRemoteDataSource.setRowData notify dataChangeHandler");
  463. // Potentially extend the range
  464. if (cached.isEmpty()) {
  465. cached = newUsefulData;
  466. } else {
  467. /*
  468. * everything might've become stale so we need to re-check for
  469. * emptiness.
  470. */
  471. if (!cached.isEmpty()) {
  472. cached = cached.combineWith(newUsefulData);
  473. // Attempt to restore invalidated items
  474. if (trackInvalidatedRows) {
  475. fillCacheFromInvalidatedRows(maxCacheRange);
  476. }
  477. } else {
  478. cached = newUsefulData;
  479. }
  480. }
  481. getHandlers().forEach(dch -> dch.dataAvailable(cached.getStart(),
  482. cached.length()));
  483. updatePinnedRows(rowData);
  484. }
  485. if (!partition[0].isEmpty() || !partition[2].isEmpty()) {
  486. /*
  487. * FIXME
  488. *
  489. * Got data that we might need in a moment if the container is
  490. * updated before the widget settings. Support for this will be
  491. * implemented later on.
  492. */
  493. // Run a dummy drop from cache for unused rows.
  494. for (int i = 0; i < partition[0].length(); ++i) {
  495. onDropFromCache(i + partition[0].getStart(), rowData.get(i));
  496. }
  497. for (int i = 0; i < partition[2].length(); ++i) {
  498. onDropFromCache(i + partition[2].getStart(), rowData.get(i));
  499. }
  500. }
  501. // Eventually check whether all needed rows are now available
  502. ensureCoverageCheck();
  503. Profiler.leave("AbstractRemoteDataSource.setRowData");
  504. }
  505. /**
  506. * Go through items invalidated by {@link #insertRowData(int, int)}. If the
  507. * server has pre-emptively sent added row data immediately after informing
  508. * of row addition, the invalid cache can be restored to proper index range.
  509. *
  510. * @param maxCacheRange
  511. * the maximum amount of rows that can cached
  512. */
  513. private void fillCacheFromInvalidatedRows(Range maxCacheRange) {
  514. if (invalidatedRows == null || invalidatedRows.isEmpty()) {
  515. // No old invalid cache available
  516. return;
  517. }
  518. Range potentialCache = maxCacheRange.partitionWith(cached)[2];
  519. int start = potentialCache.getStart();
  520. int last = start;
  521. try {
  522. if (potentialCache.isEmpty()
  523. || invalidatedRows.containsKey(start - 1)) {
  524. // Cache is already full or invalidated rows contains unexpected
  525. // indices.
  526. return;
  527. }
  528. for (int i = start; i < potentialCache.getEnd(); ++i) {
  529. if (!invalidatedRows.containsKey(i)) {
  530. return;
  531. }
  532. T row = invalidatedRows.get(i);
  533. indexToRowMap.put(i, row);
  534. keyToIndexMap.put(getRowKey(row), i);
  535. last = i;
  536. }
  537. // Cache filled from invalidated rows. Can continue as if it was
  538. // never invalidated.
  539. invalidatedRows = null;
  540. } finally {
  541. // Update cache range and clean up
  542. if (invalidatedRows != null) {
  543. invalidatedRows.clear();
  544. }
  545. Range updated = Range.between(start, last + 1);
  546. cached = cached.combineWith(updated);
  547. dataChangeHandlers.forEach(dch -> dch
  548. .dataUpdated(updated.getStart(), updated.length()));
  549. }
  550. }
  551. private Stream<DataChangeHandler> getHandlers() {
  552. Set<DataChangeHandler> copy = new LinkedHashSet<>(dataChangeHandlers);
  553. return copy.stream();
  554. }
  555. private void updatePinnedRows(final List<T> rowData) {
  556. for (final T row : rowData) {
  557. final Object key = getRowKey(row);
  558. final RowHandleImpl handle = pinnedRows.get(key);
  559. if (handle != null) {
  560. handle.setRow(row);
  561. }
  562. }
  563. }
  564. /**
  565. * Informs this data source that the server has removed data.
  566. *
  567. * @param firstRowIndex
  568. * the index of the first removed row
  569. * @param count
  570. * the number of removed rows, starting from
  571. * <code>firstRowIndex</code>
  572. */
  573. protected void removeRowData(int firstRowIndex, int count) {
  574. Profiler.enter("AbstractRemoteDataSource.removeRowData");
  575. // Cache was not filled since previous insertRowData. The old rows are
  576. // no longer useful.
  577. if (invalidatedRows != null) {
  578. invalidatedRows.clear();
  579. }
  580. size -= count;
  581. Range removedRange = Range.withLength(firstRowIndex, count);
  582. dropFromCache(removedRange);
  583. // shift indices to fill the cache correctly
  584. int firstMoved = Math.max(firstRowIndex + count, cached.getStart());
  585. for (int i = firstMoved; i < cached.getEnd(); i++) {
  586. moveRowFromIndexToIndex(i, i - count);
  587. }
  588. if (cached.isSubsetOf(removedRange)) {
  589. // Whole cache is part of the removal. Empty cache
  590. cached = Range.withLength(0, 0);
  591. } else if (removedRange.intersects(cached)) {
  592. // Removal and cache share some indices. fix accordingly.
  593. Range[] partitions = cached.partitionWith(removedRange);
  594. Range remainsBefore = partitions[0];
  595. Range transposedRemainsAfter = partitions[2]
  596. .offsetBy(-removedRange.length());
  597. // #8840 either can be empty if the removed range was over the
  598. // cached range
  599. if (remainsBefore.isEmpty()) {
  600. cached = transposedRemainsAfter;
  601. } else if (transposedRemainsAfter.isEmpty()) {
  602. cached = remainsBefore;
  603. } else {
  604. cached = remainsBefore.combineWith(transposedRemainsAfter);
  605. }
  606. } else if (removedRange.getEnd() <= cached.getStart()) {
  607. // Removal was before the cache. offset the cache.
  608. cached = cached.offsetBy(-removedRange.length());
  609. }
  610. getHandlers().forEach(dch -> dch.dataRemoved(firstRowIndex, count));
  611. ensureCoverageCheck();
  612. Profiler.leave("AbstractRemoteDataSource.removeRowData");
  613. }
  614. /**
  615. * Informs this data source that new data has been inserted from the server.
  616. *
  617. * @param firstRowIndex
  618. * the destination index of the new row data
  619. * @param count
  620. * the number of rows inserted
  621. */
  622. protected void insertRowData(int firstRowIndex, int count) {
  623. Profiler.enter("AbstractRemoteDataSource.insertRowData");
  624. // Cache was not filled since previous insertRowData. The old rows are
  625. // no longer useful.
  626. if (invalidatedRows != null) {
  627. invalidatedRows.clear();
  628. }
  629. size += count;
  630. if (firstRowIndex <= cached.getStart()) {
  631. Range oldCached = cached;
  632. cached = cached.offsetBy(count);
  633. for (int i = 1; i <= cached.length(); i++) {
  634. int oldIndex = oldCached.getEnd() - i;
  635. int newIndex = cached.getEnd() - i;
  636. moveRowFromIndexToIndex(oldIndex, newIndex);
  637. }
  638. } else if (cached.contains(firstRowIndex)) {
  639. int oldCacheEnd = cached.getEnd();
  640. Range[] splitOldCache = cached.splitAt(firstRowIndex);
  641. cached = splitOldCache[0];
  642. Range invalidated = splitOldCache[1];
  643. if (trackInvalidatedRows) {
  644. /*
  645. * We need to invalidate the cache from the inserted row
  646. * onwards, since the cache wants to be a contiguous range. It
  647. * doesn't support holes.
  648. *
  649. * If holes were supported, we could shift the higher part of
  650. * "cached" and leave a hole the size of "count" in the middle.
  651. */
  652. trackInvalidatedRowsFromCache(invalidated, count);
  653. }
  654. for (int i = firstRowIndex; i < oldCacheEnd; i++) {
  655. T row = indexToRowMap.remove(Integer.valueOf(i));
  656. keyToIndexMap.remove(getRowKey(row));
  657. }
  658. }
  659. getHandlers().forEach(dch -> dch.dataAdded(firstRowIndex, count));
  660. ensureCoverageCheck();
  661. Profiler.leave("AbstractRemoteDataSource.insertRowData");
  662. }
  663. private void trackInvalidatedRowsFromCache(Range invalidated,
  664. int insertedRowCount) {
  665. /*
  666. * If we already have a map in invalidatedRows, we're in a state where
  667. * multiple row manipulations without data received have happened and
  668. * the cache restoration is prevented completely.
  669. */
  670. if (!invalidated.isEmpty() && invalidatedRows == null) {
  671. invalidatedRows = new HashMap<>();
  672. // Store all invalidated items to a map. Indices are updated
  673. // to match what they should be after the insertion.
  674. for (int i = invalidated.getStart(); i < invalidated
  675. .getEnd(); ++i) {
  676. invalidatedRows.put(i + insertedRowCount, indexToRowMap.get(i));
  677. }
  678. }
  679. }
  680. @SuppressWarnings("boxing")
  681. private void moveRowFromIndexToIndex(int oldIndex, int newIndex) {
  682. T row = indexToRowMap.remove(oldIndex);
  683. if (indexToRowMap.containsKey(newIndex)) {
  684. // Old row is about to be overwritten. Remove it from keyCache.
  685. T row2 = indexToRowMap.remove(newIndex);
  686. if (row2 != null) {
  687. keyToIndexMap.remove(getRowKey(row2));
  688. }
  689. }
  690. indexToRowMap.put(newIndex, row);
  691. if (row != null) {
  692. keyToIndexMap.put(getRowKey(row), newIndex);
  693. }
  694. }
  695. /**
  696. * Gets the current range of cached rows.
  697. *
  698. * @return the range of currently cached rows
  699. */
  700. public Range getCachedRange() {
  701. return cached;
  702. }
  703. /**
  704. * Sets the cache strategy that is used to determine how much data is
  705. * fetched and cached.
  706. * <p>
  707. * The new strategy is immediately used to evaluate whether currently cached
  708. * rows should be discarded or new rows should be fetched.
  709. *
  710. * @param cacheStrategy
  711. * a cache strategy implementation, not <code>null</code>
  712. */
  713. public void setCacheStrategy(CacheStrategy cacheStrategy) {
  714. if (cacheStrategy == null) {
  715. throw new IllegalArgumentException();
  716. }
  717. if (this.cacheStrategy != cacheStrategy) {
  718. this.cacheStrategy = cacheStrategy;
  719. checkCacheCoverage();
  720. }
  721. }
  722. private Range getMinCacheRange() {
  723. Range availableDataRange = getAvailableRangeForCache();
  724. Range minCacheRange = cacheStrategy.getMinCacheRange(
  725. requestedAvailability, cached, availableDataRange);
  726. assert minCacheRange.isSubsetOf(availableDataRange);
  727. return minCacheRange;
  728. }
  729. private Range getMaxCacheRange() {
  730. return getMaxCacheRange(getRequestedAvailability());
  731. }
  732. private Range getMaxCacheRange(Range range) {
  733. Range availableDataRange = getAvailableRangeForCache();
  734. Range maxCacheRange = cacheStrategy.getMaxCacheRange(range, cached,
  735. availableDataRange);
  736. assert maxCacheRange.isSubsetOf(availableDataRange);
  737. return maxCacheRange;
  738. }
  739. private Range getAvailableRangeForCache() {
  740. int upperBound = size();
  741. if (upperBound == -1) {
  742. upperBound = requestedAvailability.length();
  743. }
  744. return Range.withLength(0, upperBound);
  745. }
  746. @Override
  747. public RowHandle<T> getHandle(T row) throws IllegalStateException {
  748. Object key = getRowKey(row);
  749. if (key == null) {
  750. throw new NullPointerException(
  751. "key may not be null (row: " + row + ")");
  752. }
  753. if (pinnedRows.containsKey(key)) {
  754. return pinnedRows.get(key);
  755. } else if (keyToIndexMap.containsKey(key)) {
  756. return new RowHandleImpl(row, key);
  757. } else {
  758. throw new IllegalStateException("The cache of this DataSource "
  759. + "does not currently contain the row " + row);
  760. }
  761. }
  762. /**
  763. * Gets a stable key for the row object.
  764. * <p>
  765. * This method is a workaround for the fact that there is no means to force
  766. * proper implementations for {@link #hashCode()} and
  767. * {@link #equals(Object)} methods.
  768. * <p>
  769. * Since the same row object will be created several times for the same
  770. * logical data, the DataSource needs a mechanism to be able to compare two
  771. * objects, and figure out whether or not they represent the same data. Even
  772. * if all the fields of an entity would be changed, it still could represent
  773. * the very same thing (say, a person changes all of her names.)
  774. * <p>
  775. * A very usual and simple example what this could be, is an unique ID for
  776. * this object that would also be stored in a database.
  777. *
  778. * @param row
  779. * the row object for which to get the key
  780. * @return a non-null object that uniquely and consistently represents the
  781. * row object
  782. */
  783. public abstract Object getRowKey(T row);
  784. @Override
  785. public int size() {
  786. return size;
  787. }
  788. /**
  789. * Updates the size, discarding all cached data. This method is used when
  790. * the size of the container is changed without any information about the
  791. * structure of the change. In this case, all cached data is discarded to
  792. * avoid cache offset issues.
  793. * <p>
  794. * If you have information about the structure of the change, use
  795. * {@link #insertRowData(int, int)} or {@link #removeRowData(int, int)} to
  796. * indicate where the inserted or removed rows are located.
  797. *
  798. * @param newSize
  799. * the new size of the container
  800. */
  801. protected void resetDataAndSize(int newSize) {
  802. size = newSize;
  803. indexToRowMap.clear();
  804. keyToIndexMap.clear();
  805. cached = Range.withLength(0, 0);
  806. getHandlers().forEach(dch -> dch.resetDataAndSize(newSize));
  807. }
  808. protected int indexOfKey(Object rowKey) {
  809. if (!keyToIndexMap.containsKey(rowKey)) {
  810. return -1;
  811. } else {
  812. return keyToIndexMap.get(rowKey);
  813. }
  814. }
  815. protected boolean isPinned(T row) {
  816. return pinnedRows.containsKey(getRowKey(row));
  817. }
  818. /**
  819. * Checks if it is possible to currently fetch data from the remote data
  820. * source.
  821. *
  822. * @return <code>true</code> if it is ok to try to fetch data,
  823. * <code>false</code> if it is known that fetching data will fail
  824. * and should not be tried right now.
  825. * @since 7.7.2
  826. */
  827. protected boolean canFetchData() {
  828. return true;
  829. }
  830. /**
  831. * Sets whether or not to track invalidated rows inside
  832. * {@link #insertRowData(int, int)} and use them to fill cache when
  833. * {{@link #setRowData(int, List)}} is called.
  834. *
  835. * @param trackInvalidatedRows
  836. * a boolean value specifying if to track invalidated rows or
  837. * not, default value <code>true</code>
  838. */
  839. public void setTrackInvalidatedRows(boolean trackInvalidatedRows) {
  840. this.trackInvalidatedRows = trackInvalidatedRows;
  841. }
  842. }