1e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik/* 2bdc4c86d3dff74f6634a38e2f7b316b0e823a2c8Alan Viverette * Copyright 2018 The Android Open Source Project 3e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik * 4e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik * Licensed under the Apache License, Version 2.0 (the "License"); 5e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik * you may not use this file except in compliance with the License. 6e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik * You may obtain a copy of the License at 7e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik * 8e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik * http://www.apache.org/licenses/LICENSE-2.0 9e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik * 10e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik * Unless required by applicable law or agreed to in writing, software 11e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik * distributed under the License is distributed on an "AS IS" BASIS, 12e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik * See the License for the specific language governing permissions and 14e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik * limitations under the License. 15e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik */ 16e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 17bdc4c86d3dff74f6634a38e2f7b316b0e823a2c8Alan Viverettepackage androidx.paging; 18e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 19bdc4c86d3dff74f6634a38e2f7b316b0e823a2c8Alan Viveretteimport androidx.annotation.NonNull; 20bdc4c86d3dff74f6634a38e2f7b316b0e823a2c8Alan Viveretteimport androidx.annotation.Nullable; 21e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 22e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craikimport java.util.AbstractList; 23e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craikimport java.util.ArrayList; 24e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craikimport java.util.List; 25e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 265dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craikfinal class PagedStorage<T> extends AbstractList<T> { 275dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik /** 285dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik * Lists instances are compared (with instance equality) to PLACEHOLDER_LIST to check if an item 295dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik * in that position is already loading. We use a singleton placeholder list that is distinct 305dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik * from Collections.EMPTY_LIST for safety. 315dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik */ 325dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") 335dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik private static final List PLACEHOLDER_LIST = new ArrayList(); 345dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik 35e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik // Always set 36e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik private int mLeadingNullCount; 37e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik /** 38e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik * List of pages in storage. 39e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik * 40e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik * Two storage modes: 41e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik * 42e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik * Contiguous - all content in mPages is valid and loaded, but may return false from isTiled(). 43e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik * Safe to access any item in any page. 44e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik * 45e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik * Non-contiguous - mPages may have nulls or a placeholder page, isTiled() always returns true. 46e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik * mPages may have nulls, or placeholder (empty) pages while content is loading. 47e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik */ 485dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik private final ArrayList<List<T>> mPages; 49e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik private int mTrailingNullCount; 50e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 51e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik private int mPositionOffset; 52e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik /** 53e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik * Number of items represented by {@link #mPages}. If tiling is enabled, unloaded items in 54e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik * {@link #mPages} may be null, but this value still counts them. 55e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik */ 56e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik private int mStorageCount; 57e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 58e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik // If mPageSize > 0, tiling is enabled, 'mPages' may have gaps, and leadingPages is set 59e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik private int mPageSize; 60e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 61e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik private int mNumberPrepended; 62e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik private int mNumberAppended; 63e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 64e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik PagedStorage() { 65e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik mLeadingNullCount = 0; 66e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik mPages = new ArrayList<>(); 67e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik mTrailingNullCount = 0; 68e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik mPositionOffset = 0; 69e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik mStorageCount = 0; 70e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik mPageSize = 1; 71e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik mNumberPrepended = 0; 72e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik mNumberAppended = 0; 73e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 74e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 755dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik PagedStorage(int leadingNulls, List<T> page, int trailingNulls) { 76e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik this(); 77e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik init(leadingNulls, page, trailingNulls, 0); 78e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 79e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 805dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik private PagedStorage(PagedStorage<T> other) { 81e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik mLeadingNullCount = other.mLeadingNullCount; 82e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik mPages = new ArrayList<>(other.mPages); 83e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik mTrailingNullCount = other.mTrailingNullCount; 84e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik mPositionOffset = other.mPositionOffset; 85e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik mStorageCount = other.mStorageCount; 86e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik mPageSize = other.mPageSize; 87e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik mNumberPrepended = other.mNumberPrepended; 88e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik mNumberAppended = other.mNumberAppended; 89e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 90e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 915dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik PagedStorage<T> snapshot() { 92e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik return new PagedStorage<>(this); 93e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 94e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 955dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik private void init(int leadingNulls, List<T> page, int trailingNulls, int positionOffset) { 96e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik mLeadingNullCount = leadingNulls; 97e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik mPages.clear(); 98e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik mPages.add(page); 99e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik mTrailingNullCount = trailingNulls; 100e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 101e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik mPositionOffset = positionOffset; 1025dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik mStorageCount = page.size(); 103e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 104e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik // initialized as tiled. There may be 3 nulls, 2 items, but we still call this tiled 105e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik // even if it will break if nulls convert. 1065dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik mPageSize = page.size(); 107e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 108e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik mNumberPrepended = 0; 109e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik mNumberAppended = 0; 110e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 111e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 1125dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik void init(int leadingNulls, @NonNull List<T> page, int trailingNulls, int positionOffset, 113e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik @NonNull Callback callback) { 114e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik init(leadingNulls, page, trailingNulls, positionOffset); 115e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik callback.onInitialized(size()); 116e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 117e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 118e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik @Override 1195dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik public T get(int i) { 120e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik if (i < 0 || i >= size()) { 121e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik throw new IndexOutOfBoundsException("Index: " + i + ", Size: " + size()); 122e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 123e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 124e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik // is it definitely outside 'mPages'? 125e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik int localIndex = i - mLeadingNullCount; 126e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik if (localIndex < 0 || localIndex >= mStorageCount) { 127e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik return null; 128e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 129e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 130e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik int localPageIndex; 131e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik int pageInternalIndex; 132e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 133e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik if (isTiled()) { 134e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik // it's inside mPages, and we're tiled. Jump to correct tile. 135e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik localPageIndex = localIndex / mPageSize; 136e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik pageInternalIndex = localIndex % mPageSize; 137e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } else { 138e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik // it's inside mPages, but page sizes aren't regular. Walk to correct tile. 139e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik // Pages can only be null while tiled, so accessing page count is safe. 140e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik pageInternalIndex = localIndex; 141e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik final int localPageCount = mPages.size(); 142e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik for (localPageIndex = 0; localPageIndex < localPageCount; localPageIndex++) { 1435dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik int pageSize = mPages.get(localPageIndex).size(); 144e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik if (pageSize > pageInternalIndex) { 145e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik // stop, found the page 146e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik break; 147e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 148e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik pageInternalIndex -= pageSize; 149e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 150e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 151e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 1525dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik List<T> page = mPages.get(localPageIndex); 1535dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik if (page == null || page.size() == 0) { 154e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik // can only occur in tiled case, with untouched inner/placeholder pages 155e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik return null; 156e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 1575dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik return page.get(pageInternalIndex); 158e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 159e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 160e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik /** 161e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik * Returns true if all pages are the same size, except for the last, which may be smaller 162e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik */ 163e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik boolean isTiled() { 164e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik return mPageSize > 0; 165e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 166e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 167e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik int getLeadingNullCount() { 168e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik return mLeadingNullCount; 169e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 170e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 171e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik int getTrailingNullCount() { 172e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik return mTrailingNullCount; 173e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 174e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 175e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik int getStorageCount() { 176e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik return mStorageCount; 177e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 178e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 179e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik int getNumberAppended() { 180e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik return mNumberAppended; 181e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 182e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 183e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik int getNumberPrepended() { 184e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik return mNumberPrepended; 185e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 186e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 187e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik int getPageCount() { 188e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik return mPages.size(); 189e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 190e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 191e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik interface Callback { 192e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik void onInitialized(int count); 193e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik void onPagePrepended(int leadingNulls, int changed, int added); 194e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik void onPageAppended(int endPosition, int changed, int added); 195e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik void onPagePlaceholderInserted(int pageIndex); 196e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik void onPageInserted(int start, int count); 197e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 198e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 199e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik int getPositionOffset() { 200e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik return mPositionOffset; 201e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 202e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 203e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik @Override 204e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik public int size() { 205e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik return mLeadingNullCount + mStorageCount + mTrailingNullCount; 206e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 207e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 208e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik int computeLeadingNulls() { 209e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik int total = mLeadingNullCount; 210e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik final int pageCount = mPages.size(); 211e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik for (int i = 0; i < pageCount; i++) { 2125dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik List page = mPages.get(i); 2135dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik if (page != null && page != PLACEHOLDER_LIST) { 214e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik break; 215e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 216e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik total += mPageSize; 217e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 218e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik return total; 219e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 220e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 221e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik int computeTrailingNulls() { 222e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik int total = mTrailingNullCount; 223e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik for (int i = mPages.size() - 1; i >= 0; i--) { 2245dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik List page = mPages.get(i); 2255dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik if (page != null && page != PLACEHOLDER_LIST) { 226e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik break; 227e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 228e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik total += mPageSize; 229e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 230e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik return total; 231e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 232e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 233e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik // ---------------- Contiguous API ------------------- 234e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 2355dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik T getFirstLoadedItem() { 236e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik // safe to access first page's first item here: 237e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik // If contiguous, mPages can't be empty, can't hold null Pages, and items can't be empty 2385dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik return mPages.get(0).get(0); 239e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 240e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 2415dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik T getLastLoadedItem() { 242e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik // safe to access last page's last item here: 243e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik // If contiguous, mPages can't be empty, can't hold null Pages, and items can't be empty 2445dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik List<T> page = mPages.get(mPages.size() - 1); 2455dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik return page.get(page.size() - 1); 246e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 247e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 2485dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik void prependPage(@NonNull List<T> page, @NonNull Callback callback) { 2495dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik final int count = page.size(); 250e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik if (count == 0) { 251e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik // Nothing returned from source, stop loading in this direction 252e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik return; 253e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 254e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik if (mPageSize > 0 && count != mPageSize) { 255e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik if (mPages.size() == 1 && count > mPageSize) { 256e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik // prepending to a single item - update current page size to that of 'inner' page 257e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik mPageSize = count; 258e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } else { 259e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik // no longer tiled 260e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik mPageSize = -1; 261e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 262e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 263e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 264e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik mPages.add(0, page); 265e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik mStorageCount += count; 266e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 267e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik final int changedCount = Math.min(mLeadingNullCount, count); 268e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik final int addedCount = count - changedCount; 269e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 270e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik if (changedCount != 0) { 271e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik mLeadingNullCount -= changedCount; 272e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 273e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik mPositionOffset -= addedCount; 274e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik mNumberPrepended += count; 275e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 276e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik callback.onPagePrepended(mLeadingNullCount, changedCount, addedCount); 277e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 278e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 2795dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik void appendPage(@NonNull List<T> page, @NonNull Callback callback) { 2805dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik final int count = page.size(); 281e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik if (count == 0) { 282e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik // Nothing returned from source, stop loading in this direction 283e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik return; 284e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 285e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 286e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik if (mPageSize > 0) { 287e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik // if the previous page was smaller than mPageSize, 288e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik // or if this page is larger than the previous, disable tiling 2895dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik if (mPages.get(mPages.size() - 1).size() != mPageSize 290e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik || count > mPageSize) { 291e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik mPageSize = -1; 292e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 293e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 294e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 295e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik mPages.add(page); 296e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik mStorageCount += count; 297e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 298e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik final int changedCount = Math.min(mTrailingNullCount, count); 299e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik final int addedCount = count - changedCount; 300e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 301e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik if (changedCount != 0) { 302e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik mTrailingNullCount -= changedCount; 303e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 304e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik mNumberAppended += count; 305e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik callback.onPageAppended(mLeadingNullCount + mStorageCount - count, 306e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik changedCount, addedCount); 307e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 308e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 309e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik // ------------------ Non-Contiguous API (tiling required) ---------------------- 310e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 3115dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik void initAndSplit(int leadingNulls, @NonNull List<T> multiPageList, 3125dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik int trailingNulls, int positionOffset, int pageSize, @NonNull Callback callback) { 3135dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik 3145dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik int pageCount = (multiPageList.size() + (pageSize - 1)) / pageSize; 3155dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik for (int i = 0; i < pageCount; i++) { 3165dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik int beginInclusive = i * pageSize; 3175dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik int endExclusive = Math.min(multiPageList.size(), (i + 1) * pageSize); 3185dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik 3195dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik List<T> sublist = multiPageList.subList(beginInclusive, endExclusive); 3205dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik 3215dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik if (i == 0) { 3225dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik // Trailing nulls for first page includes other pages in multiPageList 3235dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik int initialTrailingNulls = trailingNulls + multiPageList.size() - sublist.size(); 3245dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik init(leadingNulls, sublist, initialTrailingNulls, positionOffset); 3255dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik } else { 3265dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik int insertPosition = leadingNulls + beginInclusive; 3275dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik insertPage(insertPosition, sublist, null); 3285dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik } 3295dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik } 3305dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik callback.onInitialized(size()); 3315dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik } 3325dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik 3335dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik public void insertPage(int position, @NonNull List<T> page, @Nullable Callback callback) { 3345dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik final int newPageSize = page.size(); 335e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik if (newPageSize != mPageSize) { 336e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik // differing page size is OK in 2 cases, when the page is being added: 337e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik // 1) to the end (in which case, ignore new smaller size) 338e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik // 2) only the last page has been added so far (in which case, adopt new bigger size) 339e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 340e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik int size = size(); 341e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik boolean addingLastPage = position == (size - size % mPageSize) 342e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik && newPageSize < mPageSize; 343e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik boolean onlyEndPagePresent = mTrailingNullCount == 0 && mPages.size() == 1 344e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik && newPageSize > mPageSize; 345e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 346e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik // OK only if existing single page, and it's the last one 347e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik if (!onlyEndPagePresent && !addingLastPage) { 348e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik throw new IllegalArgumentException("page introduces incorrect tiling"); 349e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 350e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik if (onlyEndPagePresent) { 351e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik mPageSize = newPageSize; 352e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 353e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 354e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 355e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik int pageIndex = position / mPageSize; 356e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 357e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik allocatePageRange(pageIndex, pageIndex); 358e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 359e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik int localPageIndex = pageIndex - mLeadingNullCount / mPageSize; 360e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 3615dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik List<T> oldPage = mPages.get(localPageIndex); 3625dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik if (oldPage != null && oldPage != PLACEHOLDER_LIST) { 363e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik throw new IllegalArgumentException( 364e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik "Invalid position " + position + ": data already loaded"); 365e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 366e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik mPages.set(localPageIndex, page); 3675dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik if (callback != null) { 3685dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik callback.onPageInserted(position, page.size()); 369e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 370e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 371e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 372e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik private void allocatePageRange(final int minimumPage, final int maximumPage) { 373e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik int leadingNullPages = mLeadingNullCount / mPageSize; 374e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 375e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik if (minimumPage < leadingNullPages) { 376e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik for (int i = 0; i < leadingNullPages - minimumPage; i++) { 377e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik mPages.add(0, null); 378e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 379e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik int newStorageAllocated = (leadingNullPages - minimumPage) * mPageSize; 380e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik mStorageCount += newStorageAllocated; 381e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik mLeadingNullCount -= newStorageAllocated; 382e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 383e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik leadingNullPages = minimumPage; 384e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 385e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik if (maximumPage >= leadingNullPages + mPages.size()) { 386e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik int newStorageAllocated = Math.min(mTrailingNullCount, 387e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik (maximumPage + 1 - (leadingNullPages + mPages.size())) * mPageSize); 388e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik for (int i = mPages.size(); i <= maximumPage - leadingNullPages; i++) { 389e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik mPages.add(mPages.size(), null); 390e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 391e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik mStorageCount += newStorageAllocated; 392e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik mTrailingNullCount -= newStorageAllocated; 393e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 394e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 395e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 396e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik public void allocatePlaceholders(int index, int prefetchDistance, 397e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik int pageSize, Callback callback) { 398e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik if (pageSize != mPageSize) { 399e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik if (pageSize < mPageSize) { 400e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik throw new IllegalArgumentException("Page size cannot be reduced"); 401e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 402e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik if (mPages.size() != 1 || mTrailingNullCount != 0) { 403e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik // not in single, last page allocated case - can't change page size 404e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik throw new IllegalArgumentException( 405e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik "Page size can change only if last page is only one present"); 406e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 407e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik mPageSize = pageSize; 408e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 409e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 410e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik final int maxPageCount = (size() + mPageSize - 1) / mPageSize; 411e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik int minimumPage = Math.max((index - prefetchDistance) / mPageSize, 0); 412e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik int maximumPage = Math.min((index + prefetchDistance) / mPageSize, maxPageCount - 1); 413e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 414e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik allocatePageRange(minimumPage, maximumPage); 415e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik int leadingNullPages = mLeadingNullCount / mPageSize; 416e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik for (int pageIndex = minimumPage; pageIndex <= maximumPage; pageIndex++) { 417e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik int localPageIndex = pageIndex - leadingNullPages; 418e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik if (mPages.get(localPageIndex) == null) { 4195dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik //noinspection unchecked 4205dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik mPages.set(localPageIndex, PLACEHOLDER_LIST); 421e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik callback.onPagePlaceholderInserted(pageIndex); 422e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 423e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 424e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 425e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 426e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik public boolean hasPage(int pageSize, int index) { 427e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik // NOTE: we pass pageSize here to avoid in case mPageSize 428e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik // not fully initialized (when last page only one loaded) 429e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik int leadingNullPages = mLeadingNullCount / pageSize; 430e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 431e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik if (index < leadingNullPages || index >= leadingNullPages + mPages.size()) { 432e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik return false; 433e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 434e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 4355dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik List<T> page = mPages.get(index - leadingNullPages); 436e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 4375dc2fd49c2887578d8b76a9014e1b43d088c7fdaChris Craik return page != null && page != PLACEHOLDER_LIST; 438e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 439e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 440e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik @Override 441e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik public String toString() { 442e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik StringBuilder ret = new StringBuilder("leading " + mLeadingNullCount 443e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik + ", storage " + mStorageCount 444e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik + ", trailing " + getTrailingNullCount()); 445e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik 446e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik for (int i = 0; i < mPages.size(); i++) { 447e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik ret.append(" ").append(mPages.get(i)); 448e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 449e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik return ret.toString(); 450e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik } 451e1178edf8a3082ca7dde8477bb43d001f67db11aChris Craik} 452