2 * Copyright © 2019 Damyan Ivanov.
3 * This file is part of MoLe.
4 * MoLe is free software: you can distribute it and/or modify it
5 * under the term of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your opinion), any later version.
9 * MoLe is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License terms for details.
14 * You should have received a copy of the GNU General Public License
15 * along with MoLe. If not, see <https://www.gnu.org/licenses/>.
18 package net.ktnx.mobileledger.utils;
20 import android.os.Build;
21 import android.util.Log;
23 import java.util.Collection;
24 import java.util.Comparator;
25 import java.util.Iterator;
26 import java.util.List;
27 import java.util.ListIterator;
28 import java.util.Observable;
29 import java.util.Spliterator;
30 import java.util.concurrent.locks.ReentrantReadWriteLock;
31 import java.util.function.Consumer;
32 import java.util.function.Predicate;
33 import java.util.function.UnaryOperator;
34 import java.util.stream.Stream;
36 import androidx.annotation.NonNull;
37 import androidx.annotation.Nullable;
38 import androidx.annotation.RequiresApi;
40 public class ObservableList<T> extends Observable implements List<T> {
42 private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
43 public ObservableList(List<T> list) {
46 private void forceNotify() {
50 private void forceNotify(int index) {
52 notifyObservers(index);
55 try (LockHolder lh = lockForReading()) {
59 public boolean isEmpty() {
60 try (LockHolder lh = lockForReading()) {
61 return list.isEmpty();
64 public boolean contains(@Nullable Object o) {
65 try (LockHolder lh = lockForReading()) {
66 return list.contains(o);
70 public Iterator<T> iterator() {
71 throw new RuntimeException("Iterators break encapsulation and ignore locking");
72 // return list.iterator();
74 public Object[] toArray() {
75 try (LockHolder lh = lockForReading()) {
76 return list.toArray();
79 public <T1> T1[] toArray(@Nullable T1[] a) {
80 try (LockHolder lh = lockForReading()) {
81 return list.toArray(a);
84 public boolean add(T t) {
85 try (LockHolder lh = lockForWriting()) {
86 boolean result = list.add(t);
88 if (result) forceNotify();
92 public boolean remove(@Nullable Object o) {
93 try (LockHolder lh = lockForWriting()) {
94 boolean result = list.remove(o);
96 if (result) forceNotify();
100 public T removeQuietly(int index) {
101 return list.remove(index);
103 public boolean containsAll(@NonNull Collection<?> c) {
104 try (LockHolder lh = lockForReading()) {
105 return list.containsAll(c);
108 public boolean addAll(@NonNull Collection<? extends T> c) {
109 try (LockHolder lh = lockForWriting()) {
110 boolean result = list.addAll(c);
112 if (result) forceNotify();
116 public boolean addAll(int index, @NonNull Collection<? extends T> c) {
117 try (LockHolder lh = lockForWriting()) {
118 boolean result = list.addAll(index, c);
120 if (result) forceNotify();
124 public boolean addAllQuietly(int index, Collection<? extends T> c) {
125 return list.addAll(index, c);
127 public boolean removeAll(@NonNull Collection<?> c) {
128 try (LockHolder lh = lockForWriting()) {
129 boolean result = list.removeAll(c);
131 if (result) forceNotify();
135 public boolean retainAll(@NonNull Collection<?> c) {
136 try (LockHolder lh = lockForWriting()) {
137 boolean result = list.retainAll(c);
139 if (result) forceNotify();
143 @RequiresApi(api = Build.VERSION_CODES.N)
144 public void replaceAll(@NonNull UnaryOperator<T> operator) {
145 try (LockHolder lh = lockForWriting()) {
146 list.replaceAll(operator);
151 @RequiresApi(api = Build.VERSION_CODES.N)
152 public void sort(@Nullable Comparator<? super T> c) {
153 try (LockHolder lh = lockForWriting()) {
154 lock.writeLock().lock();
160 public void clear() {
161 try (LockHolder lh = lockForWriting()) {
162 boolean wasEmpty = list.isEmpty();
165 if (!wasEmpty) forceNotify();
168 public T get(int index) {
169 try (LockHolder lh = lockForReading()) {
170 return list.get(index);
173 public T set(int index, T element) {
174 try (LockHolder lh = lockForWriting()) {
175 T result = list.set(index, element);
181 public void add(int index, T element) {
182 try (LockHolder lh = lockForWriting()) {
183 list.add(index, element);
188 public T remove(int index) {
189 try (LockHolder lh = lockForWriting()) {
190 T result = list.remove(index);
196 public int indexOf(@Nullable Object o) {
197 try (LockHolder lh = lockForReading()) {
198 return list.indexOf(o);
201 public int lastIndexOf(@Nullable Object o) {
202 try (LockHolder lh = lockForReading()) {
203 return list.lastIndexOf(o);
206 public ListIterator<T> listIterator() {
207 if (!lock.isWriteLockedByCurrentThread()) throw new RuntimeException(
208 "Iterators break encapsulation and ignore locking. Write-lock first");
209 return list.listIterator();
211 public ListIterator<T> listIterator(int index) {
212 if (!lock.isWriteLockedByCurrentThread()) throw new RuntimeException(
213 "Iterators break encapsulation and ignore locking. Write-lock first");
214 return list.listIterator(index);
216 public List<T> subList(int fromIndex, int toIndex) {
217 try (LockHolder lh = lockForReading()) {
218 return list.subList(fromIndex, toIndex);
221 @RequiresApi(api = Build.VERSION_CODES.N)
222 public Spliterator<T> spliterator() {
223 if (!lock.isWriteLockedByCurrentThread()) throw new RuntimeException(
224 "Iterators break encapsulation and ignore locking. Write-lock first");
225 return list.spliterator();
227 @RequiresApi(api = Build.VERSION_CODES.N)
228 public boolean removeIf(Predicate<? super T> filter) {
229 try (LockHolder lh = lockForWriting()) {
230 boolean result = list.removeIf(filter);
232 if (result) forceNotify();
236 @RequiresApi(api = Build.VERSION_CODES.N)
237 public Stream<T> stream() {
238 if (!lock.isWriteLockedByCurrentThread()) throw new RuntimeException(
239 "Iterators break encapsulation and ignore locking. Write-lock first");
240 return list.stream();
242 @RequiresApi(api = Build.VERSION_CODES.N)
243 public Stream<T> parallelStream() {
244 if (!lock.isWriteLockedByCurrentThread()) throw new RuntimeException(
245 "Iterators break encapsulation and ignore locking. Write-lock first");
246 return list.parallelStream();
248 @RequiresApi(api = Build.VERSION_CODES.N)
249 public void forEach(Consumer<? super T> action) {
250 try (LockHolder lh = lockForReading()) {
251 list.forEach(action);
254 public List<T> getList() {
255 if (!lock.isWriteLockedByCurrentThread()) throw new RuntimeException(
256 "Direct list access breaks encapsulation and ignore locking. Write-lock first");
259 public void setList(List<T> aList) {
260 try (LockHolder lh = lockForWriting()) {
266 public void triggerItemChangedNotification(T item) {
267 try (LockHolder lh = lockForReading()) {
268 int index = list.indexOf(item);
270 Log.d("ObList", "??? not sending notifications for item not found in the list");
273 Log.d("ObList", "Notifying item change observers");
274 triggerItemChangedNotification(index);
277 public void triggerItemChangedNotification(int index) {
280 public LockHolder lockForWriting() {
281 ReentrantReadWriteLock.WriteLock wLock = lock.writeLock();
284 ReentrantReadWriteLock.ReadLock rLock = lock.readLock();
287 return new LockHolder(rLock, wLock);
289 public LockHolder lockForReading() {
290 ReentrantReadWriteLock.ReadLock rLock = lock.readLock();
292 return new LockHolder(rLock);