Merge "Further split DataSource callbacks, so any method available is valid" into oc-mr1-support-27.0-dev
diff --git a/paging/common/src/main/java/android/arch/paging/ContiguousDataSource.java b/paging/common/src/main/java/android/arch/paging/ContiguousDataSource.java
index 03f2e86..38b7cc0 100644
--- a/paging/common/src/main/java/android/arch/paging/ContiguousDataSource.java
+++ b/paging/common/src/main/java/android/arch/paging/ContiguousDataSource.java
@@ -19,21 +19,23 @@
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import java.util.concurrent.Executor;
+
abstract class ContiguousDataSource<Key, Value> extends DataSource<Key, Value> {
@Override
boolean isContiguous() {
return true;
}
- public abstract void loadInitial(@Nullable Key key, int initialLoadSize,
- boolean enablePlaceholders,
- @NonNull InitialLoadCallback<Value> callback);
+ abstract void loadInitial(@Nullable Key key, int initialLoadSize,
+ int pageSize, boolean enablePlaceholders,
+ @NonNull Executor mainThreadExecutor, @NonNull PageResult.Receiver<Value> receiver);
abstract void loadAfter(int currentEndIndex, @NonNull Value currentEndItem, int pageSize,
- @NonNull LoadCallback<Value> callback);
+ @NonNull Executor mainThreadExecutor, @NonNull PageResult.Receiver<Value> receiver);
abstract void loadBefore(int currentBeginIndex, @NonNull Value currentBeginItem, int pageSize,
- @NonNull LoadCallback<Value> callback);
+ @NonNull Executor mainThreadExecutor, @NonNull PageResult.Receiver<Value> receiver);
/**
* Get the key from either the position, or item, or null if position/item invalid.
diff --git a/paging/common/src/main/java/android/arch/paging/ContiguousPagedList.java b/paging/common/src/main/java/android/arch/paging/ContiguousPagedList.java
index b54cb84..a134e44 100644
--- a/paging/common/src/main/java/android/arch/paging/ContiguousPagedList.java
+++ b/paging/common/src/main/java/android/arch/paging/ContiguousPagedList.java
@@ -87,21 +87,12 @@
if (mDataSource.isInvalid()) {
detach();
} else {
- @DataSource.LoadCountType int type = mConfig.enablePlaceholders
- ? DataSource.LOAD_COUNT_ACCEPTED
- : DataSource.LOAD_COUNT_PREVENTED;
-
- DataSource.InitialLoadCallback<V> callback = new DataSource.InitialLoadCallback<>(
- type, mConfig.pageSize, mDataSource, mReceiver);
mDataSource.loadInitial(key,
mConfig.initialLoadSizeHint,
+ mConfig.pageSize,
mConfig.enablePlaceholders,
- callback);
-
- // If initialLoad's callback is not called within the body, we force any following calls
- // to post to the UI thread. This constructor may be run on a background thread, but
- // after constructor, mutation must happen on UI thread.
- callback.setPostExecutor(mMainThreadExecutor);
+ mMainThreadExecutor,
+ mReceiver);
}
}
@@ -193,9 +184,8 @@
if (mDataSource.isInvalid()) {
detach();
} else {
- DataSource.LoadCallback<V> callback = new DataSource.LoadCallback<>(
- PageResult.PREPEND, mMainThreadExecutor, mDataSource, mReceiver);
- mDataSource.loadBefore(position, item, mConfig.pageSize, callback);
+ mDataSource.loadBefore(position, item, mConfig.pageSize,
+ mMainThreadExecutor, mReceiver);
}
}
@@ -223,9 +213,8 @@
if (mDataSource.isInvalid()) {
detach();
} else {
- DataSource.LoadCallback<V> callback = new DataSource.LoadCallback<>(
- PageResult.APPEND, mMainThreadExecutor, mDataSource, mReceiver);
- mDataSource.loadAfter(position, item, mConfig.pageSize, callback);
+ mDataSource.loadAfter(position, item, mConfig.pageSize,
+ mMainThreadExecutor, mReceiver);
}
}
});
diff --git a/paging/common/src/main/java/android/arch/paging/DataSource.java b/paging/common/src/main/java/android/arch/paging/DataSource.java
index 2e41cf6..b82d4e6 100644
--- a/paging/common/src/main/java/android/arch/paging/DataSource.java
+++ b/paging/common/src/main/java/android/arch/paging/DataSource.java
@@ -16,14 +16,11 @@
package android.arch.paging;
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
import android.support.annotation.AnyThread;
-import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;
-import java.lang.annotation.Retention;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
@@ -137,173 +134,45 @@
*/
abstract boolean isContiguous();
- /**
- * Callback for DataSource initial loading methods to return data and position/count
- * information.
- * <p>
- * A callback can be called only once, and will throw if called again.
- * <p>
- * It is always valid for a DataSource loading method that takes a callback to stash the
- * callback and call it later. This enables DataSources to be fully asynchronous, and to handle
- * temporary, recoverable error states (such as a network error that can be retried).
- *
- * @param <T> Type of items being loaded.
- */
- public static class InitialLoadCallback<T> extends LoadCallback<T> {
- private final int mPageSize;
-
- InitialLoadCallback(@LoadCountType int countType, int pageSize,
- DataSource dataSource, PageResult.Receiver<T> receiver) {
- super(PageResult.INIT, countType, dataSource, receiver);
- mPageSize = pageSize;
- if (mPageSize < 1) {
- throw new IllegalArgumentException("Page size must be non-negative");
- }
- }
-
- /**
- * Called to pass initial load state from a DataSource.
- * <p>
- * Call this method from your DataSource's {@code loadInitial} function to return data,
- * and inform how many placeholders should be shown before and after. If counting is cheap
- * to compute (for example, if a network load returns the information regardless), it's
- * recommended to pass data back through this method.
- *
- * @param data List of items loaded from the DataSource. If this is empty, the DataSource
- * is treated as empty, and no further loads will occur.
- * @param position Position of the item at the front of the list, relative to the total
- * count. If there are {@code N} items before the items in data that can be
- * loaded from this DataSource, pass {@code N}.
- * @param totalCount Total number of items that may be returned from this DataSource.
- * Includes the number in the initial {@code data} parameter
- * as well as any items that can be loaded in front or behind of
- * {@code data}.
- */
- public void onResult(@NonNull List<T> data, int position, int totalCount) {
+ static class BaseLoadCallback<T> {
+ static void validateInitialLoadParams(@NonNull List<?> data, int position, int totalCount) {
if (position < 0) {
throw new IllegalArgumentException("Position must be non-negative");
}
if (data.size() + position > totalCount) {
throw new IllegalArgumentException(
- "List size + position too large; last item in list beyond totalCount");
+ "List size + position too large, last item in list beyond totalCount.");
}
if (data.size() == 0 && totalCount > 0) {
throw new IllegalArgumentException(
"Initial result cannot be empty if items are present in data set.");
}
- if (mCountType == LOAD_COUNT_REQUIRED_TILED
- && position + data.size() != totalCount
- && data.size() % mPageSize != 0) {
- throw new IllegalArgumentException("PositionalDataSource requires initial load size"
- + " to be a multiple of page size to support internal tiling.");
- }
-
- int trailingUnloadedCount = totalCount - position - data.size();
- if (mCountType == LOAD_COUNT_REQUIRED_TILED || mCountType == LOAD_COUNT_ACCEPTED) {
- dispatchResultToReceiver(new PageResult<>(
- data, position, trailingUnloadedCount, 0));
- } else {
- dispatchResultToReceiver(new PageResult<>(data, position));
- }
}
- /**
- * Called to pass initial load state from a DataSource without supporting placeholders.
- * <p>
- * Call this method from your DataSource's {@code loadInitial} function to return data,
- * if position is known but total size is not. If counting is not expensive, consider
- * calling the three parameter variant: {@link #onResult(List, int, int)}.
- *
- * @param data List of items loaded from the DataSource. If this is empty, the DataSource
- * is treated as empty, and no further loads will occur.
- * @param position Position of the item at the front of the list. If there are {@code N}
- * items before the items in data that can be provided by this DataSource,
- * pass {@code N}.
- */
- void onResult(@NonNull List<T> data, int position) {
- // not counting, don't need to check mAcceptCount
- dispatchResultToReceiver(new PageResult<>(
- data, 0, 0, position));
- }
- }
-
- @Retention(SOURCE)
- @IntDef({LOAD_COUNT_PREVENTED, LOAD_COUNT_ACCEPTED, LOAD_COUNT_REQUIRED_TILED})
- @interface LoadCountType {}
- static final int LOAD_COUNT_PREVENTED = 0;
- static final int LOAD_COUNT_ACCEPTED = 1;
- static final int LOAD_COUNT_REQUIRED_TILED = 2;
-
- /**
- * Callback for DataSource loading methods to return data.
- * <p>
- * A callback can be called only once, and will throw if called again.
- * <p>
- * It is always valid for a DataSource loading method that takes a callback to stash the
- * callback and call it later. This enables DataSources to be fully asynchronous, and to handle
- * temporary, recoverable error states (such as a network error that can be retried).
- *
- * @param <T> Type of items being loaded.
- */
- public static class LoadCallback<T> {
@PageResult.ResultType
final int mResultType;
- @LoadCountType
- final int mCountType;
private final DataSource mDataSource;
private final PageResult.Receiver<T> mReceiver;
- private int mPositionOffset = 0;
-
// mSignalLock protects mPostExecutor, and mHasSignalled
private final Object mSignalLock = new Object();
private Executor mPostExecutor = null;
private boolean mHasSignalled = false;
- private LoadCallback(@PageResult.ResultType int resultType, @LoadCountType int countType,
- DataSource dataSource, PageResult.Receiver<T> receiver) {
+ BaseLoadCallback(@PageResult.ResultType int resultType, @NonNull DataSource dataSource,
+ @Nullable Executor mainThreadExecutor, @NonNull PageResult.Receiver<T> receiver) {
mResultType = resultType;
- mCountType = countType;
- mDataSource = dataSource;
- mReceiver = receiver;
- }
-
- LoadCallback(int type, Executor mainThreadExecutor,
- DataSource dataSource, PageResult.Receiver<T> receiver) {
- mResultType = type;
- mCountType = LOAD_COUNT_PREVENTED;
mPostExecutor = mainThreadExecutor;
mDataSource = dataSource;
mReceiver = receiver;
}
- void setPositionOffset(int positionOffset) {
- mPositionOffset = positionOffset;
- }
-
void setPostExecutor(Executor postExecutor) {
synchronized (mSignalLock) {
mPostExecutor = postExecutor;
}
}
- /**
- * Called to pass loaded data from a DataSource.
- * <p>
- * Call this method from your DataSource's {@code load} methods to return data.
- *
- * @param data List of items loaded from the DataSource.
- */
- public void onResult(@NonNull List<T> data) {
- if (mCountType == LOAD_COUNT_REQUIRED_TILED && !data.isEmpty()) {
- throw new IllegalArgumentException(
- "PositionalDataSource requires calling the three argument version of"
- + " InitialLoadCallback.onResult() to pass position information");
- }
- dispatchResultToReceiver(new PageResult<>(
- data, 0, 0, mPositionOffset));
- }
-
void dispatchResultToReceiver(final @NonNull PageResult<T> result) {
Executor executor;
synchronized (mSignalLock) {
diff --git a/paging/common/src/main/java/android/arch/paging/KeyedDataSource.java b/paging/common/src/main/java/android/arch/paging/KeyedDataSource.java
index b6656f3..4f62692 100644
--- a/paging/common/src/main/java/android/arch/paging/KeyedDataSource.java
+++ b/paging/common/src/main/java/android/arch/paging/KeyedDataSource.java
@@ -20,6 +20,8 @@
import android.support.annotation.Nullable;
import java.util.List;
+import java.util.concurrent.Executor;
+
/**
* Incremental data loader for paging keyed content, where loaded content uses previously loaded
* items as input to future loads.
@@ -32,16 +34,97 @@
* @param <Value> Type of items being loaded by the DataSource.
*/
public abstract class KeyedDataSource<Key, Value> extends ContiguousDataSource<Key, Value> {
- @Override
- final void loadAfter(int currentEndIndex, @NonNull Value currentEndItem, int pageSize,
- @NonNull LoadCallback<Value> callback) {
- loadAfter(getKey(currentEndItem), pageSize, callback);
+
+ /**
+ * Callback for KeyedDataSource initial loading methods to return data and (optionally)
+ * position/count information.
+ * <p>
+ * A callback can be called only once, and will throw if called again.
+ * <p>
+ * It is always valid for a DataSource loading method that takes a callback to stash the
+ * callback and call it later. This enables DataSources to be fully asynchronous, and to handle
+ * temporary, recoverable error states (such as a network error that can be retried).
+ *
+ * @param <T> Type of items being loaded.
+ */
+ public static class InitialLoadCallback<T> extends LoadCallback<T> {
+ private final boolean mCountingEnabled;
+ InitialLoadCallback(@NonNull KeyedDataSource dataSource, boolean countingEnabled,
+ @NonNull PageResult.Receiver<T> receiver) {
+ super(dataSource, PageResult.INIT, null, receiver);
+ mCountingEnabled = countingEnabled;
+ }
+
+ /**
+ * Called to pass initial load state from a DataSource.
+ * <p>
+ * Call this method from your DataSource's {@code loadInitial} function to return data,
+ * and inform how many placeholders should be shown before and after. If counting is cheap
+ * to compute (for example, if a network load returns the information regardless), it's
+ * recommended to pass data back through this method.
+ * <p>
+ * It is always valid to pass a different amount of data than what is requested. Pass an
+ * empty list if there is no more data to load.
+ *
+ * @param data List of items loaded from the DataSource. If this is empty, the DataSource
+ * is treated as empty, and no further loads will occur.
+ * @param position Position of the item at the front of the list. If there are {@code N}
+ * items before the items in data that can be loaded from this DataSource,
+ * pass {@code N}.
+ * @param totalCount Total number of items that may be returned from this DataSource.
+ * Includes the number in the initial {@code data} parameter
+ * as well as any items that can be loaded in front or behind of
+ * {@code data}.
+ */
+ public void onResult(@NonNull List<T> data, int position, int totalCount) {
+ validateInitialLoadParams(data, position, totalCount);
+
+ int trailingUnloadedCount = totalCount - position - data.size();
+ if (mCountingEnabled) {
+ dispatchResultToReceiver(new PageResult<>(
+ data, position, trailingUnloadedCount, 0));
+ } else {
+ dispatchResultToReceiver(new PageResult<>(data, position));
+ }
+ }
}
- @Override
- final void loadBefore(int currentBeginIndex, @NonNull Value currentBeginItem, int pageSize,
- @NonNull LoadCallback<Value> callback) {
- loadBefore(getKey(currentBeginItem), pageSize, callback);
+ /**
+ * Callback for KeyedDataSource {@link #loadBefore(Object, int, LoadCallback)}
+ * and {@link #loadAfter(Object, int, LoadCallback)} methods to return data.
+ * <p>
+ * A callback can be called only once, and will throw if called again.
+ * <p>
+ * It is always valid for a DataSource loading method that takes a callback to stash the
+ * callback and call it later. This enables DataSources to be fully asynchronous, and to handle
+ * temporary, recoverable error states (such as a network error that can be retried).
+ *
+ * @param <T> Type of items being loaded.
+ */
+ public static class LoadCallback<T> extends BaseLoadCallback<T> {
+ LoadCallback(@NonNull KeyedDataSource dataSource, @PageResult.ResultType int type,
+ @Nullable Executor mainThreadExecutor, @NonNull PageResult.Receiver<T> receiver) {
+ super(type, dataSource, mainThreadExecutor, receiver);
+ }
+
+ /**
+ * Called to pass loaded data from a DataSource.
+ * <p>
+ * Call this method from your KeyedDataSource's
+ * {@link #loadBefore(Object, int, LoadCallback)} and
+ * {@link #loadAfter(Object, int, LoadCallback)} methods to return data.
+ * <p>
+ * Call this from {@link #loadInitial(Object, int, boolean, InitialLoadCallback)} to
+ * initialize without counting available data, or supporting placeholders.
+ * <p>
+ * It is always valid to pass a different amount of data than what is requested. Pass an
+ * empty list if there is no more data to load.
+ *
+ * @param data List of items loaded from the KeyedDataSource.
+ */
+ public void onResult(@NonNull List<T> data) {
+ dispatchResultToReceiver(new PageResult<>(data, 0, 0, 0));
+ }
}
@Nullable
@@ -54,6 +137,33 @@
return getKey(item);
}
+ @Override
+ public void loadInitial(@Nullable Key key, int initialLoadSize, int pageSize,
+ boolean enablePlaceholders, @NonNull Executor mainThreadExecutor,
+ @NonNull PageResult.Receiver<Value> receiver) {
+ InitialLoadCallback<Value> callback =
+ new InitialLoadCallback<>(this, enablePlaceholders, receiver);
+ loadInitial(key, initialLoadSize, enablePlaceholders, callback);
+
+ // If initialLoad's callback is not called within the body, we force any following calls
+ // to post to the UI thread. This constructor may be run on a background thread, but
+ // after constructor, mutation must happen on UI thread.
+ callback.setPostExecutor(mainThreadExecutor);
+ }
+
+ @Override
+ void loadAfter(int currentEndIndex, @NonNull Value currentEndItem, int pageSize,
+ @NonNull Executor mainThreadExecutor, @NonNull PageResult.Receiver<Value> receiver) {
+ loadAfter(getKey(currentEndItem), pageSize,
+ new LoadCallback<>(this, PageResult.APPEND, mainThreadExecutor, receiver));
+ }
+
+ @Override
+ void loadBefore(int currentBeginIndex, @NonNull Value currentBeginItem, int pageSize,
+ @NonNull Executor mainThreadExecutor, @NonNull PageResult.Receiver<Value> receiver) {
+ loadBefore(getKey(currentBeginItem), pageSize,
+ new LoadCallback<>(this, PageResult.PREPEND, mainThreadExecutor, receiver));
+ }
/**
* Load initial data.
@@ -61,7 +171,7 @@
* This method is called first to initialize a PagedList with data. If it's possible to count
* the items that can be loaded by the DataSource, it's recommended to pass the loaded data to
* the callback via the three-parameter
- * {@link DataSource.InitialLoadCallback#onResult(List, int, int)}. This enables PagedLists
+ * {@link InitialLoadCallback#onResult(List, int, int)}. This enables PagedLists
* presenting data from this source to display placeholders to represent unloaded items.
* <p>
* {@code initialLoadKey} and {@code requestedLoadSize} are hints, not requirements, so if it is
@@ -76,11 +186,10 @@
* @param requestedLoadSize Suggested number of items to load.
* @param enablePlaceholders Signals whether counting is requested. If false, you can
* potentially save work by calling the single-parameter variant of
- * {@link DataSource.LoadCallback#onResult(List)} and not counting the
+ * {@link LoadCallback#onResult(List)} and not counting the
* number of items in the data set.
* @param callback DataSource.LoadCallback that receives initial load data.
*/
- @Override
public abstract void loadInitial(@Nullable Key initialLoadKey, int requestedLoadSize,
boolean enablePlaceholders, @NonNull InitialLoadCallback<Value> callback);
diff --git a/paging/common/src/main/java/android/arch/paging/PositionalDataSource.java b/paging/common/src/main/java/android/arch/paging/PositionalDataSource.java
index 5dd3a83..d394637 100644
--- a/paging/common/src/main/java/android/arch/paging/PositionalDataSource.java
+++ b/paging/common/src/main/java/android/arch/paging/PositionalDataSource.java
@@ -21,6 +21,8 @@
import android.support.annotation.WorkerThread;
import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Executor;
/**
* Position-based data loader for a fixed-size, countable data set, supporting loads at arbitrary
@@ -44,6 +46,144 @@
* @param <T> Type of items being loaded by the PositionalDataSource.
*/
public abstract class PositionalDataSource<T> extends DataSource<Integer, T> {
+ /**
+ * Callback for PositionalDataSource initial loading methods to return data, position, and
+ * (optionally) count information.
+ * <p>
+ * A callback can be called only once, and will throw if called again.
+ * <p>
+ * It is always valid for a DataSource loading method that takes a callback to stash the
+ * callback and call it later. This enables DataSources to be fully asynchronous, and to handle
+ * temporary, recoverable error states (such as a network error that can be retried).
+ *
+ * @param <T> Type of items being loaded.
+ */
+ public static class InitialLoadCallback<T> extends BaseLoadCallback<T> {
+ private final boolean mCountingEnabled;
+ private final int mPageSize;
+
+ InitialLoadCallback(@NonNull PositionalDataSource dataSource, boolean countingEnabled,
+ int pageSize, PageResult.Receiver<T> receiver) {
+ super(PageResult.INIT, dataSource, null, receiver);
+ mCountingEnabled = countingEnabled;
+ mPageSize = pageSize;
+ if (mPageSize < 1) {
+ throw new IllegalArgumentException("Page size must be non-negative");
+ }
+ }
+
+ /**
+ * Called to pass initial load state from a DataSource.
+ * <p>
+ * Call this method from your DataSource's {@code loadInitial} function to return data,
+ * and inform how many placeholders should be shown before and after. If counting is cheap
+ * to compute (for example, if a network load returns the information regardless), it's
+ * recommended to pass data back through this method.
+ *
+ * @param data List of items loaded from the DataSource. If this is empty, the DataSource
+ * is treated as empty, and no further loads will occur.
+ * @param position Position of the item at the front of the list. If there are {@code N}
+ * items before the items in data that can be loaded from this DataSource,
+ * pass {@code N}.
+ * @param totalCount Total number of items that may be returned from this DataSource.
+ * Includes the number in the initial {@code data} parameter
+ * as well as any items that can be loaded in front or behind of
+ * {@code data}.
+ */
+ public void onResult(@NonNull List<T> data, int position, int totalCount) {
+ validateInitialLoadParams(data, position, totalCount);
+ if (position + data.size() != totalCount
+ && data.size() % mPageSize != 0) {
+ throw new IllegalArgumentException("PositionalDataSource requires initial load size"
+ + " to be a multiple of page size to support internal tiling.");
+ }
+
+ if (mCountingEnabled) {
+ int trailingUnloadedCount = totalCount - position - data.size();
+ dispatchResultToReceiver(
+ new PageResult<>(data, position, trailingUnloadedCount, 0));
+ } else {
+ // Only occurs when wrapped as contiguous
+ dispatchResultToReceiver(new PageResult<>(data, position));
+ }
+ }
+
+ /**
+ * Called to pass initial load state from a DataSource without supporting placeholders.
+ * <p>
+ * Call this method from your DataSource's {@code loadInitial} function to return data,
+ * if position is known but total size is not. If counting is not expensive, consider
+ * calling the three parameter variant: {@link #onResult(List, int, int)}.
+ *
+ * @param data List of items loaded from the DataSource. If this is empty, the DataSource
+ * is treated as empty, and no further loads will occur.
+ * @param position Position of the item at the front of the list. If there are {@code N}
+ * items before the items in data that can be provided by this DataSource,
+ * pass {@code N}.
+ */
+ void onResult(@NonNull List<T> data, int position) {
+ // not counting, don't need to check mAcceptCount
+ dispatchResultToReceiver(new PageResult<>(
+ data, 0, 0, position));
+ }
+ }
+
+ /**
+ * Callback for PositionalDataSource {@link #loadRange(int, int, LoadCallback)} methods
+ * to return data.
+ * <p>
+ * A callback can be called only once, and will throw if called again.
+ * <p>
+ * It is always valid for a DataSource loading method that takes a callback to stash the
+ * callback and call it later. This enables DataSources to be fully asynchronous, and to handle
+ * temporary, recoverable error states (such as a network error that can be retried).
+ *
+ * @param <T> Type of items being loaded.
+ */
+ public static class LoadCallback<T> extends BaseLoadCallback<T> {
+ private final int mPositionOffset;
+ LoadCallback(@NonNull PositionalDataSource dataSource, int positionOffset,
+ Executor mainThreadExecutor, PageResult.Receiver<T> receiver) {
+ super(PageResult.TILE, dataSource, mainThreadExecutor, receiver);
+ mPositionOffset = positionOffset;
+ }
+
+ /**
+ * Called to pass loaded data from a DataSource.
+ * <p>
+ * Call this method from your DataSource's {@code load} methods to return data.
+ *
+ * @param data List of items loaded from the DataSource.
+ */
+ public void onResult(@NonNull List<T> data) {
+ dispatchResultToReceiver(new PageResult<>(
+ data, 0, 0, mPositionOffset));
+ }
+ }
+
+ void loadInitial(boolean acceptCount,
+ int requestedStartPosition, int requestedLoadSize, int pageSize,
+ @NonNull Executor mainThreadExecutor, @NonNull PageResult.Receiver<T> receiver) {
+ InitialLoadCallback<T> callback =
+ new InitialLoadCallback<>(this, acceptCount, pageSize, receiver);
+ loadInitial(requestedStartPosition, requestedLoadSize, pageSize, callback);
+
+ // If initialLoad's callback is not called within the body, we force any following calls
+ // to post to the UI thread. This constructor may be run on a background thread, but
+ // after constructor, mutation must happen on UI thread.
+ callback.setPostExecutor(mainThreadExecutor);
+ }
+
+ void loadRange(int startPosition, int count,
+ @NonNull Executor mainThreadExecutor, @NonNull PageResult.Receiver<T> receiver) {
+ LoadCallback<T> callback =
+ new LoadCallback<>(this, startPosition, mainThreadExecutor, receiver);
+ if (count == 0) {
+ callback.onResult(Collections.<T>emptyList());
+ } else {
+ loadRange(startPosition, count, callback);
+ }
+ }
/**
* Load initial list data.
@@ -80,14 +220,14 @@
* @param callback DataSource.LoadCallback that receives loaded data.
*/
@WorkerThread
- public abstract void loadRange(int startPosition, int count,
- @NonNull LoadCallback<T> callback);
+ public abstract void loadRange(int startPosition, int count, @NonNull LoadCallback<T> callback);
@Override
boolean isContiguous() {
return false;
}
+
@NonNull
ContiguousDataSource<Integer, T> wrapAsContiguousWithoutPlaceholders() {
return new ContiguousWithoutPlaceholdersWrapper<>(this);
@@ -119,34 +259,39 @@
}
@Override
- public void loadInitial(@Nullable Integer position, int initialLoadSize,
- boolean enablePlaceholders, @NonNull InitialLoadCallback<Value> callback) {
+ void loadInitial(@Nullable Integer position, int initialLoadSize, int pageSize,
+ boolean enablePlaceholders, @NonNull Executor mainThreadExecutor,
+ @NonNull PageResult.Receiver<Value> receiver) {
final int convertPosition = position == null ? 0 : position;
// Note enablePlaceholders will be false here, but we don't have a way to communicate
// this to PositionalDataSource. This is fine, because only the list and its position
// offset will be consumed by the InitialLoadCallback.
- mPositionalDataSource.loadInitial(
- convertPosition, initialLoadSize, initialLoadSize, callback);
+ mPositionalDataSource.loadInitial(false, convertPosition, initialLoadSize,
+ pageSize, mainThreadExecutor, receiver);
}
@Override
void loadAfter(int currentEndIndex, @NonNull Value currentEndItem, int pageSize,
- @NonNull LoadCallback<Value> callback) {
+ @NonNull Executor mainThreadExecutor,
+ @NonNull PageResult.Receiver<Value> receiver) {
int startIndex = currentEndIndex + 1;
- mPositionalDataSource.loadRange(startIndex, pageSize, callback);
+ mPositionalDataSource.loadRange(startIndex, pageSize, mainThreadExecutor, receiver);
}
@Override
void loadBefore(int currentBeginIndex, @NonNull Value currentBeginItem, int pageSize,
- @NonNull LoadCallback<Value> callback) {
+ @NonNull Executor mainThreadExecutor,
+ @NonNull PageResult.Receiver<Value> receiver) {
+
int startIndex = currentBeginIndex - 1;
if (startIndex < 0) {
- callback.onResult(Collections.<Value>emptyList());
+ // trigger empty list load
+ mPositionalDataSource.loadRange(startIndex, 0, mainThreadExecutor, receiver);
} else {
int loadSize = Math.min(pageSize, startIndex + 1);
startIndex = startIndex - loadSize + 1;
- mPositionalDataSource.loadRange(startIndex, loadSize, callback);
+ mPositionalDataSource.loadRange(startIndex, loadSize, mainThreadExecutor, receiver);
}
}
diff --git a/paging/common/src/main/java/android/arch/paging/TiledDataSource.java b/paging/common/src/main/java/android/arch/paging/TiledDataSource.java
index 27aeda0..34d0091 100644
--- a/paging/common/src/main/java/android/arch/paging/TiledDataSource.java
+++ b/paging/common/src/main/java/android/arch/paging/TiledDataSource.java
@@ -47,10 +47,10 @@
@Override
public void loadInitial(int requestedStartPosition, int requestedLoadSize, int pageSize,
- @NonNull InitialLoadCallback<T> callback) {
+ @NonNull InitialLoadCallback callback) {
int totalCount = countItems();
if (totalCount == 0) {
- callback.onResult(Collections.<T>emptyList());
+ callback.onResult(Collections.<T>emptyList(), 0, 0);
return;
}
@@ -69,7 +69,7 @@
}
@Override
- public void loadRange(int startPosition, int count, @NonNull LoadCallback<T> callback) {
+ public void loadRange(int startPosition, int count, @NonNull LoadCallback callback) {
List<T> list = loadRange(startPosition, count);
if (list != null) {
callback.onResult(list);
diff --git a/paging/common/src/main/java/android/arch/paging/TiledPagedList.java b/paging/common/src/main/java/android/arch/paging/TiledPagedList.java
index 652b489..6c189cd 100644
--- a/paging/common/src/main/java/android/arch/paging/TiledPagedList.java
+++ b/paging/common/src/main/java/android/arch/paging/TiledPagedList.java
@@ -92,15 +92,8 @@
final int idealStart = position - firstLoadSize / 2;
final int roundedPageStart = Math.max(0, Math.round(idealStart / pageSize) * pageSize);
- DataSource.InitialLoadCallback<T> callback = new DataSource.InitialLoadCallback<>(
- DataSource.LOAD_COUNT_REQUIRED_TILED,
- mConfig.pageSize, mDataSource, mReceiver);
- mDataSource.loadInitial(roundedPageStart, firstLoadSize, pageSize, callback);
-
- // If initialLoad's callback is not called within the body, we force any following calls
- // to post to the UI thread. This constructor may be run on a background thread, but
- // after constructor, mutation must happen on UI thread.
- callback.setPostExecutor(mMainThreadExecutor);
+ mDataSource.loadInitial(true, roundedPageStart, firstLoadSize,
+ pageSize, mMainThreadExecutor, mReceiver);
}
}
@@ -185,10 +178,7 @@
} else {
int startPosition = pageIndex * pageSize;
int count = Math.min(pageSize, mStorage.size() - startPosition);
- DataSource.LoadCallback<T> callback = new DataSource.LoadCallback<>(
- PageResult.TILE, mMainThreadExecutor, mDataSource, mReceiver);
- callback.setPositionOffset(startPosition);
- mDataSource.loadRange(startPosition, count, callback);
+ mDataSource.loadRange(startPosition, count, mMainThreadExecutor, mReceiver);
}
}
});
diff --git a/paging/common/src/test/java/android/arch/paging/AsyncListDataSource.kt b/paging/common/src/test/java/android/arch/paging/AsyncListDataSource.kt
index 70636af..547c8ec 100644
--- a/paging/common/src/test/java/android/arch/paging/AsyncListDataSource.kt
+++ b/paging/common/src/test/java/android/arch/paging/AsyncListDataSource.kt
@@ -21,7 +21,10 @@
val workItems: MutableList<() -> Unit> = ArrayList()
private val listDataSource = ListDataSource(list)
- override fun loadInitial(requestedStartPosition: Int, requestedLoadSize: Int, pageSize: Int,
+ override fun loadInitial(
+ requestedStartPosition: Int,
+ requestedLoadSize: Int,
+ pageSize: Int,
callback: InitialLoadCallback<T>) {
workItems.add {
listDataSource.loadInitial(
diff --git a/paging/common/src/test/java/android/arch/paging/ContiguousPagedListTest.kt b/paging/common/src/test/java/android/arch/paging/ContiguousPagedListTest.kt
index 4b03104..8642b04 100644
--- a/paging/common/src/test/java/android/arch/paging/ContiguousPagedListTest.kt
+++ b/paging/common/src/test/java/android/arch/paging/ContiguousPagedListTest.kt
@@ -27,6 +27,7 @@
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.Mockito.verifyZeroInteractions
+import java.util.concurrent.Executor
@RunWith(Parameterized::class)
class ContiguousPagedListTest(private val mCounted: Boolean) {
@@ -43,32 +44,56 @@
private inner class TestSource(val listData: List<Item> = ITEMS)
: ContiguousDataSource<Int, Item>() {
- override fun loadInitial(key: Int?, initialLoadSize: Int,
- enablePlaceholders: Boolean, callback: InitialLoadCallback<Item>) {
+ override fun loadInitial(
+ key: Int?,
+ initialLoadSize: Int,
+ pageSize: Int,
+ enablePlaceholders: Boolean,
+ mainThreadExecutor: Executor,
+ receiver: PageResult.Receiver<Item>) {
val convertPosition = key ?: 0
- val loadPosition = Math.max(0, (convertPosition - initialLoadSize / 2))
-
- val data = getClampedRange(loadPosition, loadPosition + initialLoadSize)
+ val position = Math.max(0, (convertPosition - initialLoadSize / 2))
+ val data = getClampedRange(position, position + initialLoadSize)
+ val trailingUnloadedCount = listData.size - position - data.size
if (enablePlaceholders && mCounted) {
- callback.onResult(data, loadPosition, listData.size)
+ receiver.onPageResult(PageResult.INIT,
+ PageResult(data, position, trailingUnloadedCount, 0))
} else {
// still must pass offset, even if not counted
- callback.onResult(data, loadPosition)
+ receiver.onPageResult(PageResult.INIT,
+ PageResult(data, position))
}
}
- override fun loadAfter(currentEndIndex: Int, currentEndItem: Item, pageSize: Int,
- callback: LoadCallback<Item>) {
+ override fun loadAfter(
+ currentEndIndex: Int,
+ currentEndItem: Item,
+ pageSize: Int,
+ mainThreadExecutor: Executor,
+ receiver: PageResult.Receiver<Item>) {
val startIndex = currentEndIndex + 1
- callback.onResult(getClampedRange(startIndex, startIndex + pageSize))
+ val data = getClampedRange(startIndex, startIndex + pageSize)
+
+ mainThreadExecutor.execute {
+ receiver.onPageResult(PageResult.APPEND, PageResult(data, 0, 0, 0))
+ }
}
- override fun loadBefore(currentBeginIndex: Int, currentBeginItem: Item, pageSize: Int,
- callback: LoadCallback<Item>) {
+ override fun loadBefore(
+ currentBeginIndex: Int,
+ currentBeginItem: Item,
+ pageSize: Int,
+ mainThreadExecutor: Executor,
+ receiver: PageResult.Receiver<Item>) {
+
val startIndex = currentBeginIndex - 1
- callback.onResult(getClampedRange(startIndex - pageSize + 1, startIndex + 1))
+ val data = getClampedRange(startIndex - pageSize + 1, startIndex + 1)
+
+ mainThreadExecutor.execute {
+ receiver.onPageResult(PageResult.PREPEND, PageResult(data, 0, 0, 0))
+ }
}
override fun getKey(position: Int, item: Item?): Int {
diff --git a/paging/common/src/test/java/android/arch/paging/TestExecutor.kt b/paging/common/src/test/java/android/arch/paging/Executors.kt
similarity index 85%
rename from paging/common/src/test/java/android/arch/paging/TestExecutor.kt
rename to paging/common/src/test/java/android/arch/paging/Executors.kt
index 7034aa0..b472eed 100644
--- a/paging/common/src/test/java/android/arch/paging/TestExecutor.kt
+++ b/paging/common/src/test/java/android/arch/paging/Executors.kt
@@ -16,6 +16,7 @@
package android.arch.paging
+import org.junit.Assert.fail
import java.util.LinkedList
import java.util.concurrent.Executor
@@ -37,3 +38,9 @@
return consumed
}
}
+
+class FailExecutor(val string: String = "Executor expected to be unused") : Executor {
+ override fun execute(p0: Runnable?) {
+ fail(string)
+ }
+}
diff --git a/paging/common/src/test/java/android/arch/paging/KeyedDataSourceTest.kt b/paging/common/src/test/java/android/arch/paging/KeyedDataSourceTest.kt
index c9dbec4..b2e739a 100644
--- a/paging/common/src/test/java/android/arch/paging/KeyedDataSourceTest.kt
+++ b/paging/common/src/test/java/android/arch/paging/KeyedDataSourceTest.kt
@@ -19,6 +19,7 @@
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
+import org.junit.Assert.fail
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@@ -41,10 +42,8 @@
val captor = ArgumentCaptor.forClass(PageResult::class.java)
as ArgumentCaptor<PageResult<Item>>
- val callback = DataSource.InitialLoadCallback(
- DataSource.LOAD_COUNT_ACCEPTED, /* ignored page size */ 10, dataSource, receiver)
-
- dataSource.loadInitial(key, initialLoadSize, enablePlaceholders, callback)
+ dataSource.loadInitial(key, initialLoadSize,
+ /* ignored pageSize */ 10, enablePlaceholders, FailExecutor(), receiver)
verify(receiver).onPageResult(anyInt(), captor.capture())
verifyNoMoreInteractions(receiver)
@@ -186,9 +185,10 @@
fun loadBefore() {
val dataSource = ItemDataSource()
@Suppress("UNCHECKED_CAST")
- val callback = mock(DataSource.LoadCallback::class.java) as DataSource.LoadCallback<Item>
+ val callback = mock(KeyedDataSource.LoadCallback::class.java)
+ as KeyedDataSource.LoadCallback<Item>
- dataSource.loadBefore(5, ITEMS_BY_NAME_ID[5], 5, callback)
+ dataSource.loadBefore(dataSource.getKey(ITEMS_BY_NAME_ID[5]), 5, callback)
@Suppress("UNCHECKED_CAST")
val argument = ArgumentCaptor.forClass(List::class.java) as ArgumentCaptor<List<Item>>
@@ -253,6 +253,81 @@
}
}
+ private fun performInitialLoad(
+ callbackInvoker: (callback: KeyedDataSource.InitialLoadCallback<String>) -> Unit) {
+ val dataSource = object : KeyedDataSource<String, String>() {
+ override fun getKey(item: String): String {
+ return ""
+ }
+
+ override fun loadInitial(
+ initialLoadKey: String?,
+ requestedLoadSize: Int,
+ enablePlaceholders: Boolean,
+ callback: InitialLoadCallback<String>) {
+ callbackInvoker(callback)
+ }
+
+ override fun loadAfter(
+ currentEndKey: String,
+ pageSize: Int,
+ callback: LoadCallback<String>) {
+ fail("loadRange not expected")
+ }
+
+ override fun loadBefore(
+ currentBeginKey: String,
+ pageSize: Int,
+ callback: LoadCallback<String>) {
+ fail("loadRange not expected")
+ }
+ }
+
+ ContiguousPagedList<String, String>(
+ dataSource, FailExecutor(), FailExecutor(), null,
+ PagedList.Config.Builder()
+ .setPageSize(10)
+ .build(),
+ "")
+ }
+
+ @Test
+ fun initialLoadCallbackSuccess() = performInitialLoad {
+ // InitialLoadCallback correct usage
+ it.onResult(listOf("a", "b"), 0, 2)
+ }
+
+ @Test
+ fun initialLoadCallbackNotPageSizeMultiple() = performInitialLoad {
+ // Keyed InitialLoadCallback *can* accept result that's not a multiple of page size
+ val elevenLetterList = List(11) { "" + 'a' + it }
+ it.onResult(elevenLetterList, 0, 12)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun initialLoadCallbackListTooBig() = performInitialLoad {
+ // InitialLoadCallback can't accept pos + list > totalCount
+ it.onResult(listOf("a", "b", "c"), 0, 2)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun initialLoadCallbackPositionTooLarge() = performInitialLoad {
+ // InitialLoadCallback can't accept pos + list > totalCount
+ it.onResult(listOf("a", "b"), 1, 2)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun initialLoadCallbackPositionNegative() = performInitialLoad {
+ // InitialLoadCallback can't accept negative position
+ it.onResult(listOf("a", "b", "c"), -1, 2)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun initialLoadCallbackEmptyCannotHavePlaceholders() = performInitialLoad {
+ // InitialLoadCallback can't accept empty result unless data set is empty
+ it.onResult(emptyList(), 0, 2)
+ }
+
companion object {
private val ITEM_COMPARATOR = compareBy<Item>({ it.name }).thenByDescending({ it.id })
private val KEY_COMPARATOR = compareBy<Key>({ it.name }).thenByDescending({ it.id })
diff --git a/paging/common/src/test/java/android/arch/paging/PositionalDataSourceTest.kt b/paging/common/src/test/java/android/arch/paging/PositionalDataSourceTest.kt
index 34e0a57..12db934 100644
--- a/paging/common/src/test/java/android/arch/paging/PositionalDataSourceTest.kt
+++ b/paging/common/src/test/java/android/arch/paging/PositionalDataSourceTest.kt
@@ -17,6 +17,7 @@
package android.arch.paging
import org.junit.Assert.assertEquals
+import org.junit.Assert.fail
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@@ -47,4 +48,61 @@
fun computeFirstLoadPositionEndAdjustedAndAligned() {
assertEquals(70, PositionalDataSource.computeFirstLoadPosition(99, 35, 10, 100))
}
+
+ private fun performInitialLoad(
+ callbackInvoker: (callback: PositionalDataSource.InitialLoadCallback<String>) -> Unit) {
+ val dataSource = object : PositionalDataSource<String>() {
+ override fun loadInitial(requestedStartPosition: Int, requestedLoadSize: Int,
+ pageSize: Int, callback: InitialLoadCallback<String>) {
+ callbackInvoker(callback)
+ }
+ override fun loadRange(startPosition: Int, count: Int, callback: LoadCallback<String>) {
+ fail("loadRange not expected")
+ }
+ }
+
+ TiledPagedList(
+ dataSource, FailExecutor(), FailExecutor(), null,
+ PagedList.Config.Builder()
+ .setPageSize(10)
+ .build(),
+ 0)
+ }
+
+ @Test
+ fun initialLoadCallbackSuccess() = performInitialLoad {
+ // InitialLoadCallback correct usage
+ it.onResult(listOf("a", "b"), 0, 2)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun initialLoadCallbackNotPageSizeMultiple() = performInitialLoad {
+ // Positional InitialLoadCallback can't accept result that's not a multiple of page size
+ val elevenLetterList = List(11) { "" + 'a' + it }
+ it.onResult(elevenLetterList, 0, 12)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun initialLoadCallbackListTooBig() = performInitialLoad {
+ // InitialLoadCallback can't accept pos + list > totalCount
+ it.onResult(listOf("a", "b", "c"), 0, 2)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun initialLoadCallbackPositionTooLarge() = performInitialLoad {
+ // InitialLoadCallback can't accept pos + list > totalCount
+ it.onResult(listOf("a", "b"), 1, 2)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun initialLoadCallbackPositionNegative() = performInitialLoad {
+ // InitialLoadCallback can't accept negative position
+ it.onResult(listOf("a", "b", "c"), -1, 2)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun initialLoadCallbackEmptyCannotHavePlaceholders() = performInitialLoad {
+ // InitialLoadCallback can't accept empty result unless data set is empty
+ it.onResult(emptyList(), 0, 2)
+ }
}
diff --git a/paging/common/src/test/java/android/arch/paging/TiledDataSourceTest.kt b/paging/common/src/test/java/android/arch/paging/TiledDataSourceTest.kt
index 03a9a7b..15b787a 100644
--- a/paging/common/src/test/java/android/arch/paging/TiledDataSourceTest.kt
+++ b/paging/common/src/test/java/android/arch/paging/TiledDataSourceTest.kt
@@ -36,10 +36,7 @@
@Suppress("UNCHECKED_CAST")
val receiver = mock(PageResult.Receiver::class.java) as PageResult.Receiver<String>
- val callback = DataSource.InitialLoadCallback(
- DataSource.LOAD_COUNT_REQUIRED_TILED, pageSize, this, receiver)
-
- this.loadInitial(startPosition, count, pageSize, callback)
+ this.loadInitial(true, startPosition, count, pageSize, FailExecutor(), receiver)
@Suppress("UNCHECKED_CAST")
val argument = ArgumentCaptor.forClass(PageResult::class.java)
diff --git a/paging/common/src/test/java/android/arch/paging/TiledPagedListTest.kt b/paging/common/src/test/java/android/arch/paging/TiledPagedListTest.kt
index 66049a4..9069a1a 100644
--- a/paging/common/src/test/java/android/arch/paging/TiledPagedListTest.kt
+++ b/paging/common/src/test/java/android/arch/paging/TiledPagedListTest.kt
@@ -21,7 +21,6 @@
import org.junit.Assert.assertNull
import org.junit.Assert.assertSame
import org.junit.Assert.assertTrue
-import org.junit.Assert.fail
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@@ -399,74 +398,6 @@
verifyNoMoreInteractions(boundaryCallback)
}
- private fun performInitialLoad(
- callbackInvoker: (callback: DataSource.InitialLoadCallback<String>) -> Unit) {
- val dataSource = object : PositionalDataSource<String>() {
- override fun loadInitial(requestedStartPosition: Int, requestedLoadSize: Int,
- pageSize: Int, callback: InitialLoadCallback<String>) {
- callbackInvoker(callback)
- }
- override fun loadRange(startPosition: Int, count: Int, callback: LoadCallback<String>) {
- fail("loadRange not expected")
- }
- }
- TiledPagedList(
- dataSource, mMainThread, mBackgroundThread, null,
- PagedList.Config.Builder()
- .setPageSize(PAGE_SIZE)
- .build(),
- 0)
- }
-
- @Test
- fun initialLoadCallbackSuccess() = performInitialLoad {
- // InitialLoadCallback correct usage
- it.onResult(listOf("a", "b"), 0, 2)
- }
-
- @Test
- fun initialLoadCallbackEmptySuccess() = performInitialLoad {
- // InitialLoadCallback correct usage - empty special case
- it.onResult(emptyList())
- }
-
- @Test(expected = IllegalArgumentException::class)
- fun initialLoadCallbackNotPageSizeMultiple() = performInitialLoad {
- // Positional InitialLoadCallback can't accept result that's not a multiple of page size
- val elevenLetterList = List(11) { "" + 'a' + it }
- it.onResult(elevenLetterList, 0, 12)
- }
-
- @Test(expected = IllegalArgumentException::class)
- fun initialLoadCallbackMissingPlaceholders() = performInitialLoad {
- // Positional InitialLoadCallback can't accept list-only call
- it.onResult(listOf("a", "b"))
- }
-
- @Test(expected = IllegalArgumentException::class)
- fun initialLoadCallbackListTooBig() = performInitialLoad {
- // InitialLoadCallback can't accept pos + list > totalCount
- it.onResult(listOf("a", "b", "c"), 0, 2)
- }
-
- @Test(expected = IllegalArgumentException::class)
- fun initialLoadCallbackPositionTooLarge() = performInitialLoad {
- // InitialLoadCallback can't accept pos + list > totalCount
- it.onResult(listOf("a", "b"), 1, 2)
- }
-
- @Test(expected = IllegalArgumentException::class)
- fun initialLoadCallbackPositionNegative() = performInitialLoad {
- // InitialLoadCallback can't accept negative position
- it.onResult(listOf("a", "b", "c"), -1, 2)
- }
-
- @Test(expected = IllegalArgumentException::class)
- fun initialLoadCallbackEmptyCannotHavePlaceholders() = performInitialLoad {
- // Positional InitialLoadCallback can't accept empty result unless data set is empty
- it.onResult(emptyList(), 0, 2)
- }
-
private fun drain() {
var executed: Boolean
do {