]> git.ktnx.net Git - mobile-ledger.git/blob - app/src/main/java/net/ktnx/mobileledger/utils/ObservableList.java
b5ddb8780a368bcd1166061faf8120a276786de3
[mobile-ledger.git] / app / src / main / java / net / ktnx / mobileledger / utils / ObservableList.java
1 /*
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.
8  *
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.
13  *
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/>.
16  */
17
18 package net.ktnx.mobileledger.utils;
19
20 import android.os.Build;
21 import android.util.Log;
22
23 import org.jetbrains.annotations.NotNull;
24
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;
37
38 import androidx.annotation.NonNull;
39 import androidx.annotation.Nullable;
40 import androidx.annotation.RequiresApi;
41
42 public class ObservableList<T> extends Observable implements List<T> {
43     private List<T> list;
44     private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
45     private int notificationBlocks = 0;
46     private boolean notificationWasBlocked = false;
47     public ObservableList(List<T> list) {
48         this.list = list;
49     }
50     private void forceNotify() {
51         if (notificationBlocked()) return;
52         setChanged();
53         notifyObservers();
54     }
55     private boolean notificationBlocked() {
56         return notificationWasBlocked = (notificationBlocks > 0);
57     }
58     private void forceNotify(int index) {
59         if (notificationBlocked()) return;
60         setChanged();
61         notifyObservers(index);
62     }
63     public int size() {
64         try (LockHolder lh = lockForReading()) {
65             return list.size();
66         }
67     }
68     public boolean isEmpty() {
69         try (LockHolder lh = lockForReading()) {
70             return list.isEmpty();
71         }
72     }
73     public boolean contains(@Nullable Object o) {
74         try (LockHolder lh = lockForReading()) {
75             return list.contains(o);
76         }
77     }
78     @NonNull
79     public Iterator<T> iterator() {
80         throw new RuntimeException("Iterators break encapsulation and ignore locking");
81 //        return list.iterator();
82     }
83     public Object[] toArray() {
84         try (LockHolder lh = lockForReading()) {
85             return list.toArray();
86         }
87     }
88     public <T1> T1[] toArray(@Nullable T1[] a) {
89         try (LockHolder lh = lockForReading()) {
90             return list.toArray(a);
91         }
92     }
93     public boolean add(T t) {
94         try (LockHolder lh = lockForWriting()) {
95             boolean result = list.add(t);
96             lh.downgrade();
97             if (result) forceNotify();
98             return result;
99         }
100     }
101     public boolean remove(@Nullable Object o) {
102         try (LockHolder lh = lockForWriting()) {
103             boolean result = list.remove(o);
104             lh.downgrade();
105             if (result) forceNotify();
106             return result;
107         }
108     }
109     public T removeQuietly(int index) {
110         return list.remove(index);
111     }
112     public boolean containsAll(@NonNull Collection<?> c) {
113         try (LockHolder lh = lockForReading()) {
114             return list.containsAll(c);
115         }
116     }
117     public boolean addAll(@NonNull Collection<? extends T> c) {
118         try (LockHolder lh = lockForWriting()) {
119             boolean result = list.addAll(c);
120             lh.downgrade();
121             if (result) forceNotify();
122             return result;
123         }
124     }
125     public boolean addAll(int index, @NonNull Collection<? extends T> c) {
126         try (LockHolder lh = lockForWriting()) {
127             boolean result = list.addAll(index, c);
128             lh.downgrade();
129             if (result) forceNotify();
130             return result;
131         }
132     }
133     public boolean addAllQuietly(int index, Collection<? extends T> c) {
134         return list.addAll(index, c);
135     }
136     public boolean removeAll(@NonNull Collection<?> c) {
137         try (LockHolder lh = lockForWriting()) {
138             boolean result = list.removeAll(c);
139             lh.downgrade();
140             if (result) forceNotify();
141             return result;
142         }
143     }
144     public boolean retainAll(@NonNull Collection<?> c) {
145         try (LockHolder lh = lockForWriting()) {
146             boolean result = list.retainAll(c);
147             lh.downgrade();
148             if (result) forceNotify();
149             return result;
150         }
151     }
152     @RequiresApi(api = Build.VERSION_CODES.N)
153     public void replaceAll(@NonNull UnaryOperator<T> operator) {
154         try (LockHolder lh = lockForWriting()) {
155             list.replaceAll(operator);
156             lh.downgrade();
157             forceNotify();
158         }
159     }
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();
164             list.sort(c);
165             lh.downgrade();
166             forceNotify();
167         }
168     }
169     public void clear() {
170         try (LockHolder lh = lockForWriting()) {
171             boolean wasEmpty = list.isEmpty();
172             list.clear();
173             lh.downgrade();
174             if (!wasEmpty) forceNotify();
175         }
176     }
177     public T get(int index) {
178         try (LockHolder lh = lockForReading()) {
179             return list.get(index);
180         }
181     }
182     public T set(int index, T element) {
183         try (LockHolder lh = lockForWriting()) {
184             T result = list.set(index, element);
185             lh.downgrade();
186             forceNotify();
187             return result;
188         }
189     }
190     public void add(int index, T element) {
191         try (LockHolder lh = lockForWriting()) {
192             list.add(index, element);
193             lh.downgrade();
194             forceNotify();
195         }
196     }
197     public T remove(int index) {
198         try (LockHolder lh = lockForWriting()) {
199             T result = list.remove(index);
200             lh.downgrade();
201             forceNotify();
202             return result;
203         }
204     }
205     public int indexOf(@Nullable Object o) {
206         try (LockHolder lh = lockForReading()) {
207             return list.indexOf(o);
208         }
209     }
210     public int lastIndexOf(@Nullable Object o) {
211         try (LockHolder lh = lockForReading()) {
212             return list.lastIndexOf(o);
213         }
214     }
215     @NotNull
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();
220     }
221     @NotNull
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);
226     }
227     @NotNull
228     public List<T> subList(int fromIndex, int toIndex) {
229         try (LockHolder lh = lockForReading()) {
230             return list.subList(fromIndex, toIndex);
231         }
232     }
233     @NotNull
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();
239     }
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);
244             lh.downgrade();
245             if (result) forceNotify();
246             return result;
247         }
248     }
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();
254     }
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();
260     }
261     @RequiresApi(api = Build.VERSION_CODES.N)
262     public void forEach(Consumer<? super T> action) {
263         try (LockHolder lh = lockForReading()) {
264             list.forEach(action);
265         }
266     }
267     public List<T> getList() {
268         if (!lock.isWriteLockedByCurrentThread()) throw new RuntimeException(
269                 "Direct list access breaks encapsulation and ignore locking. Write-lock first");
270         return list;
271     }
272     public void setList(List<T> aList) {
273         try (LockHolder lh = lockForWriting()) {
274             list = aList;
275             lh.downgrade();
276             forceNotify();
277         }
278     }
279     public void triggerItemChangedNotification(T item) {
280         try (LockHolder lh = lockForReading()) {
281             int index = list.indexOf(item);
282             if (index == -1) {
283                 Log.d("ObList", "??? not sending notifications for item not found in the list");
284                 return;
285             }
286             Log.d("ObList", "Notifying item change observers");
287             triggerItemChangedNotification(index);
288         }
289     }
290     public void triggerItemChangedNotification(int index) {
291         forceNotify(index);
292     }
293     public LockHolder lockForWriting() {
294         ReentrantReadWriteLock.WriteLock wLock = lock.writeLock();
295         wLock.lock();
296
297         ReentrantReadWriteLock.ReadLock rLock = lock.readLock();
298         rLock.lock();
299
300         return new LockHolder(rLock, wLock);
301     }
302     public LockHolder lockForReading() {
303         ReentrantReadWriteLock.ReadLock rLock = lock.readLock();
304         rLock.lock();
305         return new LockHolder(rLock);
306     }
307     public void blockNotifications() {
308         notificationBlocks++;
309     }
310     public void unblockNotifications() {
311         notificationBlocks--;
312         if ((notificationBlocks == 0) && notificationWasBlocked) notifyObservers();
313     }
314 }