1 /*
2  * Copyright (C) 2023 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 com.android.systemui.util
18 
19 import java.util.concurrent.CopyOnWriteArrayList
20 import java.util.function.Consumer
21 
22 /**
23  * A collection of listeners, observers, callbacks, etc.
24  *
25  * This container is optimized for infrequent mutation and frequent iteration, with thread safety
26  * and reentrant-safety guarantees as well. Specifically, to ensure that
27  * [ConcurrentModificationException] is never thrown, this iterator will not reflect changes made to
28  * the set after the iterator is constructed.
29  *
30  * This class provides all the abilities of [ListenerSet], except that each listener has a name
31  * calculated at runtime which can be used for time-efficient tracing of listener invocations.
32  */
33 class NamedListenerSet<E : Any>(
34     private val getName: (E) -> String = { it.javaClass.name },
35 ) : IListenerSet<E> {
36     private val listeners = CopyOnWriteArrayList<NamedListener>()
37 
38     override val size: Int
39         get() = listeners.size
40 
41     override fun isEmpty() = listeners.isEmpty()
42 
43     override fun iterator(): Iterator<E> = iterator {
44         listeners.iterator().forEach { yield(it.listener) }
45     }
46 
47     override fun containsAll(elements: Collection<E>) =
48         listeners.count { it.listener in elements } == elements.size
49 
50     override fun contains(element: E) = listeners.firstOrNull { it.listener == element } != null
51 
52     override fun addIfAbsent(element: E): Boolean = listeners.addIfAbsent(NamedListener(element))
53 
54     override fun remove(element: E): Boolean = listeners.removeIf { it.listener == element }
55 
56     /** A wrapper for the listener with an associated name. */
57     inner class NamedListener(val listener: E) {
58         val name: String = getName(listener)
59 
60         override fun hashCode(): Int {
61             return listener.hashCode()
62         }
63 
64         override fun equals(other: Any?): Boolean =
65             when {
66                 other === null -> false
67                 other === this -> true
68                 other !is NamedListenerSet<*>.NamedListener -> false
69                 listener == other.listener -> true
70                 else -> false
71             }
72     }
73 
74     /** Iterate the listeners in the set, providing the name for each one as well. */
75     inline fun forEachNamed(block: (String, E) -> Unit) =
76         namedIterator().forEach { element -> block(element.name, element.listener) }
77 
78     /**
79      * Iterate the listeners in the set, wrapping each call to the block with [traceSection] using
80      * the listener name.
81      */
82     inline fun forEachTraced(block: (E) -> Unit) = forEachNamed { name, listener ->
83         traceSection(name) { block(listener) }
84     }
85 
86     /**
87      * Iterate the listeners in the set, wrapping each call to the block with [traceSection] using
88      * the listener name.
89      */
90     fun forEachTraced(consumer: Consumer<E>) = forEachNamed { name, listener ->
91         traceSection(name) { consumer.accept(listener) }
92     }
93 
94     /** Iterate over the [NamedListener]s currently in the set. */
95     fun namedIterator(): Iterator<NamedListener> = listeners.iterator()
96 }
97