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