1 /*
2  * Copyright 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.compose.runtime.snapshots
18 
19 import androidx.compose.runtime.Stable
20 import androidx.compose.runtime.external.kotlinx.collections.immutable.PersistentList
21 import androidx.compose.runtime.external.kotlinx.collections.immutable.persistentListOf
22 import androidx.compose.runtime.platform.makeSynchronizedObject
23 import androidx.compose.runtime.platform.synchronized
24 import androidx.compose.runtime.requirePrecondition
25 import kotlin.jvm.JvmName
26 
27 /**
28  * An implementation of [MutableList] that can be observed and snapshot. This is the result type
29  * created by [androidx.compose.runtime.mutableStateListOf].
30  *
31  * This class closely implements the same semantics as [ArrayList].
32  *
33  * @see androidx.compose.runtime.mutableStateListOf
34  */
35 @Stable
36 class SnapshotStateList<T> internal constructor(persistentList: PersistentList<T>) :
37     StateObject, MutableList<T>, RandomAccess {
38     constructor() : this(persistentListOf())
39 
40     override var firstStateRecord: StateRecord = stateRecordWith(persistentList)
41         private set
42 
prependStateRecordnull43     override fun prependStateRecord(value: StateRecord) {
44         value.next = firstStateRecord
45         @Suppress("UNCHECKED_CAST")
46         firstStateRecord = value as StateListStateRecord<T>
47     }
48 
49     /**
50      * Return a list containing all the elements of this list.
51      *
52      * The list returned is immutable and returned will not change even if the content of the list
53      * is changed in the same snapshot. It also will be the same instance until the content is
54      * changed. It is not, however, guaranteed to be the same instance for the same list as adding
55      * and removing the same item from the this list might produce a different instance with the
56      * same content.
57      *
58      * This operation is O(1) and does not involve a physically copying the list. It instead returns
59      * the underlying immutable list used internally to store the content of the list.
60      *
61      * It is recommended to use [toList] when using returning the value of this list from
62      * [androidx.compose.runtime.snapshotFlow].
63      */
toListnull64     fun toList(): List<T> = readable.list
65 
66     internal val structure: Int
67         get() = withCurrent { structuralChange }
68 
69     @Suppress("UNCHECKED_CAST")
70     internal val readable: StateListStateRecord<T>
71         get() = (firstStateRecord as StateListStateRecord<T>).readable(this)
72 
73     /** This is an internal implementation class of [SnapshotStateList]. Do not use. */
74     internal class StateListStateRecord<T>
75     internal constructor(snapshotId: SnapshotId, internal var list: PersistentList<T>) :
76         StateRecord(snapshotId) {
77         internal var modification = 0
78         internal var structuralChange = 0
79 
assignnull80         override fun assign(value: StateRecord) {
81             synchronized(sync) {
82                 @Suppress("UNCHECKED_CAST")
83                 list = (value as StateListStateRecord<T>).list
84                 modification = value.modification
85                 structuralChange = value.structuralChange
86             }
87         }
88 
createnull89         override fun create(): StateRecord = create(currentSnapshot().snapshotId)
90 
91         override fun create(snapshotId: SnapshotId): StateRecord =
92             StateListStateRecord(snapshotId, list)
93     }
94 
95     override val size: Int
96         get() = readable.list.size
97 
98     override fun contains(element: T) = readable.list.contains(element)
99 
100     override fun containsAll(elements: Collection<T>) = readable.list.containsAll(elements)
101 
102     override fun get(index: Int) = readable.list[index]
103 
104     override fun indexOf(element: T): Int = readable.list.indexOf(element)
105 
106     override fun isEmpty() = readable.list.isEmpty()
107 
108     override fun iterator(): MutableIterator<T> = listIterator()
109 
110     override fun lastIndexOf(element: T) = readable.list.lastIndexOf(element)
111 
112     override fun listIterator(): MutableListIterator<T> = StateListIterator(this, 0)
113 
114     override fun listIterator(index: Int): MutableListIterator<T> = StateListIterator(this, index)
115 
116     override fun subList(fromIndex: Int, toIndex: Int): MutableList<T> {
117         requirePrecondition(fromIndex in 0..toIndex && toIndex <= size) {
118             "fromIndex or toIndex are out of bounds"
119         }
120         return SubList(this, fromIndex, toIndex)
121     }
122 
123     @Suppress("UNCHECKED_CAST")
toStringnull124     override fun toString(): String =
125         (firstStateRecord as StateListStateRecord<T>).withCurrent {
126             "SnapshotStateList(value=${it.list})@${hashCode()}"
127         }
128 
addnull129     override fun add(element: T) = conditionalUpdate { it.add(element) }
130 
addnull131     override fun add(index: Int, element: T) = update { it.add(index, element) }
132 
<lambda>null133     override fun addAll(index: Int, elements: Collection<T>) = mutateBoolean {
134         it.addAll(index, elements)
135     }
136 
<lambda>null137     override fun addAll(elements: Collection<T>) = conditionalUpdate { it.addAll(elements) }
138 
clearnull139     override fun clear() {
140         writable {
141             synchronized(sync) {
142                 list = persistentListOf()
143                 modification++
144                 structuralChange++
145             }
146         }
147     }
148 
<lambda>null149     override fun remove(element: T) = conditionalUpdate { it.remove(element) }
150 
<lambda>null151     override fun removeAll(elements: Collection<T>) = conditionalUpdate { it.removeAll(elements) }
152 
<lambda>null153     override fun removeAt(index: Int): T = get(index).also { update { it.removeAt(index) } }
154 
<lambda>null155     override fun retainAll(elements: Collection<T>) = mutateBoolean { it.retainAll(elements) }
156 
setnull157     override fun set(index: Int, element: T): T =
158         get(index).also { update(structural = false) { it.set(index, element) } }
159 
removeRangenull160     fun removeRange(fromIndex: Int, toIndex: Int) {
161         mutate { it.subList(fromIndex, toIndex).clear() }
162     }
163 
retainAllInRangenull164     internal fun retainAllInRange(elements: Collection<T>, start: Int, end: Int): Int {
165         val startSize = size
166         mutate<Unit> { it.subList(start, end).retainAll(elements) }
167         return startSize - size
168     }
169 
170     /**
171      * An internal function used by the debugger to display the value of the current list without
172      * triggering read observers.
173      */
174     @Suppress("unused")
175     internal val debuggerDisplayValue: List<T>
<lambda>null176         @JvmName("getDebuggerDisplayValue") get() = withCurrent { list }
177 
writablenull178     private inline fun <R> writable(block: StateListStateRecord<T>.() -> R): R =
179         @Suppress("UNCHECKED_CAST")
180         (firstStateRecord as StateListStateRecord<T>).writable(this, block)
181 
182     private inline fun <R> withCurrent(block: StateListStateRecord<T>.() -> R): R =
183         @Suppress("UNCHECKED_CAST") (firstStateRecord as StateListStateRecord<T>).withCurrent(block)
184 
185     private fun mutateBoolean(block: (MutableList<T>) -> Boolean): Boolean = mutate(block)
186 
187     private inline fun <R> mutate(block: (MutableList<T>) -> R): R {
188         var result: R
189         while (true) {
190             var oldList: PersistentList<T>? = null
191             var currentModification = 0
192             synchronized(sync) {
193                 val current = withCurrent { this }
194                 currentModification = current.modification
195                 oldList = current.list
196             }
197             val builder = oldList!!.builder()
198             result = block(builder)
199             val newList = builder.build()
200             if (
201                 newList == oldList ||
202                     writable { attemptUpdate(currentModification, newList, structural = true) }
203             )
204                 break
205         }
206         return result
207     }
208 
updatenull209     private inline fun update(
210         structural: Boolean = true,
211         block: (PersistentList<T>) -> PersistentList<T>
212     ) {
213         conditionalUpdate(structural, block)
214     }
215 
conditionalUpdatenull216     private inline fun conditionalUpdate(
217         structural: Boolean = true,
218         block: (PersistentList<T>) -> PersistentList<T>
219     ) = run {
220         val result: Boolean
221         while (true) {
222             var oldList: PersistentList<T>? = null
223             var currentModification = 0
224             synchronized(sync) {
225                 val current = withCurrent { this }
226                 currentModification = current.modification
227                 oldList = current.list
228             }
229             val newList = block(oldList!!)
230             if (newList == oldList) {
231                 result = false
232                 break
233             }
234             if (writable { attemptUpdate(currentModification, newList, structural) }) {
235                 result = true
236                 break
237             }
238         }
239         result
240     }
241 
242     // NOTE: do not inline this method to avoid class verification failures, see b/369909868
StateListStateRecordnull243     private fun StateListStateRecord<T>.attemptUpdate(
244         currentModification: Int,
245         newList: PersistentList<T>,
246         structural: Boolean
247     ): Boolean =
248         synchronized(sync) {
249             if (modification == currentModification) {
250                 list = newList
251                 if (structural) structuralChange++
252                 modification++
253                 true
254             } else false
255         }
256 
stateRecordWithnull257     private fun stateRecordWith(list: PersistentList<T>): StateRecord {
258         val snapshot = currentSnapshot()
259         return StateListStateRecord(snapshot.snapshotId, list).also {
260             if (snapshot !is GlobalSnapshot) {
261                 it.next = StateListStateRecord(Snapshot.PreexistingSnapshotId.toSnapshotId(), list)
262             }
263         }
264     }
265 }
266 
267 /**
268  * Creates a new snapshot state list with the specified [size], where each element is calculated by
269  * calling the specified [init] function.
270  *
271  * The function [init] is called for each list element sequentially starting from the first one. It
272  * should return the value for a list element given its index.
273  */
SnapshotStateListnull274 fun <T> SnapshotStateList(size: Int, init: (index: Int) -> T): SnapshotStateList<T> {
275     if (size == 0) {
276         return SnapshotStateList()
277     }
278 
279     val builder = persistentListOf<T>().builder()
280     for (i in 0 until size) {
281         builder.add(init(i))
282     }
283     return SnapshotStateList(builder.build())
284 }
285 
286 /**
287  * This lock is used to ensure that the value of modification and the list in the state record, when
288  * used together, are atomically read and written.
289  *
290  * A global sync object is used to avoid having to allocate a sync object and initialize a monitor
291  * for each instance the list. This avoid additional allocations but introduces some contention
292  * between lists. As there is already contention on the global snapshot lock to write so the
293  * additional contention introduced by this lock is nominal.
294  *
295  * In code the requires this lock and calls `writable` (or other operation that acquires the
296  * snapshot global lock), this lock *MUST* be acquired last to avoid deadlocks. In other words, the
297  * lock must be taken in the `writable` lambda, if `writable` is used.
298  */
299 private val sync = makeSynchronizedObject()
300 
modificationErrornull301 private fun modificationError(): Nothing = error("Cannot modify a state list through an iterator")
302 
303 private fun validateRange(index: Int, size: Int) {
304     if (index !in 0 until size) {
305         throw IndexOutOfBoundsException("index ($index) is out of bound of [0, $size)")
306     }
307 }
308 
invalidIteratorSetnull309 private fun invalidIteratorSet(): Nothing =
310     error(
311         "Cannot call set before the first call to next() or previous() " +
312             "or immediately after a call to add() or remove()"
313     )
314 
315 private class StateListIterator<T>(val list: SnapshotStateList<T>, offset: Int) :
316     MutableListIterator<T> {
317     private var index = offset - 1
318     private var lastRequested = -1
319     private var structure = list.structure
320 
321     override fun hasPrevious() = index >= 0
322 
323     override fun nextIndex() = index + 1
324 
325     override fun previous(): T {
326         validateModification()
327         validateRange(index, list.size)
328         lastRequested = index
329         return list[index].also { index-- }
330     }
331 
332     override fun previousIndex(): Int = index
333 
334     override fun add(element: T) {
335         validateModification()
336         list.add(index + 1, element)
337         lastRequested = -1
338         index++
339         structure = list.structure
340     }
341 
342     override fun hasNext() = index < list.size - 1
343 
344     override fun next(): T {
345         validateModification()
346         val newIndex = index + 1
347         lastRequested = newIndex
348         validateRange(newIndex, list.size)
349         return list[newIndex].also { index = newIndex }
350     }
351 
352     override fun remove() {
353         validateModification()
354         list.removeAt(index)
355         index--
356         lastRequested = -1
357         structure = list.structure
358     }
359 
360     override fun set(element: T) {
361         validateModification()
362         if (lastRequested < 0) invalidIteratorSet()
363         list.set(lastRequested, element)
364         structure = list.structure
365     }
366 
367     private fun validateModification() {
368         if (list.structure != structure) {
369             throw ConcurrentModificationException()
370         }
371     }
372 }
373 
374 private class SubList<T>(val parentList: SnapshotStateList<T>, fromIndex: Int, toIndex: Int) :
375     MutableList<T> {
376     private val offset = fromIndex
377     private var structure = parentList.structure
378     override var size = toIndex - fromIndex
379         private set
380 
containsnull381     override fun contains(element: T): Boolean = indexOf(element) >= 0
382 
383     override fun containsAll(elements: Collection<T>): Boolean = elements.all { contains(it) }
384 
getnull385     override fun get(index: Int): T {
386         validateModification()
387         validateRange(index, size)
388         return parentList[offset + index]
389     }
390 
indexOfnull391     override fun indexOf(element: T): Int {
392         validateModification()
393         (offset until offset + size).forEach { if (element == parentList[it]) return it - offset }
394         return -1
395     }
396 
isEmptynull397     override fun isEmpty(): Boolean = size == 0
398 
399     override fun iterator(): MutableIterator<T> = listIterator()
400 
401     override fun lastIndexOf(element: T): Int {
402         validateModification()
403         var index = offset + size - 1
404         while (index >= offset) {
405             if (element == parentList[index]) return index - offset
406             index--
407         }
408         return -1
409     }
410 
addnull411     override fun add(element: T): Boolean {
412         validateModification()
413         parentList.add(offset + size, element)
414         size++
415         structure = parentList.structure
416         return true
417     }
418 
addnull419     override fun add(index: Int, element: T) {
420         validateModification()
421         parentList.add(offset + index, element)
422         size++
423         structure = parentList.structure
424     }
425 
addAllnull426     override fun addAll(index: Int, elements: Collection<T>): Boolean {
427         validateModification()
428         val result = parentList.addAll(index + offset, elements)
429         if (result) {
430             size += elements.size
431             structure = parentList.structure
432         }
433         return result
434     }
435 
addAllnull436     override fun addAll(elements: Collection<T>): Boolean = addAll(size, elements)
437 
438     override fun clear() {
439         if (size > 0) {
440             validateModification()
441             parentList.removeRange(offset, offset + size)
442             size = 0
443             structure = parentList.structure
444         }
445     }
446 
listIteratornull447     override fun listIterator(): MutableListIterator<T> = listIterator(0)
448 
449     override fun listIterator(index: Int): MutableListIterator<T> {
450         validateModification()
451         var current = index - 1
452         return object : MutableListIterator<T> {
453             override fun hasPrevious() = current >= 0
454 
455             override fun nextIndex(): Int = current + 1
456 
457             override fun previous(): T {
458                 val oldCurrent = current
459                 validateRange(oldCurrent, size)
460                 current = oldCurrent - 1
461                 return this@SubList[oldCurrent]
462             }
463 
464             override fun previousIndex(): Int = current
465 
466             override fun add(element: T) = modificationError()
467 
468             override fun hasNext(): Boolean = current < size - 1
469 
470             override fun next(): T {
471                 val newCurrent = current + 1
472                 validateRange(newCurrent, size)
473                 current = newCurrent
474                 return this@SubList[newCurrent]
475             }
476 
477             override fun remove() = modificationError()
478 
479             override fun set(element: T) = modificationError()
480         }
481     }
482 
removenull483     override fun remove(element: T): Boolean {
484         val index = indexOf(element)
485         return if (index >= 0) {
486             removeAt(index)
487             true
488         } else false
489     }
490 
removeAllnull491     override fun removeAll(elements: Collection<T>): Boolean {
492         var removed = false
493         for (element in elements) {
494             removed = remove(element) || removed
495         }
496         return removed
497     }
498 
removeAtnull499     override fun removeAt(index: Int): T {
500         validateModification()
501         return parentList.removeAt(offset + index).also {
502             size--
503             structure = parentList.structure
504         }
505     }
506 
retainAllnull507     override fun retainAll(elements: Collection<T>): Boolean {
508         validateModification()
509         val removed = parentList.retainAllInRange(elements, offset, offset + size)
510         if (removed > 0) {
511             structure = parentList.structure
512             size -= removed
513         }
514         return removed > 0
515     }
516 
setnull517     override fun set(index: Int, element: T): T {
518         validateRange(index, size)
519         validateModification()
520         val result = parentList.set(index + offset, element)
521         structure = parentList.structure
522         return result
523     }
524 
subListnull525     override fun subList(fromIndex: Int, toIndex: Int): MutableList<T> {
526         requirePrecondition(fromIndex in 0..toIndex && toIndex <= size) {
527             "fromIndex or toIndex are out of bounds"
528         }
529         validateModification()
530         return SubList(parentList, fromIndex + offset, toIndex + offset)
531     }
532 
validateModificationnull533     private fun validateModification() {
534         if (parentList.structure != structure) {
535             throw ConcurrentModificationException()
536         }
537     }
538 }
539