1/*
2 * Copyright (C) 2014 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 */
16
17package android.support.v7.util;
18
19import java.lang.reflect.Array;
20import java.util.Arrays;
21import java.util.Collection;
22import java.util.Comparator;
23
24/**
25 * A Sorted list implementation that can keep items in order and also notify for changes in the
26 * list
27 * such that it can be bound to a {@link android.support.v7.widget.RecyclerView.Adapter
28 * RecyclerView.Adapter}.
29 * <p>
30 * It keeps items ordered using the {@link Callback#compare(Object, Object)} method and uses
31 * binary search to retrieve items. If the sorting criteria of your items may change, make sure you
32 * call appropriate methods while editing them to avoid data inconsistencies.
33 * <p>
34 * You can control the order of items and change notifications via the {@link Callback} parameter.
35 */
36@SuppressWarnings("unchecked")
37public class SortedList<T> {
38
39    /**
40     * Used by {@link #indexOf(Object)} when he item cannot be found in the list.
41     */
42    public static final int INVALID_POSITION = -1;
43
44    private static final int MIN_CAPACITY = 10;
45    private static final int CAPACITY_GROWTH = MIN_CAPACITY;
46    private static final int INSERTION = 1;
47    private static final int DELETION = 1 << 1;
48    private static final int LOOKUP = 1 << 2;
49    T[] mData;
50
51    /**
52     * A copy of the previous list contents used during the merge phase of addAll.
53     */
54    private T[] mOldData;
55    private int mOldDataStart;
56    private int mOldDataSize;
57
58    /**
59     * The size of the valid portion of mData during the merge phase of addAll.
60     */
61    private int mMergedSize;
62
63
64    /**
65     * The callback instance that controls the behavior of the SortedList and get notified when
66     * changes happen.
67     */
68    private Callback mCallback;
69
70    private BatchedCallback mBatchedCallback;
71
72    private int mSize;
73    private final Class<T> mTClass;
74
75    /**
76     * Creates a new SortedList of type T.
77     *
78     * @param klass    The class of the contents of the SortedList.
79     * @param callback The callback that controls the behavior of SortedList.
80     */
81    public SortedList(Class<T> klass, Callback<T> callback) {
82        this(klass, callback, MIN_CAPACITY);
83    }
84
85    /**
86     * Creates a new SortedList of type T.
87     *
88     * @param klass           The class of the contents of the SortedList.
89     * @param callback        The callback that controls the behavior of SortedList.
90     * @param initialCapacity The initial capacity to hold items.
91     */
92    public SortedList(Class<T> klass, Callback<T> callback, int initialCapacity) {
93        mTClass = klass;
94        mData = (T[]) Array.newInstance(klass, initialCapacity);
95        mCallback = callback;
96        mSize = 0;
97    }
98
99    /**
100     * The number of items in the list.
101     *
102     * @return The number of items in the list.
103     */
104    public int size() {
105        return mSize;
106    }
107
108    /**
109     * Adds the given item to the list. If this is a new item, SortedList calls
110     * {@link Callback#onInserted(int, int)}.
111     * <p>
112     * If the item already exists in the list and its sorting criteria is not changed, it is
113     * replaced with the existing Item. SortedList uses
114     * {@link Callback#areItemsTheSame(Object, Object)} to check if two items are the same item
115     * and uses {@link Callback#areContentsTheSame(Object, Object)} to decide whether it should
116     * call {@link Callback#onChanged(int, int)} or not. In both cases, it always removes the
117     * reference to the old item and puts the new item into the backing array even if
118     * {@link Callback#areContentsTheSame(Object, Object)} returns false.
119     * <p>
120     * If the sorting criteria of the item is changed, SortedList won't be able to find
121     * its duplicate in the list which will result in having a duplicate of the Item in the list.
122     * If you need to update sorting criteria of an item that already exists in the list,
123     * use {@link #updateItemAt(int, Object)}. You can find the index of the item using
124     * {@link #indexOf(Object)} before you update the object.
125     *
126     * @param item The item to be added into the list.
127     *
128     * @return The index of the newly added item.
129     * @see Callback#compare(Object, Object)
130     * @see Callback#areItemsTheSame(Object, Object)
131     * @see Callback#areContentsTheSame(Object, Object)}
132     */
133    public int add(T item) {
134        throwIfMerging();
135        return add(item, true);
136    }
137
138    /**
139     * Adds the given items to the list. Equivalent to calling {@link SortedList#add} in a loop,
140     * except the callback events may be in a different order/granularity since addAll can batch
141     * them for better performance.
142     * <p>
143     * If allowed, may modify the input array and even take the ownership over it in order
144     * to avoid extra memory allocation during sorting and deduplication.
145     * </p>
146     * @param items Array of items to be added into the list.
147     * @param mayModifyInput If true, SortedList is allowed to modify the input.
148     * @see SortedList#addAll(Object[] items)
149     */
150    public void addAll(T[] items, boolean mayModifyInput) {
151        throwIfMerging();
152        if (items.length == 0) {
153            return;
154        }
155        if (mayModifyInput) {
156            addAllInternal(items);
157        } else {
158            T[] copy = (T[]) Array.newInstance(mTClass, items.length);
159            System.arraycopy(items, 0, copy, 0, items.length);
160            addAllInternal(copy);
161        }
162
163    }
164
165    /**
166     * Adds the given items to the list. Does not modify the input.
167     *
168     * @see SortedList#addAll(T[] items, boolean mayModifyInput)
169     *
170     * @param items Array of items to be added into the list.
171     */
172    public void addAll(T... items) {
173        addAll(items, false);
174    }
175
176    /**
177     * Adds the given items to the list. Does not modify the input.
178     *
179     * @see SortedList#addAll(T[] items, boolean mayModifyInput)
180     *
181     * @param items Collection of items to be added into the list.
182     */
183    public void addAll(Collection<T> items) {
184        T[] copy = (T[]) Array.newInstance(mTClass, items.size());
185        addAll(items.toArray(copy), true);
186    }
187
188    private void addAllInternal(T[] newItems) {
189        final boolean forceBatchedUpdates = !(mCallback instanceof BatchedCallback);
190        if (forceBatchedUpdates) {
191            beginBatchedUpdates();
192        }
193
194        mOldData = mData;
195        mOldDataStart = 0;
196        mOldDataSize = mSize;
197
198        Arrays.sort(newItems, mCallback);  // Arrays.sort is stable.
199
200        final int newSize = deduplicate(newItems);
201        if (mSize == 0) {
202            mData = newItems;
203            mSize = newSize;
204            mMergedSize = newSize;
205            mCallback.onInserted(0, newSize);
206        } else {
207            merge(newItems, newSize);
208        }
209
210        mOldData = null;
211
212        if (forceBatchedUpdates) {
213            endBatchedUpdates();
214        }
215    }
216
217    /**
218     * Remove duplicate items, leaving only the last item from each group of "same" items.
219     * Move the remaining items to the beginning of the array.
220     *
221     * @return Number of deduplicated items at the beginning of the array.
222     */
223    private int deduplicate(T[] items) {
224        if (items.length == 0) {
225            throw new IllegalArgumentException("Input array must be non-empty");
226        }
227
228        // Keep track of the range of equal items at the end of the output.
229        // Start with the range containing just the first item.
230        int rangeStart = 0;
231        int rangeEnd = 1;
232
233        for (int i = 1; i < items.length; ++i) {
234            T currentItem = items[i];
235
236            int compare = mCallback.compare(items[rangeStart], currentItem);
237            if (compare > 0) {
238                throw new IllegalArgumentException("Input must be sorted in ascending order.");
239            }
240
241            if (compare == 0) {
242                // The range of equal items continues, update it.
243                final int sameItemPos = findSameItem(currentItem, items, rangeStart, rangeEnd);
244                if (sameItemPos != INVALID_POSITION) {
245                    // Replace the duplicate item.
246                    items[sameItemPos] = currentItem;
247                } else {
248                    // Expand the range.
249                    if (rangeEnd != i) {  // Avoid redundant copy.
250                        items[rangeEnd] = currentItem;
251                    }
252                    rangeEnd++;
253                }
254            } else {
255                // The range has ended. Reset it to contain just the current item.
256                if (rangeEnd != i) {  // Avoid redundant copy.
257                    items[rangeEnd] = currentItem;
258                }
259                rangeStart = rangeEnd++;
260            }
261        }
262        return rangeEnd;
263    }
264
265
266    private int findSameItem(T item, T[] items, int from, int to) {
267        for (int pos = from; pos < to; pos++) {
268            if (mCallback.areItemsTheSame(items[pos], item)) {
269                return pos;
270            }
271        }
272        return INVALID_POSITION;
273    }
274
275    /**
276     * This method assumes that newItems are sorted and deduplicated.
277     */
278    private void merge(T[] newData, int newDataSize) {
279        final int mergedCapacity = mSize + newDataSize + CAPACITY_GROWTH;
280        mData = (T[]) Array.newInstance(mTClass, mergedCapacity);
281        mMergedSize = 0;
282
283        int newDataStart = 0;
284        while (mOldDataStart < mOldDataSize || newDataStart < newDataSize) {
285            if (mOldDataStart == mOldDataSize) {
286                // No more old items, copy the remaining new items.
287                int itemCount = newDataSize - newDataStart;
288                System.arraycopy(newData, newDataStart, mData, mMergedSize, itemCount);
289                mMergedSize += itemCount;
290                mSize += itemCount;
291                mCallback.onInserted(mMergedSize - itemCount, itemCount);
292                break;
293            }
294
295            if (newDataStart == newDataSize) {
296                // No more new items, copy the remaining old items.
297                int itemCount = mOldDataSize - mOldDataStart;
298                System.arraycopy(mOldData, mOldDataStart, mData, mMergedSize, itemCount);
299                mMergedSize += itemCount;
300                break;
301            }
302
303            T oldItem = mOldData[mOldDataStart];
304            T newItem = newData[newDataStart];
305            int compare = mCallback.compare(oldItem, newItem);
306            if (compare > 0) {
307                // New item is lower, output it.
308                mData[mMergedSize++] = newItem;
309                mSize++;
310                newDataStart++;
311                mCallback.onInserted(mMergedSize - 1, 1);
312            } else if (compare == 0 && mCallback.areItemsTheSame(oldItem, newItem)) {
313                // Items are the same. Output the new item, but consume both.
314                mData[mMergedSize++] = newItem;
315                newDataStart++;
316                mOldDataStart++;
317                if (!mCallback.areContentsTheSame(oldItem, newItem)) {
318                    mCallback.onChanged(mMergedSize - 1, 1);
319                }
320            } else {
321                // Old item is lower than or equal to (but not the same as the new). Output it.
322                // New item with the same sort order will be inserted later.
323                mData[mMergedSize++] = oldItem;
324                mOldDataStart++;
325            }
326        }
327    }
328
329    private void throwIfMerging() {
330        if (mOldData != null) {
331            throw new IllegalStateException("Cannot call this method from within addAll");
332        }
333    }
334
335    /**
336     * Batches adapter updates that happen between calling this method until calling
337     * {@link #endBatchedUpdates()}. For example, if you add multiple items in a loop
338     * and they are placed into consecutive indices, SortedList calls
339     * {@link Callback#onInserted(int, int)} only once with the proper item count. If an event
340     * cannot be merged with the previous event, the previous event is dispatched
341     * to the callback instantly.
342     * <p>
343     * After running your data updates, you <b>must</b> call {@link #endBatchedUpdates()}
344     * which will dispatch any deferred data change event to the current callback.
345     * <p>
346     * A sample implementation may look like this:
347     * <pre>
348     *     mSortedList.beginBatchedUpdates();
349     *     try {
350     *         mSortedList.add(item1)
351     *         mSortedList.add(item2)
352     *         mSortedList.remove(item3)
353     *         ...
354     *     } finally {
355     *         mSortedList.endBatchedUpdates();
356     *     }
357     * </pre>
358     * <p>
359     * Instead of using this method to batch calls, you can use a Callback that extends
360     * {@link BatchedCallback}. In that case, you must make sure that you are manually calling
361     * {@link BatchedCallback#dispatchLastEvent()} right after you complete your data changes.
362     * Failing to do so may create data inconsistencies with the Callback.
363     * <p>
364     * If the current Callback is an instance of {@link BatchedCallback}, calling this method
365     * has no effect.
366     */
367    public void beginBatchedUpdates() {
368        throwIfMerging();
369        if (mCallback instanceof BatchedCallback) {
370            return;
371        }
372        if (mBatchedCallback == null) {
373            mBatchedCallback = new BatchedCallback(mCallback);
374        }
375        mCallback = mBatchedCallback;
376    }
377
378    /**
379     * Ends the update transaction and dispatches any remaining event to the callback.
380     */
381    public void endBatchedUpdates() {
382        throwIfMerging();
383        if (mCallback instanceof BatchedCallback) {
384            ((BatchedCallback) mCallback).dispatchLastEvent();
385        }
386        if (mCallback == mBatchedCallback) {
387            mCallback = mBatchedCallback.mWrappedCallback;
388        }
389    }
390
391    private int add(T item, boolean notify) {
392        int index = findIndexOf(item, mData, 0, mSize, INSERTION);
393        if (index == INVALID_POSITION) {
394            index = 0;
395        } else if (index < mSize) {
396            T existing = mData[index];
397            if (mCallback.areItemsTheSame(existing, item)) {
398                if (mCallback.areContentsTheSame(existing, item)) {
399                    //no change but still replace the item
400                    mData[index] = item;
401                    return index;
402                } else {
403                    mData[index] = item;
404                    mCallback.onChanged(index, 1);
405                    return index;
406                }
407            }
408        }
409        addToData(index, item);
410        if (notify) {
411            mCallback.onInserted(index, 1);
412        }
413        return index;
414    }
415
416    /**
417     * Removes the provided item from the list and calls {@link Callback#onRemoved(int, int)}.
418     *
419     * @param item The item to be removed from the list.
420     *
421     * @return True if item is removed, false if item cannot be found in the list.
422     */
423    public boolean remove(T item) {
424        throwIfMerging();
425        return remove(item, true);
426    }
427
428    /**
429     * Removes the item at the given index and calls {@link Callback#onRemoved(int, int)}.
430     *
431     * @param index The index of the item to be removed.
432     *
433     * @return The removed item.
434     */
435    public T removeItemAt(int index) {
436        throwIfMerging();
437        T item = get(index);
438        removeItemAtIndex(index, true);
439        return item;
440    }
441
442    private boolean remove(T item, boolean notify) {
443        int index = findIndexOf(item, mData, 0, mSize, DELETION);
444        if (index == INVALID_POSITION) {
445            return false;
446        }
447        removeItemAtIndex(index, notify);
448        return true;
449    }
450
451    private void removeItemAtIndex(int index, boolean notify) {
452        System.arraycopy(mData, index + 1, mData, index, mSize - index - 1);
453        mSize--;
454        mData[mSize] = null;
455        if (notify) {
456            mCallback.onRemoved(index, 1);
457        }
458    }
459
460    /**
461     * Updates the item at the given index and calls {@link Callback#onChanged(int, int)} and/or
462     * {@link Callback#onMoved(int, int)} if necessary.
463     * <p>
464     * You can use this method if you need to change an existing Item such that its position in the
465     * list may change.
466     * <p>
467     * If the new object is a different object (<code>get(index) != item</code>) and
468     * {@link Callback#areContentsTheSame(Object, Object)} returns <code>true</code>, SortedList
469     * avoids calling {@link Callback#onChanged(int, int)} otherwise it calls
470     * {@link Callback#onChanged(int, int)}.
471     * <p>
472     * If the new position of the item is different than the provided <code>index</code>,
473     * SortedList
474     * calls {@link Callback#onMoved(int, int)}.
475     *
476     * @param index The index of the item to replace
477     * @param item  The item to replace the item at the given Index.
478     * @see #add(Object)
479     */
480    public void updateItemAt(int index, T item) {
481        throwIfMerging();
482        final T existing = get(index);
483        // assume changed if the same object is given back
484        boolean contentsChanged = existing == item || !mCallback.areContentsTheSame(existing, item);
485        if (existing != item) {
486            // different items, we can use comparison and may avoid lookup
487            final int cmp = mCallback.compare(existing, item);
488            if (cmp == 0) {
489                mData[index] = item;
490                if (contentsChanged) {
491                    mCallback.onChanged(index, 1);
492                }
493                return;
494            }
495        }
496        if (contentsChanged) {
497            mCallback.onChanged(index, 1);
498        }
499        // TODO this done in 1 pass to avoid shifting twice.
500        removeItemAtIndex(index, false);
501        int newIndex = add(item, false);
502        if (index != newIndex) {
503            mCallback.onMoved(index, newIndex);
504        }
505    }
506
507    /**
508     * This method can be used to recalculate the position of the item at the given index, without
509     * triggering an {@link Callback#onChanged(int, int)} callback.
510     * <p>
511     * If you are editing objects in the list such that their position in the list may change but
512     * you don't want to trigger an onChange animation, you can use this method to re-position it.
513     * If the item changes position, SortedList will call {@link Callback#onMoved(int, int)}
514     * without
515     * calling {@link Callback#onChanged(int, int)}.
516     * <p>
517     * A sample usage may look like:
518     *
519     * <pre>
520     *     final int position = mSortedList.indexOf(item);
521     *     item.incrementPriority(); // assume items are sorted by priority
522     *     mSortedList.recalculatePositionOfItemAt(position);
523     * </pre>
524     * In the example above, because the sorting criteria of the item has been changed,
525     * mSortedList.indexOf(item) will not be able to find the item. This is why the code above
526     * first
527     * gets the position before editing the item, edits it and informs the SortedList that item
528     * should be repositioned.
529     *
530     * @param index The current index of the Item whose position should be re-calculated.
531     * @see #updateItemAt(int, Object)
532     * @see #add(Object)
533     */
534    public void recalculatePositionOfItemAt(int index) {
535        throwIfMerging();
536        // TODO can be improved
537        final T item = get(index);
538        removeItemAtIndex(index, false);
539        int newIndex = add(item, false);
540        if (index != newIndex) {
541            mCallback.onMoved(index, newIndex);
542        }
543    }
544
545    /**
546     * Returns the item at the given index.
547     *
548     * @param index The index of the item to retrieve.
549     *
550     * @return The item at the given index.
551     * @throws java.lang.IndexOutOfBoundsException if provided index is negative or larger than the
552     *                                             size of the list.
553     */
554    public T get(int index) throws IndexOutOfBoundsException {
555        if (index >= mSize || index < 0) {
556            throw new IndexOutOfBoundsException("Asked to get item at " + index + " but size is "
557                    + mSize);
558        }
559        if (mOldData != null) {
560            // The call is made from a callback during addAll execution. The data is split
561            // between mData and mOldData.
562            if (index >= mMergedSize) {
563                return mOldData[index - mMergedSize + mOldDataStart];
564            }
565        }
566        return mData[index];
567    }
568
569    /**
570     * Returns the position of the provided item.
571     *
572     * @param item The item to query for position.
573     *
574     * @return The position of the provided item or {@link #INVALID_POSITION} if item is not in the
575     * list.
576     */
577    public int indexOf(T item) {
578        if (mOldData != null) {
579            int index = findIndexOf(item, mData, 0, mMergedSize, LOOKUP);
580            if (index != INVALID_POSITION) {
581                return index;
582            }
583            index = findIndexOf(item, mOldData, mOldDataStart, mOldDataSize, LOOKUP);
584            if (index != INVALID_POSITION) {
585                return index - mOldDataStart + mMergedSize;
586            }
587            return INVALID_POSITION;
588        }
589        return findIndexOf(item, mData, 0, mSize, LOOKUP);
590    }
591
592    private int findIndexOf(T item, T[] mData, int left, int right, int reason) {
593        while (left < right) {
594            final int middle = (left + right) / 2;
595            T myItem = mData[middle];
596            final int cmp = mCallback.compare(myItem, item);
597            if (cmp < 0) {
598                left = middle + 1;
599            } else if (cmp == 0) {
600                if (mCallback.areItemsTheSame(myItem, item)) {
601                    return middle;
602                } else {
603                    int exact = linearEqualitySearch(item, middle, left, right);
604                    if (reason == INSERTION) {
605                        return exact == INVALID_POSITION ? middle : exact;
606                    } else {
607                        return exact;
608                    }
609                }
610            } else {
611                right = middle;
612            }
613        }
614        return reason == INSERTION ? left : INVALID_POSITION;
615    }
616
617    private int linearEqualitySearch(T item, int middle, int left, int right) {
618        // go left
619        for (int next = middle - 1; next >= left; next--) {
620            T nextItem = mData[next];
621            int cmp = mCallback.compare(nextItem, item);
622            if (cmp != 0) {
623                break;
624            }
625            if (mCallback.areItemsTheSame(nextItem, item)) {
626                return next;
627            }
628        }
629        for (int next = middle + 1; next < right; next++) {
630            T nextItem = mData[next];
631            int cmp = mCallback.compare(nextItem, item);
632            if (cmp != 0) {
633                break;
634            }
635            if (mCallback.areItemsTheSame(nextItem, item)) {
636                return next;
637            }
638        }
639        return INVALID_POSITION;
640    }
641
642    private void addToData(int index, T item) {
643        if (index > mSize) {
644            throw new IndexOutOfBoundsException(
645                    "cannot add item to " + index + " because size is " + mSize);
646        }
647        if (mSize == mData.length) {
648            // we are at the limit enlarge
649            T[] newData = (T[]) Array.newInstance(mTClass, mData.length + CAPACITY_GROWTH);
650            System.arraycopy(mData, 0, newData, 0, index);
651            newData[index] = item;
652            System.arraycopy(mData, index, newData, index + 1, mSize - index);
653            mData = newData;
654        } else {
655            // just shift, we fit
656            System.arraycopy(mData, index, mData, index + 1, mSize - index);
657            mData[index] = item;
658        }
659        mSize++;
660    }
661
662    /**
663     * Removes all items from the SortedList.
664     */
665    public void clear() {
666        throwIfMerging();
667        if (mSize == 0) {
668            return;
669        }
670        final int prevSize = mSize;
671        Arrays.fill(mData, 0, prevSize, null);
672        mSize = 0;
673        mCallback.onRemoved(0, prevSize);
674    }
675
676    /**
677     * The class that controls the behavior of the {@link SortedList}.
678     * <p>
679     * It defines how items should be sorted and how duplicates should be handled.
680     * <p>
681     * SortedList calls the callback methods on this class to notify changes about the underlying
682     * data.
683     */
684    public static abstract class Callback<T2> implements Comparator<T2>, ListUpdateCallback {
685
686        /**
687         * Similar to {@link java.util.Comparator#compare(Object, Object)}, should compare two and
688         * return how they should be ordered.
689         *
690         * @param o1 The first object to compare.
691         * @param o2 The second object to compare.
692         *
693         * @return a negative integer, zero, or a positive integer as the
694         * first argument is less than, equal to, or greater than the
695         * second.
696         */
697        @Override
698        abstract public int compare(T2 o1, T2 o2);
699
700        /**
701         * Called by the SortedList when the item at the given position is updated.
702         *
703         * @param position The position of the item which has been updated.
704         * @param count    The number of items which has changed.
705         */
706        abstract public void onChanged(int position, int count);
707
708        @Override
709        public void onChanged(int position, int count, Object payload) {
710            onChanged(position, count);
711        }
712
713        /**
714         * Called by the SortedList when it wants to check whether two items have the same data
715         * or not. SortedList uses this information to decide whether it should call
716         * {@link #onChanged(int, int)} or not.
717         * <p>
718         * SortedList uses this method to check equality instead of {@link Object#equals(Object)}
719         * so
720         * that you can change its behavior depending on your UI.
721         * <p>
722         * For example, if you are using SortedList with a {@link android.support.v7.widget.RecyclerView.Adapter
723         * RecyclerView.Adapter}, you should
724         * return whether the items' visual representations are the same or not.
725         *
726         * @param oldItem The previous representation of the object.
727         * @param newItem The new object that replaces the previous one.
728         *
729         * @return True if the contents of the items are the same or false if they are different.
730         */
731        abstract public boolean areContentsTheSame(T2 oldItem, T2 newItem);
732
733        /**
734         * Called by the SortedList to decide whether two object represent the same Item or not.
735         * <p>
736         * For example, if your items have unique ids, this method should check their equality.
737         *
738         * @param item1 The first item to check.
739         * @param item2 The second item to check.
740         *
741         * @return True if the two items represent the same object or false if they are different.
742         */
743        abstract public boolean areItemsTheSame(T2 item1, T2 item2);
744    }
745
746    /**
747     * A callback implementation that can batch notify events dispatched by the SortedList.
748     * <p>
749     * This class can be useful if you want to do multiple operations on a SortedList but don't
750     * want to dispatch each event one by one, which may result in a performance issue.
751     * <p>
752     * For example, if you are going to add multiple items to a SortedList, BatchedCallback call
753     * convert individual <code>onInserted(index, 1)</code> calls into one
754     * <code>onInserted(index, N)</code> if items are added into consecutive indices. This change
755     * can help RecyclerView resolve changes much more easily.
756     * <p>
757     * If consecutive changes in the SortedList are not suitable for batching, BatchingCallback
758     * dispatches them as soon as such case is detected. After your edits on the SortedList is
759     * complete, you <b>must</b> always call {@link BatchedCallback#dispatchLastEvent()} to flush
760     * all changes to the Callback.
761     */
762    public static class BatchedCallback<T2> extends Callback<T2> {
763
764        final Callback<T2> mWrappedCallback;
765        private final BatchingListUpdateCallback mBatchingListUpdateCallback;
766        /**
767         * Creates a new BatchedCallback that wraps the provided Callback.
768         *
769         * @param wrappedCallback The Callback which should received the data change callbacks.
770         *                        Other method calls (e.g. {@link #compare(Object, Object)} from
771         *                        the SortedList are directly forwarded to this Callback.
772         */
773        public BatchedCallback(Callback<T2> wrappedCallback) {
774            mWrappedCallback = wrappedCallback;
775            mBatchingListUpdateCallback = new BatchingListUpdateCallback(mWrappedCallback);
776        }
777
778        @Override
779        public int compare(T2 o1, T2 o2) {
780            return mWrappedCallback.compare(o1, o2);
781        }
782
783        @Override
784        public void onInserted(int position, int count) {
785            mBatchingListUpdateCallback.onInserted(position, count);
786        }
787
788        @Override
789        public void onRemoved(int position, int count) {
790            mBatchingListUpdateCallback.onRemoved(position, count);
791        }
792
793        @Override
794        public void onMoved(int fromPosition, int toPosition) {
795            mBatchingListUpdateCallback.onMoved(fromPosition, toPosition);
796        }
797
798        @Override
799        public void onChanged(int position, int count) {
800            mBatchingListUpdateCallback.onChanged(position, count, null);
801        }
802
803        @Override
804        public boolean areContentsTheSame(T2 oldItem, T2 newItem) {
805            return mWrappedCallback.areContentsTheSame(oldItem, newItem);
806        }
807
808        @Override
809        public boolean areItemsTheSame(T2 item1, T2 item2) {
810            return mWrappedCallback.areItemsTheSame(item1, item2);
811        }
812
813        /**
814         * This method dispatches any pending event notifications to the wrapped Callback.
815         * You <b>must</b> always call this method after you are done with editing the SortedList.
816         */
817        public void dispatchLastEvent() {
818            mBatchingListUpdateCallback.dispatchLastEvent();
819        }
820    }
821}
822