X-Git-Url: https://git.ktnx.net/?a=blobdiff_plain;f=app%2Fsrc%2Fmain%2Fjava%2Fnet%2Fktnx%2Fmobileledger%2Futils%2FObservableList.java;h=4ca3e695ae0a5d2cb9595fcd337fb62688129a6e;hb=e58019b5058f781dcc4860fb222808ca885ab491;hp=383148fa8b3a3827a32fbd083ccbac345d7b51a4;hpb=b06a7a291e35add2dfc89313d226c5efd1bae3b3;p=mobile-ledger.git
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 383148fa..4ca3e695 100644
--- a/app/src/main/java/net/ktnx/mobileledger/utils/ObservableList.java
+++ b/app/src/main/java/net/ktnx/mobileledger/utils/ObservableList.java
@@ -1,27 +1,29 @@
/*
- * Copyright © 2019 Damyan Ivanov.
- * This file is part of Mobile-Ledger.
- * Mobile-Ledger is free software: you can distribute it and/or modify it
+ * Copyright © 2020 Damyan Ivanov.
+ * This file is part of MoLe.
+ * MoLe is free software: you can distribute it and/or modify it
* under the term of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your opinion), any later version.
*
- * Mobile-Ledger is distributed in the hope that it will be useful,
+ * MoLe is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License terms for details.
*
* You should have received a copy of the GNU General Public License
- * along with Mobile-Ledger. If not, see .
+ * along with MoLe. If not, see .
*/
package net.ktnx.mobileledger.utils;
import android.os.Build;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.annotation.RequiresApi;
-import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+
+import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.Comparator;
@@ -30,161 +32,294 @@ 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 static net.ktnx.mobileledger.utils.Logger.debug;
+
+public class ObservableList extends Observable implements List {
private List list;
+ private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
+ private int notificationBlocks = 0;
+ private boolean notificationWasBlocked = false;
public ObservableList(List list) {
this.list = list;
}
private void forceNotify() {
+ if (notificationBlocked()) return;
setChanged();
notifyObservers();
}
+ private boolean notificationBlocked() {
+ return notificationWasBlocked = (notificationBlocks > 0);
+ }
private void forceNotify(int index) {
+ if (notificationBlocked()) return;
setChanged();
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();
}
+ @NotNull
public Object[] toArray() {
- return list.toArray();
+ try (LockHolder lh = lockForReading()) {
+ return list.toArray();
+ }
}
+ @Override
+ @NotNull
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 extends T> 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 extends T> 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 extends T> 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 super T> 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);
+ }
}
+ @NotNull
public ListIterator listIterator() {
+ if (!lock.isWriteLockedByCurrentThread()) throw new RuntimeException(
+ "Iterators break encapsulation and ignore locking. Write-lock first");
return list.listIterator();
}
+ @NotNull
public ListIterator listIterator(int index) {
+ if (!lock.isWriteLockedByCurrentThread()) throw new RuntimeException(
+ "Iterators break encapsulation and ignore locking. Write-lock first");
return list.listIterator(index);
}
+ @NotNull
public List subList(int fromIndex, int toIndex) {
- return list.subList(fromIndex, toIndex);
+ try (LockHolder lh = lockForReading()) {
+ return list.subList(fromIndex, toIndex);
+ }
}
+ @NotNull
@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 super T> filter) {
- boolean result = list.removeIf(filter);
- if (result) forceNotify();
- return result;
+ public boolean removeIf(@NotNull Predicate super T> filter) {
+ try (LockHolder lh = lockForWriting()) {
+ boolean result = list.removeIf(filter);
+ lh.downgrade();
+ if (result)
+ forceNotify();
+ return result;
+ }
}
+ @NotNull
@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();
}
+ @NotNull
@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 super T> action) {
- list.forEach(action);
+ public void forEach(@NotNull Consumer super T> 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) {
+ debug("ObList", "??? not sending notifications for item not found in the list");
+ return;
+ }
+ debug("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);
+ }
+ public void blockNotifications() {
+ notificationBlocks++;
+ }
+ public void unblockNotifications() {
+ notificationBlocks--;
+ if ((notificationBlocks == 0) && notificationWasBlocked) notifyObservers();
+ }
}
\ No newline at end of file