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;
21 import android.util.Log;
23 import org.jetbrains.annotations.NotNull;
25 import java.util.Collection;
26 import java.util.Comparator;
27 import java.util.Iterator;
28 import java.util.List;
29 import java.util.ListIterator;
30 import java.util.Observable;
31 import java.util.Spliterator;
32 import java.util.concurrent.locks.ReentrantReadWriteLock;
33 import java.util.function.Consumer;
34 import java.util.function.Predicate;
35 import java.util.function.UnaryOperator;
36 import java.util.stream.Stream;
38 import androidx.annotation.NonNull;
39 import androidx.annotation.Nullable;
40 import androidx.annotation.RequiresApi;
42 public class ObservableList<T> extends Observable implements List<T> {
44 private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
45 public ObservableList(List<T> list) {
48 private void forceNotify() {
52 private void forceNotify(int index) {
54 notifyObservers(index);
57 try (LockHolder lh = lockForReading()) {
61 public boolean isEmpty() {
62 try (LockHolder lh = lockForReading()) {
63 return list.isEmpty();
66 public boolean contains(@Nullable Object o) {
67 try (LockHolder lh = lockForReading()) {
68 return list.contains(o);
72 public Iterator<T> iterator() {
73 throw new RuntimeException("Iterators break encapsulation and ignore locking");
74 // return list.iterator();
76 public Object[] toArray() {
77 try (LockHolder lh = lockForReading()) {
78 return list.toArray();
81 public <T1> T1[] toArray(@Nullable T1[] a) {
82 try (LockHolder lh = lockForReading()) {
83 return list.toArray(a);
86 public boolean add(T t) {
87 try (LockHolder lh = lockForWriting()) {
88 boolean result = list.add(t);
90 if (result) forceNotify();
94 public boolean remove(@Nullable Object o) {
95 try (LockHolder lh = lockForWriting()) {
96 boolean result = list.remove(o);
98 if (result) forceNotify();
102 public T removeQuietly(int index) {
103 return list.remove(index);
105 public boolean containsAll(@NonNull Collection<?> c) {
106 try (LockHolder lh = lockForReading()) {
107 return list.containsAll(c);
110 public boolean addAll(@NonNull Collection<? extends T> c) {
111 try (LockHolder lh = lockForWriting()) {
112 boolean result = list.addAll(c);
114 if (result) forceNotify();
118 public boolean addAll(int index, @NonNull Collection<? extends T> c) {
119 try (LockHolder lh = lockForWriting()) {
120 boolean result = list.addAll(index, c);
122 if (result) forceNotify();
126 public boolean addAllQuietly(int index, Collection<? extends T> c) {
127 return list.addAll(index, c);
129 public boolean removeAll(@NonNull Collection<?> c) {
130 try (LockHolder lh = lockForWriting()) {
131 boolean result = list.removeAll(c);
133 if (result) forceNotify();
137 public boolean retainAll(@NonNull Collection<?> c) {
138 try (LockHolder lh = lockForWriting()) {
139 boolean result = list.retainAll(c);
141 if (result) forceNotify();
145 @RequiresApi(api = Build.VERSION_CODES.N)
146 public void replaceAll(@NonNull UnaryOperator<T> operator) {
147 try (LockHolder lh = lockForWriting()) {
148 list.replaceAll(operator);
153 @RequiresApi(api = Build.VERSION_CODES.N)
154 public void sort(@Nullable Comparator<? super T> c) {
155 try (LockHolder lh = lockForWriting()) {
156 lock.writeLock().lock();
162 public void clear() {
163 try (LockHolder lh = lockForWriting()) {
164 boolean wasEmpty = list.isEmpty();
167 if (!wasEmpty) forceNotify();
170 public T get(int index) {
171 try (LockHolder lh = lockForReading()) {
172 return list.get(index);
175 public T set(int index, T element) {
176 try (LockHolder lh = lockForWriting()) {
177 T result = list.set(index, element);
183 public void add(int index, T element) {
184 try (LockHolder lh = lockForWriting()) {
185 list.add(index, element);
190 public T remove(int index) {
191 try (LockHolder lh = lockForWriting()) {
192 T result = list.remove(index);
198 public int indexOf(@Nullable Object o) {
199 try (LockHolder lh = lockForReading()) {
200 return list.indexOf(o);
203 public int lastIndexOf(@Nullable Object o) {
204 try (LockHolder lh = lockForReading()) {
205 return list.lastIndexOf(o);
209 public ListIterator<T> listIterator() {
210 if (!lock.isWriteLockedByCurrentThread()) throw new RuntimeException(
211 "Iterators break encapsulation and ignore locking. Write-lock first");
212 return list.listIterator();
215 public ListIterator<T> listIterator(int index) {
216 if (!lock.isWriteLockedByCurrentThread()) throw new RuntimeException(
217 "Iterators break encapsulation and ignore locking. Write-lock first");
218 return list.listIterator(index);
221 public List<T> subList(int fromIndex, int toIndex) {
222 try (LockHolder lh = lockForReading()) {
223 return list.subList(fromIndex, toIndex);
227 @RequiresApi(api = Build.VERSION_CODES.N)
228 public Spliterator<T> spliterator() {
229 if (!lock.isWriteLockedByCurrentThread()) throw new RuntimeException(
230 "Iterators break encapsulation and ignore locking. Write-lock first");
231 return list.spliterator();
233 @RequiresApi(api = Build.VERSION_CODES.N)
234 public boolean removeIf(Predicate<? super T> filter) {
235 try (LockHolder lh = lockForWriting()) {
236 boolean result = list.removeIf(filter);
238 if (result) forceNotify();
242 @RequiresApi(api = Build.VERSION_CODES.N)
243 public Stream<T> stream() {
244 if (!lock.isWriteLockedByCurrentThread()) throw new RuntimeException(
245 "Iterators break encapsulation and ignore locking. Write-lock first");
246 return list.stream();
248 @RequiresApi(api = Build.VERSION_CODES.N)
249 public Stream<T> parallelStream() {
250 if (!lock.isWriteLockedByCurrentThread()) throw new RuntimeException(
251 "Iterators break encapsulation and ignore locking. Write-lock first");
252 return list.parallelStream();
254 @RequiresApi(api = Build.VERSION_CODES.N)
255 public void forEach(Consumer<? super T> action) {
256 try (LockHolder lh = lockForReading()) {
257 list.forEach(action);
260 public List<T> getList() {
261 if (!lock.isWriteLockedByCurrentThread()) throw new RuntimeException(
262 "Direct list access breaks encapsulation and ignore locking. Write-lock first");
265 public void setList(List<T> aList) {
266 try (LockHolder lh = lockForWriting()) {
272 public void triggerItemChangedNotification(T item) {
273 try (LockHolder lh = lockForReading()) {
274 int index = list.indexOf(item);
276 Log.d("ObList", "??? not sending notifications for item not found in the list");
279 Log.d("ObList", "Notifying item change observers");
280 triggerItemChangedNotification(index);
283 public void triggerItemChangedNotification(int index) {
286 public LockHolder lockForWriting() {
287 ReentrantReadWriteLock.WriteLock wLock = lock.writeLock();
290 ReentrantReadWriteLock.ReadLock rLock = lock.readLock();
293 return new LockHolder(rLock, wLock);
295 public LockHolder lockForReading() {
296 ReentrantReadWriteLock.ReadLock rLock = lock.readLock();
298 return new LockHolder(rLock);