1/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package android.support.v7.util;
17
18/**
19 * Wraps a {@link ListUpdateCallback} callback and batches operations that can be merged.
20 * <p>
21 * For instance, when 2 add operations comes that adds 2 consecutive elements,
22 * BatchingListUpdateCallback merges them and calls the wrapped callback only once.
23 * <p>
24 * This is a general purpose class and is also used by
25 * {@link android.support.v7.util.DiffUtil.DiffResult DiffResult} and
26 * {@link SortedList} to minimize the number of updates that are dispatched.
27 * <p>
28 * If you use this class to batch updates, you must call {@link #dispatchLastEvent()} when the
29 * stream of update events drain.
30 */
31public class BatchingListUpdateCallback implements ListUpdateCallback {
32    private static final int TYPE_NONE = 0;
33    private static final int TYPE_ADD = 1;
34    private static final int TYPE_REMOVE = 2;
35    private static final int TYPE_CHANGE = 3;
36
37    final ListUpdateCallback mWrapped;
38
39    int mLastEventType = TYPE_NONE;
40    int mLastEventPosition = -1;
41    int mLastEventCount = -1;
42    Object mLastEventPayload = null;
43
44    public BatchingListUpdateCallback(ListUpdateCallback callback) {
45        mWrapped = callback;
46    }
47
48    /**
49     * BatchingListUpdateCallback holds onto the last event to see if it can be merged with the
50     * next one. When stream of events finish, you should call this method to dispatch the last
51     * event.
52     */
53    public void dispatchLastEvent() {
54        if (mLastEventType == TYPE_NONE) {
55            return;
56        }
57        switch (mLastEventType) {
58            case TYPE_ADD:
59                mWrapped.onInserted(mLastEventPosition, mLastEventCount);
60                break;
61            case TYPE_REMOVE:
62                mWrapped.onRemoved(mLastEventPosition, mLastEventCount);
63                break;
64            case TYPE_CHANGE:
65                mWrapped.onChanged(mLastEventPosition, mLastEventCount, mLastEventPayload);
66                break;
67        }
68        mLastEventPayload = null;
69        mLastEventType = TYPE_NONE;
70    }
71
72    @Override
73    public void onInserted(int position, int count) {
74        if (mLastEventType == TYPE_ADD && position >= mLastEventPosition
75                && position <= mLastEventPosition + mLastEventCount) {
76            mLastEventCount += count;
77            mLastEventPosition = Math.min(position, mLastEventPosition);
78            return;
79        }
80        dispatchLastEvent();
81        mLastEventPosition = position;
82        mLastEventCount = count;
83        mLastEventType = TYPE_ADD;
84    }
85
86    @Override
87    public void onRemoved(int position, int count) {
88        if (mLastEventType == TYPE_REMOVE && mLastEventPosition >= position &&
89                mLastEventPosition <= position + count) {
90            mLastEventCount += count;
91            mLastEventPosition = position;
92            return;
93        }
94        dispatchLastEvent();
95        mLastEventPosition = position;
96        mLastEventCount = count;
97        mLastEventType = TYPE_REMOVE;
98    }
99
100    @Override
101    public void onMoved(int fromPosition, int toPosition) {
102        dispatchLastEvent(); // moves are not merged
103        mWrapped.onMoved(fromPosition, toPosition);
104    }
105
106    @Override
107    public void onChanged(int position, int count, Object payload) {
108        if (mLastEventType == TYPE_CHANGE &&
109                !(position > mLastEventPosition + mLastEventCount
110                        || position + count < mLastEventPosition || mLastEventPayload != payload)) {
111            // take potential overlap into account
112            int previousEnd = mLastEventPosition + mLastEventCount;
113            mLastEventPosition = Math.min(position, mLastEventPosition);
114            mLastEventCount = Math.max(previousEnd, position + count) - mLastEventPosition;
115            return;
116        }
117        dispatchLastEvent();
118        mLastEventPosition = position;
119        mLastEventCount = count;
120        mLastEventPayload = payload;
121        mLastEventType = TYPE_CHANGE;
122    }
123}
124