diff --git a/src/main/java/ru/touchin/roboswag/core/android/support/v7/util/BatchingListUpdateCallback.java b/src/main/java/ru/touchin/roboswag/core/android/support/v7/util/BatchingListUpdateCallback.java deleted file mode 100644 index 1b801c6..0000000 --- a/src/main/java/ru/touchin/roboswag/core/android/support/v7/util/BatchingListUpdateCallback.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * 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 ru.touchin.roboswag.core.android.support.v7.util; - -/** - * Wraps a {@link ListUpdateCallback} callback and batches operations that can be merged. - * - *

For instance, when 2 add operations comes that adds 2 consecutive elements, - * BatchingListUpdateCallback merges them and calls the wrapped callback only once. - * - *

This is a general purpose class and is also used by - * {@link DiffUtil.DiffResult DiffResult} - * - *

If you use this class to batch updates, you must call {@link #dispatchLastEvent()} when the - * stream of update events drain. - */ -@SuppressWarnings({"PMD", "checkstyle:all"}) -public class BatchingListUpdateCallback implements ListUpdateCallback { - - private static final int TYPE_NONE = 0; - private static final int TYPE_ADD = 1; - private static final int TYPE_REMOVE = 2; - private static final int TYPE_CHANGE = 3; - - private final ListUpdateCallback mWrapped; - - private int mLastEventType = TYPE_NONE; - private int mLastEventPosition = -1; - private int mLastEventCount = -1; - private Object mLastEventPayload = null; - - public BatchingListUpdateCallback(ListUpdateCallback callback) { - mWrapped = callback; - } - - /** - * BatchingListUpdateCallback holds onto the last event to see if it can be merged with the - * next one. When stream of events finish, you should call this method to dispatch the last - * event. - */ - public void dispatchLastEvent() { - if (mLastEventType == TYPE_NONE) { - return; - } - switch (mLastEventType) { - case TYPE_ADD: - mWrapped.onInserted(mLastEventPosition, mLastEventCount); - break; - case TYPE_REMOVE: - mWrapped.onRemoved(mLastEventPosition, mLastEventCount); - break; - case TYPE_CHANGE: - mWrapped.onChanged(mLastEventPosition, mLastEventCount, mLastEventPayload); - break; - } - mLastEventPayload = null; - mLastEventType = TYPE_NONE; - } - - @Override - public void onInserted(int position, int count) { - if (mLastEventType == TYPE_ADD && position >= mLastEventPosition - && position <= mLastEventPosition + mLastEventCount) { - mLastEventCount += count; - mLastEventPosition = Math.min(position, mLastEventPosition); - return; - } - dispatchLastEvent(); - mLastEventPosition = position; - mLastEventCount = count; - mLastEventType = TYPE_ADD; - } - - @Override - public void onRemoved(int position, int count) { - if (mLastEventType == TYPE_REMOVE && mLastEventPosition >= position && - mLastEventPosition <= position + count) { - mLastEventCount += count; - mLastEventPosition = position; - return; - } - dispatchLastEvent(); - mLastEventPosition = position; - mLastEventCount = count; - mLastEventType = TYPE_REMOVE; - } - - @Override - public void onMoved(int fromPosition, int toPosition) { - dispatchLastEvent(); // moves are not merged - mWrapped.onMoved(fromPosition, toPosition); - } - - @Override - public void onChanged(int position, int count, Object payload) { - if (mLastEventType == TYPE_CHANGE && - !(position > mLastEventPosition + mLastEventCount - || position + count < mLastEventPosition || mLastEventPayload != payload)) { - // take potential overlap into account - int previousEnd = mLastEventPosition + mLastEventCount; - mLastEventPosition = Math.min(position, mLastEventPosition); - mLastEventCount = Math.max(previousEnd, position + count) - mLastEventPosition; - return; - } - dispatchLastEvent(); - mLastEventPosition = position; - mLastEventCount = count; - mLastEventPayload = payload; - mLastEventType = TYPE_CHANGE; - } - -} diff --git a/src/main/java/ru/touchin/roboswag/core/android/support/v7/util/DiffUtil.java b/src/main/java/ru/touchin/roboswag/core/android/support/v7/util/DiffUtil.java deleted file mode 100644 index 4bdc364..0000000 --- a/src/main/java/ru/touchin/roboswag/core/android/support/v7/util/DiffUtil.java +++ /dev/null @@ -1,783 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * 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 ru.touchin.roboswag.core.android.support.v7.util; - -import android.support.annotation.Nullable; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - -/** - * DiffUtil is a utility class that can calculate the difference between two lists and output a - * list of update operations that converts the first list into the second one. - * - *

It can be used to calculate updates for a RecyclerView Adapter. - * - *

DiffUtil uses Eugene W. Myers's difference algorithm to calculate the minimal number of updates - * to convert one list into another. Myers's algorithm does not handle items that are moved so - * DiffUtil runs a second pass on the result to detect items that were moved. - * - *

If the lists are large, this operation may take significant time so you are advised to run this - * on a background thread, get the {@link DiffResult} then apply it on the RecyclerView on the main - * thread. - * - *

This algorithm is optimized for space and uses O(N) space to find the minimal - * number of addition and removal operations between the two lists. It has O(N + D^2) expected time - * performance where D is the length of the edit script. - * - *

If move detection is enabled, it takes an additional O(N^2) time where N is the total number of - * added and removed items. If your lists are already sorted by the same constraint (e.g. a created - * timestamp for a list of posts), you can disable move detection to improve performance. - * - *

The actual runtime of the algorithm significantly depends on the number of changes in the list - * and the cost of your comparison methods. Below are some average run times for reference: - * (The areSame list is composed of random UUID Strings and the tests are run on Nexus 5X with M) - *

- * - *

Due to implementation constraints, the max size of the list can be 2^26. - */ -@SuppressWarnings({"PMD", "checkstyle:all"}) -public class DiffUtil { - - private DiffUtil() { - // utility class, no instance. - } - - private static final Comparator SNAKE_COMPARATOR = new Comparator() { - @Override - public int compare(Snake o1, Snake o2) { - int cmpX = o1.x - o2.x; - return cmpX == 0 ? o1.y - o2.y : cmpX; - } - }; - - // Myers' algorithm uses two lists as axis labels. In DiffUtil's implementation, `x` axis is - // used for old list and `y` axis is used for new list. - - /** - * Calculates the list of update operations that can covert one list into the other one. - * - * @param cb The callback that acts as a gateway to the backing list data - * @return A DiffResult that contains the information about the edit sequence to convert the - * old list into the new list. - */ - public static DiffResult calculateDiff(Callback cb) { - return calculateDiff(cb, true); - } - - /** - * Calculates the list of update operations that can covert one list into the other one. - *

- * If your old and new lists are sorted by the same constraint and items never move (swap - * positions), you can disable move detection which takes O(N^2) time where - * N is the number of added, moved, removed items. - * - * @param cb The callback that acts as a gateway to the backing list data - * @param detectMoves True if DiffUtil should try to detect moved items, false otherwise. - * @return A DiffResult that contains the information about the edit sequence to convert the - * old list into the new list. - */ - public static DiffResult calculateDiff(Callback cb, boolean detectMoves) { - final int oldSize = cb.getOldListSize(); - final int newSize = cb.getNewListSize(); - - final List snakes = new ArrayList<>(); - - // instead of a recursive implementation, we keep our own stack to avoid potential stack - // overflow exceptions - final List stack = new ArrayList<>(); - - stack.add(new Range(0, oldSize, 0, newSize)); - - final int max = oldSize + newSize + Math.abs(oldSize - newSize); - // allocate forward and backward k-lines. K lines are diagonal lines in the matrix. (see the - // paper for details) - // These arrays lines keep the max reachable position for each k-line. - final int[] forward = new int[max * 2]; - final int[] backward = new int[max * 2]; - - // We pool the ranges to avoid allocations for each recursive call. - final List rangePool = new ArrayList<>(); - while (!stack.isEmpty()) { - final Range range = stack.remove(stack.size() - 1); - final Snake snake = diffPartial(cb, range.oldListStart, range.oldListEnd, - range.newListStart, range.newListEnd, forward, backward, max); - if (snake != null) { - if (snake.size > 0) { - snakes.add(snake); - } - // offset the snake to convert its coordinates from the Range's area to global - snake.x += range.oldListStart; - snake.y += range.newListStart; - - // add new ranges for left and right - final Range left = rangePool.isEmpty() ? new Range() : rangePool.remove( - rangePool.size() - 1); - left.oldListStart = range.oldListStart; - left.newListStart = range.newListStart; - if (snake.reverse) { - left.oldListEnd = snake.x; - left.newListEnd = snake.y; - } else { - if (snake.removal) { - left.oldListEnd = snake.x - 1; - left.newListEnd = snake.y; - } else { - left.oldListEnd = snake.x; - left.newListEnd = snake.y - 1; - } - } - stack.add(left); - - // re-use range for right - //noinspection UnnecessaryLocalVariable - final Range right = range; - if (snake.reverse) { - if (snake.removal) { - right.oldListStart = snake.x + snake.size + 1; - right.newListStart = snake.y + snake.size; - } else { - right.oldListStart = snake.x + snake.size; - right.newListStart = snake.y + snake.size + 1; - } - } else { - right.oldListStart = snake.x + snake.size; - right.newListStart = snake.y + snake.size; - } - stack.add(right); - } else { - rangePool.add(range); - } - - } - // sort snakes - Collections.sort(snakes, SNAKE_COMPARATOR); - - return new DiffResult(cb, snakes, forward, backward, detectMoves); - - } - - private static Snake diffPartial(Callback cb, int startOld, int endOld, - int startNew, int endNew, int[] forward, int[] backward, int kOffset) { - final int oldSize = endOld - startOld; - final int newSize = endNew - startNew; - - if (endOld - startOld < 1 || endNew - startNew < 1) { - return null; - } - - final int delta = oldSize - newSize; - final int dLimit = (oldSize + newSize + 1) / 2; - Arrays.fill(forward, kOffset - dLimit - 1, kOffset + dLimit + 1, 0); - Arrays.fill(backward, kOffset - dLimit - 1 + delta, kOffset + dLimit + 1 + delta, oldSize); - final boolean checkInFwd = delta % 2 != 0; - for (int d = 0; d <= dLimit; d++) { - for (int k = -d; k <= d; k += 2) { - // find forward path - // we can reach k from k - 1 or k + 1. Check which one is further in the graph - int x; - final boolean removal; - if (k == -d || k != d && forward[kOffset + k - 1] < forward[kOffset + k + 1]) { - x = forward[kOffset + k + 1]; - removal = false; - } else { - x = forward[kOffset + k - 1] + 1; - removal = true; - } - // set y based on x - int y = x - k; - // move diagonal as long as items match - while (x < oldSize && y < newSize - && cb.areItemsTheSame(startOld + x, startNew + y)) { - x++; - y++; - } - forward[kOffset + k] = x; - if (checkInFwd && k >= delta - d + 1 && k <= delta + d - 1) { - if (forward[kOffset + k] >= backward[kOffset + k]) { - Snake outSnake = new Snake(); - outSnake.x = backward[kOffset + k]; - outSnake.y = outSnake.x - k; - outSnake.size = forward[kOffset + k] - backward[kOffset + k]; - outSnake.removal = removal; - outSnake.reverse = false; - return outSnake; - } - } - } - for (int k = -d; k <= d; k += 2) { - // find reverse path at k + delta, in reverse - final int backwardK = k + delta; - int x; - final boolean removal; - if (backwardK == d + delta || backwardK != -d + delta - && backward[kOffset + backwardK - 1] < backward[kOffset + backwardK + 1]) { - x = backward[kOffset + backwardK - 1]; - removal = false; - } else { - x = backward[kOffset + backwardK + 1] - 1; - removal = true; - } - - // set y based on x - int y = x - backwardK; - // move diagonal as long as items match - while (x > 0 && y > 0 - && cb.areItemsTheSame(startOld + x - 1, startNew + y - 1)) { - x--; - y--; - } - backward[kOffset + backwardK] = x; - if (!checkInFwd && k + delta >= -d && k + delta <= d) { - if (forward[kOffset + backwardK] >= backward[kOffset + backwardK]) { - Snake outSnake = new Snake(); - outSnake.x = backward[kOffset + backwardK]; - outSnake.y = outSnake.x - backwardK; - outSnake.size = - forward[kOffset + backwardK] - backward[kOffset + backwardK]; - outSnake.removal = removal; - outSnake.reverse = true; - return outSnake; - } - } - } - } - throw new IllegalStateException("DiffUtil hit an unexpected case while trying to calculate" - + " the optimal path. Please make sure your data is not changing during the" - + " diff calculation."); - } - - /** - * A Callback class used by DiffUtil while calculating the diff between two lists. - */ - public abstract static class Callback { - /** - * Returns the size of the old list. - * - * @return The size of the old list. - */ - public abstract int getOldListSize(); - - /** - * Returns the size of the new list. - * - * @return The size of the new list. - */ - public abstract int getNewListSize(); - - /** - * Called by the DiffUtil to decide whether two object represent the same Item. - *

- * For example, if your items have unique ids, this method should check their id equality. - * - * @param oldItemPosition The position of the item in the old list - * @param newItemPosition The position of the item in the new list - * @return True if the two items represent the same object or false if they are different. - */ - public abstract boolean areItemsTheSame(int oldItemPosition, int newItemPosition); - - /** - * Called by the DiffUtil when it wants to check whether two items have the same data. - * DiffUtil uses this information to detect if the contents of an item has changed. - *

- * DiffUtil uses this method to check equality instead of {@link Object#equals(Object)} - * so that you can change its behavior depending on your UI. - *

- * This method is called only if {@link #areItemsTheSame(int, int)} returns - * {@code true} for these items. - * - * @param oldItemPosition The position of the item in the old list - * @param newItemPosition The position of the item in the new list which replaces the - * oldItem - * @return True if the contents of the items are the same or false if they are different. - */ - public abstract boolean areContentsTheSame(int oldItemPosition, int newItemPosition); - - /** - * When {@link #areItemsTheSame(int, int)} returns {@code true} for two items and - * {@link #areContentsTheSame(int, int)} returns false for them, DiffUtil - * calls this method to get a payload about the change. - *

- * Default implementation returns {@code null}. - * - * @param oldItemPosition The position of the item in the old list - * @param newItemPosition The position of the item in the new list - * @return A payload object that represents the change between the two items. - */ - @Nullable - public Object getChangePayload(int oldItemPosition, int newItemPosition) { - return null; - } - } - - /** - * Snakes represent a match between two lists. It is optionally prefixed or postfixed with an - * add or remove operation. See the Myers' paper for details. - */ - static class Snake { - /** - * Position in the old list - */ - int x; - - /** - * Position in the new list - */ - int y; - - /** - * Number of matches. Might be 0. - */ - int size; - - /** - * If true, this is a removal from the original list followed by {@code size} matches. - * If false, this is an addition from the new list followed by {@code size} matches. - */ - boolean removal; - - /** - * If true, the addition or removal is at the end of the snake. - * If false, the addition or removal is at the beginning of the snake. - */ - boolean reverse; - } - - /** - * Represents a range in two lists that needs to be solved. - *

- * This internal class is used when running Myers' algorithm without recursion. - */ - static class Range { - - int oldListStart, oldListEnd; - - int newListStart, newListEnd; - - public Range() { - } - - public Range(int oldListStart, int oldListEnd, int newListStart, int newListEnd) { - this.oldListStart = oldListStart; - this.oldListEnd = oldListEnd; - this.newListStart = newListStart; - this.newListEnd = newListEnd; - } - } - - /** - * This class holds the information about the result of a - * {@link DiffUtil#calculateDiff(Callback, boolean)} call. - *

- * You can consume the updates in a DiffResult via - * {@link #dispatchUpdatesTo(ListUpdateCallback)} - */ - public static class DiffResult { - /** - * While reading the flags below, keep in mind that when multiple items move in a list, - * Myers's may pick any of them as the anchor item and consider that one NOT_CHANGED while - * picking others as additions and removals. This is completely fine as we later detect - * all moves. - *

- * Below, when an item is mentioned to stay in the same "location", it means we won't - * dispatch a move/add/remove for it, it DOES NOT mean the item is still in the same - * position. - */ - // item stayed the same. - private static final int FLAG_NOT_CHANGED = 1; - // item stayed in the same location but changed. - private static final int FLAG_CHANGED = FLAG_NOT_CHANGED << 1; - // Item has moved and also changed. - private static final int FLAG_MOVED_CHANGED = FLAG_CHANGED << 1; - // Item has moved but did not change. - private static final int FLAG_MOVED_NOT_CHANGED = FLAG_MOVED_CHANGED << 1; - // Ignore this update. - // If this is an addition from the new list, it means the item is actually removed from an - // earlier position and its move will be dispatched when we process the matching removal - // from the old list. - // If this is a removal from the old list, it means the item is actually added back to an - // earlier index in the new list and we'll dispatch its move when we are processing that - // addition. - private static final int FLAG_IGNORE = FLAG_MOVED_NOT_CHANGED << 1; - - // since we are re-using the int arrays that were created in the Myers' step, we mask - // change flags - private static final int FLAG_OFFSET = 5; - - private static final int FLAG_MASK = (1 << FLAG_OFFSET) - 1; - - // The Myers' snakes. At this point, we only care about their diagonal sections. - private final List mSnakes; - - // The list to keep oldItemStatuses. As we traverse old items, we assign flags to them - // which also includes whether they were a real removal or a move (and its new index). - private final int[] mOldItemStatuses; - // The list to keep newItemStatuses. As we traverse new items, we assign flags to them - // which also includes whether they were a real addition or a move(and its old index). - private final int[] mNewItemStatuses; - // The callback that was given to calcualte diff method. - private final Callback mCallback; - - private final int mOldListSize; - - private final int mNewListSize; - - private final boolean mDetectMoves; - - /** - * @param callback The callback that was used to calculate the diff - * @param snakes The list of Myers' snakes - * @param oldItemStatuses An int[] that can be re-purposed to keep metadata - * @param newItemStatuses An int[] that can be re-purposed to keep metadata - * @param detectMoves True if this DiffResult will try to detect moved items - */ - DiffResult(Callback callback, List snakes, int[] oldItemStatuses, - int[] newItemStatuses, boolean detectMoves) { - mSnakes = snakes; - mOldItemStatuses = oldItemStatuses; - mNewItemStatuses = newItemStatuses; - Arrays.fill(mOldItemStatuses, 0); - Arrays.fill(mNewItemStatuses, 0); - mCallback = callback; - mOldListSize = callback.getOldListSize(); - mNewListSize = callback.getNewListSize(); - mDetectMoves = detectMoves; - addRootSnake(); - findMatchingItems(); - } - - /** - * We always add a Snake to 0/0 so that we can run loops from end to beginning and be done - * when we run out of snakes. - */ - private void addRootSnake() { - Snake firstSnake = mSnakes.isEmpty() ? null : mSnakes.get(0); - if (firstSnake == null || firstSnake.x != 0 || firstSnake.y != 0) { - Snake root = new Snake(); - root.x = 0; - root.y = 0; - root.removal = false; - root.size = 0; - root.reverse = false; - mSnakes.add(0, root); - } - } - - /** - * This method traverses each addition / removal and tries to match it to a previous - * removal / addition. This is how we detect move operations. - *

- * This class also flags whether an item has been changed or not. - *

- * DiffUtil does this pre-processing so that if it is running on a big list, it can be moved - * to background thread where most of the expensive stuff will be calculated and kept in - * the statuses maps. DiffResult uses this pre-calculated information while dispatching - * the updates (which is probably being called on the main thread). - */ - private void findMatchingItems() { - int posOld = mOldListSize; - int posNew = mNewListSize; - // traverse the matrix from right bottom to 0,0. - for (int i = mSnakes.size() - 1; i >= 0; i--) { - final Snake snake = mSnakes.get(i); - final int endX = snake.x + snake.size; - final int endY = snake.y + snake.size; - if (mDetectMoves) { - while (posOld > endX) { - // this is a removal. Check remaining snakes to see if this was added before - findAddition(posOld, posNew, i); - posOld--; - } - while (posNew > endY) { - // this is an addition. Check remaining snakes to see if this was removed - // before - findRemoval(posOld, posNew, i); - posNew--; - } - } - for (int j = 0; j < snake.size; j++) { - // matching items. Check if it is changed or not - final int oldItemPos = snake.x + j; - final int newItemPos = snake.y + j; - final boolean theSame = mCallback - .areContentsTheSame(oldItemPos, newItemPos); - final int changeFlag = theSame ? FLAG_NOT_CHANGED : FLAG_CHANGED; - mOldItemStatuses[oldItemPos] = (newItemPos << FLAG_OFFSET) | changeFlag; - mNewItemStatuses[newItemPos] = (oldItemPos << FLAG_OFFSET) | changeFlag; - } - posOld = snake.x; - posNew = snake.y; - } - } - - private void findAddition(int x, int y, int snakeIndex) { - if (mOldItemStatuses[x - 1] != 0) { - return; // already set by a latter item - } - findMatchingItem(x, y, snakeIndex, false); - } - - private void findRemoval(int x, int y, int snakeIndex) { - if (mNewItemStatuses[y - 1] != 0) { - return; // already set by a latter item - } - findMatchingItem(x, y, snakeIndex, true); - } - - /** - * Finds a matching item that is before the given coordinates in the matrix - * (before : left and above). - * - * @param x The x position in the matrix (position in the old list) - * @param y The y position in the matrix (position in the new list) - * @param snakeIndex The current snake index - * @param removal True if we are looking for a removal, false otherwise - * @return True if such item is found. - */ - private boolean findMatchingItem(final int x, final int y, final int snakeIndex, - final boolean removal) { - final int myItemPos; - int curX; - int curY; - if (removal) { - myItemPos = y - 1; - curX = x; - curY = y - 1; - } else { - myItemPos = x - 1; - curX = x - 1; - curY = y; - } - for (int i = snakeIndex; i >= 0; i--) { - final Snake snake = mSnakes.get(i); - final int endX = snake.x + snake.size; - final int endY = snake.y + snake.size; - if (removal) { - // check removals for a match - for (int pos = curX - 1; pos >= endX; pos--) { - if (mCallback.areItemsTheSame(pos, myItemPos)) { - // found! - final boolean theSame = mCallback.areContentsTheSame(pos, myItemPos); - final int changeFlag = theSame ? FLAG_MOVED_NOT_CHANGED - : FLAG_MOVED_CHANGED; - mNewItemStatuses[myItemPos] = (pos << FLAG_OFFSET) | FLAG_IGNORE; - mOldItemStatuses[pos] = (myItemPos << FLAG_OFFSET) | changeFlag; - return true; - } - } - } else { - // check for additions for a match - for (int pos = curY - 1; pos >= endY; pos--) { - if (mCallback.areItemsTheSame(myItemPos, pos)) { - // found - final boolean theSame = mCallback.areContentsTheSame(myItemPos, pos); - final int changeFlag = theSame ? FLAG_MOVED_NOT_CHANGED - : FLAG_MOVED_CHANGED; - mOldItemStatuses[x - 1] = (pos << FLAG_OFFSET) | FLAG_IGNORE; - mNewItemStatuses[pos] = ((x - 1) << FLAG_OFFSET) | changeFlag; - return true; - } - } - } - curX = snake.x; - curY = snake.y; - } - return false; - } - - /** - * Dispatches update operations to the given Callback. - *

- * These updates are atomic such that the first update call effects every update call that - * comes after it (the same as RecyclerView). - * - * @param updateCallback The callback to receive the update operations. - */ - public void dispatchUpdatesTo(ListUpdateCallback updateCallback) { - final BatchingListUpdateCallback batchingCallback; - if (updateCallback instanceof BatchingListUpdateCallback) { - batchingCallback = (BatchingListUpdateCallback) updateCallback; - } else { - batchingCallback = new BatchingListUpdateCallback(updateCallback); - // replace updateCallback with a batching callback and override references to - // updateCallback so that we don't call it directly by mistake - //noinspection UnusedAssignment - updateCallback = batchingCallback; - } - // These are add/remove ops that are converted to moves. We track their positions until - // their respective update operations are processed. - final List postponedUpdates = new ArrayList<>(); - int posOld = mOldListSize; - int posNew = mNewListSize; - for (int snakeIndex = mSnakes.size() - 1; snakeIndex >= 0; snakeIndex--) { - final Snake snake = mSnakes.get(snakeIndex); - final int snakeSize = snake.size; - final int endX = snake.x + snakeSize; - final int endY = snake.y + snakeSize; - if (endX < posOld) { - dispatchRemovals(postponedUpdates, batchingCallback, endX, posOld - endX, endX); - } - - if (endY < posNew) { - dispatchAdditions(postponedUpdates, batchingCallback, endX, posNew - endY, - endY); - } - for (int i = snakeSize - 1; i >= 0; i--) { - if ((mOldItemStatuses[snake.x + i] & FLAG_MASK) == FLAG_CHANGED) { - batchingCallback.onChanged(snake.x + i, 1, - mCallback.getChangePayload(snake.x + i, snake.y + i)); - } - } - posOld = snake.x; - posNew = snake.y; - } - batchingCallback.dispatchLastEvent(); - } - - private static PostponedUpdate removePostponedUpdate(List updates, - int pos, boolean removal) { - for (int i = updates.size() - 1; i >= 0; i--) { - final PostponedUpdate update = updates.get(i); - if (update.posInOwnerList == pos && update.removal == removal) { - updates.remove(i); - for (int j = i; j < updates.size(); j++) { - // offset other ops since they swapped positions - updates.get(j).currentPos += removal ? 1 : -1; - } - return update; - } - } - return null; - } - - private void dispatchAdditions(List postponedUpdates, - ListUpdateCallback updateCallback, int start, int count, int globalIndex) { - if (!mDetectMoves) { - updateCallback.onInserted(start, count); - return; - } - for (int i = count - 1; i >= 0; i--) { - int status = mNewItemStatuses[globalIndex + i] & FLAG_MASK; - switch (status) { - case 0: // real addition - updateCallback.onInserted(start, 1); - for (PostponedUpdate update : postponedUpdates) { - update.currentPos += 1; - } - break; - case FLAG_MOVED_CHANGED: - case FLAG_MOVED_NOT_CHANGED: - final int pos = mNewItemStatuses[globalIndex + i] >> FLAG_OFFSET; - final PostponedUpdate update = removePostponedUpdate(postponedUpdates, pos, - true); - // the item was moved from that position - //noinspection ConstantConditions - updateCallback.onMoved(update.currentPos, start); - if (status == FLAG_MOVED_CHANGED) { - // also dispatch a change - updateCallback.onChanged(start, 1, - mCallback.getChangePayload(pos, globalIndex + i)); - } - break; - case FLAG_IGNORE: // ignoring this - postponedUpdates.add(new PostponedUpdate(globalIndex + i, start, false)); - break; - default: - throw new IllegalStateException( - "unknown flag for pos " + (globalIndex + i) + " " + Long - .toBinaryString(status)); - } - } - } - - private void dispatchRemovals(List postponedUpdates, - ListUpdateCallback updateCallback, int start, int count, int globalIndex) { - if (!mDetectMoves) { - updateCallback.onRemoved(start, count); - return; - } - for (int i = count - 1; i >= 0; i--) { - final int status = mOldItemStatuses[globalIndex + i] & FLAG_MASK; - switch (status) { - case 0: // real removal - updateCallback.onRemoved(start + i, 1); - for (PostponedUpdate update : postponedUpdates) { - update.currentPos -= 1; - } - break; - case FLAG_MOVED_CHANGED: - case FLAG_MOVED_NOT_CHANGED: - final int pos = mOldItemStatuses[globalIndex + i] >> FLAG_OFFSET; - final PostponedUpdate update = removePostponedUpdate(postponedUpdates, pos, - false); - // the item was moved to that position. we do -1 because this is a move not - // add and removing current item offsets the target move by 1 - //noinspection ConstantConditions - updateCallback.onMoved(start + i, update.currentPos - 1); - if (status == FLAG_MOVED_CHANGED) { - // also dispatch a change - updateCallback.onChanged(update.currentPos - 1, 1, - mCallback.getChangePayload(globalIndex + i, pos)); - } - break; - case FLAG_IGNORE: // ignoring this - postponedUpdates.add(new PostponedUpdate(globalIndex + i, start + i, true)); - break; - default: - throw new IllegalStateException( - "unknown flag for pos " + (globalIndex + i) + " " + Long - .toBinaryString(status)); - } - } - } - } - - /** - * Represents an update that we skipped because it was a move. - *

- * When an update is skipped, it is tracked as other updates are dispatched until the matching - * add/remove operation is found at which point the tracked position is used to dispatch the - * update. - */ - private static class PostponedUpdate { - - int posInOwnerList; - - int currentPos; - - boolean removal; - - public PostponedUpdate(int posInOwnerList, int currentPos, boolean removal) { - this.posInOwnerList = posInOwnerList; - this.currentPos = currentPos; - this.removal = removal; - } - - } - -} diff --git a/src/main/java/ru/touchin/roboswag/core/android/support/v7/util/ListUpdateCallback.java b/src/main/java/ru/touchin/roboswag/core/android/support/v7/util/ListUpdateCallback.java deleted file mode 100644 index 06d9dc0..0000000 --- a/src/main/java/ru/touchin/roboswag/core/android/support/v7/util/ListUpdateCallback.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * 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 ru.touchin.roboswag.core.android.support.v7.util; - -/** - * An interface that can receive Update operations that are applied to a list. - * - *

This class can be used together with DiffUtil to detect changes between two lists. - */ -@SuppressWarnings({"PMD", "checkstyle:all"}) -public interface ListUpdateCallback { - - /** - * Called when {@code count} number of items are inserted at the given position. - * - * @param position The position of the new item. - * @param count The number of items that have been added. - */ - void onInserted(int position, int count); - - /** - * Called when {@code count} number of items are removed from the given position. - * - * @param position The position of the item which has been removed. - * @param count The number of items which have been removed. - */ - void onRemoved(int position, int count); - - /** - * Called when an item changes its position in the list. - * - * @param fromPosition The previous position of the item before the move. - * @param toPosition The new position of the item. - */ - void onMoved(int fromPosition, int toPosition); - - /** - * Called when {@code count} number of items are updated at the given position. - * - * @param position The position of the item which has been updated. - * @param count The number of items which has changed. - */ - void onChanged(int position, int count, Object payload); - -} diff --git a/src/main/java/ru/touchin/roboswag/core/observables/collections/ObservableCollection.java b/src/main/java/ru/touchin/roboswag/core/observables/collections/ObservableCollection.java deleted file mode 100644 index 4001596..0000000 --- a/src/main/java/ru/touchin/roboswag/core/observables/collections/ObservableCollection.java +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov) - * - * This file is part of RoboSwag library. - * - * 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 ru.touchin.roboswag.core.observables.collections; - -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -import ru.touchin.roboswag.core.observables.collections.changes.Change; -import ru.touchin.roboswag.core.observables.collections.changes.CollectionChanges; -import io.reactivex.Emitter; -import io.reactivex.Observable; - -/** - * Created by Gavriil Sitnikov on 23/05/16. - * Class to represent collection which is providing it's inner changes in Rx observable way. - * Use {@link #observeChanges()} and {@link #observeItems()} to observe collection changes. - * Methods {@link #size()} and {@link #get(int)} will return only already loaded items info. - * - * @param Type of collection's items. - */ -public abstract class ObservableCollection { - - private int changesCount; - @NonNull - private transient Observable> changesObservable; - @NonNull - private transient Observable> itemsObservable; - @Nullable - private transient Emitter> changesEmitter; - - public ObservableCollection() { - this.changesObservable = createChangesObservable(); - this.itemsObservable = createItemsObservable(); - } - - @NonNull - private Observable> createChangesObservable() { - return Observable - .>create(emitter -> this.changesEmitter = emitter) - .doOnDispose(() -> this.changesEmitter = null) - .share(); - } - - @NonNull - private Observable> createItemsObservable() { - return Observable - //switchOnNext to calculate getItems() on subscription but not on that method calling moment - .switchOnNext(Observable.fromCallable(() -> observeChanges().map(changes -> getItems()).startWith(getItems()))) - .replay(1) - .refCount(); - } - - /** - * Return changes count number since collection creation. - * - * @return Changes count. - */ - public int getChangesCount() { - return changesCount; - } - - /** - * Method to notify that collection have changed. - * - * @param change Change of collection. - */ - protected void notifyAboutChange(@NonNull final List insertedItems, - @NonNull final List removedItems, - @NonNull final Change change) { - notifyAboutChanges(insertedItems, removedItems, Collections.singleton(change)); - } - - /** - * Method to notify that collection have changed. - * - * @param insertedItems Collection of inserted items; - * @param removedItems Collection of removed items; - * @param changes Changes of collection. - */ - protected void notifyAboutChanges(@NonNull final List insertedItems, - @NonNull final List removedItems, - @NonNull final Collection changes) { - if (changes.isEmpty()) { - return; - } - changesCount++; - if (changesEmitter != null) { - changesEmitter.onNext(new CollectionChanges<>(changesCount, insertedItems, removedItems, changes)); - } - } - - /** - * Observes changes so it can be used to update UI based on changes etc. - * - * @return List of changes applied to collection. - */ - @NonNull - public Observable> observeChanges() { - return changesObservable; - } - - /** - * Returns already loaded item by position. - * Use it carefully for collections which are loading asynchronously. - * - * @param position Position of item to get; - * @return Item in collection by position. - */ - @NonNull - public abstract TItem get(int position); - - /** - * Returns already loaded items. - * Use it carefully for collections which are loading asynchronously. - * - * @return Collection of items. - */ - @NonNull - public abstract Collection getItems(); - - /** - * Returns {@link Observable} to observe items collection. - * Collection returned in onNext is not inner collection but it's copy, actually so you can't modify it. - * - * @return Collection's {@link Observable}. - */ - @NonNull - public Observable> observeItems() { - return itemsObservable; - } - - /** - * Returns size of already loaded items. - * - * @return Size. - */ - public abstract int size(); - - /** - * Returns if already loaded items are empty or not. - * - * @return True if items are empty. - */ - public boolean isEmpty() { - return size() == 0; - } - - private void writeObject(@NonNull final ObjectOutputStream outputStream) throws IOException { - outputStream.writeInt(changesCount); - } - - private void readObject(@NonNull final ObjectInputStream inputStream) throws IOException, ClassNotFoundException { - changesCount = inputStream.readInt(); - this.changesObservable = createChangesObservable(); - this.itemsObservable = createItemsObservable(); - } - -} diff --git a/src/main/java/ru/touchin/roboswag/core/observables/collections/ObservableFilteredList.java b/src/main/java/ru/touchin/roboswag/core/observables/collections/ObservableFilteredList.java deleted file mode 100644 index e92550f..0000000 --- a/src/main/java/ru/touchin/roboswag/core/observables/collections/ObservableFilteredList.java +++ /dev/null @@ -1,159 +0,0 @@ -package ru.touchin.roboswag.core.observables.collections; - -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.Executors; - -import ru.touchin.roboswag.core.observables.collections.changes.DefaultCollectionsChangesCalculator; -import io.reactivex.Scheduler; -import io.reactivex.disposables.Disposable; -import io.reactivex.functions.Function; -import io.reactivex.schedulers.Schedulers; -import ru.touchin.roboswag.core.log.Lc; - -/** - * Created by Gavriil Sitnikov on 02/06/2016. - * {@link ObservableCollection} based on simple collection with filter inside. - * Changing filter or collection will provide changes from {@link #observeChanges()}. - * - * @param Type of collection's items. - */ -public class ObservableFilteredList extends ObservableCollection { - - // we need to filter on 1 thread to prevent parallel filtering - private static final Scheduler FILTER_SCHEDULER = Schedulers.from(Executors.newSingleThreadExecutor()); - - @NonNull - private static List filterCollection(@NonNull final Collection sourceCollection, - @Nullable final Function filter) { - if (filter == null) { - return new ArrayList<>(sourceCollection); - } - final List result = new ArrayList<>(sourceCollection.size()); - try { - for (final TItem item : sourceCollection) { - if (filter.apply(item)) { - result.add(item); - } - } - } catch (final Exception exception) { - Lc.assertion(exception); - } - return result; - } - - @NonNull - private List filteredList; - @NonNull - private ObservableCollection sourceCollection; - @Nullable - private Function filter; - @Nullable - private Disposable sourceCollectionSubscription; - - public ObservableFilteredList() { - this(new ArrayList<>(), null); - } - - public ObservableFilteredList(@NonNull final Function filter) { - this(new ArrayList<>(), filter); - } - - public ObservableFilteredList(@NonNull final Collection sourceCollection, @Nullable final Function filter) { - this(new ObservableList<>(sourceCollection), filter); - } - - public ObservableFilteredList(@NonNull final ObservableCollection sourceCollection, @Nullable final Function filter) { - super(); - this.filter = filter; - this.sourceCollection = sourceCollection; - this.filteredList = filterCollection(this.sourceCollection.getItems(), this.filter); - updateInternal(); - } - - /** - * Sets collection of items to filter. - * - * @param sourceCollection Collection with items. - */ - public void setSourceCollection(@Nullable final ObservableCollection sourceCollection) { - this.sourceCollection = sourceCollection != null ? sourceCollection : new ObservableList<>(); - updateInternal(); - } - - /** - * Sets collection of items to filter. - * - * @param sourceCollection Collection with items. - */ - public void setSourceCollection(@Nullable final Collection sourceCollection) { - this.sourceCollection = sourceCollection != null ? new ObservableList<>(sourceCollection) : new ObservableList<>(); - updateInternal(); - } - - /** - * Sets filter that should return false as result of call to filter item. - * - * @param filter Function to filter item. True - item will stay, false - item will be filtered. - */ - public void setFilter(@Nullable final Function filter) { - this.filter = filter; - updateInternal(); - } - - private void updateInternal() { - if (sourceCollectionSubscription != null) { - sourceCollectionSubscription.dispose(); - sourceCollectionSubscription = null; - } - sourceCollectionSubscription = sourceCollection.observeItems() - .observeOn(FILTER_SCHEDULER) - .subscribe(items -> { - final List oldFilteredList = filteredList; - filteredList = filterCollection(items, filter); - final DefaultCollectionsChangesCalculator calculator - = new DefaultCollectionsChangesCalculator<>(oldFilteredList, filteredList, false); - notifyAboutChanges(calculator.calculateInsertedItems(), calculator.calculateRemovedItems(), calculator.calculateChanges()); - }); - } - - /** - * Updates collection by current filter. Use it if some item's parameter which is important for filtering have changing. - */ - public void update() { - updateInternal(); - } - - @Override - public int size() { - return filteredList.size(); - } - - @NonNull - @Override - public TItem get(final int position) { - return filteredList.get(position); - } - - @NonNull - @Override - public Collection getItems() { - return Collections.unmodifiableCollection(filteredList); - } - - /** - * Returns source non-filtered observable collection of items. - * - * @return Non-filtered collection of items. - */ - @NonNull - public ObservableCollection getSourceCollection() { - return sourceCollection; - } - -} diff --git a/src/main/java/ru/touchin/roboswag/core/observables/collections/ObservableList.java b/src/main/java/ru/touchin/roboswag/core/observables/collections/ObservableList.java deleted file mode 100644 index 14dcd19..0000000 --- a/src/main/java/ru/touchin/roboswag/core/observables/collections/ObservableList.java +++ /dev/null @@ -1,319 +0,0 @@ -/* - * Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov) - * - * This file is part of RoboSwag library. - * - * 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 ru.touchin.roboswag.core.observables.collections; - -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -import ru.touchin.roboswag.core.log.Lc; -import ru.touchin.roboswag.core.observables.collections.changes.Change; -import ru.touchin.roboswag.core.observables.collections.changes.ChangePayloadProducer; -import ru.touchin.roboswag.core.observables.collections.changes.CollectionsChangesCalculator; -import ru.touchin.roboswag.core.observables.collections.changes.DefaultCollectionsChangesCalculator; -import ru.touchin.roboswag.core.observables.collections.changes.DiffCollectionsChangesCalculator; -import ru.touchin.roboswag.core.observables.collections.changes.SameItemsPredicate; - -/** - * Created by Gavriil Sitnikov on 23/05/16. - * {@link ObservableCollection} that is based on list. - * So it is providing similar List's methods like adding/removing/clearing etc. - * But! You can observe it's changes. - * - * @param Type of collection's items. - */ -public class ObservableList extends ObservableCollection implements Serializable { - - private static final long serialVersionUID = 1L; - - @NonNull - private List items; - private boolean detectMoves; - @Nullable - private SameItemsPredicate sameItemsPredicate; - @Nullable - private ChangePayloadProducer changePayloadProducer; - @Nullable - private ObservableList diffUtilsSource; - - public ObservableList() { - super(); - items = new ArrayList<>(); - } - - public ObservableList(@NonNull final Collection initialItems) { - super(); - items = new ArrayList<>(initialItems); - } - - /** - * Adding item at the end of list. - * - * @param item Item to add. - */ - public void add(@NonNull final TItem item) { - add(items.size(), item); - } - - /** - * Adding item at specific list position. - * - * @param position Position to add item to; - * @param item Item to add. - */ - public void add(final int position, @NonNull final TItem item) { - synchronized (this) { - items.add(position, item); - notifyAboutChange(Collections.singletonList(item), Collections.emptyList(), new Change.Inserted(position, 1)); - } - } - - /** - * Adding items at the end of list. - * - * @param itemsToAdd Items to add. - */ - public void addAll(@NonNull final Collection itemsToAdd) { - addAll(items.size(), itemsToAdd); - } - - /** - * Adding items at specific list position. - * - * @param position Position to add items to; - * @param itemsToAdd Items to add. - */ - public void addAll(final int position, @NonNull final Collection itemsToAdd) { - synchronized (this) { - if (!itemsToAdd.isEmpty()) { - items.addAll(position, itemsToAdd); - notifyAboutChange(new ArrayList<>(itemsToAdd), Collections.emptyList(), new Change.Inserted(position, itemsToAdd.size())); - } - } - } - - /** - * Removing item. - * - * @param item Item to remove. - */ - public void remove(@NonNull final TItem item) { - synchronized (this) { - final int position = indexOf(item); - if (position < 0) { - Lc.assertion("Illegal removing of item " + item); - return; - } - remove(position); - } - } - - /** - * Removing item by position. - * - * @param position Position to remove item from. - */ - public void remove(final int position) { - remove(position, 1); - } - - /** - * Removing items by position. - * - * @param position Position to remove items from; - * @param count Count of items to remove. - */ - public void remove(final int position, final int count) { - if (count == 0) { - return; - } - synchronized (this) { - final List removedItems = new ArrayList<>(count); - for (int i = 0; i < count; i++) { - removedItems.add(items.remove(position)); - } - notifyAboutChange(Collections.emptyList(), removedItems, new Change.Removed(position, count)); - } - } - - /** - * Removing all items from list. - */ - public void clear() { - synchronized (this) { - if (!items.isEmpty()) { - final List removedItems = new ArrayList<>(items); - items.clear(); - notifyAboutChange(Collections.emptyList(), removedItems, new Change.Removed(0, removedItems.size())); - } - } - } - - @NonNull - @Override - public TItem get(final int position) { - synchronized (this) { - return items.get(position); - } - } - - @NonNull - @Override - public Collection getItems() { - synchronized (this) { - return Collections.unmodifiableCollection(new ArrayList<>(items)); - } - } - - /** - * Replace item at specific position. - * - * @param position Position to replace item; - * @param item Item to place. - */ - public void update(final int position, @NonNull final TItem item) { - update(position, Collections.singleton(item)); - } - - /** - * Replace items at specific position. - * - * @param position Position to replace items; - * @param updatedItems Items to place. - */ - public void update(final int position, @NonNull final Collection updatedItems) { - if (updatedItems.isEmpty()) { - return; - } - int index = position; - synchronized (this) { - for (final TItem item : updatedItems) { - items.set(index, item); - index++; - } - notifyAboutChange(Collections.emptyList(), Collections.emptyList(), new Change.Changed(position, updatedItems.size(), null)); - } - } - - /** - * Resetting all items in list to new ones. - * - * @param newItems New items to set. - */ - public void set(@NonNull final Collection newItems) { - synchronized (this) { - final List oldList = new ArrayList<>(items); - final List newList = new ArrayList<>(newItems); - final CollectionsChangesCalculator calculator; - if (diffUtilsSource != null) { - if (diffUtilsSource.sameItemsPredicate != null) { - calculator = new DiffCollectionsChangesCalculator<>(oldList, newList, - diffUtilsSource.detectMoves, diffUtilsSource.sameItemsPredicate, diffUtilsSource.changePayloadProducer); - } else { - calculator = new DefaultCollectionsChangesCalculator<>(oldList, newList, false); - } - } else if (sameItemsPredicate != null) { - calculator = new DiffCollectionsChangesCalculator<>(oldList, newList, detectMoves, sameItemsPredicate, changePayloadProducer); - } else { - calculator = new DefaultCollectionsChangesCalculator<>(oldList, newList, false); - } - items.clear(); - items.addAll(newItems); - notifyAboutChanges(calculator.calculateInsertedItems(), calculator.calculateRemovedItems(), calculator.calculateChanges()); - } - } - - @Override - public int size() { - synchronized (this) { - return items.size(); - } - } - - /** - * Enable diff utils algorithm in collection changes. - * - * @param detectMoves The flag that determines whether the {@link Change.Moved} changes will be generated or not; - * @param sameItemsPredicate Predicate for the determination of the same elements; - * @param changePayloadProducer Function that calculate change payload when items the same but contents are different. - */ - public void enableDiffUtils(final boolean detectMoves, - @NonNull final SameItemsPredicate sameItemsPredicate, - @Nullable final ChangePayloadProducer changePayloadProducer) { - this.detectMoves = detectMoves; - this.sameItemsPredicate = sameItemsPredicate; - this.changePayloadProducer = changePayloadProducer; - } - - /** - * Disable diff utils algorithm. - */ - public void disableDiffUtils() { - this.sameItemsPredicate = null; - } - - /** - * Returns enabled flag of diff utils. - * - * @return true if diff utils is enabled. - */ - public boolean diffUtilsIsEnabled() { - return diffUtilsSource != null ? diffUtilsSource.diffUtilsIsEnabled() : sameItemsPredicate != null; - } - - /** - * Sets observableCollection as a source of diff utils parameters; - * - * @param diffUtilsSource Source of diff utils parameters. - */ - public void setDiffUtilsSource(@Nullable final ObservableList diffUtilsSource) { - this.diffUtilsSource = diffUtilsSource; - } - - /** - * Returns position of item in list. - * - * @param item Item to find index of; - * @return Position of item in list or -1 if item not found. - */ - public int indexOf(@NonNull final TItem item) { - synchronized (this) { - return items.indexOf(item); - } - } - - private void writeObject(@NonNull final ObjectOutputStream outputStream) throws IOException { - outputStream.writeObject(items); - } - - @SuppressWarnings("unchecked") - private void readObject(@NonNull final ObjectInputStream inputStream) throws IOException, ClassNotFoundException { - items = (List) inputStream.readObject(); - } - -} diff --git a/src/main/java/ru/touchin/roboswag/core/observables/collections/changes/Change.java b/src/main/java/ru/touchin/roboswag/core/observables/collections/changes/Change.java deleted file mode 100644 index a1b1c80..0000000 --- a/src/main/java/ru/touchin/roboswag/core/observables/collections/changes/Change.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov) - * - * This file is part of RoboSwag library. - * - * 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 ru.touchin.roboswag.core.observables.collections.changes; - -import android.support.annotation.Nullable; - -/** - * Created by Gavriil Sitnikov on 23/05/16. - * Class representing simple change of collection like insertion, remove or replacing/changing items. - */ -public abstract class Change { - - /** - * Represents a insert operation in collection. - */ - public static class Inserted extends Change { - - private final int position; - private final int count; - - public Inserted(final int position, final int count) { - super(); - this.position = position; - this.count = count; - } - - public int getPosition() { - return position; - } - - public int getCount() { - return count; - } - - } - - /** - * Represents a remove operation from collection. - */ - public static class Removed extends Change { - - private final int position; - private final int count; - - public Removed(final int position, final int count) { - super(); - this.position = position; - this.count = count; - } - - public int getPosition() { - return position; - } - - public int getCount() { - return count; - } - - } - - /** - * Represents a move operation in collection. - */ - public static class Moved extends Change { - - private final int fromPosition; - private final int toPosition; - - public Moved(final int fromPosition, final int toPosition) { - super(); - this.fromPosition = fromPosition; - this.toPosition = toPosition; - } - - public int getFromPosition() { - return fromPosition; - } - - public int getToPosition() { - return toPosition; - } - - } - - /** - * Represents a modification operation in a collection. - */ - public static class Changed extends Change { - - private final int position; - private final int count; - @Nullable - private final Object payload; - - public Changed(final int position, final int count, @Nullable final Object payload) { - super(); - this.position = position; - this.count = count; - this.payload = payload; - } - - public int getPosition() { - return position; - } - - public int getCount() { - return count; - } - - @Nullable - public Object getPayload() { - return payload; - } - - } - -} diff --git a/src/main/java/ru/touchin/roboswag/core/observables/collections/changes/ChangePayloadProducer.java b/src/main/java/ru/touchin/roboswag/core/observables/collections/changes/ChangePayloadProducer.java deleted file mode 100644 index ffbbb03..0000000 --- a/src/main/java/ru/touchin/roboswag/core/observables/collections/changes/ChangePayloadProducer.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2017 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov) - * - * This file is part of RoboSwag library. - * - * 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 ru.touchin.roboswag.core.observables.collections.changes; - -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -/** - * Functional interface for calculating change payload between two items same type. - * Payload calculating when items are same {@link SameItemsPredicate}, but content different. - */ -public interface ChangePayloadProducer { - - - /** - * Calculate change payload between two items. - * - * @param item1 First item; - * @param item2 Second item; - * @return Object that represents minimal changes between two items. - */ - @Nullable - Object getChangePayload(@NonNull TItem item1, @NonNull TItem item2); - -} diff --git a/src/main/java/ru/touchin/roboswag/core/observables/collections/changes/CollectionChanges.java b/src/main/java/ru/touchin/roboswag/core/observables/collections/changes/CollectionChanges.java deleted file mode 100644 index f161155..0000000 --- a/src/main/java/ru/touchin/roboswag/core/observables/collections/changes/CollectionChanges.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (c) 2017 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov) - * - * This file is part of RoboSwag library. - * - * 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 ru.touchin.roboswag.core.observables.collections.changes; - -import android.support.annotation.NonNull; - -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -/** - * Class which is representing change of collection. There could be multiple changes applied to collection. - */ -public class CollectionChanges { - - private final int number; - @NonNull - private final List insertedItems; - @NonNull - private final List removedItems; - @NonNull - private final Collection changes; - - public CollectionChanges(final int number, - @NonNull final List insertedItems, - @NonNull final List removedItems, - @NonNull final Collection changes) { - this.number = number; - this.insertedItems = Collections.unmodifiableList(insertedItems); - this.removedItems = Collections.unmodifiableList(removedItems); - this.changes = Collections.unmodifiableCollection(changes); - } - - /** - * Returns number of change. - * - * @return Number of change. - */ - public int getNumber() { - return number; - } - - /** - * Returns collection of changes. - * - * @return Collection of changes. - */ - @NonNull - public Collection getChanges() { - return changes; - } - - /** - * Returns inserted items in change. - * - * @return Inserted items. - */ - @NonNull - public List getInsertedItems() { - return insertedItems; - } - - /** - * Returns removed items in change. - * - * @return Removed items. - */ - @NonNull - public List getRemovedItems() { - return removedItems; - } - -} diff --git a/src/main/java/ru/touchin/roboswag/core/observables/collections/changes/CollectionsChangesCalculator.java b/src/main/java/ru/touchin/roboswag/core/observables/collections/changes/CollectionsChangesCalculator.java deleted file mode 100644 index 9f6d026..0000000 --- a/src/main/java/ru/touchin/roboswag/core/observables/collections/changes/CollectionsChangesCalculator.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) 2017 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov) - * - * This file is part of RoboSwag library. - * - * 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 ru.touchin.roboswag.core.observables.collections.changes; - -import android.support.annotation.NonNull; - -import java.util.List; - -/** - * Interface that represent changes calculator between two collections. - */ -public interface CollectionsChangesCalculator { - - /** - * Calculate changes between two collection as collection of objects {@link Change}. - * - * @return List of changes. - */ - @NonNull - List calculateChanges(); - - /** - * Calculate changes between two collection as collection of inserted items. - * - * @return List of inserted item. - */ - @NonNull - List calculateInsertedItems(); - - /** - * Calculate changes between two collection as collection of removed items. - * - * @return List of removed item. - */ - @NonNull - List calculateRemovedItems(); - -} diff --git a/src/main/java/ru/touchin/roboswag/core/observables/collections/changes/DefaultCollectionsChangesCalculator.java b/src/main/java/ru/touchin/roboswag/core/observables/collections/changes/DefaultCollectionsChangesCalculator.java deleted file mode 100644 index 09cd790..0000000 --- a/src/main/java/ru/touchin/roboswag/core/observables/collections/changes/DefaultCollectionsChangesCalculator.java +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright (c) 2017 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov) - * - * This file is part of RoboSwag library. - * - * 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 ru.touchin.roboswag.core.observables.collections.changes; - -import android.support.annotation.NonNull; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -/** - * Default calculator between two collections that use equals function. - */ -public class DefaultCollectionsChangesCalculator implements CollectionsChangesCalculator { - - @NonNull - private final Collection initialCollection; - @NonNull - private final Collection modifiedCollection; - private final boolean shrinkChangesToModifiedSize; - @NonNull - private final Collection itemsToAdd = new ArrayList<>(); - private int currentSize; - private int oldSize; - private int newSize; - private int couldBeAdded; - - /** - * Default calculator of changes between two collections. - * - * @param initialCollection Initial collection; - * @param modifiedCollection Changed collection; - * @param shrinkChangesToModifiedSize Flag to make position of changed items be less then modified collection size. - * It is needed sometimes to not get exceptions like {@link ArrayIndexOutOfBoundsException}. - */ - public DefaultCollectionsChangesCalculator(@NonNull final Collection initialCollection, - @NonNull final Collection modifiedCollection, - final boolean shrinkChangesToModifiedSize) { - super(); - this.initialCollection = initialCollection; - this.modifiedCollection = modifiedCollection; - this.shrinkChangesToModifiedSize = shrinkChangesToModifiedSize; - } - - @NonNull - @Override - public List calculateChanges() { - int initialOffset = 0; - itemsToAdd.clear(); - currentSize = 0; - oldSize = initialCollection.size(); - newSize = modifiedCollection.size(); - couldBeAdded = modifiedCollection.size() - initialCollection.size(); - final List result = new ArrayList<>(); - for (final TItem modifiedItem : modifiedCollection) { - int foundPosition = 0; - for (final Object initialObject : initialCollection) { - if (foundPosition >= initialOffset && modifiedItem.equals(initialObject)) { - if (tryAddSkipped(result) == MethodAction.RETURN - || tryRemoveRest(result, foundPosition - initialOffset) == MethodAction.RETURN) { - return result; - } - initialOffset = foundPosition + 1; - currentSize++; - break; - } - foundPosition++; - } - // if not found - if (foundPosition >= initialCollection.size()) { - itemsToAdd.add(modifiedItem); - } - } - - if (tryAddSkipped(result) == MethodAction.RETURN) { - return result; - } - tryRemoveRest(result, initialCollection.size() - initialOffset); - return result; - } - - @NonNull - @Override - public List calculateInsertedItems() { - final List insertedItems = new ArrayList<>(); - for (final TItem newItem : modifiedCollection) { - if (!initialCollection.contains(newItem)) { - insertedItems.add(newItem); - } - } - return insertedItems; - } - - @NonNull - @Override - public List calculateRemovedItems() { - final List removedItems = new ArrayList<>(); - for (final TItem oldItem : initialCollection) { - if (!modifiedCollection.contains(oldItem)) { - removedItems.add(oldItem); - } - } - return removedItems; - } - - @NonNull - private MethodAction tryAddSkipped(@NonNull final Collection changes) { - if (!itemsToAdd.isEmpty()) { - if (shrinkChangesToModifiedSize && couldBeAdded < itemsToAdd.size()) { - addSimpleDifferenceChanges(changes); - return MethodAction.RETURN; - } - changes.add(new Change.Inserted(currentSize, itemsToAdd.size())); - currentSize += itemsToAdd.size(); - couldBeAdded -= itemsToAdd.size(); - itemsToAdd.clear(); - } - return MethodAction.CONTINUE; - } - - @NonNull - private MethodAction tryRemoveRest(@NonNull final Collection changes, final int itemsToRemove) { - if (itemsToRemove > 0) { - if (shrinkChangesToModifiedSize && couldBeAdded < -itemsToRemove) { - addSimpleDifferenceChanges(changes); - return MethodAction.RETURN; - } - changes.add(new Change.Removed(currentSize, itemsToRemove)); - } - return MethodAction.CONTINUE; - } - - private void addSimpleDifferenceChanges(@NonNull final Collection changes) { - changes.add(new Change.Changed(currentSize, newSize - currentSize, null)); - if (oldSize - newSize > 0) { - changes.add(new Change.Removed(newSize, oldSize - newSize)); - } - } - - private enum MethodAction { - RETURN, - CONTINUE - } - -} diff --git a/src/main/java/ru/touchin/roboswag/core/observables/collections/changes/DiffCollectionsChangesCalculator.java b/src/main/java/ru/touchin/roboswag/core/observables/collections/changes/DiffCollectionsChangesCalculator.java deleted file mode 100644 index 7b72772..0000000 --- a/src/main/java/ru/touchin/roboswag/core/observables/collections/changes/DiffCollectionsChangesCalculator.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright (c) 2017 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov) - * - * This file is part of RoboSwag library. - * - * 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 ru.touchin.roboswag.core.observables.collections.changes; - -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -import java.util.ArrayList; -import java.util.List; - -import ru.touchin.roboswag.core.android.support.v7.util.DiffUtil; -import ru.touchin.roboswag.core.android.support.v7.util.ListUpdateCallback; - -/** - * Implementation of {@link CollectionsChangesCalculator} based on DiffUtils from support library. - */ -public class DiffCollectionsChangesCalculator extends DiffUtil.Callback implements CollectionsChangesCalculator { - - @NonNull - private final List oldList; - @NonNull - private final List newList; - private final boolean detectMoves; - @NonNull - private final SameItemsPredicate sameItemsPredicate; - @Nullable - private final ChangePayloadProducer changePayloadProducer; - - public DiffCollectionsChangesCalculator(@NonNull final List oldList, - @NonNull final List newList, - final boolean detectMoves, - @NonNull final SameItemsPredicate sameItemsPredicate, - @Nullable final ChangePayloadProducer changePayloadProducer) { - super(); - this.oldList = oldList; - this.newList = newList; - this.detectMoves = detectMoves; - this.sameItemsPredicate = sameItemsPredicate; - this.changePayloadProducer = changePayloadProducer; - } - - @NonNull - @Override - public List calculateChanges() { - final List changes = new ArrayList<>(); - DiffUtil.calculateDiff(this, detectMoves).dispatchUpdatesTo(new ListUpdateCallback() { - @Override - public void onInserted(final int position, final int count) { - changes.add(new Change.Inserted(position, count)); - } - - @Override - public void onRemoved(final int position, final int count) { - changes.add(new Change.Removed(position, count)); - } - - @Override - public void onMoved(final int fromPosition, final int toPosition) { - changes.add(new Change.Moved(fromPosition, toPosition)); - } - - @Override - public void onChanged(final int position, final int count, @Nullable final Object payload) { - changes.add(new Change.Changed(position, count, payload)); - } - }); - return changes; - } - - @NonNull - @Override - public List calculateInsertedItems() { - final List insertedItems = new ArrayList<>(); - for (final TItem newItem : newList) { - if (!containsByPredicate(newItem, oldList)) { - insertedItems.add(newItem); - } - } - return insertedItems; - } - - @NonNull - @Override - public List calculateRemovedItems() { - final List removedItems = new ArrayList<>(); - for (final TItem oldItem : oldList) { - if (!containsByPredicate(oldItem, newList)) { - removedItems.add(oldItem); - } - } - return removedItems; - } - - @Override - public int getOldListSize() { - return oldList.size(); - } - - @Override - public int getNewListSize() { - return newList.size(); - } - - @Override - public boolean areItemsTheSame(final int oldItemPosition, final int newItemPosition) { - return sameItemsPredicate.areSame(oldList.get(oldItemPosition), newList.get(newItemPosition)); - } - - @Override - public boolean areContentsTheSame(final int oldItemPosition, final int newItemPosition) { - return oldList.get(oldItemPosition).equals(newList.get(newItemPosition)); - } - - @Nullable - @Override - public Object getChangePayload(final int oldItemPosition, final int newItemPosition) { - return changePayloadProducer != null - ? changePayloadProducer.getChangePayload(oldList.get(oldItemPosition), newList.get(newItemPosition)) : null; - } - - private boolean containsByPredicate(@NonNull final TItem searchedItem, @NonNull final List items) { - for (final TItem item : items) { - if (sameItemsPredicate.areSame(item, searchedItem)) { - return true; - } - } - return false; - } - -} diff --git a/src/main/java/ru/touchin/roboswag/core/observables/collections/changes/SameItemsPredicate.java b/src/main/java/ru/touchin/roboswag/core/observables/collections/changes/SameItemsPredicate.java deleted file mode 100644 index 611f6a5..0000000 --- a/src/main/java/ru/touchin/roboswag/core/observables/collections/changes/SameItemsPredicate.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2017 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov) - * - * This file is part of RoboSwag library. - * - * 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 ru.touchin.roboswag.core.observables.collections.changes; - -import android.support.annotation.NonNull; - -/** - * Functional interface for determine same objects. Usually this is just the comparison by id. - * - * @param Type of objects - */ -public interface SameItemsPredicate { - - /** - * Function for determine same objects. - * - * @param item1 First object; - * @param item2 Second object; - * @return True if items are same. - */ - boolean areSame(@NonNull TItem item1, @NonNull TItem item2); - -} diff --git a/src/main/java/ru/touchin/roboswag/core/observables/collections/loadable/LoadedItems.java b/src/main/java/ru/touchin/roboswag/core/observables/collections/loadable/LoadedItems.java deleted file mode 100644 index 3099253..0000000 --- a/src/main/java/ru/touchin/roboswag/core/observables/collections/loadable/LoadedItems.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov) - * - * This file is part of RoboSwag library. - * - * 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 ru.touchin.roboswag.core.observables.collections.loadable; - -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -import java.util.Collection; - -/** - * Created by Gavriil Sitnikov on 23/05/16. - * Object represents loaded items with reference to load other parts and info of are there more items to load or not. - * - * @param Type of items to load; - * @param Type of reference to load other parts of items. - */ -public interface LoadedItems { - - int UNKNOWN_ITEMS_COUNT = -1; - - /** - * Returns count of items that could be loaded more. - * - * @return Count of items to load more or UNKNOWN_ITEMS_COUNT if it's unknown info. - */ - int getMoreItemsCount(); - - /** - * Returns loaded items. - * - * @return Loaded items. - */ - @NonNull - Collection getItems(); - - /** - * Returns reference that could be used to load other parts of items. - * - * @return Reference object. - */ - @Nullable - TReference getReference(); - -} diff --git a/src/main/java/ru/touchin/roboswag/core/observables/collections/loadable/LoadingMoreList.java b/src/main/java/ru/touchin/roboswag/core/observables/collections/loadable/LoadingMoreList.java deleted file mode 100644 index fd8f374..0000000 --- a/src/main/java/ru/touchin/roboswag/core/observables/collections/loadable/LoadingMoreList.java +++ /dev/null @@ -1,402 +0,0 @@ -/* - * Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov) - * - * This file is part of RoboSwag library. - * - * 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 ru.touchin.roboswag.core.observables.collections.loadable; - -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.Callable; -import java.util.concurrent.Executors; - -import io.reactivex.Observable; -import io.reactivex.Scheduler; -import io.reactivex.Single; -import io.reactivex.functions.Function; -import io.reactivex.schedulers.Schedulers; -import io.reactivex.subjects.BehaviorSubject; -import ru.touchin.roboswag.core.log.Lc; -import ru.touchin.roboswag.core.observables.collections.ObservableCollection; -import ru.touchin.roboswag.core.observables.collections.ObservableList; -import ru.touchin.roboswag.core.observables.collections.changes.Change; -import ru.touchin.roboswag.core.observables.collections.changes.CollectionChanges; -import ru.touchin.roboswag.core.utils.Optional; - -/** - * Created by Gavriil Sitnikov on 23/05/16. - * {@link ObservableCollection} which is loading items more and more by paging/limit-offset/reference-based mechanisms. - * To use this collection {@link MoreItemsLoader} should be created. - * {@link MoreItemsLoader} is an object to load next block of items by info from previous loaded block (last loaded item/reference etc.). - * Use {@link #loadItem(int)} and {@link #loadRange(int, int)} to load items asynchronously. - * - * @param Type of collection's items; - * @param Type of reference object to help rightly loading next block of items; - * @param Type of loading block of items. - */ -public class LoadingMoreList> - extends ObservableCollection { - - private static final int RETRY_LOADING_AFTER_CHANGE_COUNT = 5; - - private static final LoadedItemsFilter DUPLICATES_REMOVER = (collectionObject, loadedItemsObject) -> - collectionObject.equals(loadedItemsObject) ? FilterAction.REMOVE_FROM_LOADED_ITEMS : FilterAction.DO_NOTHING; - - @NonNull - private final Scheduler loaderScheduler = Schedulers.from(Executors.newSingleThreadExecutor()); - @NonNull - private Observable loadingMoreObservable; - @NonNull - private final BehaviorSubject moreItemsCount = BehaviorSubject.createDefault(LoadedItems.UNKNOWN_ITEMS_COUNT); - @NonNull - private final ObservableList innerList = new ObservableList<>(); - @Nullable - private LoadedItemsFilter loadedItemsFilter; - @Nullable - private TMoreReference moreItemsReference; - - public LoadingMoreList(@NonNull final MoreItemsLoader moreMoreItemsLoader) { - this(moreMoreItemsLoader, null); - } - - @SuppressWarnings("PMD.ConstructorCallsOverridableMethod") - //ConstructorCallsOverridableMethod: actually it is calling in lambda callback - public LoadingMoreList(@NonNull final MoreItemsLoader moreMoreItemsLoader, - @Nullable final LoadedItems initialItems) { - super(); - this.loadingMoreObservable = Observable - .switchOnNext(Observable - .fromCallable(() -> createLoadRequestBasedObservable(this::createActualRequest, moreMoreItemsLoader::load).toObservable())) - .doOnNext(loadedItems -> onItemsLoaded(loadedItems, size(), false)) - .replay(1) - .refCount(); - - if (initialItems != null) { - innerOnItemsLoaded(initialItems, 0, false); - } - } - - @Nullable - public TMoreReference getMoreItemsReference() { - return moreItemsReference; - } - - @NonNull - private MoreLoadRequest createActualRequest() { - return new MoreLoadRequest<>(moreItemsReference, Math.max(0, size())); - } - - @NonNull - protected Single createLoadRequestBasedObservable(@NonNull final Callable requestCreator, - @NonNull final Function> observableCreator) { - return Single - .fromCallable(requestCreator) - .flatMap(loadRequest -> observableCreator.apply(loadRequest) - .subscribeOn(Schedulers.io()) - .observeOn(loaderScheduler) - .doOnSuccess(ignored -> { - if (!requestCreator.call().equals(loadRequest)) { - throw new RequestChangedDuringLoadingException(); - } - })) - .retry((number, throwable) -> - number <= RETRY_LOADING_AFTER_CHANGE_COUNT && throwable instanceof RequestChangedDuringLoadingException); - } - - @NonNull - protected final Scheduler getLoaderScheduler() { - return loaderScheduler; - } - - @NonNull - @Override - public Observable> observeChanges() { - return innerList.observeChanges(); - } - - @Override - protected void notifyAboutChanges(@NonNull final List insertedItems, - @NonNull final List removedItems, - @NonNull final Collection changes) { - Lc.assertion("Illegal operation. Modify getInnerList()"); - } - - /** - * Returns {@link ObservableList} of already loaded items so you can modify it. - * - * @return {@link ObservableList} of already loaded items. - */ - @NonNull - protected ObservableList getInnerList() { - return innerList; - } - - /** - * Returns if there are more items to load. - * - * @return True if there are more items to load. - */ - public boolean hasMoreItems() { - return moreItemsCount.getValue() != 0; - } - - /** - * Returns {@link Observable} which is providing status of if is there are more items to load or not. - * - * @return {@link Observable} of more items availability status. - */ - @NonNull - public Observable observeHasMoreItems() { - return moreItemsCount.map(count -> count != 0).distinctUntilChanged(); - } - - /** - * Returns {@link Observable} which is providing count of more items to load. - * - * @return {@link Observable} of more items availability status. - */ - @NonNull - public Observable observeMoreItemsCount() { - return moreItemsCount.distinctUntilChanged(); - } - - /** - * Sets if duplicates (compared by {@link #equals(Object)}) should be removed from loaded part of items right after loading. - * - * @param removeDuplicates True if duplicates should be removed. - */ - @SuppressWarnings("unchecked") - //unchecked: it's OK as we are using private static filter - public void setRemoveDuplicates(final boolean removeDuplicates) { - if (this.loadedItemsFilter != null && this.loadedItemsFilter != DUPLICATES_REMOVER) { - Lc.assertion("Remove old filter manually first"); - return; - } - this.loadedItemsFilter = removeDuplicates ? (LoadedItemsFilter) DUPLICATES_REMOVER : null; - } - - /** - * Sets specific filter object which will remove items from already loaded part or from new loaded items right after loading. - * - * @param loadedItemsFilter {@link LoadedItemsFilter} to make decision of removing items. - */ - public void setLoadedItemsFilter(@Nullable final LoadedItemsFilter loadedItemsFilter) { - this.loadedItemsFilter = loadedItemsFilter; - } - - private void innerOnItemsLoaded(@NonNull final LoadedItems loadedItems, final int insertPosition, final boolean reset) { - final List items = new ArrayList<>(loadedItems.getItems()); - final boolean lastPage = reset || insertPosition > size() - 1; - if (reset) { - resetState(); - if (insertPosition != 0) { - Lc.assertion("Wrong insert position " + insertPosition); - } - innerList.set(items); - } else { - if (this.loadedItemsFilter != null) { - filterList(items, this.loadedItemsFilter); - } - innerList.addAll(insertPosition, items); - } - if (lastPage) { - moreItemsReference = loadedItems.getReference(); - moreItemsCount.onNext(loadedItems.getMoreItemsCount()); - } - } - - /** - * Calls when any new items part loaded. - * - * @param loadedItems Loaded items; - * @param insertPosition Position to insert loaded items; - * @param reset Flag to reset previously loaded items or not. - */ - protected void onItemsLoaded(@NonNull final TLoadedItems loadedItems, final int insertPosition, final boolean reset) { - innerOnItemsLoaded(loadedItems, insertPosition, reset); - } - - private void filterList(@NonNull final List items, @NonNull final LoadedItemsFilter loadedItemsFilter) { - for (int i = items.size() - 1; i >= 0; i--) { - for (int j = innerList.size() - 1; j >= 0; j--) { - final FilterAction filterAction = loadedItemsFilter.decideFilterAction(innerList.get(j), items.get(i)); - if (filterAction == FilterAction.REMOVE_FROM_LOADED_ITEMS) { - items.remove(i); - break; - } - if (filterAction == FilterAction.REMOVE_FROM_COLLECTION) { - innerList.remove(j); - } - if (filterAction == FilterAction.REPLACE_SOURCE_ITEM_WITH_LOADED) { - innerList.update(j, items.get(i)); - items.remove(i); - break; - } - } - } - } - - @Override - public int size() { - return innerList.size(); - } - - @NonNull - @Override - public TItem get(final int position) { - return innerList.get(position); - } - - @NonNull - @Override - public Collection getItems() { - return innerList.getItems(); - } - - /** - * Returns {@link Observable} that is loading new items. - * - * @return {@link Observable} that is loading new items. - */ - @NonNull - protected Observable getLoadingMoreObservable() { - return loadingMoreObservable; - } - - /** - * Returns {@link Observable} which is loading item by position. - * It could return null in onNext callback if there is no item to load for such position. - * - * @param position Position to load item; - * @return {@link Observable} to load item. - */ - @NonNull - public Single> loadItem(final int position) { - return Observable.switchOnNext(Observable - .fromCallable(() -> { - if (position < size()) { - return Observable.just(new Optional<>(get(position))); - } else if (moreItemsCount.getValue() == 0) { - return Observable.just(new Optional(null)); - } else { - return loadingMoreObservable.switchMap(ignored -> Observable.>error(new NotLoadedYetException())); - } - })) - .subscribeOn(loaderScheduler) - .retry((number, throwable) -> throwable instanceof NotLoadedYetException) - .firstOrError(); - } - - /** - * Returns {@link Observable} which is loading item by range. - * It will return collection of loaded items in onNext callback. - * - * @param first First position of item to load; - * @param last Last position of item to load; - * @return {@link Observable} to load items. - */ - @NonNull - @SuppressWarnings("unchecked") - //unchecked: it's OK for such zip operator - public Single> loadRange(final int first, final int last) { - final List>> itemsRequests = new ArrayList<>(); - for (int i = first; i <= last; i++) { - itemsRequests.add(loadItem(i)); - } - return Single.zip(itemsRequests, - items -> { - final List result = new ArrayList<>(); - for (final Object item : items) { - final Optional optional = (Optional) item; - if (optional.get() != null) { - result.add(optional.get()); - } - } - return Collections.unmodifiableCollection(result); - }); - } - - /** - * Remove all loaded items and resets collection's state. - */ - public void reset() { - innerList.clear(); - resetState(); - } - - /** - * Remove all loaded items and resets collection's state but sets some initial items. - * - * @param initialItems initial items to be set after reset. - */ - public void reset(@NonNull final TLoadedItems initialItems) { - onItemsLoaded(initialItems, 0, true); - } - - protected void resetState() { - moreItemsReference = null; - moreItemsCount.onNext(LoadedItems.UNKNOWN_ITEMS_COUNT); - } - - /** - * Action to do with some items while new part of items have loaded. - */ - public enum FilterAction { - DO_NOTHING, - REMOVE_FROM_COLLECTION, - REMOVE_FROM_LOADED_ITEMS, - REPLACE_SOURCE_ITEM_WITH_LOADED - } - - /** - * Class that is representing object to decide what to do with some items from already loaded and newly loaded part. - * It should remove duplicates or items with changed data. - * - * @param Type of collection's items. - */ - public interface LoadedItemsFilter { - - /** - * Returns action to do based on items: do nothing, remove already loaded item or remove newly loaded item. - * - * @param collectionObject Item from collection of already loaded items; - * @param loadedItemsObject Item from collection of newly loaded items part; - * @return Action to do with items. - */ - @NonNull - FilterAction decideFilterAction(@NonNull TItem collectionObject, @NonNull TItem loadedItemsObject); - - } - - /** - * Helper exception happens if {@link #loadItem(int)} called with big index and latest loading items part still not reached such item. - */ - protected static class NotLoadedYetException extends Exception { - } - - /** - * Exception happens if loading request changed during loading so loaded items are not actual anymore. - */ - protected static class RequestChangedDuringLoadingException extends Exception { - } - -} diff --git a/src/main/java/ru/touchin/roboswag/core/observables/collections/loadable/MoreItemsLoader.java b/src/main/java/ru/touchin/roboswag/core/observables/collections/loadable/MoreItemsLoader.java deleted file mode 100644 index 8a64530..0000000 --- a/src/main/java/ru/touchin/roboswag/core/observables/collections/loadable/MoreItemsLoader.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov) - * - * This file is part of RoboSwag library. - * - * 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 ru.touchin.roboswag.core.observables.collections.loadable; - -import android.support.annotation.NonNull; - -import io.reactivex.Single; - -/** - * Created by Gavriil Sitnikov on 02/06/2016. - * Object that is loading next part of items by reference or position. - * - * @param Type of items to be loaded; - * @param Type of reference to be used to load next part of items; - * @param Type of loaded items part. - */ -public interface MoreItemsLoader> { - - /** - * Returns {@link Single} that could load next part of items. - * - * @param moreLoadRequest Request with info inside to load next part of items; - * @return {@link Single} of loading items. - */ - @NonNull - Single load(@NonNull final MoreLoadRequest moreLoadRequest); - -} \ No newline at end of file diff --git a/src/main/java/ru/touchin/roboswag/core/observables/collections/loadable/MoreLoadRequest.java b/src/main/java/ru/touchin/roboswag/core/observables/collections/loadable/MoreLoadRequest.java deleted file mode 100644 index 6cac3fc..0000000 --- a/src/main/java/ru/touchin/roboswag/core/observables/collections/loadable/MoreLoadRequest.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (c) 2015 RoboSwag (Gavriil Sitnikov, Vsevolod Ivanov) - * - * This file is part of RoboSwag library. - * - * 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 ru.touchin.roboswag.core.observables.collections.loadable; - -import android.support.annotation.Nullable; - -import ru.touchin.roboswag.core.utils.ObjectUtils; - -/** - * Created by Gavriil Sitnikov on 02/06/2016. - * Request represents request to load next part of items. - * - * @param Type of reference to load next part of items. - */ -public class MoreLoadRequest { - - @Nullable - private final TMoreReference moreReference; - private final int nextPosition; - - public MoreLoadRequest(@Nullable final TMoreReference moreReference, final int nextPosition) { - this.moreReference = moreReference; - this.nextPosition = nextPosition; - } - - /** - * Returns reference to be used to load next part of items. - * - * @return Reference object. - */ - @Nullable - public TMoreReference getReference() { - return moreReference; - } - - /** - * Returns position of next item to load. - * - * @return Position of next item. - */ - public int getNextPosition() { - return nextPosition; - } - - @Override - public boolean equals(@Nullable final Object object) { - return object instanceof MoreLoadRequest - && ObjectUtils.equals(((MoreLoadRequest) object).moreReference, moreReference) - && ((MoreLoadRequest) object).nextPosition == nextPosition; - } - - @Override - public int hashCode() { - return nextPosition + (moreReference != null ? moreReference.hashCode() : 0); - } - -} \ No newline at end of file diff --git a/src/main/java/ru/touchin/roboswag/core/utils/ObjectUtils.java b/src/main/java/ru/touchin/roboswag/core/utils/ObjectUtils.java index cb41c00..a8c2baf 100644 --- a/src/main/java/ru/touchin/roboswag/core/utils/ObjectUtils.java +++ b/src/main/java/ru/touchin/roboswag/core/utils/ObjectUtils.java @@ -27,8 +27,6 @@ import java.util.Collection; import java.util.Iterator; import java.util.Map; -import ru.touchin.roboswag.core.observables.collections.ObservableCollection; - /** * Created by Gavriil Sitnikov on 04/10/2015. * Some utilities related to objects. @@ -163,16 +161,6 @@ public final class ObjectUtils { return collection == null || collection.isEmpty(); } - /** - * Returns true if ObservableCollection is null or empty. - * - * @param observableCollection observableCollection to check; - * @return True if observableCollection is null or empty. - */ - public static boolean isNullOrEmpty(@Nullable final ObservableCollection observableCollection) { - return observableCollection == null || observableCollection.isEmpty(); - } - /** * Returns true if map is null or empty. *