diff options
9 files changed, 219 insertions, 132 deletions
diff --git a/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java b/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java index c9a01c0c5e..5f8a06ca10 100644 --- a/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java +++ b/client/src/com/vaadin/client/connectors/RpcDataSourceConnector.java @@ -27,7 +27,6 @@ import com.vaadin.client.ServerConnector; import com.vaadin.client.data.AbstractRemoteDataSource; import com.vaadin.client.extensions.AbstractExtensionConnector; import com.vaadin.shared.data.DataProviderRpc; -import com.vaadin.shared.data.DataProviderState; import com.vaadin.shared.data.DataRequestRpc; import com.vaadin.shared.ui.Connect; import com.vaadin.shared.ui.grid.GridState; @@ -89,7 +88,20 @@ public class RpcDataSourceConnector extends AbstractExtensionConnector { private DataRequestRpc rpcProxy = getRpcProxy(DataRequestRpc.class); @Override - protected void requestRows(int firstRowIndex, int numberOfRows) { + protected void requestRows(int firstRowIndex, int numberOfRows, + RequestRowsCallback<JSONObject> callback) { + /* + * If you're looking at this code because you want to learn how to + * use AbstactRemoteDataSource, please look somewhere else instead. + * + * We're not doing things in the conventional way with the callback + * here since Vaadin doesn't directly support RPC with return + * values. We're instead asking the server to push us some data, and + * when we receive pushed data, we just push it along to the + * underlying cache in the same way no matter if it was a genuine + * push or just a result of us requesting rows. + */ + Range cached = getCachedRange(); rpcProxy.requestRows(firstRowIndex, numberOfRows, @@ -113,11 +125,6 @@ public class RpcDataSourceConnector extends AbstractExtensionConnector { } @Override - public int size() { - return getState().containerSize; - } - - @Override protected void pinHandle(RowHandleImpl handle) { // Server only knows if something is pinned or not. No need to pin // multiple times. @@ -146,9 +153,4 @@ public class RpcDataSourceConnector extends AbstractExtensionConnector { protected void extend(ServerConnector target) { ((GridConnector) target).setDataSource(dataSource); } - - @Override - public DataProviderState getState() { - return (DataProviderState) super.getState(); - } } diff --git a/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java b/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java index 7546ac6054..26b60bd2ae 100644 --- a/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java +++ b/client/src/com/vaadin/client/data/AbstractRemoteDataSource.java @@ -32,10 +32,8 @@ import com.vaadin.shared.ui.grid.Range; * Base implementation for data sources that fetch data from a remote system. * This class takes care of caching data and communicating with the data source * user. An implementation of this class should override - * {@link #requestRows(int, int)} to trigger asynchronously loading of data. - * When data is received from the server, new row data should be passed to - * {@link #setRowData(int, List)}. {@link #setEstimatedSize(int)} should be used - * based on estimations of how many rows are available. + * {@link #requestRows(int, int, RequestRowsCallback)} to trigger asynchronously + * loading of data and then pass the loaded data into the provided callback. * * @since * @author Vaadin Ltd @@ -44,6 +42,60 @@ import com.vaadin.shared.ui.grid.Range; */ public abstract class AbstractRemoteDataSource<T> implements DataSource<T> { + /** + * Callback used by + * {@link AbstractRemoteDataSource#requestRows(int, int, RequestRowsCallback)} + * to pass data to the underlying implementation when data has been fetched. + */ + public static class RequestRowsCallback<T> { + private final Range requestedRange; + private final double requestStart; + private final AbstractRemoteDataSource<T> source; + + /** + * Creates a new callback + * + * @param source + * the data source for which the request is made + * @param requestedRange + * the requested row range + */ + protected RequestRowsCallback(AbstractRemoteDataSource<T> source, + Range requestedRange) { + this.source = source; + this.requestedRange = requestedRange; + + requestStart = Duration.currentTimeMillis(); + } + + /** + * Called by the + * {@link AbstractRemoteDataSource#requestRows(int, int, RequestRowsCallback)} + * implementation when data has been received. + * + * @param rowData + * a list of row objects starting at the requested offset + * @param totalSize + * the total number of rows available at the remote end + */ + public void onResponse(List<T> rowData, int totalSize) { + if (source.size != totalSize) { + source.resetDataAndSize(totalSize); + } + source.setRowData(requestedRange.getStart(), rowData); + } + + /** + * Gets the range of rows that was requested. + * + * @return the requsted row range + */ + public Range getRequestedRange() { + return requestedRange; + } + + } + protected class RowHandleImpl extends RowHandle<T> { private T row; private final Object key; @@ -120,13 +172,7 @@ public abstract class AbstractRemoteDataSource<T> implements DataSource<T> { } } - /** - * Records the start of the previously requested range. This is used when - * tracking request timings to distinguish between explicit responses and - * arbitrary updates pushed from the server. - */ - private int lastRequestStart = -1; - private double pendingRequestTime; + private RequestRowsCallback<T> currentRequestCallback; private boolean coverageCheckPending = false; @@ -153,6 +199,9 @@ public abstract class AbstractRemoteDataSource<T> implements DataSource<T> { private Map<Object, RowHandleImpl> pinnedRows = new HashMap<Object, RowHandleImpl>(); protected Collection<T> temporarilyPinnedRows = Collections.emptySet(); + // Size not yet known + private int size = -1; + private void ensureCoverageCheck() { if (!coverageCheckPending) { coverageCheckPending = true; @@ -214,8 +263,9 @@ public abstract class AbstractRemoteDataSource<T> implements DataSource<T> { } private void checkCacheCoverage() { - if (lastRequestStart != -1) { - // Anyone clearing lastRequestStart should run this method again + if (currentRequestCallback != null) { + // Anyone clearing currentRequestCallback should run this method + // again return; } @@ -269,22 +319,23 @@ public abstract class AbstractRemoteDataSource<T> implements DataSource<T> { if (range.isEmpty()) { return; } - lastRequestStart = range.getStart(); - pendingRequestTime = Duration.currentTimeMillis(); - requestRows(range.getStart(), range.length()); + currentRequestCallback = new RequestRowsCallback<T>(this, range); + requestRows(range.getStart(), range.length(), currentRequestCallback); } /** - * Triggers fetching rows from the remote data source. - * {@link #setRowData(int, List)} should be invoked with data for the - * requested rows when they have been received. + * Triggers fetching rows from the remote data source. The provided callback + * should be informed when the requested rows have been received. * * @param firstRowIndex * the index of the first row to fetch * @param numberOfRows * the number of rows to fetch + * @param callback + * callback to inform when the requested rows are available */ - protected abstract void requestRows(int firstRowIndex, int numberOfRows); + protected abstract void requestRows(int firstRowIndex, int numberOfRows, + RequestRowsCallback<T> callback); @Override public T getRow(int rowIndex) { @@ -321,16 +372,18 @@ public abstract class AbstractRemoteDataSource<T> implements DataSource<T> { */ protected void setRowData(int firstRowIndex, List<T> rowData) { + assert firstRowIndex + rowData.size() <= size(); + Profiler.enter("AbstractRemoteDataSource.setRowData"); Range received = Range.withLength(firstRowIndex, rowData.size()); - if (firstRowIndex == lastRequestStart) { - // Provide timing information if we know when we asked for this data + if (currentRequestCallback != null) { cacheStrategy.onDataArrive(Duration.currentTimeMillis() - - pendingRequestTime, received.length()); + - currentRequestCallback.requestStart, received.length()); + + currentRequestCallback = null; } - lastRequestStart = -1; Range maxCacheRange = getMaxCacheRange(); @@ -411,6 +464,8 @@ public abstract class AbstractRemoteDataSource<T> implements DataSource<T> { protected void removeRowData(int firstRowIndex, int count) { Profiler.enter("AbstractRemoteDataSource.removeRowData"); + size -= count; + // shift indices to fill the cache correctly for (int i = firstRowIndex + count; i < cached.getEnd(); i++) { moveRowFromIndexToIndex(i, i - count); @@ -445,6 +500,8 @@ public abstract class AbstractRemoteDataSource<T> implements DataSource<T> { protected void insertRowData(int firstRowIndex, int count) { Profiler.enter("AbstractRemoteDataSource.insertRowData"); + size += count; + if (cached.contains(firstRowIndex)) { int oldCacheEnd = cached.getEnd(); /* @@ -523,7 +580,7 @@ public abstract class AbstractRemoteDataSource<T> implements DataSource<T> { } private Range getMinCacheRange() { - Range availableDataRange = Range.withLength(0, size()); + Range availableDataRange = getAvailableRangeForCache(); Range minCacheRange = cacheStrategy.getMinCacheRange( requestedAvailability, cached, availableDataRange); @@ -533,7 +590,7 @@ public abstract class AbstractRemoteDataSource<T> implements DataSource<T> { } private Range getMaxCacheRange() { - Range availableDataRange = Range.withLength(0, size()); + Range availableDataRange = getAvailableRangeForCache(); Range maxCacheRange = cacheStrategy.getMaxCacheRange( requestedAvailability, cached, availableDataRange); @@ -542,6 +599,14 @@ public abstract class AbstractRemoteDataSource<T> implements DataSource<T> { return maxCacheRange; } + private Range getAvailableRangeForCache() { + int upperBound = size(); + if (upperBound == -1) { + upperBound = requestedAvailability.length(); + } + return Range.withLength(0, upperBound); + } + @Override public RowHandle<T> getHandle(T row) throws IllegalStateException { Object key = getRowKey(row); @@ -584,7 +649,26 @@ public abstract class AbstractRemoteDataSource<T> implements DataSource<T> { */ abstract public Object getRowKey(T row); + @Override + public int size() { + return size; + } + + /** + * Updates the size, discarding all cached data. This method is used when + * the size of the container is changed without any information about the + * structure of the change. In this case, all cached data is discarded to + * avoid cache offset issues. + * <p> + * If you have information about the structure of the change, use + * {@link #insertRowData(int, int)} or {@link #removeRowData(int, int)} to + * indicate where the inserted or removed rows are located. + * + * @param newSize + * the new size of the container + */ protected void resetDataAndSize(int newSize) { + size = newSize; dropFromCache(getCachedRange()); cached = Range.withLength(0, 0); assertDataChangeHandlerIsInjected(); diff --git a/client/src/com/vaadin/client/ui/grid/Escalator.java b/client/src/com/vaadin/client/ui/grid/Escalator.java index deb80b9e00..1bbcaaf166 100644 --- a/client/src/com/vaadin/client/ui/grid/Escalator.java +++ b/client/src/com/vaadin/client/ui/grid/Escalator.java @@ -5047,4 +5047,14 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker Scheduler.get().scheduleDeferred(layoutCommand); } } + + /** + * Gets the maximum number of body rows that can be visible on the screen at + * once. + * + * @return the maximum capacity + */ + public int getMaxVisibleRowCount() { + return body.getMaxEscalatorRowCapacity(); + } } diff --git a/client/src/com/vaadin/client/ui/grid/Grid.java b/client/src/com/vaadin/client/ui/grid/Grid.java index 2ebe1b62a3..91d796692c 100644 --- a/client/src/com/vaadin/client/ui/grid/Grid.java +++ b/client/src/com/vaadin/client/ui/grid/Grid.java @@ -2934,7 +2934,7 @@ public class Grid<T> extends ResizeComposite implements @Override public void onRowVisibilityChange( RowVisibilityChangeEvent event) { - if (dataSource != null && dataSource.size() > 0) { + if (dataSource != null && dataSource.size() != 0) { dataIsBeingFetched = true; dataSource.ensureAvailability( event.getFirstVisibleRow(), @@ -3661,7 +3661,18 @@ public class Grid<T> extends ResizeComposite implements escalator.getBody().removeRows(0, previousRowCount); } + setEscalatorSizeFromDataSource(); + } + + private void setEscalatorSizeFromDataSource() { + assert escalator.getBody().getRowCount() == 0; + int size = dataSource.size(); + if (size == -1 && isAttached()) { + // Exact size is not yet known, start with some reasonable guess + // just to get an initial backend request going + size = getEscalator().getMaxVisibleRowCount(); + } if (size > 0) { escalator.getBody().insertRows(0, size); } @@ -5075,4 +5086,13 @@ public class Grid<T> extends ResizeComposite implements public Widget getEditorRowWidget(GridColumn<?, T> column) { return editorRow.getWidget(column); } + + @Override + protected void onAttach() { + super.onAttach(); + + if (getEscalator().getBody().getRowCount() == 0 && dataSource != null) { + setEscalatorSizeFromDataSource(); + } + } } diff --git a/server/src/com/vaadin/data/RpcDataProviderExtension.java b/server/src/com/vaadin/data/RpcDataProviderExtension.java index 9f7c783537..f28d95f610 100644 --- a/server/src/com/vaadin/data/RpcDataProviderExtension.java +++ b/server/src/com/vaadin/data/RpcDataProviderExtension.java @@ -45,7 +45,6 @@ import com.vaadin.server.AbstractExtension; import com.vaadin.server.ClientConnector; import com.vaadin.server.KeyMapper; import com.vaadin.shared.data.DataProviderRpc; -import com.vaadin.shared.data.DataProviderState; import com.vaadin.shared.data.DataRequestRpc; import com.vaadin.shared.ui.grid.GridState; import com.vaadin.shared.ui.grid.Range; @@ -638,7 +637,6 @@ public class RpcDataProviderExtension extends AbstractExtension { activeRowHandler.activeRange = Range.withLength(0, 0); activeRowHandler.valueChangeListeners.clear(); rpc.resetDataAndSize(event.getContainer().size()); - getState().containerSize = event.getContainer().size(); } } }; @@ -665,20 +663,8 @@ public class RpcDataProviderExtension extends AbstractExtension { public void requestRows(int firstRow, int numberOfRows, int firstCachedRowIndex, int cacheSize) { - Range active = Range.withLength(firstRow, numberOfRows); - if (cacheSize != 0) { - Range cached = Range.withLength(firstCachedRowIndex, - cacheSize); - active = active.combineWith(cached); - } - - List<?> itemIds = RpcDataProviderExtension.this.container - .getItemIds(firstRow, numberOfRows); - keyMapper.preActiveRowsChange(active, firstRow, itemIds); - pushRows(firstRow, itemIds); - - activeRowHandler.setActiveRows(active.getStart(), - active.length()); + pushRowData(firstRow, numberOfRows, firstCachedRowIndex, + cacheSize); } @Override @@ -696,8 +682,6 @@ public class RpcDataProviderExtension extends AbstractExtension { } }); - getState().containerSize = container.size(); - if (container instanceof ItemSetChangeNotifier) { ((ItemSetChangeNotifier) container) .addItemSetChangeListener(itemListener); @@ -708,15 +692,43 @@ public class RpcDataProviderExtension extends AbstractExtension { @Override public void beforeClientResponse(boolean initial) { super.beforeClientResponse(initial); - clientInitialized = true; + if (!clientInitialized) { + clientInitialized = true; + + /* + * Push initial set of rows, assuming Grid will initially be + * rendered scrolled to the top and with a decent amount of rows + * visible. If this guess is right, initial data can be shown + * without a round-trip and if it's wrong, the data will simply be + * discarded. + */ + int size = container.size(); + rpc.resetDataAndSize(size); + + int numberOfRows = Math.min(40, size); + pushRowData(0, numberOfRows, 0, 0); + } } - private void pushRows(int firstRow, List<?> itemIds) { + private void pushRowData(int firstRowToPush, int numberOfRows, + int firstCachedRowIndex, int cacheSize) { + Range active = Range.withLength(firstRowToPush, numberOfRows); + if (cacheSize != 0) { + Range cached = Range.withLength(firstCachedRowIndex, cacheSize); + active = active.combineWith(cached); + } + + List<?> itemIds = RpcDataProviderExtension.this.container.getItemIds( + firstRowToPush, numberOfRows); + keyMapper.preActiveRowsChange(active, firstRowToPush, itemIds); + JsonArray rows = Json.createArray(); for (int i = 0; i < itemIds.size(); ++i) { rows.set(i, getRowData(getGrid().getColumns(), itemIds.get(i))); } - rpc.setRowData(firstRow, rows.toJson()); + rpc.setRowData(firstRowToPush, rows.toJson()); + + activeRowHandler.setActiveRows(active.getStart(), active.length()); } private JsonValue getRowData(Collection<Column> columns, Object itemId) { @@ -776,11 +788,6 @@ public class RpcDataProviderExtension extends AbstractExtension { } } - @Override - protected DataProviderState getState() { - return (DataProviderState) super.getState(); - } - /** * Makes the data source available to the given {@link Grid} component. * @@ -802,7 +809,6 @@ public class RpcDataProviderExtension extends AbstractExtension { * the number of rows inserted at <code>index</code> */ private void insertRowData(int index, int count) { - getState().containerSize += count; if (clientInitialized) { rpc.insertRowData(index, count); } @@ -821,7 +827,6 @@ public class RpcDataProviderExtension extends AbstractExtension { * the item id of the first removed item */ private void removeRowData(int firstIndex, int count) { - getState().containerSize -= count; if (clientInitialized) { rpc.removeRowData(firstIndex, count); } @@ -864,9 +869,7 @@ public class RpcDataProviderExtension extends AbstractExtension { int firstRow = activeRowHandler.activeRange.getStart(); int numberOfRows = activeRowHandler.activeRange.length(); - List<?> itemIds = RpcDataProviderExtension.this.container.getItemIds( - firstRow, numberOfRows); - pushRows(firstRow, itemIds); + pushRowData(firstRow, numberOfRows, firstRow, numberOfRows); } @Override diff --git a/shared/src/com/vaadin/shared/data/DataProviderState.java b/shared/src/com/vaadin/shared/data/DataProviderState.java deleted file mode 100644 index 76d68e8352..0000000000 --- a/shared/src/com/vaadin/shared/data/DataProviderState.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2000-2014 Vaadin Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.vaadin.shared.data; - -import com.vaadin.shared.communication.SharedState; - -/** - * Shared state used by client-side data sources. - * - * @since - * @author Vaadin Ltd - */ -public class DataProviderState extends SharedState { - /** - * The size of the container. - */ - public int containerSize; -} diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridEditorRowTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridEditorRowTest.java index 7f3d4ff325..d649e4a97c 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridEditorRowTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridEditorRowTest.java @@ -55,7 +55,7 @@ public class GridEditorRowTest extends GridBasicFeaturesTest { selectMenuPath("Component", "Editor row", "Edit item 5"); assertNull(getEditorRow()); assertEquals( - "4. Exception occured, java.lang.IllegalStateExceptionEditor row is not enabled", + "5. Exception occured, java.lang.IllegalStateExceptionEditor row is not enabled", getLogRow(0)); } @@ -65,7 +65,7 @@ public class GridEditorRowTest extends GridBasicFeaturesTest { selectMenuPath("Component", "Editor row", "Enabled"); assertNotNull(getEditorRow()); assertEquals( - "4. Exception occured, java.lang.IllegalStateExceptionCannot disable the editor row while an item (5) is being edited.", + "5. Exception occured, java.lang.IllegalStateExceptionCannot disable the editor row while an item (5) is being edited.", getLogRow(0)); } diff --git a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridStaticSectionComponentTest.java b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridStaticSectionComponentTest.java index 21bf667bae..76382da035 100644 --- a/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridStaticSectionComponentTest.java +++ b/uitest/src/com/vaadin/tests/components/grid/basicfeatures/server/GridStaticSectionComponentTest.java @@ -35,7 +35,7 @@ public class GridStaticSectionComponentTest extends GridBasicFeaturesTest { getGridElement().$(ButtonElement.class).first().click(); - assertEquals("2. Button clicked!", getLogRow(0)); + assertEquals("3. Button clicked!", getLogRow(0)); } @Test @@ -49,6 +49,6 @@ public class GridStaticSectionComponentTest extends GridBasicFeaturesTest { getGridElement().$(ButtonElement.class).first().click(); - assertEquals("4. Button clicked!", getLogRow(0)); + assertEquals("5. Button clicked!", getLogRow(0)); } } diff --git a/uitest/src/com/vaadin/tests/widgetset/client/grid/GridClientDataSourcesWidget.java b/uitest/src/com/vaadin/tests/widgetset/client/grid/GridClientDataSourcesWidget.java index 76a146bfd2..aca11cfab3 100644 --- a/uitest/src/com/vaadin/tests/widgetset/client/grid/GridClientDataSourcesWidget.java +++ b/uitest/src/com/vaadin/tests/widgetset/client/grid/GridClientDataSourcesWidget.java @@ -18,6 +18,7 @@ package com.vaadin.tests.widgetset.client.grid; import java.util.ArrayList; import java.util.List; +import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.vaadin.client.data.AbstractRemoteDataSource; import com.vaadin.client.ui.grid.Grid; @@ -28,6 +29,10 @@ import com.vaadin.client.ui.grid.renderers.TextRenderer; public class GridClientDataSourcesWidget extends PureGWTTestApplication<Grid<String[]>> { + private interface RestCallback { + void onResponse(RestishDataSource.Backend.Result result); + } + /** * This is an emulated datasource that has a back-end that changes size * constantly. The back-end is unable to actively push data to Grid. @@ -66,8 +71,6 @@ public class GridClientDataSourcesWidget extends * </ol> */ private class RestishDataSource extends AbstractRemoteDataSource<String[]> { - private int currentSize = 0; - /** * Pretend like this class doesn't exist. It just simulates a backend * somewhere. @@ -83,14 +86,22 @@ public class GridClientDataSourcesWidget extends private int size = 200; private int modCount = 0; - public Result query(int firstRowIndex, int numberOfRows) { - Result result = new Result(); + public void query(int firstRowIndex, int numberOfRows, + final RestCallback callback) { + final Result result = new Result(); result.size = size; - result.rows = getRows(firstRowIndex, numberOfRows); - return result; + result.rows = fetchRows(firstRowIndex, numberOfRows); + + Scheduler.get().scheduleDeferred(new ScheduledCommand() { + @Override + public void execute() { + callback.onResponse(result); + } + }); + } - private List<String[]> getRows(int firstRowIndex, int numberOfRows) { + private List<String[]> fetchRows(int firstRowIndex, int numberOfRows) { List<String[]> rows = new ArrayList<String[]>(); for (int i = 0; i < numberOfRows; i++) { String id = String.valueOf(firstRowIndex + i); @@ -121,29 +132,18 @@ public class GridClientDataSourcesWidget extends public RestishDataSource() { backend = new Backend(); - currentSize = backend.size; - } - - @Override - public int size() { - return currentSize; } @Override - protected void requestRows(int firstRowIndex, int numberOfRows) { - Backend.Result result = backend.query(firstRowIndex, numberOfRows); - final List<String[]> newRows = result.rows; - - // order matters: first set row data, only then modify size. + protected void requestRows(int firstRowIndex, int numberOfRows, + final RequestRowsCallback<String[]> callback) { - /* Here's the requested data. Process it, please. */ - setRowData(firstRowIndex, newRows); - - /* Let's check whether the backend size changed. */ - if (currentSize != result.size) { - currentSize = result.size; - resetDataAndSize(currentSize); - } + backend.query(firstRowIndex, numberOfRows, new RestCallback() { + @Override + public void onResponse(Backend.Result result) { + callback.onResponse(result.rows, result.size); + } + }); } @Override |