Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

AbstractRemoteDataSource.java 32KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938
  1. /*
  2. * Copyright 2000-2016 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 HashMap<Integer, T> indexToRowMap = new HashMap<>();
  160. private final HashMap<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. private Set<DataChangeHandler> dataChangeHandlers = new LinkedHashSet<>();
  179. private CacheStrategy cacheStrategy = new CacheStrategy.DefaultCacheStrategy();
  180. private final ScheduledCommand coverageChecker = new ScheduledCommand() {
  181. @Override
  182. public void execute() {
  183. coverageCheckPending = false;
  184. checkCacheCoverage();
  185. }
  186. };
  187. private Map<Object, Integer> pinnedCounts = new HashMap<>();
  188. private Map<Object, RowHandleImpl> pinnedRows = new HashMap<>();
  189. // Size not yet known
  190. private int size = -1;
  191. private void ensureCoverageCheck() {
  192. if (!coverageCheckPending) {
  193. coverageCheckPending = true;
  194. Scheduler.get().scheduleDeferred(coverageChecker);
  195. }
  196. }
  197. /**
  198. * Pins a row with given handle. This function can be overridden to do
  199. * specific logic related to pinning rows.
  200. *
  201. * @param handle
  202. * row handle to pin
  203. */
  204. protected void pinHandle(RowHandleImpl handle) {
  205. Object key = handle.key;
  206. Integer count = pinnedCounts.get(key);
  207. if (count == null) {
  208. count = Integer.valueOf(0);
  209. pinnedRows.put(key, handle);
  210. }
  211. pinnedCounts.put(key, Integer.valueOf(count.intValue() + 1));
  212. }
  213. /**
  214. * Unpins a previously pinned row with given handle. This function can be
  215. * overridden to do specific logic related to unpinning rows.
  216. *
  217. * @param handle
  218. * row handle to unpin
  219. *
  220. * @throws IllegalStateException
  221. * if given row handle has not been pinned before
  222. */
  223. protected void unpinHandle(RowHandleImpl handle)
  224. throws IllegalStateException {
  225. Object key = handle.key;
  226. final Integer count = pinnedCounts.get(key);
  227. if (count == null) {
  228. throw new IllegalStateException("Row " + handle.getRow()
  229. + " with key " + key + " was not pinned to begin with");
  230. } else if (count.equals(Integer.valueOf(1))) {
  231. pinnedRows.remove(key);
  232. pinnedCounts.remove(key);
  233. } else {
  234. pinnedCounts.put(key, Integer.valueOf(count.intValue() - 1));
  235. }
  236. }
  237. @Override
  238. public void ensureAvailability(int firstRowIndex, int numberOfRows) {
  239. requestedAvailability = Range.withLength(firstRowIndex, numberOfRows);
  240. /*
  241. * Don't request any data right away since the data might be included in
  242. * a message that has been received but not yet fully processed.
  243. */
  244. ensureCoverageCheck();
  245. }
  246. /**
  247. * Gets the row index range that was requested by the previous call to
  248. * {@link #ensureAvailability(int, int)}.
  249. *
  250. * @return the requested availability range
  251. */
  252. public Range getRequestedAvailability() {
  253. return requestedAvailability;
  254. }
  255. private void checkCacheCoverage() {
  256. if (isWaitingForData()) {
  257. // Anyone clearing the waiting status should run this method again
  258. return;
  259. }
  260. Profiler.enter("AbstractRemoteDataSource.checkCacheCoverage");
  261. // Clean up invalidated data
  262. invalidatedRows = null;
  263. Range minCacheRange = getMinCacheRange();
  264. if (!minCacheRange.intersects(cached) || cached.isEmpty()) {
  265. /*
  266. * Simple case: no overlap between cached data and needed data.
  267. * Clear the cache and request new data
  268. */
  269. dropFromCache(cached);
  270. cached = Range.between(0, 0);
  271. Range maxCacheRange = getMaxCacheRange();
  272. if (!maxCacheRange.isEmpty()) {
  273. handleMissingRows(maxCacheRange);
  274. } else {
  275. // There is nothing to fetch. We're done here.
  276. getHandlers().forEach(dch -> dch
  277. .dataAvailable(cached.getStart(), cached.length()));
  278. }
  279. } else {
  280. discardStaleCacheEntries();
  281. // Might need more rows -> request them
  282. if (!minCacheRange.isSubsetOf(cached)) {
  283. Range[] missingCachePartition = getMaxCacheRange()
  284. .partitionWith(cached);
  285. handleMissingRows(missingCachePartition[0]);
  286. handleMissingRows(missingCachePartition[2]);
  287. } else {
  288. getHandlers().forEach(dch -> dch
  289. .dataAvailable(cached.getStart(), cached.length()));
  290. }
  291. }
  292. Profiler.leave("AbstractRemoteDataSource.checkCacheCoverage");
  293. }
  294. /**
  295. * Checks whether this data source is currently waiting for more rows to
  296. * become available.
  297. *
  298. * @return <code>true</code> if waiting for data; otherwise
  299. * <code>false</code>
  300. */
  301. @Override
  302. public boolean isWaitingForData() {
  303. return currentRequestCallback != null;
  304. }
  305. private void discardStaleCacheEntries() {
  306. Range[] cacheParition = cached.partitionWith(getMaxCacheRange());
  307. dropFromCache(cacheParition[0]);
  308. cached = cacheParition[1];
  309. dropFromCache(cacheParition[2]);
  310. }
  311. /**
  312. * Drop the given range of rows from this data source's cache.
  313. *
  314. * @param range
  315. * the range of rows to drop
  316. */
  317. protected void dropFromCache(Range range) {
  318. for (int i = range.getStart(); i < range.getEnd(); i++) {
  319. // Called after dropping from cache. Dropped row is passed as a
  320. // parameter, but is no longer present in the DataSource
  321. T removed = indexToRowMap.remove(Integer.valueOf(i));
  322. if (removed != null) {
  323. onDropFromCache(i, removed);
  324. keyToIndexMap.remove(getRowKey(removed));
  325. }
  326. }
  327. }
  328. /**
  329. * A hook that can be overridden to do something whenever a row has been
  330. * dropped from the cache. DataSource no longer has anything in the given
  331. * index.
  332. * <p>
  333. * NOTE: This method has been replaced. Override
  334. * {@link #onDropFromCache(int, Object)} instead of this method.
  335. *
  336. * @since 7.5.0
  337. * @param rowIndex
  338. * the index of the dropped row
  339. * @deprecated replaced by {@link #onDropFromCache(int, Object)}
  340. */
  341. @Deprecated
  342. protected void onDropFromCache(int rowIndex) {
  343. // noop
  344. }
  345. /**
  346. * A hook that can be overridden to do something whenever a row has been
  347. * dropped from the cache. DataSource no longer has anything in the given
  348. * index.
  349. *
  350. * @since 7.6
  351. * @param rowIndex
  352. * the index of the dropped row
  353. * @param removed
  354. * the removed row object
  355. */
  356. protected void onDropFromCache(int rowIndex, T removed) {
  357. // Call old version as a fallback (someone might have used it)
  358. onDropFromCache(rowIndex);
  359. }
  360. private void handleMissingRows(Range range) {
  361. if (range.isEmpty() || !canFetchData()) {
  362. return;
  363. }
  364. currentRequestCallback = new RequestRowsCallback<>(this, range);
  365. requestRows(range.getStart(), range.length(), currentRequestCallback);
  366. }
  367. /**
  368. * Triggers fetching rows from the remote data source. The provided callback
  369. * should be informed when the requested rows have been received.
  370. *
  371. * @param firstRowIndex
  372. * the index of the first row to fetch
  373. * @param numberOfRows
  374. * the number of rows to fetch
  375. * @param callback
  376. * callback to inform when the requested rows are available
  377. */
  378. protected abstract void requestRows(int firstRowIndex, int numberOfRows,
  379. RequestRowsCallback<T> callback);
  380. @Override
  381. public T getRow(int rowIndex) {
  382. return indexToRowMap.get(Integer.valueOf(rowIndex));
  383. }
  384. /**
  385. * Retrieves the index for given row object.
  386. * <p>
  387. * <em>Note:</em> This method does not verify that the given row object
  388. * exists at all in this DataSource.
  389. *
  390. * @param row
  391. * the row object
  392. * @return index of the row; or <code>-1</code> if row is not available
  393. */
  394. public int indexOf(T row) {
  395. Object key = getRowKey(row);
  396. if (keyToIndexMap.containsKey(key)) {
  397. return keyToIndexMap.get(key);
  398. }
  399. return -1;
  400. }
  401. @Override
  402. public Registration addDataChangeHandler(
  403. final DataChangeHandler dataChangeHandler) {
  404. Objects.requireNonNull(dataChangeHandler,
  405. "DataChangeHandler can't be null");
  406. dataChangeHandlers.add(dataChangeHandler);
  407. if (!cached.isEmpty()) {
  408. // Push currently cached data to the implementation
  409. dataChangeHandler.dataUpdated(cached.getStart(), cached.length());
  410. dataChangeHandler.dataAvailable(cached.getStart(), cached.length());
  411. }
  412. return () -> dataChangeHandlers.remove(dataChangeHandler);
  413. }
  414. /**
  415. * Informs this data source that updated data has been sent from the server.
  416. *
  417. * @param firstRowIndex
  418. * the index of the first received row
  419. * @param rowData
  420. * a list of rows, starting from <code>firstRowIndex</code>
  421. */
  422. protected void setRowData(int firstRowIndex, List<T> rowData) {
  423. assert firstRowIndex + rowData.size() <= size();
  424. Profiler.enter("AbstractRemoteDataSource.setRowData");
  425. Range received = Range.withLength(firstRowIndex, rowData.size());
  426. if (isWaitingForData()) {
  427. cacheStrategy.onDataArrive(
  428. Duration.currentTimeMillis()
  429. - currentRequestCallback.requestStart,
  430. received.length());
  431. currentRequestCallback = null;
  432. }
  433. Range maxCacheRange = getMaxCacheRange(received);
  434. Range[] partition = received.partitionWith(maxCacheRange);
  435. Range newUsefulData = partition[1];
  436. if (!newUsefulData.isEmpty()) {
  437. // Update the parts that are actually inside
  438. int start = newUsefulData.getStart();
  439. for (int i = start; i < newUsefulData.getEnd(); i++) {
  440. final T row = rowData.get(i - firstRowIndex);
  441. indexToRowMap.put(Integer.valueOf(i), row);
  442. keyToIndexMap.put(getRowKey(row), Integer.valueOf(i));
  443. }
  444. Profiler.enter(
  445. "AbstractRemoteDataSource.setRowData notify dataChangeHandler");
  446. int length = newUsefulData.length();
  447. getHandlers().forEach(dch -> dch.dataUpdated(start, length));
  448. Profiler.leave(
  449. "AbstractRemoteDataSource.setRowData notify dataChangeHandler");
  450. // Potentially extend the range
  451. if (cached.isEmpty()) {
  452. cached = newUsefulData;
  453. } else {
  454. discardStaleCacheEntries();
  455. /*
  456. * everything might've become stale so we need to re-check for
  457. * emptiness.
  458. */
  459. if (!cached.isEmpty()) {
  460. cached = cached.combineWith(newUsefulData);
  461. // Attempt to restore invalidated items
  462. fillCacheFromInvalidatedRows(maxCacheRange);
  463. } else {
  464. cached = newUsefulData;
  465. }
  466. }
  467. getHandlers().forEach(dch -> dch.dataAvailable(cached.getStart(),
  468. cached.length()));
  469. updatePinnedRows(rowData);
  470. }
  471. if (!partition[0].isEmpty() || !partition[2].isEmpty()) {
  472. /*
  473. * FIXME
  474. *
  475. * Got data that we might need in a moment if the container is
  476. * updated before the widget settings. Support for this will be
  477. * implemented later on.
  478. */
  479. // Run a dummy drop from cache for unused rows.
  480. for (int i = 0; i < partition[0].length(); ++i) {
  481. onDropFromCache(i + partition[0].getStart(), rowData.get(i));
  482. }
  483. for (int i = 0; i < partition[2].length(); ++i) {
  484. onDropFromCache(i + partition[2].getStart(), rowData.get(i));
  485. }
  486. }
  487. // Eventually check whether all needed rows are now available
  488. ensureCoverageCheck();
  489. Profiler.leave("AbstractRemoteDataSource.setRowData");
  490. }
  491. /**
  492. * Go through items invalidated by {@link #insertRowData(int, int)}. If the
  493. * server has pre-emptively sent added row data immediately after informing
  494. * of row addition, the invalid cache can be restored to proper index range.
  495. *
  496. * @param maxCacheRange
  497. * the maximum amount of rows that can cached
  498. */
  499. private void fillCacheFromInvalidatedRows(Range maxCacheRange) {
  500. if (invalidatedRows == null || invalidatedRows.isEmpty()) {
  501. // No old invalid cache available
  502. return;
  503. }
  504. Range potentialCache = maxCacheRange.partitionWith(cached)[2];
  505. int start = potentialCache.getStart();
  506. int last = start;
  507. try {
  508. if (potentialCache.isEmpty()
  509. || invalidatedRows.containsKey(start - 1)) {
  510. // Cache is already full or invalidated rows contains unexpected
  511. // indices.
  512. return;
  513. }
  514. for (int i = start; i < potentialCache.getEnd(); ++i) {
  515. if (!invalidatedRows.containsKey(i)) {
  516. return;
  517. }
  518. T row = invalidatedRows.get(i);
  519. indexToRowMap.put(i, row);
  520. keyToIndexMap.put(getRowKey(row), i);
  521. last = i;
  522. }
  523. // Cache filled from invalidated rows. Can continue as if it was
  524. // never invalidated.
  525. invalidatedRows = null;
  526. } finally {
  527. // Update cache range and clean up
  528. if (invalidatedRows != null) {
  529. invalidatedRows.clear();
  530. }
  531. Range updated = Range.between(start, last + 1);
  532. cached = cached.combineWith(updated);
  533. dataChangeHandlers.forEach(dch -> dch
  534. .dataUpdated(updated.getStart(), updated.length()));
  535. }
  536. }
  537. private Stream<DataChangeHandler> getHandlers() {
  538. Set<DataChangeHandler> copy = new LinkedHashSet<>(dataChangeHandlers);
  539. return copy.stream();
  540. }
  541. private void updatePinnedRows(final List<T> rowData) {
  542. for (final T row : rowData) {
  543. final Object key = getRowKey(row);
  544. final RowHandleImpl handle = pinnedRows.get(key);
  545. if (handle != null) {
  546. handle.setRow(row);
  547. }
  548. }
  549. }
  550. /**
  551. * Informs this data source that the server has removed data.
  552. *
  553. * @param firstRowIndex
  554. * the index of the first removed row
  555. * @param count
  556. * the number of removed rows, starting from
  557. * <code>firstRowIndex</code>
  558. */
  559. protected void removeRowData(int firstRowIndex, int count) {
  560. Profiler.enter("AbstractRemoteDataSource.removeRowData");
  561. // Cache was not filled since previous insertRowData. The old rows are
  562. // no longer useful.
  563. if (invalidatedRows != null) {
  564. invalidatedRows.clear();
  565. }
  566. size -= count;
  567. Range removedRange = Range.withLength(firstRowIndex, count);
  568. dropFromCache(removedRange);
  569. // shift indices to fill the cache correctly
  570. int firstMoved = Math.max(firstRowIndex + count, cached.getStart());
  571. for (int i = firstMoved; i < cached.getEnd(); i++) {
  572. moveRowFromIndexToIndex(i, i - count);
  573. }
  574. if (cached.isSubsetOf(removedRange)) {
  575. // Whole cache is part of the removal. Empty cache
  576. cached = Range.withLength(0, 0);
  577. } else if (removedRange.intersects(cached)) {
  578. // Removal and cache share some indices. fix accordingly.
  579. Range[] partitions = cached.partitionWith(removedRange);
  580. Range remainsBefore = partitions[0];
  581. Range transposedRemainsAfter = partitions[2]
  582. .offsetBy(-removedRange.length());
  583. // #8840 either can be empty if the removed range was over the
  584. // cached range
  585. if (remainsBefore.isEmpty()) {
  586. cached = transposedRemainsAfter;
  587. } else if (transposedRemainsAfter.isEmpty()) {
  588. cached = remainsBefore;
  589. } else {
  590. cached = remainsBefore.combineWith(transposedRemainsAfter);
  591. }
  592. } else if (removedRange.getEnd() <= cached.getStart()) {
  593. // Removal was before the cache. offset the cache.
  594. cached = cached.offsetBy(-removedRange.length());
  595. }
  596. getHandlers().forEach(dch -> dch.dataRemoved(firstRowIndex, count));
  597. ensureCoverageCheck();
  598. Profiler.leave("AbstractRemoteDataSource.removeRowData");
  599. }
  600. /**
  601. * Informs this data source that new data has been inserted from the server.
  602. *
  603. * @param firstRowIndex
  604. * the destination index of the new row data
  605. * @param count
  606. * the number of rows inserted
  607. */
  608. protected void insertRowData(int firstRowIndex, int count) {
  609. Profiler.enter("AbstractRemoteDataSource.insertRowData");
  610. // Cache was not filled since previous insertRowData. The old rows are
  611. // no longer useful.
  612. if (invalidatedRows != null) {
  613. invalidatedRows.clear();
  614. }
  615. size += count;
  616. if (firstRowIndex <= cached.getStart()) {
  617. Range oldCached = cached;
  618. cached = cached.offsetBy(count);
  619. for (int i = 1; i <= cached.length(); i++) {
  620. int oldIndex = oldCached.getEnd() - i;
  621. int newIndex = cached.getEnd() - i;
  622. moveRowFromIndexToIndex(oldIndex, newIndex);
  623. }
  624. } else if (cached.contains(firstRowIndex)) {
  625. int oldCacheEnd = cached.getEnd();
  626. /*
  627. * We need to invalidate the cache from the inserted row onwards,
  628. * since the cache wants to be a contiguous range. It doesn't
  629. * support holes.
  630. *
  631. * If holes were supported, we could shift the higher part of
  632. * "cached" and leave a hole the size of "count" in the middle.
  633. */
  634. Range[] splitAt = cached.splitAt(firstRowIndex);
  635. cached = splitAt[0];
  636. Range invalid = splitAt[1];
  637. /*
  638. * If we already have a map in invalidatedRows, we're in a state
  639. * where multiple row manipulations without data received have
  640. * happened and the cache restoration is prevented completely.
  641. */
  642. if (!invalid.isEmpty() && invalidatedRows == null) {
  643. invalidatedRows = new HashMap<>();
  644. // Store all invalidated items to a map. Indices are updated to
  645. // match what they should be after the insertion.
  646. for (int i = invalid.getStart(); i < invalid.getEnd(); ++i) {
  647. invalidatedRows.put(i + count, indexToRowMap.get(i));
  648. }
  649. }
  650. for (int i = firstRowIndex; i < oldCacheEnd; i++) {
  651. T row = indexToRowMap.remove(Integer.valueOf(i));
  652. keyToIndexMap.remove(getRowKey(row));
  653. }
  654. }
  655. getHandlers().forEach(dch -> dch.dataAdded(firstRowIndex, count));
  656. ensureCoverageCheck();
  657. Profiler.leave("AbstractRemoteDataSource.insertRowData");
  658. }
  659. @SuppressWarnings("boxing")
  660. private void moveRowFromIndexToIndex(int oldIndex, int newIndex) {
  661. T row = indexToRowMap.remove(oldIndex);
  662. if (indexToRowMap.containsKey(newIndex)) {
  663. // Old row is about to be overwritten. Remove it from keyCache.
  664. T row2 = indexToRowMap.remove(newIndex);
  665. if (row2 != null) {
  666. keyToIndexMap.remove(getRowKey(row2));
  667. }
  668. }
  669. indexToRowMap.put(newIndex, row);
  670. if (row != null) {
  671. keyToIndexMap.put(getRowKey(row), newIndex);
  672. }
  673. }
  674. /**
  675. * Gets the current range of cached rows.
  676. *
  677. * @return the range of currently cached rows
  678. */
  679. public Range getCachedRange() {
  680. return cached;
  681. }
  682. /**
  683. * Sets the cache strategy that is used to determine how much data is
  684. * fetched and cached.
  685. * <p>
  686. * The new strategy is immediately used to evaluate whether currently cached
  687. * rows should be discarded or new rows should be fetched.
  688. *
  689. * @param cacheStrategy
  690. * a cache strategy implementation, not <code>null</code>
  691. */
  692. public void setCacheStrategy(CacheStrategy cacheStrategy) {
  693. if (cacheStrategy == null) {
  694. throw new IllegalArgumentException();
  695. }
  696. if (this.cacheStrategy != cacheStrategy) {
  697. this.cacheStrategy = cacheStrategy;
  698. checkCacheCoverage();
  699. }
  700. }
  701. private Range getMinCacheRange() {
  702. Range availableDataRange = getAvailableRangeForCache();
  703. Range minCacheRange = cacheStrategy.getMinCacheRange(
  704. requestedAvailability, cached, availableDataRange);
  705. assert minCacheRange.isSubsetOf(availableDataRange);
  706. return minCacheRange;
  707. }
  708. private Range getMaxCacheRange() {
  709. return getMaxCacheRange(getRequestedAvailability());
  710. }
  711. private Range getMaxCacheRange(Range range) {
  712. Range availableDataRange = getAvailableRangeForCache();
  713. Range maxCacheRange = cacheStrategy.getMaxCacheRange(range, cached,
  714. availableDataRange);
  715. assert maxCacheRange.isSubsetOf(availableDataRange);
  716. return maxCacheRange;
  717. }
  718. private Range getAvailableRangeForCache() {
  719. int upperBound = size();
  720. if (upperBound == -1) {
  721. upperBound = requestedAvailability.length();
  722. }
  723. return Range.withLength(0, upperBound);
  724. }
  725. @Override
  726. public RowHandle<T> getHandle(T row) throws IllegalStateException {
  727. Object key = getRowKey(row);
  728. if (key == null) {
  729. throw new NullPointerException(
  730. "key may not be null (row: " + row + ")");
  731. }
  732. if (pinnedRows.containsKey(key)) {
  733. return pinnedRows.get(key);
  734. } else if (keyToIndexMap.containsKey(key)) {
  735. return new RowHandleImpl(row, key);
  736. } else {
  737. throw new IllegalStateException("The cache of this DataSource "
  738. + "does not currently contain the row " + row);
  739. }
  740. }
  741. /**
  742. * Gets a stable key for the row object.
  743. * <p>
  744. * This method is a workaround for the fact that there is no means to force
  745. * proper implementations for {@link #hashCode()} and
  746. * {@link #equals(Object)} methods.
  747. * <p>
  748. * Since the same row object will be created several times for the same
  749. * logical data, the DataSource needs a mechanism to be able to compare two
  750. * objects, and figure out whether or not they represent the same data. Even
  751. * if all the fields of an entity would be changed, it still could represent
  752. * the very same thing (say, a person changes all of her names.)
  753. * <p>
  754. * A very usual and simple example what this could be, is an unique ID for
  755. * this object that would also be stored in a database.
  756. *
  757. * @param row
  758. * the row object for which to get the key
  759. * @return a non-null object that uniquely and consistently represents the
  760. * row object
  761. */
  762. public abstract Object getRowKey(T row);
  763. @Override
  764. public int size() {
  765. return size;
  766. }
  767. /**
  768. * Updates the size, discarding all cached data. This method is used when
  769. * the size of the container is changed without any information about the
  770. * structure of the change. In this case, all cached data is discarded to
  771. * avoid cache offset issues.
  772. * <p>
  773. * If you have information about the structure of the change, use
  774. * {@link #insertRowData(int, int)} or {@link #removeRowData(int, int)} to
  775. * indicate where the inserted or removed rows are located.
  776. *
  777. * @param newSize
  778. * the new size of the container
  779. */
  780. protected void resetDataAndSize(int newSize) {
  781. size = newSize;
  782. indexToRowMap.clear();
  783. keyToIndexMap.clear();
  784. cached = Range.withLength(0, 0);
  785. getHandlers().forEach(dch -> dch.resetDataAndSize(newSize));
  786. }
  787. protected int indexOfKey(Object rowKey) {
  788. if (!keyToIndexMap.containsKey(rowKey)) {
  789. return -1;
  790. } else {
  791. return keyToIndexMap.get(rowKey);
  792. }
  793. }
  794. protected boolean isPinned(T row) {
  795. return pinnedRows.containsKey(getRowKey(row));
  796. }
  797. /**
  798. * Checks if it is possible to currently fetch data from the remote data
  799. * source.
  800. *
  801. * @return <code>true</code> if it is ok to try to fetch data,
  802. * <code>false</code> if it is known that fetching data will fail
  803. * and should not be tried right now.
  804. * @since 7.7.2
  805. */
  806. protected boolean canFetchData() {
  807. return true;
  808. }
  809. }