Skip to content

Commit 9284acc

Browse files
Merge pull request #9982 from rafaeltonholo/feat/9572/show-higher-priority-banners-global
feat(in-app-notification): implement priority queue for banner global
2 parents cb7a741 + 4cf2e93 commit 9284acc

File tree

11 files changed

+747
-27
lines changed

11 files changed

+747
-27
lines changed
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
package net.thunderbird.core.common.collections
2+
3+
/**
4+
* An unbounded priority queue based on a binary heap.
5+
*
6+
* The elements of the priority queue are ordered according to the provided [Comparator].
7+
* The head of this queue is the *least* element with respect to the specified ordering.
8+
* If multiple elements are tied for least value, the head is one of those elements — ties
9+
* are broken arbitrarily.
10+
*
11+
* The queue does not permit `null` elements.
12+
*
13+
* This class and its iterator implement all of the *optional* methods of the [Collection] and
14+
* [Iterator] interfaces. The iterator returned by the `iterator()` method is *not* guaranteed
15+
* to traverse the elements of the priority queue in any particular order.
16+
* If you need ordered traversal, consider draining the queue into a list, for example:
17+
*
18+
* ```kotlin
19+
* val list = mutableListOf<T>()
20+
* while (queue.isNotEmpty()) {
21+
* list.add(queue.poll())
22+
* }
23+
* ```
24+
*
25+
* This implementation is not thread-safe.
26+
*
27+
* @param T The type of elements held in this collection.
28+
* @param comparator The comparator that will be used to order this priority queue.
29+
* @param elements An optional list of initial elements to be added to the queue.
30+
*/
31+
class PriorityQueue<T>(
32+
private val comparator: Comparator<in T>,
33+
elements: List<T> = emptyList(),
34+
) : AbstractQueue<T>() {
35+
private val heap = elements.toMutableList()
36+
37+
init {
38+
if (heap.isNotEmpty()) {
39+
heapify()
40+
}
41+
}
42+
43+
override val size: Int get() = heap.size
44+
override fun isEmpty(): Boolean = heap.isEmpty()
45+
46+
override fun iterator(): MutableIterator<T> = HeapIterator()
47+
48+
override fun offer(element: T): Boolean {
49+
heap.add(element)
50+
siftUp(heap.lastIndex)
51+
return true
52+
}
53+
54+
override fun poll(): T? {
55+
if (heap.isEmpty()) {
56+
return null
57+
}
58+
val result = heap.first()
59+
val last = heap.removeAt(heap.lastIndex)
60+
if (heap.isNotEmpty()) {
61+
heap[0] = last
62+
siftDown(0)
63+
}
64+
return result
65+
}
66+
67+
override fun peek(): T? = heap.firstOrNull()
68+
69+
private fun siftUp(index: Int) {
70+
var childIndex = index
71+
fun parentIndex(childIndex: Int) = (childIndex - 1) / 2
72+
73+
while (childIndex > 0 && comparator.compare(heap[childIndex], heap[parentIndex(childIndex)]) < 0) {
74+
swap(childIndex, parentIndex(childIndex))
75+
childIndex = parentIndex(childIndex)
76+
}
77+
}
78+
79+
private fun siftDown(index: Int) {
80+
var parentIndex = index
81+
fun leftChildIndex(parentIndex: Int) = (parentIndex * 2) + 1
82+
val half = heap.size / 2
83+
while (parentIndex < half) {
84+
var childIndex = leftChildIndex(parentIndex)
85+
val rightChildIndex = childIndex + 1
86+
if (rightChildIndex < heap.size &&
87+
comparator.compare(heap[childIndex], heap[rightChildIndex]) > 0
88+
) {
89+
childIndex = rightChildIndex
90+
}
91+
92+
if (comparator.compare(heap[parentIndex], heap[childIndex]) <= 0) {
93+
break
94+
}
95+
96+
swap(parentIndex, childIndex)
97+
parentIndex = childIndex
98+
}
99+
}
100+
101+
private fun heapify() {
102+
for (index in ((heap.lastIndex - 1) / 2) downTo 0) {
103+
siftDown(index)
104+
}
105+
}
106+
107+
private fun swap(first: Int, second: Int) {
108+
val temp = heap[first]
109+
heap[first] = heap[second]
110+
heap[second] = temp
111+
}
112+
113+
private fun removeAt(i: Int) {
114+
val lastIndex = heap.lastIndex
115+
if (i == lastIndex) {
116+
heap.removeAt(lastIndex)
117+
return
118+
}
119+
val moved = heap.removeAt(lastIndex)
120+
heap[i] = moved
121+
siftDown(i)
122+
siftUp(i)
123+
}
124+
125+
private inner class HeapIterator : MutableIterator<T> {
126+
private var index = 0
127+
private var lastReturned = -1
128+
129+
override fun hasNext(): Boolean = index < heap.size
130+
131+
override fun next(): T {
132+
if (!hasNext()) throw NoSuchElementException("Queue is empty")
133+
lastReturned = index++
134+
return heap[lastReturned]
135+
}
136+
137+
override fun remove() {
138+
require(lastReturned >= 0) { "next() has not been called" }
139+
removeAt(lastReturned)
140+
index = lastReturned--
141+
}
142+
}
143+
}
144+
145+
/**
146+
* Creates a [PriorityQueue] that orders its elements according to their natural-order,
147+
* resulting in a min-priority queue (the smallest element is at the head).
148+
*
149+
* The elements must be [Comparable].
150+
*
151+
* @param T the type of elements in the priority queue.
152+
* @param elements an optional [Iterable] of initial elements to be added to the queue.
153+
* @return a [PriorityQueue] containing the specified elements, ordered by their natural ascending order.
154+
*/
155+
inline fun <reified T> minPriorityQueueOf(
156+
elements: Iterable<T> = emptyList(),
157+
): PriorityQueue<T> where T : Comparable<T> = PriorityQueue(
158+
comparator = { a, b -> a.compareTo(b) },
159+
elements = elements.toList(),
160+
)
161+
162+
/**
163+
* Creates a [PriorityQueue] that orders its elements according to their reverse-order,
164+
* resulting in a max-priority queue (the largest element is at the head).
165+
*
166+
* The elements must be [Comparable].
167+
*
168+
* @param T the type of elements in the priority queue.
169+
* @param elements an optional [Iterable] of initial elements to be added to the queue.
170+
* @return a [PriorityQueue] containing the specified elements, ordered by their natural descending order.
171+
*/
172+
inline fun <reified T> maxPriorityQueueOf(
173+
elements: Iterable<T> = emptyList(),
174+
): PriorityQueue<T> where T : Comparable<T> = PriorityQueue(
175+
comparator = { a, b -> b.compareTo(a) },
176+
elements = elements.toList(),
177+
)
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package net.thunderbird.core.common.collections
2+
3+
/**
4+
* A collection designed for holding elements prior to processing.
5+
* Besides basic [Collection] operations, queues provide additional insertion, extraction, and inspection operations.
6+
*
7+
* Queues typically, but do not necessarily, order elements in a FIFO (first-in-first-out) manner.
8+
*/
9+
interface Queue<T> : Collection<T> {
10+
/**
11+
* Inserts the specified element into this queue.
12+
*
13+
* @return `true` if the element was added to this queue.
14+
* @throws IllegalStateException if the element cannot be added at this time due to capacity restrictions.
15+
*/
16+
fun add(element: T): Boolean
17+
18+
/**
19+
* Inserts the specified element into this queue.
20+
*
21+
* @return `true` if the element was added to this queue, else `false`.
22+
*/
23+
fun offer(element: T): Boolean
24+
25+
/**
26+
* Retrieves and removes the head of this queue.
27+
*
28+
* @return the head of this queue.
29+
* @throws NoSuchElementException if this queue is empty.
30+
*/
31+
fun remove(): T
32+
33+
/**
34+
* @return Retrieves and removes the head of this queue, or returns `null` if this queue is empty.
35+
*/
36+
fun poll(): T?
37+
38+
/**
39+
* Retrieves, but does not remove, the head of this queue.
40+
*
41+
* @return the head of this queue.
42+
* @throws NoSuchElementException if this queue is empty.
43+
*/
44+
fun element(): T
45+
46+
/**
47+
* @return Retrieves, but does not remove, the head of this queue, or returns `null` if this queue is empty.
48+
*/
49+
fun peek(): T?
50+
}
51+
52+
abstract class AbstractQueue<T> : AbstractMutableCollection<T>(), Queue<T> {
53+
54+
override fun element(): T {
55+
val head = peek()
56+
if (head != null) {
57+
return head
58+
} else {
59+
error("Collection empty")
60+
}
61+
}
62+
63+
override fun add(element: T): Boolean {
64+
if (offer(element)) {
65+
return true
66+
} else {
67+
error("Collection full")
68+
}
69+
}
70+
71+
override fun addAll(elements: Collection<T>): Boolean {
72+
require(elements != this) { "Collection can't addAll itself" }
73+
var modified = false
74+
for (element in elements) {
75+
if (add(element)) {
76+
modified = true
77+
}
78+
}
79+
return modified
80+
}
81+
82+
override fun remove(): T {
83+
val head = poll()
84+
if (head != null) {
85+
return head
86+
} else {
87+
throw NoSuchElementException("Queue is empty")
88+
}
89+
}
90+
91+
override fun clear() {
92+
while (!isEmpty()) {
93+
remove()
94+
}
95+
}
96+
}

0 commit comments

Comments
 (0)