locks around observable list's access
[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 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;
35
36 import androidx.annotation.NonNull;
37 import androidx.annotation.Nullable;
38 import androidx.annotation.RequiresApi;
39
40 public class ObservableList<T> extends Observable implements List<T> {
41     private List<T> list;
42     private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
43     public ObservableList(List<T> list) {
44         this.list = list;
45     }
46     private void forceNotify() {
47         setChanged();
48         notifyObservers();
49     }
50     private void forceNotify(int index) {
51         setChanged();
52         notifyObservers(index);
53     }
54     public int size() {
55         try (LockHolder lh = lockForReading()) {
56             return list.size();
57         }
58     }
59     public boolean isEmpty() {
60         try (LockHolder lh = lockForReading()) {
61             return list.isEmpty();
62         }
63     }
64     public boolean contains(@Nullable Object o) {
65         try (LockHolder lh = lockForReading()) {
66             return list.contains(o);
67         }
68     }
69     @NonNull
70     public Iterator<T> iterator() {
71         throw new RuntimeException("Iterators break encapsulation and ignore locking");
72 //        return list.iterator();
73     }
74     public Object[] toArray() {
75         try (LockHolder lh = lockForReading()) {
76             return list.toArray();
77         }
78     }
79     public <T1> T1[] toArray(@Nullable T1[] a) {
80         try (LockHolder lh = lockForReading()) {
81             return list.toArray(a);
82         }
83     }
84     public boolean add(T t) {
85         try (LockHolder lh = lockForWriting()) {
86             boolean result = list.add(t);
87             lh.downgrade();
88             if (result) forceNotify();
89             return result;
90         }
91     }
92     public boolean remove(@Nullable Object o) {
93         try (LockHolder lh = lockForWriting()) {
94             boolean result = list.remove(o);
95             lh.downgrade();
96             if (result) forceNotify();
97             return result;
98         }
99     }
100     public T removeQuietly(int index) {
101         return list.remove(index);
102     }
103     public boolean containsAll(@NonNull Collection<?> c) {
104         try (LockHolder lh = lockForReading()) {
105             return list.containsAll(c);
106         }
107     }
108     public boolean addAll(@NonNull Collection<? extends T> c) {
109         try (LockHolder lh = lockForWriting()) {
110             boolean result = list.addAll(c);
111             lh.downgrade();
112             if (result) forceNotify();
113             return result;
114         }
115     }
116     public boolean addAll(int index, @NonNull Collection<? extends T> c) {
117         try (LockHolder lh = lockForWriting()) {
118             boolean result = list.addAll(index, c);
119             lh.downgrade();
120             if (result) forceNotify();
121             return result;
122         }
123     }
124     public boolean addAllQuietly(int index, Collection<? extends T> c) {
125         return list.addAll(index, c);
126     }
127     public boolean removeAll(@NonNull Collection<?> c) {
128         try (LockHolder lh = lockForWriting()) {
129             boolean result = list.removeAll(c);
130             lh.downgrade();
131             if (result) forceNotify();
132             return result;
133         }
134     }
135     public boolean retainAll(@NonNull Collection<?> c) {
136         try (LockHolder lh = lockForWriting()) {
137             boolean result = list.retainAll(c);
138             lh.downgrade();
139             if (result) forceNotify();
140             return result;
141         }
142     }
143     @RequiresApi(api = Build.VERSION_CODES.N)
144     public void replaceAll(@NonNull UnaryOperator<T> operator) {
145         try (LockHolder lh = lockForWriting()) {
146             list.replaceAll(operator);
147             lh.downgrade();
148             forceNotify();
149         }
150     }
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();
155             list.sort(c);
156             lh.downgrade();
157             forceNotify();
158         }
159     }
160     public void clear() {
161         try (LockHolder lh = lockForWriting()) {
162             boolean wasEmpty = list.isEmpty();
163             list.clear();
164             lh.downgrade();
165             if (!wasEmpty) forceNotify();
166         }
167     }
168     public T get(int index) {
169         try (LockHolder lh = lockForReading()) {
170             return list.get(index);
171         }
172     }
173     public T set(int index, T element) {
174         try (LockHolder lh = lockForWriting()) {
175             T result = list.set(index, element);
176             lh.downgrade();
177             forceNotify();
178             return result;
179         }
180     }
181     public void add(int index, T element) {
182         try (LockHolder lh = lockForWriting()) {
183             list.add(index, element);
184             lh.downgrade();
185             forceNotify();
186         }
187     }
188     public T remove(int index) {
189         try (LockHolder lh = lockForWriting()) {
190             T result = list.remove(index);
191             lh.downgrade();
192             forceNotify();
193             return result;
194         }
195     }
196     public int indexOf(@Nullable Object o) {
197         try (LockHolder lh = lockForReading()) {
198             return list.indexOf(o);
199         }
200     }
201     public int lastIndexOf(@Nullable Object o) {
202         try (LockHolder lh = lockForReading()) {
203             return list.lastIndexOf(o);
204         }
205     }
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();
210     }
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);
215     }
216     public List<T> subList(int fromIndex, int toIndex) {
217         try (LockHolder lh = lockForReading()) {
218             return list.subList(fromIndex, toIndex);
219         }
220     }
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();
226     }
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);
231             lh.downgrade();
232             if (result) forceNotify();
233             return result;
234         }
235     }
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();
241     }
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();
247     }
248     @RequiresApi(api = Build.VERSION_CODES.N)
249     public void forEach(Consumer<? super T> action) {
250         try (LockHolder lh = lockForReading()) {
251             list.forEach(action);
252         }
253     }
254     public List<T> getList() {
255         if (!lock.isWriteLockedByCurrentThread()) throw new RuntimeException(
256                 "Direct list access breaks encapsulation and ignore locking. Write-lock first");
257         return list;
258     }
259     public void setList(List<T> aList) {
260         try (LockHolder lh = lockForWriting()) {
261             list = aList;
262             lh.downgrade();
263             forceNotify();
264         }
265     }
266     public void triggerItemChangedNotification(T item) {
267         try (LockHolder lh = lockForReading()) {
268             int index = list.indexOf(item);
269             if (index == -1) {
270                 Log.d("ObList", "??? not sending notifications for item not found in the list");
271                 return;
272             }
273             Log.d("ObList", "Notifying item change observers");
274             triggerItemChangedNotification(index);
275         }
276     }
277     public void triggerItemChangedNotification(int index) {
278         forceNotify(index);
279     }
280     public LockHolder lockForWriting() {
281         ReentrantReadWriteLock.WriteLock wLock = lock.writeLock();
282         wLock.lock();
283
284         ReentrantReadWriteLock.ReadLock rLock = lock.readLock();
285         rLock.lock();
286
287         return new LockHolder(rLock, wLock);
288     }
289     public LockHolder lockForReading() {
290         ReentrantReadWriteLock.ReadLock rLock = lock.readLock();
291         rLock.lock();
292         return new LockHolder(rLock);
293     }
294 }