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