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;
22 import org.jetbrains.annotations.NotNull;
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;
37 import androidx.annotation.NonNull;
38 import androidx.annotation.Nullable;
39 import androidx.annotation.RequiresApi;
41 import static net.ktnx.mobileledger.utils.Logger.debug;
43 public class ObservableList<T> extends Observable implements List<T> {
45 private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
46 private int notificationBlocks = 0;
47 private boolean notificationWasBlocked = false;
48 public ObservableList(List<T> list) {
51 private void forceNotify() {
52 if (notificationBlocked()) return;
56 private boolean notificationBlocked() {
57 return notificationWasBlocked = (notificationBlocks > 0);
59 private void forceNotify(int index) {
60 if (notificationBlocked()) return;
62 notifyObservers(index);
65 try (LockHolder lh = lockForReading()) {
69 public boolean isEmpty() {
70 try (LockHolder lh = lockForReading()) {
71 return list.isEmpty();
74 public boolean contains(@Nullable Object o) {
75 try (LockHolder lh = lockForReading()) {
76 return list.contains(o);
80 public Iterator<T> iterator() {
81 throw new RuntimeException("Iterators break encapsulation and ignore locking");
82 // return list.iterator();
84 public Object[] toArray() {
85 try (LockHolder lh = lockForReading()) {
86 return list.toArray();
89 public <T1> T1[] toArray(@Nullable T1[] a) {
90 try (LockHolder lh = lockForReading()) {
91 return list.toArray(a);
94 public boolean add(T t) {
95 try (LockHolder lh = lockForWriting()) {
96 boolean result = list.add(t);
98 if (result) forceNotify();
102 public boolean remove(@Nullable Object o) {
103 try (LockHolder lh = lockForWriting()) {
104 boolean result = list.remove(o);
106 if (result) forceNotify();
110 public T removeQuietly(int index) {
111 return list.remove(index);
113 public boolean containsAll(@NonNull Collection<?> c) {
114 try (LockHolder lh = lockForReading()) {
115 return list.containsAll(c);
118 public boolean addAll(@NonNull Collection<? extends T> c) {
119 try (LockHolder lh = lockForWriting()) {
120 boolean result = list.addAll(c);
122 if (result) forceNotify();
126 public boolean addAll(int index, @NonNull Collection<? extends T> c) {
127 try (LockHolder lh = lockForWriting()) {
128 boolean result = list.addAll(index, c);
130 if (result) forceNotify();
134 public boolean addAllQuietly(int index, Collection<? extends T> c) {
135 return list.addAll(index, c);
137 public boolean removeAll(@NonNull Collection<?> c) {
138 try (LockHolder lh = lockForWriting()) {
139 boolean result = list.removeAll(c);
141 if (result) forceNotify();
145 public boolean retainAll(@NonNull Collection<?> c) {
146 try (LockHolder lh = lockForWriting()) {
147 boolean result = list.retainAll(c);
149 if (result) forceNotify();
153 @RequiresApi(api = Build.VERSION_CODES.N)
154 public void replaceAll(@NonNull UnaryOperator<T> operator) {
155 try (LockHolder lh = lockForWriting()) {
156 list.replaceAll(operator);
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();
170 public void clear() {
171 try (LockHolder lh = lockForWriting()) {
172 boolean wasEmpty = list.isEmpty();
175 if (!wasEmpty) forceNotify();
178 public T get(int index) {
179 try (LockHolder lh = lockForReading()) {
180 return list.get(index);
183 public T set(int index, T element) {
184 try (LockHolder lh = lockForWriting()) {
185 T result = list.set(index, element);
191 public void add(int index, T element) {
192 try (LockHolder lh = lockForWriting()) {
193 list.add(index, element);
198 public T remove(int index) {
199 try (LockHolder lh = lockForWriting()) {
200 T result = list.remove(index);
206 public int indexOf(@Nullable Object o) {
207 try (LockHolder lh = lockForReading()) {
208 return list.indexOf(o);
211 public int lastIndexOf(@Nullable Object o) {
212 try (LockHolder lh = lockForReading()) {
213 return list.lastIndexOf(o);
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();
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);
229 public List<T> subList(int fromIndex, int toIndex) {
230 try (LockHolder lh = lockForReading()) {
231 return list.subList(fromIndex, toIndex);
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();
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);
246 if (result) forceNotify();
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();
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();
262 @RequiresApi(api = Build.VERSION_CODES.N)
263 public void forEach(Consumer<? super T> action) {
264 try (LockHolder lh = lockForReading()) {
265 list.forEach(action);
268 public List<T> getList() {
269 if (!lock.isWriteLockedByCurrentThread()) throw new RuntimeException(
270 "Direct list access breaks encapsulation and ignore locking. Write-lock first");
273 public void setList(List<T> aList) {
274 try (LockHolder lh = lockForWriting()) {
280 public void triggerItemChangedNotification(T item) {
281 try (LockHolder lh = lockForReading()) {
282 int index = list.indexOf(item);
284 debug("ObList", "??? not sending notifications for item not found in the list");
287 debug("ObList", "Notifying item change observers");
288 triggerItemChangedNotification(index);
291 public void triggerItemChangedNotification(int index) {
294 public LockHolder lockForWriting() {
295 ReentrantReadWriteLock.WriteLock wLock = lock.writeLock();
298 ReentrantReadWriteLock.ReadLock rLock = lock.readLock();
301 return new LockHolder(rLock, wLock);
303 public LockHolder lockForReading() {
304 ReentrantReadWriteLock.ReadLock rLock = lock.readLock();
306 return new LockHolder(rLock);
308 public void blockNotifications() {
309 notificationBlocks++;
311 public void unblockNotifications() {
312 notificationBlocks--;
313 if ((notificationBlocks == 0) && notificationWasBlocked) notifyObservers();