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 org.jetbrains.annotations.NotNull;
25 import java.util.Collection;
26 import java.util.Comparator;
27 import java.util.Iterator;
28 import java.util.List;
29 import java.util.ListIterator;
30 import java.util.Observable;
31 import java.util.Spliterator;
32 import java.util.concurrent.locks.ReentrantReadWriteLock;
33 import java.util.function.Consumer;
34 import java.util.function.Predicate;
35 import java.util.function.UnaryOperator;
36 import java.util.stream.Stream;
38 import androidx.annotation.NonNull;
39 import androidx.annotation.Nullable;
40 import androidx.annotation.RequiresApi;
42 public class ObservableList<T> extends Observable implements List<T> {
44 private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
45 private int notificationBlocks = 0;
46 private boolean notificationWasBlocked = false;
47 public ObservableList(List<T> list) {
50 private void forceNotify() {
51 if (notificationBlocked()) return;
55 private boolean notificationBlocked() {
56 return notificationWasBlocked = (notificationBlocks > 0);
58 private void forceNotify(int index) {
59 if (notificationBlocked()) return;
61 notifyObservers(index);
64 try (LockHolder lh = lockForReading()) {
68 public boolean isEmpty() {
69 try (LockHolder lh = lockForReading()) {
70 return list.isEmpty();
73 public boolean contains(@Nullable Object o) {
74 try (LockHolder lh = lockForReading()) {
75 return list.contains(o);
79 public Iterator<T> iterator() {
80 throw new RuntimeException("Iterators break encapsulation and ignore locking");
81 // return list.iterator();
83 public Object[] toArray() {
84 try (LockHolder lh = lockForReading()) {
85 return list.toArray();
88 public <T1> T1[] toArray(@Nullable T1[] a) {
89 try (LockHolder lh = lockForReading()) {
90 return list.toArray(a);
93 public boolean add(T t) {
94 try (LockHolder lh = lockForWriting()) {
95 boolean result = list.add(t);
97 if (result) forceNotify();
101 public boolean remove(@Nullable Object o) {
102 try (LockHolder lh = lockForWriting()) {
103 boolean result = list.remove(o);
105 if (result) forceNotify();
109 public T removeQuietly(int index) {
110 return list.remove(index);
112 public boolean containsAll(@NonNull Collection<?> c) {
113 try (LockHolder lh = lockForReading()) {
114 return list.containsAll(c);
117 public boolean addAll(@NonNull Collection<? extends T> c) {
118 try (LockHolder lh = lockForWriting()) {
119 boolean result = list.addAll(c);
121 if (result) forceNotify();
125 public boolean addAll(int index, @NonNull Collection<? extends T> c) {
126 try (LockHolder lh = lockForWriting()) {
127 boolean result = list.addAll(index, c);
129 if (result) forceNotify();
133 public boolean addAllQuietly(int index, Collection<? extends T> c) {
134 return list.addAll(index, c);
136 public boolean removeAll(@NonNull Collection<?> c) {
137 try (LockHolder lh = lockForWriting()) {
138 boolean result = list.removeAll(c);
140 if (result) forceNotify();
144 public boolean retainAll(@NonNull Collection<?> c) {
145 try (LockHolder lh = lockForWriting()) {
146 boolean result = list.retainAll(c);
148 if (result) forceNotify();
152 @RequiresApi(api = Build.VERSION_CODES.N)
153 public void replaceAll(@NonNull UnaryOperator<T> operator) {
154 try (LockHolder lh = lockForWriting()) {
155 list.replaceAll(operator);
160 @RequiresApi(api = Build.VERSION_CODES.N)
161 public void sort(@Nullable Comparator<? super T> c) {
162 try (LockHolder lh = lockForWriting()) {
163 lock.writeLock().lock();
169 public void clear() {
170 try (LockHolder lh = lockForWriting()) {
171 boolean wasEmpty = list.isEmpty();
174 if (!wasEmpty) forceNotify();
177 public T get(int index) {
178 try (LockHolder lh = lockForReading()) {
179 return list.get(index);
182 public T set(int index, T element) {
183 try (LockHolder lh = lockForWriting()) {
184 T result = list.set(index, element);
190 public void add(int index, T element) {
191 try (LockHolder lh = lockForWriting()) {
192 list.add(index, element);
197 public T remove(int index) {
198 try (LockHolder lh = lockForWriting()) {
199 T result = list.remove(index);
205 public int indexOf(@Nullable Object o) {
206 try (LockHolder lh = lockForReading()) {
207 return list.indexOf(o);
210 public int lastIndexOf(@Nullable Object o) {
211 try (LockHolder lh = lockForReading()) {
212 return list.lastIndexOf(o);
216 public ListIterator<T> listIterator() {
217 if (!lock.isWriteLockedByCurrentThread()) throw new RuntimeException(
218 "Iterators break encapsulation and ignore locking. Write-lock first");
219 return list.listIterator();
222 public ListIterator<T> listIterator(int index) {
223 if (!lock.isWriteLockedByCurrentThread()) throw new RuntimeException(
224 "Iterators break encapsulation and ignore locking. Write-lock first");
225 return list.listIterator(index);
228 public List<T> subList(int fromIndex, int toIndex) {
229 try (LockHolder lh = lockForReading()) {
230 return list.subList(fromIndex, toIndex);
234 @RequiresApi(api = Build.VERSION_CODES.N)
235 public Spliterator<T> spliterator() {
236 if (!lock.isWriteLockedByCurrentThread()) throw new RuntimeException(
237 "Iterators break encapsulation and ignore locking. Write-lock first");
238 return list.spliterator();
240 @RequiresApi(api = Build.VERSION_CODES.N)
241 public boolean removeIf(Predicate<? super T> filter) {
242 try (LockHolder lh = lockForWriting()) {
243 boolean result = list.removeIf(filter);
245 if (result) forceNotify();
249 @RequiresApi(api = Build.VERSION_CODES.N)
250 public Stream<T> stream() {
251 if (!lock.isWriteLockedByCurrentThread()) throw new RuntimeException(
252 "Iterators break encapsulation and ignore locking. Write-lock first");
253 return list.stream();
255 @RequiresApi(api = Build.VERSION_CODES.N)
256 public Stream<T> parallelStream() {
257 if (!lock.isWriteLockedByCurrentThread()) throw new RuntimeException(
258 "Iterators break encapsulation and ignore locking. Write-lock first");
259 return list.parallelStream();
261 @RequiresApi(api = Build.VERSION_CODES.N)
262 public void forEach(Consumer<? super T> action) {
263 try (LockHolder lh = lockForReading()) {
264 list.forEach(action);
267 public List<T> getList() {
268 if (!lock.isWriteLockedByCurrentThread()) throw new RuntimeException(
269 "Direct list access breaks encapsulation and ignore locking. Write-lock first");
272 public void setList(List<T> aList) {
273 try (LockHolder lh = lockForWriting()) {
279 public void triggerItemChangedNotification(T item) {
280 try (LockHolder lh = lockForReading()) {
281 int index = list.indexOf(item);
283 Log.d("ObList", "??? not sending notifications for item not found in the list");
286 Log.d("ObList", "Notifying item change observers");
287 triggerItemChangedNotification(index);
290 public void triggerItemChangedNotification(int index) {
293 public LockHolder lockForWriting() {
294 ReentrantReadWriteLock.WriteLock wLock = lock.writeLock();
297 ReentrantReadWriteLock.ReadLock rLock = lock.readLock();
300 return new LockHolder(rLock, wLock);
302 public LockHolder lockForReading() {
303 ReentrantReadWriteLock.ReadLock rLock = lock.readLock();
305 return new LockHolder(rLock);
307 public void blockNotifications() {
308 notificationBlocks++;
310 public void unblockNotifications() {
311 notificationBlocks--;
312 if ((notificationBlocks == 0) && notificationWasBlocked) notifyObservers();