aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/ui/src/main/java/com/wireguard/android/databinding/ObservableSortedKeyedArrayList.kt
blob: a09d726f8cd6ada02112fe8b95a85542dd772b9a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
/*
 * Copyright © 2017-2023 WireGuard LLC. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */
package com.wireguard.android.databinding

import java.util.AbstractList
import java.util.Collections
import java.util.Comparator
import java.util.Spliterator

/**
 * KeyedArrayList that enforces uniqueness and sorted order across the set of keys. This class uses
 * binary search to improve lookup and replacement times to O(log(n)). However, due to the
 * array-based nature of this class, insertion and removal of elements with anything but the largest
 * key still require O(n) time.
 */
class ObservableSortedKeyedArrayList<K, E : Keyed<out K>>(private val comparator: Comparator<in K>) : ObservableKeyedArrayList<K, E>() {
    @Transient
    private val keyList = KeyList(this)

    override fun add(element: E): Boolean {
        val insertionPoint = getInsertionPoint(element)
        if (insertionPoint < 0) {
            // Skipping insertion is non-destructive if the new and existing objects are the same.
            if (element === get(-insertionPoint - 1)) return false
            throw IllegalArgumentException("Element with same key already exists in list")
        }
        super.add(insertionPoint, element)
        return true
    }

    override fun add(index: Int, element: E) {
        val insertionPoint = getInsertionPoint(element)
        require(insertionPoint >= 0) { "Element with same key already exists in list" }
        if (insertionPoint != index) throw IndexOutOfBoundsException("Wrong index given for element")
        super.add(index, element)
    }

    override fun addAll(elements: Collection<E>): Boolean {
        var didChange = false
        for (e in elements) {
            if (add(e))
                didChange = true
        }
        return didChange
    }

    override fun addAll(index: Int, elements: Collection<E>): Boolean {
        var i = index
        for (e in elements)
            add(i++, e)
        return true
    }

    private fun getInsertionPoint(e: E) = -Collections.binarySearch(keyList, e.key, comparator) - 1

    override fun indexOfKey(key: K): Int {
        val index = Collections.binarySearch(keyList, key, comparator)
        return if (index >= 0) index else -1
    }

    override fun set(index: Int, element: E): E {
        val order = comparator.compare(element.key, get(index).key)
        if (order != 0) {
            // Allow replacement if the new key would be inserted adjacent to the replaced element.
            val insertionPoint = getInsertionPoint(element)
            if (insertionPoint < index || insertionPoint > index + 1)
                throw IndexOutOfBoundsException("Wrong index given for element")
        }
        return super.set(index, element)
    }

    private class KeyList<K, E : Keyed<out K>>(private val list: ObservableSortedKeyedArrayList<K, E>) : AbstractList<K>(), Set<K> {
        override fun get(index: Int): K = list[index].key

        override val size
            get() = list.size

        override fun spliterator(): Spliterator<K> = super<AbstractList>.spliterator()
    }
}