package me.tatarka.bindingcollectionadapter2.collections;
import android.databinding.ListChangeRegistry;
import android.databinding.ObservableList;
import android.support.annotation.NonNull;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* An {@link ObservableList} that presents multiple lists and items as one contiguous source.
* Changes to any of the given lists will be reflected here. You cannot modify {@code
* MergeObservableList} itself other than adding and removing backing lists or items with {@link
* #insertItem(Object)} and {@link #insertList(ObservableList)} respectively. This is a good case
* where you have multiple data sources, or a handful of fixed items mixed in with lists of data.
*/
public class MergeObservableList<T> extends AbstractList<T> implements ObservableList<T> {
private final ArrayList<List<? extends T>> lists = new ArrayList<>();
private final ListChangeCallback callback = new ListChangeCallback();
private final ListChangeRegistry listeners = new ListChangeRegistry();
@Override
public void addOnListChangedCallback(OnListChangedCallback<? extends ObservableList<T>> listener) {
listeners.add(listener);
}
@Override
public void removeOnListChangedCallback(OnListChangedCallback<? extends ObservableList<T>> listener) {
listeners.remove(listener);
}
/**
* Inserts the given item into the merge list.
*/
public MergeObservableList<T> insertItem(T object) {
lists.add(Collections.singletonList(object));
modCount += 1;
listeners.notifyInserted(this, size() - 1, 1);
return this;
}
/**
* Inserts the given {@link ObservableList} into the merge list. Any changes in the given list
* will be reflected and propagated here.
*/
@SuppressWarnings("unchecked")
public MergeObservableList<T> insertList(@NonNull ObservableList<? extends T> list) {
list.addOnListChangedCallback(callback);
int oldSize = size();
lists.add(list);
modCount += 1;
if (!list.isEmpty()) {
listeners.notifyInserted(this, oldSize, list.size());
}
return this;
}
/**
* Removes the given item from the merge list.
*/
public boolean removeItem(T object) {
int size = 0;
for (int i = 0, listsSize = lists.size(); i < listsSize; i++) {
List<? extends T> list = lists.get(i);
if (list.size() == 1) {
Object item = list.get(0);
if ((object == null) ? (item == null) : object.equals(item)) {
lists.remove(i);
modCount += 1;
listeners.notifyRemoved(this, size, 1);
return true;
}
}
size += list.size();
}
return false;
}
/**
* Removes the given {@link ObservableList} from the merge list.
*/
@SuppressWarnings("unchecked")
public boolean removeList(ObservableList<? extends T> listToRemove) {
int size = 0;
for (int i = 0, listsSize = lists.size(); i < listsSize; i++) {
List<? extends T> list = lists.get(i);
if (list == listToRemove) {
listToRemove.removeOnListChangedCallback(callback);
lists.remove(i);
modCount += 1;
listeners.notifyRemoved(this, size, list.size());
return true;
}
size += list.size();
}
return false;
}
/**
* Converts an index into this merge list into an into an index of the given backing list.
*
* @throws IndexOutOfBoundsException for an invalid index.
* @throws IllegalArgumentException if the given list is not backing this merge list.
*/
public int mergeToBackingIndex(ObservableList<? extends T> backingList, int index) {
if (index < 0) {
throw new IndexOutOfBoundsException();
}
int size = 0;
for (int i = 0, listsSize = lists.size(); i < listsSize; i++) {
List<? extends T> list = lists.get(i);
if (backingList == list) {
if (index < list.size()) {
return size + index;
} else {
throw new IndexOutOfBoundsException();
}
}
size += list.size();
}
throw new IllegalArgumentException();
}
/**
* Converts an index into a backing list into an index into this merge list.
*
* @throws IndexOutOfBoundsException for an invalid index.
* @throws IllegalArgumentException if the given list is not backing this merge list.
*/
public int backingIndexToMerge(ObservableList<? extends T> backingList, int index) {
if (index < 0) {
throw new IndexOutOfBoundsException();
}
int size = 0;
for (int i = 0, listsSize = lists.size(); i < listsSize; i++) {
List<? extends T> list = lists.get(i);
if (backingList == list) {
if (index - size < list.size()) {
return index - size;
} else {
throw new IndexOutOfBoundsException();
}
}
size += list.size();
}
throw new IllegalArgumentException();
}
@Override
public T get(int location) {
if (location < 0) {
throw new IndexOutOfBoundsException();
}
int size = 0;
for (int i = 0, listsSize = lists.size(); i < listsSize; i++) {
List<? extends T> list = lists.get(i);
if (location - size < list.size()) {
return list.get(location - size);
}
size += list.size();
}
throw new IndexOutOfBoundsException();
}
@Override
public int size() {
int size = 0;
for (int i = 0, listsSize = lists.size(); i < listsSize; i++) {
List<? extends T> list = lists.get(i);
size += list.size();
}
return size;
}
class ListChangeCallback extends OnListChangedCallback {
@Override
public void onChanged(ObservableList sender) {
modCount += 1;
listeners.notifyChanged(MergeObservableList.this);
}
@Override
public void onItemRangeChanged(ObservableList sender, int positionStart, int itemCount) {
int size = 0;
for (int i = 0, listsSize = lists.size(); i < listsSize; i++) {
List list = lists.get(i);
if (list == sender) {
listeners.notifyChanged(MergeObservableList.this, size + positionStart, itemCount);
return;
}
size += list.size();
}
}
@Override
public void onItemRangeInserted(ObservableList sender, int positionStart, int itemCount) {
modCount += 1;
int size = 0;
for (int i = 0, listsSize = lists.size(); i < listsSize; i++) {
List list = lists.get(i);
if (list == sender) {
listeners.notifyInserted(MergeObservableList.this, size + positionStart, itemCount);
return;
}
size += list.size();
}
}
@Override
public void onItemRangeMoved(ObservableList sender, int fromPosition, int toPosition, int itemCount) {
int size = 0;
for (int i = 0, listsSize = lists.size(); i < listsSize; i++) {
List list = lists.get(i);
if (list == sender) {
listeners.notifyMoved(MergeObservableList.this, size + fromPosition, size + toPosition, itemCount);
return;
}
size += list.size();
}
}
@Override
public void onItemRangeRemoved(ObservableList sender, int positionStart, int itemCount) {
modCount += 1;
int size = 0;
for (int i = 0, listsSize = lists.size(); i < listsSize; i++) {
List list = lists.get(i);
if (list == sender) {
listeners.notifyRemoved(MergeObservableList.this, size + positionStart, itemCount);
return;
}
size += list.size();
}
}
}
}