X-Git-Url: https://git.ktnx.net/?p=mobile-ledger.git;a=blobdiff_plain;f=app%2Fsrc%2Fmain%2Fjava%2Fnet%2Fktnx%2Fmobileledger%2Futils%2FObservableList.java;h=eac023767186d668583be3c7bcd9328547d4a9e4;hp=e74271aff99e49ae60bfeb507512bf61f3006afd;hb=7165b89c8ff2b9d8f69e02354197127ec27a4a47;hpb=09e26d2279484b4dfe0de218b05f075362fff4b5 diff --git a/app/src/main/java/net/ktnx/mobileledger/utils/ObservableList.java b/app/src/main/java/net/ktnx/mobileledger/utils/ObservableList.java index e74271af..eac02376 100644 --- a/app/src/main/java/net/ktnx/mobileledger/utils/ObservableList.java +++ b/app/src/main/java/net/ktnx/mobileledger/utils/ObservableList.java @@ -18,9 +18,6 @@ package net.ktnx.mobileledger.utils; import android.os.Build; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; import android.util.Log; import java.util.Collection; @@ -30,13 +27,19 @@ import java.util.List; import java.util.ListIterator; import java.util.Observable; import java.util.Spliterator; +import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.function.Consumer; import java.util.function.Predicate; import java.util.function.UnaryOperator; import java.util.stream.Stream; -public class ObservableList extends Observable { +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; + +public class ObservableList extends Observable implements List { private List list; + private ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); public ObservableList(List list) { this.list = list; } @@ -49,142 +52,243 @@ public class ObservableList extends Observable { notifyObservers(index); } public int size() { - return list.size(); + try (LockHolder lh = lockForReading()) { + return list.size(); + } } public boolean isEmpty() { - return list.isEmpty(); + try (LockHolder lh = lockForReading()) { + return list.isEmpty(); + } } public boolean contains(@Nullable Object o) { - return list.contains(o); + try (LockHolder lh = lockForReading()) { + return list.contains(o); + } } + @NonNull public Iterator iterator() { - return list.iterator(); + throw new RuntimeException("Iterators break encapsulation and ignore locking"); +// return list.iterator(); } public Object[] toArray() { - return list.toArray(); + try (LockHolder lh = lockForReading()) { + return list.toArray(); + } } public T1[] toArray(@Nullable T1[] a) { - return list.toArray(a); + try (LockHolder lh = lockForReading()) { + return list.toArray(a); + } } public boolean add(T t) { - boolean result = list.add(t); - if (result) forceNotify(); - return result; + try (LockHolder lh = lockForWriting()) { + boolean result = list.add(t); + lh.downgrade(); + if (result) forceNotify(); + return result; + } } public boolean remove(@Nullable Object o) { - boolean result = list.remove(o); - if (result) forceNotify(); - return result; + try (LockHolder lh = lockForWriting()) { + boolean result = list.remove(o); + lh.downgrade(); + if (result) forceNotify(); + return result; + } + } + public T removeQuietly(int index) { + return list.remove(index); } public boolean containsAll(@NonNull Collection c) { - return list.containsAll(c); + try (LockHolder lh = lockForReading()) { + return list.containsAll(c); + } } public boolean addAll(@NonNull Collection c) { - boolean result = list.addAll(c); - if (result) forceNotify(); - return result; + try (LockHolder lh = lockForWriting()) { + boolean result = list.addAll(c); + lh.downgrade(); + if (result) forceNotify(); + return result; + } } public boolean addAll(int index, @NonNull Collection c) { - boolean result = list.addAll(index, c); - if (result) forceNotify(); - return result; + try (LockHolder lh = lockForWriting()) { + boolean result = list.addAll(index, c); + lh.downgrade(); + if (result) forceNotify(); + return result; + } + } + public boolean addAllQuietly(int index, Collection c) { + return list.addAll(index, c); } public boolean removeAll(@NonNull Collection c) { - boolean result = list.removeAll(c); - if (result) forceNotify(); - return result; + try (LockHolder lh = lockForWriting()) { + boolean result = list.removeAll(c); + lh.downgrade(); + if (result) forceNotify(); + return result; + } } public boolean retainAll(@NonNull Collection c) { - boolean result = list.retainAll(c); - if (result) forceNotify(); - return result; + try (LockHolder lh = lockForWriting()) { + boolean result = list.retainAll(c); + lh.downgrade(); + if (result) forceNotify(); + return result; + } } @RequiresApi(api = Build.VERSION_CODES.N) public void replaceAll(@NonNull UnaryOperator operator) { - list.replaceAll(operator); - forceNotify(); + try (LockHolder lh = lockForWriting()) { + list.replaceAll(operator); + lh.downgrade(); + forceNotify(); + } } @RequiresApi(api = Build.VERSION_CODES.N) public void sort(@Nullable Comparator c) { - list.sort(c); - forceNotify(); + try (LockHolder lh = lockForWriting()) { + lock.writeLock().lock(); + list.sort(c); + lh.downgrade(); + forceNotify(); + } } public void clear() { - boolean wasEmpty = list.isEmpty(); - list.clear(); - if (!wasEmpty) forceNotify(); + try (LockHolder lh = lockForWriting()) { + boolean wasEmpty = list.isEmpty(); + list.clear(); + lh.downgrade(); + if (!wasEmpty) forceNotify(); + } } public T get(int index) { - return list.get(index); + try (LockHolder lh = lockForReading()) { + return list.get(index); + } } public T set(int index, T element) { - T result = list.set(index, element); - forceNotify(); - return result; + try (LockHolder lh = lockForWriting()) { + T result = list.set(index, element); + lh.downgrade(); + forceNotify(); + return result; + } } public void add(int index, T element) { - list.add(index, element); - forceNotify(); + try (LockHolder lh = lockForWriting()) { + list.add(index, element); + lh.downgrade(); + forceNotify(); + } } public T remove(int index) { - T result = list.remove(index); - forceNotify(); - return result; + try (LockHolder lh = lockForWriting()) { + T result = list.remove(index); + lh.downgrade(); + forceNotify(); + return result; + } } public int indexOf(@Nullable Object o) { - return list.indexOf(o); + try (LockHolder lh = lockForReading()) { + return list.indexOf(o); + } } public int lastIndexOf(@Nullable Object o) { - return list.lastIndexOf(o); + try (LockHolder lh = lockForReading()) { + return list.lastIndexOf(o); + } } public ListIterator listIterator() { + if (!lock.isWriteLockedByCurrentThread()) throw new RuntimeException( + "Iterators break encapsulation and ignore locking. Write-lock first"); return list.listIterator(); } public ListIterator listIterator(int index) { + if (!lock.isWriteLockedByCurrentThread()) throw new RuntimeException( + "Iterators break encapsulation and ignore locking. Write-lock first"); return list.listIterator(index); } public List subList(int fromIndex, int toIndex) { - return list.subList(fromIndex, toIndex); + try (LockHolder lh = lockForReading()) { + return list.subList(fromIndex, toIndex); + } } @RequiresApi(api = Build.VERSION_CODES.N) public Spliterator spliterator() { + if (!lock.isWriteLockedByCurrentThread()) throw new RuntimeException( + "Iterators break encapsulation and ignore locking. Write-lock first"); return list.spliterator(); } @RequiresApi(api = Build.VERSION_CODES.N) public boolean removeIf(Predicate filter) { - boolean result = list.removeIf(filter); - if (result) forceNotify(); - return result; + try (LockHolder lh = lockForWriting()) { + boolean result = list.removeIf(filter); + lh.downgrade(); + if (result) forceNotify(); + return result; + } } @RequiresApi(api = Build.VERSION_CODES.N) public Stream stream() { + if (!lock.isWriteLockedByCurrentThread()) throw new RuntimeException( + "Iterators break encapsulation and ignore locking. Write-lock first"); return list.stream(); } @RequiresApi(api = Build.VERSION_CODES.N) public Stream parallelStream() { + if (!lock.isWriteLockedByCurrentThread()) throw new RuntimeException( + "Iterators break encapsulation and ignore locking. Write-lock first"); return list.parallelStream(); } @RequiresApi(api = Build.VERSION_CODES.N) public void forEach(Consumer action) { - list.forEach(action); + try (LockHolder lh = lockForReading()) { + list.forEach(action); + } } public List getList() { + if (!lock.isWriteLockedByCurrentThread()) throw new RuntimeException( + "Direct list access breaks encapsulation and ignore locking. Write-lock first"); return list; } public void setList(List aList) { - list = aList; - forceNotify(); + try (LockHolder lh = lockForWriting()) { + list = aList; + lh.downgrade(); + forceNotify(); + } } public void triggerItemChangedNotification(T item) { - int index = list.indexOf(item); - if (index == -1) { - Log.d("ObList", "??? not sending notifications for item not found in the list"); - return; + try (LockHolder lh = lockForReading()) { + int index = list.indexOf(item); + if (index == -1) { + Log.d("ObList", "??? not sending notifications for item not found in the list"); + return; + } + Log.d("ObList", "Notifying item change observers"); + triggerItemChangedNotification(index); } - Log.d("ObList", "Notifying item change observers"); - triggerItemChangedNotification(index); } public void triggerItemChangedNotification(int index) { forceNotify(index); } + public LockHolder lockForWriting() { + ReentrantReadWriteLock.WriteLock wLock = lock.writeLock(); + wLock.lock(); + + ReentrantReadWriteLock.ReadLock rLock = lock.readLock(); + rLock.lock(); + + return new LockHolder(rLock, wLock); + } + public LockHolder lockForReading() { + ReentrantReadWriteLock.ReadLock rLock = lock.readLock(); + rLock.lock(); + return new LockHolder(rLock); + } } \ No newline at end of file