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