Merge branch 'vitorpamplona:main' into profiles-list-management

This commit is contained in:
KotlinGeekDev
2025-08-18 16:23:06 +00:00
committed by GitHub
21 changed files with 711 additions and 1091 deletions
@@ -21,6 +21,7 @@
package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common
import androidx.compose.runtime.Immutable
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.quartz.nip01Core.relay.client.stats.RelayStat
import com.vitorpamplona.quartz.nip01Core.relay.client.stats.RelayStats
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
@@ -31,6 +32,7 @@ data class BasicRelaySetupInfo(
val relayStat: RelayStat,
val paidRelay: Boolean = false,
val forcesTor: Boolean = false,
val users: List<User> = emptyList(),
)
fun relaySetupInfoBuilder(
@@ -22,26 +22,37 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.text.AnnotatedString
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.LocalCache.users
import com.vitorpamplona.amethyst.ui.navigation.navs.EmptyNav.nav
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.note.RenderRelayIcon
import com.vitorpamplona.amethyst.ui.note.UserPicture
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.ephemChat.header.loadRelayInfo
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.HalfHorzPadding
import com.vitorpamplona.amethyst.ui.theme.HalfStartPadding
import com.vitorpamplona.amethyst.ui.theme.HalfVertPadding
import com.vitorpamplona.amethyst.ui.theme.Height25Modifier
import com.vitorpamplona.amethyst.ui.theme.LargeRelayIconModifier
import com.vitorpamplona.amethyst.ui.theme.ReactionRowHeightChatMaxWidth
import com.vitorpamplona.amethyst.ui.theme.Size25dp
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.displayUrl
@OptIn(ExperimentalFoundationApi::class)
@@ -53,6 +64,7 @@ fun BasicRelaySetupInfoClickableRow(
onDelete: ((BasicRelaySetupInfo) -> Unit)?,
onClick: () -> Unit,
accountViewModel: AccountViewModel,
nav: INav,
) {
val clipboardManager = LocalClipboardManager.current
Column(
@@ -90,6 +102,8 @@ fun BasicRelaySetupInfoClickableRow(
ReactionRowHeightChatMaxWidth,
)
UsedBy(item, accountViewModel, nav)
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = ReactionRowHeightChatMaxWidth,
@@ -106,3 +120,56 @@ fun BasicRelaySetupInfoClickableRow(
HorizontalDivider(thickness = DividerThickness)
}
}
@OptIn(ExperimentalLayoutApi::class)
@Composable
fun UsedBy(
item: BasicRelaySetupInfo,
accountViewModel: AccountViewModel,
nav: INav,
) {
if (item.users.isNotEmpty()) {
Row(verticalAlignment = Alignment.CenterVertically) {
item.users.getOrNull(0)?.let {
UserPicture(
user = it,
size = Size25dp,
accountViewModel = accountViewModel,
nav = nav,
)
}
item.users.getOrNull(1)?.let {
UserPicture(
user = it,
size = Size25dp,
accountViewModel = accountViewModel,
nav = nav,
)
}
item.users.getOrNull(2)?.let {
UserPicture(
user = it,
size = Size25dp,
accountViewModel = accountViewModel,
nav = nav,
)
}
item.users.getOrNull(3)?.let {
UserPicture(
user = it,
size = Size25dp,
accountViewModel = accountViewModel,
nav = nav,
)
}
if (item.users.size > 4) {
Box(contentAlignment = Alignment.Center, modifier = Height25Modifier) {
Text(
text = stringRes(R.string.and_more, item.users.size - 4),
maxLines = 1,
)
}
}
}
}
}
@@ -44,5 +44,6 @@ fun BasicRelaySetupInfoDialog(
onDelete = onDelete,
accountViewModel = accountViewModel,
onClick = { nav.nav(Route.RelayInfo(item.relay.url)) },
nav = nav,
)
}
@@ -83,22 +83,24 @@ abstract class BasicRelaySetupInfoModel : ViewModel() {
}
}
fun clear() {
_relays.update {
val relayList = getRelayList() ?: emptyList()
open fun relayListBuilder(): List<BasicRelaySetupInfo> {
val relayList = getRelayList() ?: emptyList()
relayList
.map {
relaySetupInfoBuilder(
normalized = it,
forcesTor =
account.torRelayState.flow.value
.useTor(it),
)
}.distinctBy { it.relay }
.sortedBy { it.relayStat.receivedBytes }
.reversed()
}
return relayList
.map {
relaySetupInfoBuilder(
normalized = it,
forcesTor =
account.torRelayState.flow.value
.useTor(it),
)
}.distinctBy { it.relay }
.sortedBy { it.relayStat.receivedBytes }
.reversed()
}
fun clear() {
_relays.update { relayListBuilder() }
}
fun addRelay(relay: BasicRelaySetupInfo) {
@@ -20,10 +20,34 @@
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.connected
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoModel
import com.vitorpamplona.quartz.nip01Core.relay.client.stats.RelayStats
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
class ConnectedRelayListViewModel : BasicRelaySetupInfoModel() {
override fun relayListBuilder(): List<BasicRelaySetupInfo> {
val relayList = getRelayList()
return relayList
.map {
BasicRelaySetupInfo(
relay = it,
relayStat = RelayStats.get(it),
forcesTor =
account.torRelayState.flow.value
.useTor(it),
users =
account.followsPerRelay.value[it]?.mapNotNull { hex ->
LocalCache.checkGetOrCreateUser(hex)
} ?: emptyList(),
)
}.distinctBy { it.relay }
.sortedBy { it.relayStat.receivedBytes }
.reversed()
}
override fun getRelayList(): List<NormalizedRelayUrl> =
account.client
.relayStatusFlow()
@@ -191,6 +191,7 @@ val UserNameMaxRowHeight = Modifier.fillMaxWidth()
val Height24dpModifier = Modifier.height(24.dp)
val Height4dpModifier = Modifier.height(4.dp)
val Height25Modifier = Modifier.height(Size25dp)
val Height24dpFilledModifier = Modifier.fillMaxWidth().height(24.dp)
val Height4dpFilledModifier = Modifier.fillMaxWidth().height(4.dp)
+1
View File
@@ -146,3 +146,4 @@ jetbrainsKotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "k
jetbrainsKotlinJvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
jetbrainsComposeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
serialization = { id = 'org.jetbrains.kotlin.plugin.serialization', version.ref = 'kotlinxSerializationPlugin' }
parcelize = { id = "kotlin-parcelize" }
+1
View File
@@ -3,6 +3,7 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
alias(libs.plugins.androidLibrary)
alias(libs.plugins.jetbrainsKotlinAndroid)
alias(libs.plugins.parcelize)
}
android {
@@ -32,61 +32,59 @@ data class V2(
)
data class Valid(
@JsonProperty("get_conversation_key")
val getConversationKey: ArrayList<GetConversationKey> = arrayListOf(),
@JsonProperty("get_message_keys") val getMessageKeys: GetMessageKeys? = GetMessageKeys(),
@JsonProperty("calc_padded_len") val calcPaddedLen: ArrayList<ArrayList<Int>> = arrayListOf(),
@JsonProperty("encrypt_decrypt") val encryptDecrypt: ArrayList<EncryptDecrypt> = arrayListOf(),
@JsonProperty("encrypt_decrypt_long_msg")
@field:JsonProperty("get_conversation_key") val getConversationKey: ArrayList<GetConversationKey> = arrayListOf(),
@field:JsonProperty("get_message_keys") val getMessageKeys: GetMessageKeys? = GetMessageKeys(),
@field:JsonProperty("calc_padded_len") val calcPaddedLen: ArrayList<ArrayList<Int>> = arrayListOf(),
@field:JsonProperty("encrypt_decrypt") val encryptDecrypt: ArrayList<EncryptDecrypt> = arrayListOf(),
@field:JsonProperty("encrypt_decrypt_long_msg")
val encryptDecryptLongMsg: ArrayList<EncryptDecryptLongMsg> = arrayListOf(),
)
data class Invalid(
@JsonProperty("encrypt_msg_lengths") val encryptMsgLengths: ArrayList<Int> = arrayListOf(),
@JsonProperty("get_conversation_key")
val getConversationKey: ArrayList<GetConversationKey> = arrayListOf(),
@JsonProperty("decrypt") val decrypt: ArrayList<Decrypt> = arrayListOf(),
@field:JsonProperty("encrypt_msg_lengths") val encryptMsgLengths: ArrayList<Int> = arrayListOf(),
@field:JsonProperty("get_conversation_key") val getConversationKey: ArrayList<GetConversationKey> = arrayListOf(),
@field:JsonProperty("decrypt") val decrypt: ArrayList<Decrypt> = arrayListOf(),
)
data class GetConversationKey(
val sec1: String? = null,
val pub2: String? = null,
val note: String? = null,
@JsonProperty("conversation_key") val conversationKey: String? = null,
@field:JsonProperty("conversation_key") val conversationKey: String? = null,
)
data class GetMessageKeys(
@JsonProperty("conversation_key") val conversationKey: String? = null,
@field:JsonProperty("conversation_key") val conversationKey: String? = null,
val keys: ArrayList<Keys> = arrayListOf(),
)
data class Keys(
@JsonProperty("nonce") val nonce: String? = null,
@JsonProperty("chacha_key") val chachaKey: String? = null,
@JsonProperty("chacha_nonce") val chachaNonce: String? = null,
@JsonProperty("hmac_key") val hmacKey: String? = null,
@field:JsonProperty("nonce") val nonce: String? = null,
@field:JsonProperty("chacha_key") val chachaKey: String? = null,
@field:JsonProperty("chacha_nonce") val chachaNonce: String? = null,
@field:JsonProperty("hmac_key") val hmacKey: String? = null,
)
data class EncryptDecrypt(
val sec1: String? = null,
val sec2: String? = null,
@JsonProperty("conversation_key") val conversationKey: String? = null,
@field:JsonProperty("conversation_key") val conversationKey: String? = null,
val nonce: String? = null,
val plaintext: String? = null,
val payload: String? = null,
)
data class EncryptDecryptLongMsg(
@JsonProperty("conversation_key") val conversationKey: String? = null,
@field:JsonProperty("conversation_key") val conversationKey: String? = null,
val nonce: String? = null,
val pattern: String? = null,
val repeat: Int? = null,
@JsonProperty("plaintext_sha256") val plaintextSha256: String? = null,
@JsonProperty("payload_sha256") val payloadSha256: String? = null,
@field:JsonProperty("plaintext_sha256") val plaintextSha256: String? = null,
@field:JsonProperty("payload_sha256") val payloadSha256: String? = null,
)
data class Decrypt(
@JsonProperty("conversation_key") val conversationKey: String? = null,
@field:JsonProperty("conversation_key") val conversationKey: String? = null,
val nonce: String? = null,
val plaintext: String? = null,
val payload: String? = null,
@@ -23,36 +23,20 @@ package com.vitorpamplona.quartz.experimental.limits
import com.fasterxml.jackson.annotation.JsonProperty
class Limits(
@JsonProperty("can_write")
val canWrite: Boolean?,
@JsonProperty("can_read")
val canRead: Boolean?,
@JsonProperty("accepted_event_kinds")
val acceptedEventKinds: Set<Int>?,
@JsonProperty("blocked_event_kinds")
val blockedEventKinds: Set<Int>?,
@JsonProperty("min_pow_difficulty")
val minPoW: Int?,
@JsonProperty("max_message_length")
val maxMessageLength: Int?,
@JsonProperty("max_subscriptions")
val maxSubscriptions: Int?,
@JsonProperty("max_filters")
val maxFilters: Int?,
@JsonProperty("max_limit")
val maxLimit: Int?,
@JsonProperty("max_event_tags")
val maxEventTags: Int?,
@JsonProperty("max_content_length")
val maxContentLength: Int?,
@JsonProperty("created_at_msecs_ago")
val createdAtMillisecsAgo: Long?,
@JsonProperty("created_at_msecs_ahead")
val createdAtMillisecsAhead: Long?,
@JsonProperty("filter_rate_limit")
val filterRateLimit: Long?,
@JsonProperty("publishing_rate_limit")
val publishingRateLimit: Long?,
@JsonProperty("required_tags")
val requiredTags: Array<Array<String>>?,
@field:JsonProperty("can_write") val canWrite: Boolean?,
@field:JsonProperty("can_read") val canRead: Boolean?,
@field:JsonProperty("accepted_event_kinds") val acceptedEventKinds: Set<Int>?,
@field:JsonProperty("blocked_event_kinds") val blockedEventKinds: Set<Int>?,
@field:JsonProperty("min_pow_difficulty") val minPoW: Int?,
@field:JsonProperty("max_message_length") val maxMessageLength: Int?,
@field:JsonProperty("max_subscriptions") val maxSubscriptions: Int?,
@field:JsonProperty("max_filters") val maxFilters: Int?,
@field:JsonProperty("max_limit") val maxLimit: Int?,
@field:JsonProperty("max_event_tags") val maxEventTags: Int?,
@field:JsonProperty("max_content_length") val maxContentLength: Int?,
@field:JsonProperty("created_at_msecs_ago") val createdAtMillisecsAgo: Long?,
@field:JsonProperty("created_at_msecs_ahead") val createdAtMillisecsAhead: Long?,
@field:JsonProperty("filter_rate_limit") val filterRateLimit: Long?,
@field:JsonProperty("publishing_rate_limit") val publishingRateLimit: Long?,
@field:JsonProperty("required_tags") val requiredTags: Array<Array<String>>?,
)
@@ -74,7 +74,7 @@ class RelayPool(
var lastReconnectCall = TimeUtils.now()
fun reconnectIfNeedsToORIfItIsTime() {
if (lastReconnectCall < TimeUtils.tenSecondsAgo()) {
if (lastReconnectCall < TimeUtils.oneMinuteAgo()) {
relays.forEach { url, relay ->
if (relay.isConnected()) {
if (relay.needsToReconnect()) {
@@ -30,7 +30,7 @@ import com.vitorpamplona.quartz.nip01Core.jackson.JsonMapper
import com.vitorpamplona.quartz.utils.TimeUtils
class EventTemplate<T : Event>(
@JsonProperty("created_at")
@field:JsonProperty("created_at")
val createdAt: Long,
val kind: Int,
val tags: TagArray,
@@ -20,6 +20,7 @@
*/
package com.vitorpamplona.quartz.nip01Core.tags.addressables
import android.os.Parcelable
import android.util.Log
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.Kind
@@ -28,12 +29,15 @@ import com.vitorpamplona.quartz.nip19Bech32.entities.NAddress
import com.vitorpamplona.quartz.utils.Hex
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
import kotlinx.parcelize.Parcelize
@Parcelize
data class Address(
val kind: Kind,
val pubKeyHex: HexKey,
val dTag: String = "",
) : Comparable<Address> {
) : Comparable<Address>,
Parcelable {
fun toValue() = assemble(kind, pubKeyHex, dTag)
fun countMemory(): Long =
@@ -26,7 +26,7 @@ import com.vitorpamplona.quartz.utils.pointerSizeInBytes
// RESPONSE OBJECTS
abstract class Response(
@JsonProperty("result_type") val resultType: String,
@field:JsonProperty("result_type") val resultType: String,
) {
abstract fun countMemory(): Long
}
@@ -30,8 +30,8 @@ import com.vitorpamplona.quartz.nip59Giftwrap.seals.SealedRumorEvent
class Rumor(
val id: HexKey?,
@JsonProperty("pubkey") val pubKey: HexKey?,
@JsonProperty("created_at") val createdAt: Long?,
@field:JsonProperty("pubkey") val pubKey: HexKey?,
@field:JsonProperty("created_at") val createdAt: Long?,
val kind: Int?,
val tags: Array<Array<String>>?,
val content: String?,
@@ -27,11 +27,9 @@ import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
data class UploadResult(
val status: String? = null,
val message: String? = null,
@JsonProperty("processing_url")
val processingUrl: String? = null,
@field:JsonProperty("processing_url") val processingUrl: String? = null,
val percentage: Int? = null,
@JsonProperty("nip94_event")
val nip94Event: PartialEvent? = null,
@field:JsonProperty("nip94_event") val nip94Event: PartialEvent? = null,
) {
companion object {
fun parse(body: String): UploadResult {
@@ -27,33 +27,20 @@ typealias PlanName = String
typealias MimeType = String
data class ServerInfo(
@JsonProperty("api_url")
val apiUrl: String,
@JsonProperty("download_url")
val downloadUrl: String? = null,
@JsonProperty("delegated_to_url")
val delegatedToUrl: String? = null,
@JsonProperty("supported_nips")
val supportedNips: ArrayList<Int> = arrayListOf(),
@JsonProperty("tos_url") val
tosUrl: String? = null,
@JsonProperty("content_types") val
contentTypes: ArrayList<MimeType> = arrayListOf(),
@JsonProperty("plans") val
plans: Map<PlanName, Plan> = mapOf(),
@field:JsonProperty("api_url") val apiUrl: String,
@field:JsonProperty("download_url") val downloadUrl: String? = null,
@field:JsonProperty("delegated_to_url") val delegatedToUrl: String? = null,
@field:JsonProperty("supported_nips") val supportedNips: ArrayList<Int> = arrayListOf(),
@field:JsonProperty("tos_url") val tosUrl: String? = null,
@field:JsonProperty("content_types") val contentTypes: ArrayList<MimeType> = arrayListOf(),
@field:JsonProperty("plans") val plans: Map<PlanName, Plan> = mapOf(),
)
data class Plan(
@JsonProperty("name") val
name: String? = null,
@JsonProperty("is_nip98_required") val
isNip98Required: Boolean? = null,
@JsonProperty("url") val
url: String? = null,
@JsonProperty("max_byte_size") val
maxByteSize: Long? = null,
@JsonProperty("file_expiration") val
fileExpiration: ArrayList<Int> = arrayListOf(),
@JsonProperty("media_transformations")
val mediaTransformations: Map<MimeType, Array<String>> = emptyMap(),
@field:JsonProperty("name") val name: String? = null,
@field:JsonProperty("is_nip98_required") val isNip98Required: Boolean? = null,
@field:JsonProperty("url") val url: String? = null,
@field:JsonProperty("max_byte_size") val maxByteSize: Long? = null,
@field:JsonProperty("file_expiration") val fileExpiration: ArrayList<Int> = arrayListOf(),
@field:JsonProperty("media_transformations") val mediaTransformations: Map<MimeType, Array<String>> = emptyMap(),
)
@@ -0,0 +1,367 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.utils
import com.vitorpamplona.quartz.utils.CacheCollectors.BiFilter
import com.vitorpamplona.quartz.utils.CacheCollectors.BiMapper
import com.vitorpamplona.quartz.utils.CacheCollectors.BiMapperNotNull
import com.vitorpamplona.quartz.utils.CacheCollectors.BiNotNullMapper
import com.vitorpamplona.quartz.utils.CacheCollectors.BiSumOfLong
import java.util.function.BiConsumer
class BiFilterCollector<K, V>(
private val filter: BiFilter<K, V>,
) : BiConsumer<K, V> {
val results: ArrayList<V> = ArrayList()
override fun accept(
k: K,
v: V,
) {
if (filter.filter(k, v)) {
results.add(v)
}
}
}
class BiFilterUniqueCollector<K, V>(
private val filter: BiFilter<K, V>,
) : BiConsumer<K, V> {
val results: HashSet<V> = HashSet()
override fun accept(
k: K,
v: V,
) {
if (filter.filter(k, v)) {
results.add(v)
}
}
}
class BiMapCollector<K, V, R>(
private val mapper: BiMapper<K, V, R?>,
) : BiConsumer<K, V> {
val results: ArrayList<R> = ArrayList()
override fun accept(
k: K,
v: V,
) {
val result = mapper.map(k, v)
if (result != null) {
results.add(result)
}
}
}
class BiMapUniqueCollector<K, V, R>(
private val mapper: BiMapper<K, V, R?>,
) : BiConsumer<K, V> {
val results: HashSet<R> = HashSet()
override fun accept(
k: K,
v: V,
) {
val result = mapper.map(k, v)
if (result != null) {
results.add(result)
}
}
}
class BiMapFlattenCollector<K, V, R>(
private val mapper: BiMapper<K, V, Collection<R>?>,
) : BiConsumer<K, V> {
val results: ArrayList<R> = ArrayList()
override fun accept(
k: K,
v: V,
) {
val result = mapper.map(k, v)
if (result != null) {
results.addAll(result)
}
}
}
class BiMapFlattenUniqueCollector<K, V, R>(
private val mapper: BiMapper<K, V, Collection<R>?>,
) : BiConsumer<K, V> {
val results: HashSet<R> = HashSet()
override fun accept(
k: K,
v: V,
) {
val result = mapper.map(k, v)
if (result != null) {
results.addAll(result)
}
}
}
class BiNotNullMapCollector<K, V, R>(
private val mapper: BiNotNullMapper<K, V, R>,
) : BiConsumer<K, V> {
val results: ArrayList<R> = ArrayList()
override fun accept(
k: K,
v: V,
) {
results.add(mapper.map(k, v))
}
}
class BiMaxOfCollector<K, V>(
private val filter: BiFilter<K, V>,
private val comparator: Comparator<V>,
) : BiConsumer<K, V> {
private var _maxK: K? = null
private var _maxV: V? = null
val maxK: K? get() = _maxK
val maxV: V? get() = _maxV
override fun accept(
k: K,
v: V,
) {
if (filter.filter(k, v)) {
if (_maxK == null || comparator.compare(v, _maxV) > 0) {
_maxK = k
_maxV = v
}
}
}
}
class BiSumOfCollector<K, V>(
private val mapper: CacheCollectors.BiSumOf<K, V>,
) : BiConsumer<K, V> {
private var _sum = 0
val sum: Int get() = _sum
override fun accept(
k: K,
v: V,
) {
_sum += mapper.map(k, v)
}
}
class BiSumOfLongCollector<K, V>(
private val mapper: BiSumOfLong<K, V>,
) : BiConsumer<K, V> {
private var _sum = 0L
val sum: Long get() = _sum
override fun accept(
k: K,
v: V,
) {
_sum += mapper.map(k, v)
}
}
class BiGroupByCollector<K, V, R>(
private val mapper: BiNotNullMapper<K, V, R>,
) : BiConsumer<K, V> {
val results = HashMap<R, ArrayList<V>>()
override fun accept(
k: K,
v: V,
) {
val group = mapper.map(k, v)
val list = results[group]
if (list == null) {
val answer = ArrayList<V>()
answer.add(v)
results[group] = answer
} else {
list.add(v)
}
}
}
class BiCountByGroupCollector<K, V, R>(
private val mapper: BiNotNullMapper<K, V, R>,
) : BiConsumer<K, V> {
val results = HashMap<R, Int>()
override fun accept(
k: K,
v: V,
) {
val group = mapper.map(k, v)
val count = results[group]
if (count == null) {
results[group] = 1
} else {
results[group] = count + 1
}
}
}
class BiSumByGroupCollector<K, V, R>(
private val mapper: BiNotNullMapper<K, V, R>,
private val sumOf: BiNotNullMapper<K, V, Long>,
) : BiConsumer<K, V> {
val results = HashMap<R, Long>()
override fun accept(
k: K,
v: V,
) {
val group = mapper.map(k, v)
val sum = results[group]
if (sum == null) {
results[group] = sumOf.map(k, v)
} else {
results[group] = sum + sumOf.map(k, v)
}
}
}
class BiCountIfCollector<K, V>(
private val filter: BiFilter<K, V>,
) : BiConsumer<K, V> {
private var _count = 0
val count: Int get() = _count
override fun accept(
k: K,
v: V,
) {
if (filter.filter(k, v)) _count++
}
}
class BiAssociateCollector<K, V, T, U>(
size: Int,
private val mapper: BiMapperNotNull<K, V, Pair<T, U>>,
) : BiConsumer<K, V> {
val results: LinkedHashMap<T, U> = LinkedHashMap(size)
override fun accept(
k: K,
v: V,
) {
val pair = mapper.map(k, v)
results[pair.first] = pair.second
}
}
class BiAssociateNotNullCollector<K, V, T, U>(
size: Int,
private val mapper: BiMapper<K, V, Pair<T, U>?>,
) : BiConsumer<K, V> {
val results: LinkedHashMap<T, U> = LinkedHashMap(size)
override fun accept(
k: K,
v: V,
) {
val pair = mapper.map(k, v)
if (pair != null) {
results[pair.first] = pair.second
}
}
}
class BiAssociateWithCollector<K, V, U>(
size: Int,
private val mapper: BiMapper<K, V, U?>,
) : BiConsumer<K, V> {
val results: LinkedHashMap<K, U?> = LinkedHashMap(size)
override fun accept(
k: K,
v: V,
) {
results[k] = mapper.map(k, v)
}
}
class BiAssociateNotNullWithCollector<K, V, U>(
size: Int,
private val mapper: BiMapper<K, V, U>,
) : BiConsumer<K, V> {
val results: LinkedHashMap<K, U> = LinkedHashMap(size)
override fun accept(
k: K,
v: V,
) {
val newValue = mapper.map(k, v)
if (newValue != null) {
results[k] = newValue
}
}
}
object CacheCollectors {
fun interface BiFilter<K, V> {
fun filter(
k: K,
v: V,
): Boolean
}
fun interface BiMapper<K, V, R> {
fun map(
k: K,
v: V,
): R?
}
fun interface BiMapperNotNull<K, V, R> {
fun map(
k: K,
v: V,
): R
}
fun interface BiNotNullMapper<K, V, R> {
fun map(
k: K,
v: V,
): R
}
fun interface BiSumOf<K, V> {
fun map(
k: K,
v: V,
): Int
}
fun interface BiSumOfLong<K, V> {
fun map(
k: K,
v: V,
): Long
}
}
@@ -0,0 +1,161 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.utils
import java.util.function.BiConsumer
interface CacheOperations<K, V> {
fun forEach(consumer: BiConsumer<K, V>)
fun size(): Int
fun filter(consumer: CacheCollectors.BiFilter<K, V>): List<V> {
val runner = BiFilterCollector(consumer)
forEach(runner)
return runner.results
}
fun filterIntoSet(consumer: CacheCollectors.BiFilter<K, V>): Set<V> {
val runner = BiFilterUniqueCollector(consumer)
forEach(runner)
return runner.results
}
fun <R> map(consumer: CacheCollectors.BiNotNullMapper<K, V, R>): List<R> {
val runner = BiNotNullMapCollector(consumer)
forEach(runner)
return runner.results
}
fun <R> mapNotNull(consumer: CacheCollectors.BiMapper<K, V, R?>): List<R> {
val runner = BiMapCollector(consumer)
forEach(runner)
return runner.results
}
fun <R> mapNotNullIntoSet(consumer: CacheCollectors.BiMapper<K, V, R?>): Set<R> {
val runner = BiMapUniqueCollector(consumer)
forEach(runner)
return runner.results
}
fun <R> mapFlatten(consumer: CacheCollectors.BiMapper<K, V, Collection<R>?>): List<R> {
val runner = BiMapFlattenCollector(consumer)
forEach(runner)
return runner.results
}
fun <R> mapFlattenIntoSet(consumer: CacheCollectors.BiMapper<K, V, Collection<R>?>): Set<R> {
val runner = BiMapFlattenUniqueCollector(consumer)
forEach(runner)
return runner.results
}
fun maxOrNullOf(
filter: CacheCollectors.BiFilter<K, V>,
comparator: Comparator<V>,
): V? {
val runner = BiMaxOfCollector(filter, comparator)
forEach(runner)
return runner.maxV
}
fun sumOf(consumer: CacheCollectors.BiSumOf<K, V>): Int {
val runner = BiSumOfCollector(consumer)
forEach(runner)
return runner.sum
}
fun sumOfLong(consumer: CacheCollectors.BiSumOfLong<K, V>): Long {
val runner = BiSumOfLongCollector(consumer)
forEach(runner)
return runner.sum
}
fun <R> groupBy(consumer: CacheCollectors.BiNotNullMapper<K, V, R>): Map<R, List<V>> {
val runner = BiGroupByCollector(consumer)
forEach(runner)
return runner.results
}
fun <R> countByGroup(consumer: CacheCollectors.BiNotNullMapper<K, V, R>): Map<R, Int> {
val runner = BiCountByGroupCollector(consumer)
forEach(runner)
return runner.results
}
fun <R> sumByGroup(
groupMap: CacheCollectors.BiNotNullMapper<K, V, R>,
sumOf: CacheCollectors.BiNotNullMapper<K, V, Long>,
): Map<R, Long> {
val runner = BiSumByGroupCollector(groupMap, sumOf)
forEach(runner)
return runner.results
}
fun count(consumer: CacheCollectors.BiFilter<K, V>): Int {
val runner = BiCountIfCollector(consumer)
forEach(runner)
return runner.count
}
fun <T, U> associate(transform: (K, V) -> Pair<T, U>): Map<T, U> {
val runner = BiAssociateCollector(size(), transform)
forEach(runner)
return runner.results
}
fun <U> associateWith(transform: (K, V) -> U?): Map<K, U?> {
val runner = BiAssociateWithCollector(size(), transform)
forEach(runner)
return runner.results
}
fun joinToString(
separator: CharSequence = ", ",
prefix: CharSequence = "",
postfix: CharSequence = "",
limit: Int = -1,
truncated: CharSequence = "...",
transform: ((K, V) -> CharSequence)? = null,
): String {
val buffer = StringBuilder()
buffer.append(prefix)
var count = 0
forEach { key, value ->
val str = if (transform != null) transform(key, value) else ""
if (str.isNotEmpty()) {
if (++count > 1) buffer.append(separator)
if (limit < 0 || count <= limit) {
when {
transform != null -> buffer.append(str)
else -> buffer.append("$key $value")
}
} else {
return@forEach
}
}
}
if (limit >= 0 && count > limit) buffer.append(truncated)
buffer.append(postfix)
return buffer.toString()
}
}
@@ -23,7 +23,7 @@ package com.vitorpamplona.quartz.utils
import java.util.concurrent.ConcurrentSkipListMap
import java.util.function.BiConsumer
class LargeCache<K, V> {
class LargeCache<K, V> : CacheOperations<K, V> {
private val cache = ConcurrentSkipListMap<K, V>()
fun keys() = cache.keys
@@ -34,7 +34,7 @@ class LargeCache<K, V> {
fun remove(key: K) = cache.remove(key)
fun size() = cache.size
override fun size() = cache.size
fun isEmpty() = cache.isEmpty()
@@ -76,489 +76,7 @@ class LargeCache<K, V> {
}
}
fun forEach(consumer: BiConsumer<K, V>) {
innerForEach(consumer)
}
fun filter(consumer: BiFilter<K, V>): List<V> {
val runner = BiFilterCollector(consumer)
innerForEach(runner)
return runner.results
}
fun filterIntoSet(consumer: BiFilter<K, V>): Set<V> {
val runner = BiFilterUniqueCollector(consumer)
innerForEach(runner)
return runner.results
}
fun <R> map(consumer: BiNotNullMapper<K, V, R>): List<R> {
val runner = BiNotNullMapCollector(consumer)
innerForEach(runner)
return runner.results
}
fun <R> mapNotNull(consumer: BiMapper<K, V, R?>): List<R> {
val runner = BiMapCollector(consumer)
innerForEach(runner)
return runner.results
}
fun <R> mapNotNullIntoSet(consumer: BiMapper<K, V, R?>): Set<R> {
val runner = BiMapUniqueCollector(consumer)
innerForEach(runner)
return runner.results
}
fun <R> mapFlatten(consumer: BiMapper<K, V, Collection<R>?>): List<R> {
val runner = BiMapFlattenCollector(consumer)
innerForEach(runner)
return runner.results
}
fun <R> mapFlattenIntoSet(consumer: BiMapper<K, V, Collection<R>?>): Set<R> {
val runner = BiMapFlattenUniqueCollector(consumer)
innerForEach(runner)
return runner.results
}
fun maxOrNullOf(
filter: BiFilter<K, V>,
comparator: Comparator<V>,
): V? {
val runner = BiMaxOfCollector(filter, comparator)
innerForEach(runner)
return runner.maxV
}
fun sumOf(consumer: BiSumOf<K, V>): Int {
val runner = BiSumOfCollector(consumer)
innerForEach(runner)
return runner.sum
}
fun sumOfLong(consumer: BiSumOfLong<K, V>): Long {
val runner = BiSumOfLongCollector(consumer)
innerForEach(runner)
return runner.sum
}
fun <R> groupBy(consumer: BiNotNullMapper<K, V, R>): Map<R, List<V>> {
val runner = BiGroupByCollector(consumer)
innerForEach(runner)
return runner.results
}
fun <R> countByGroup(consumer: BiNotNullMapper<K, V, R>): Map<R, Int> {
val runner = BiCountByGroupCollector(consumer)
innerForEach(runner)
return runner.results
}
fun <R> sumByGroup(
groupMap: BiNotNullMapper<K, V, R>,
sumOf: BiNotNullMapper<K, V, Long>,
): Map<R, Long> {
val runner = BiSumByGroupCollector(groupMap, sumOf)
innerForEach(runner)
return runner.results
}
fun count(consumer: BiFilter<K, V>): Int {
val runner = BiCountIfCollector(consumer)
innerForEach(runner)
return runner.count
}
fun <T, U> associate(transform: (K, V) -> Pair<T, U>): Map<T, U> {
val runner = BiAssociateCollector(size(), transform)
innerForEach(runner)
return runner.results
}
fun <T, U> associateNotNull(transform: (K, V) -> Pair<T, U>?): Map<T, U> {
val runner = BiAssociateNotNullCollector(size(), transform)
innerForEach(runner)
return runner.results
}
fun <U> associateWith(transform: (K, V) -> U?): Map<K, U?> {
val runner = BiAssociateWithCollector(size(), transform)
innerForEach(runner)
return runner.results
}
fun <U> associateNotNullWith(transform: (K, V) -> U): Map<K, U> {
val runner = BiAssociateNotNullWithCollector(size(), transform)
innerForEach(runner)
return runner.results
}
private fun innerForEach(runner: BiConsumer<K, V>) {
cache.forEach(runner)
}
fun joinToString(
separator: CharSequence = ", ",
prefix: CharSequence = "",
postfix: CharSequence = "",
limit: Int = -1,
truncated: CharSequence = "...",
transform: ((K, V) -> CharSequence)? = null,
): String {
val buffer = StringBuilder()
buffer.append(prefix)
var count = 0
forEach { key, value ->
val str = if (transform != null) transform(key, value) else ""
if (str.isNotEmpty()) {
if (++count > 1) buffer.append(separator)
if (limit < 0 || count <= limit) {
when {
transform != null -> buffer.append(str)
else -> buffer.append("$key $value")
}
} else {
return@forEach
}
}
}
if (limit >= 0 && count > limit) buffer.append(truncated)
buffer.append(postfix)
return buffer.toString()
}
fun interface BiFilter<K, V> {
fun filter(
k: K,
v: V,
): Boolean
}
class BiFilterCollector<K, V>(
val filter: BiFilter<K, V>,
) : BiConsumer<K, V> {
var results: ArrayList<V> = ArrayList()
override fun accept(
k: K,
v: V,
) {
if (filter.filter(k, v)) {
results.add(v)
}
}
}
class BiFilterUniqueCollector<K, V>(
val filter: BiFilter<K, V>,
) : BiConsumer<K, V> {
var results: HashSet<V> = HashSet()
override fun accept(
k: K,
v: V,
) {
if (filter.filter(k, v)) {
results.add(v)
}
}
}
fun interface BiMapper<K, V, R> {
fun map(
k: K,
v: V,
): R?
}
fun interface BiMapperNotNull<K, V, R> {
fun map(
k: K,
v: V,
): R
}
class BiMapCollector<K, V, R>(
val mapper: BiMapper<K, V, R?>,
) : BiConsumer<K, V> {
var results: ArrayList<R> = ArrayList()
override fun accept(
k: K,
v: V,
) {
val result = mapper.map(k, v)
if (result != null) {
results.add(result)
}
}
}
class BiAssociateCollector<K, V, T, U>(
val size: Int,
val mapper: BiMapperNotNull<K, V, Pair<T, U>>,
) : BiConsumer<K, V> {
var results: LinkedHashMap<T, U> = LinkedHashMap(size)
override fun accept(
k: K,
v: V,
) {
val pair = mapper.map(k, v)
results.put(pair.first, pair.second)
}
}
class BiAssociateNotNullCollector<K, V, T, U>(
val size: Int,
val mapper: BiMapper<K, V, Pair<T, U>?>,
) : BiConsumer<K, V> {
var results: LinkedHashMap<T, U> = LinkedHashMap(size)
override fun accept(
k: K,
v: V,
) {
val pair = mapper.map(k, v)
if (pair != null) {
results.put(pair.first, pair.second)
}
}
}
class BiAssociateWithCollector<K, V, U>(
val size: Int,
val mapper: BiMapper<K, V, U?>,
) : BiConsumer<K, V> {
var results: LinkedHashMap<K, U?> = LinkedHashMap(size)
override fun accept(
k: K,
v: V,
) {
results.put(k, mapper.map(k, v))
}
}
class BiAssociateNotNullWithCollector<K, V, U>(
val size: Int,
val mapper: BiMapper<K, V, U>,
) : BiConsumer<K, V> {
var results: LinkedHashMap<K, U> = LinkedHashMap(size)
override fun accept(
k: K,
v: V,
) {
val newValue = mapper.map(k, v)
if (newValue != null) {
results.put(k, newValue)
}
}
}
class BiMapUniqueCollector<K, V, R>(
val mapper: BiMapper<K, V, R?>,
) : BiConsumer<K, V> {
var results: HashSet<R> = HashSet()
override fun accept(
k: K,
v: V,
) {
val result = mapper.map(k, v)
if (result != null) {
results.add(result)
}
}
}
class BiMapFlattenCollector<K, V, R>(
val mapper: BiMapper<K, V, Collection<R>?>,
) : BiConsumer<K, V> {
var results: ArrayList<R> = ArrayList()
override fun accept(
k: K,
v: V,
) {
val result = mapper.map(k, v)
if (result != null) {
results.addAll(result)
}
}
}
class BiMapFlattenUniqueCollector<K, V, R>(
val mapper: BiMapper<K, V, Collection<R>?>,
) : BiConsumer<K, V> {
var results: HashSet<R> = HashSet()
override fun accept(
k: K,
v: V,
) {
val result = mapper.map(k, v)
if (result != null) {
results.addAll(result)
}
}
}
fun interface BiNotNullMapper<K, V, R> {
fun map(
k: K,
v: V,
): R
}
class BiNotNullMapCollector<K, V, R>(
val mapper: BiNotNullMapper<K, V, R>,
) : BiConsumer<K, V> {
var results: ArrayList<R> = ArrayList()
override fun accept(
k: K,
v: V,
) {
results.add(mapper.map(k, v))
}
}
fun interface BiSumOf<K, V> {
fun map(
k: K,
v: V,
): Int
}
class BiMaxOfCollector<K, V>(
val filter: BiFilter<K, V>,
val comparator: Comparator<V>,
) : BiConsumer<K, V> {
var maxK: K? = null
var maxV: V? = null
override fun accept(
k: K,
v: V,
) {
if (filter.filter(k, v)) {
if (maxK == null || comparator.compare(v, maxV) > 0) {
maxK = k
maxV = v
}
}
}
}
class BiSumOfCollector<K, V>(
val mapper: BiSumOf<K, V>,
) : BiConsumer<K, V> {
var sum = 0
override fun accept(
k: K,
v: V,
) {
sum += mapper.map(k, v)
}
}
fun interface BiSumOfLong<K, V> {
fun map(
k: K,
v: V,
): Long
}
class BiSumOfLongCollector<K, V>(
val mapper: BiSumOfLong<K, V>,
) : BiConsumer<K, V> {
var sum = 0L
override fun accept(
k: K,
v: V,
) {
sum += mapper.map(k, v)
}
}
class BiGroupByCollector<K, V, R>(
val mapper: BiNotNullMapper<K, V, R>,
) : BiConsumer<K, V> {
var results = HashMap<R, ArrayList<V>>()
override fun accept(
k: K,
v: V,
) {
val group = mapper.map(k, v)
val list = results[group]
if (list == null) {
val answer = ArrayList<V>()
answer.add(v)
results[group] = answer
} else {
list.add(v)
}
}
}
class BiCountByGroupCollector<K, V, R>(
val mapper: BiNotNullMapper<K, V, R>,
) : BiConsumer<K, V> {
var results = HashMap<R, Int>()
override fun accept(
k: K,
v: V,
) {
val group = mapper.map(k, v)
val count = results[group]
if (count == null) {
results[group] = 1
} else {
results[group] = count + 1
}
}
}
class BiSumByGroupCollector<K, V, R>(
val mapper: BiNotNullMapper<K, V, R>,
val sumOf: BiNotNullMapper<K, V, Long>,
) : BiConsumer<K, V> {
var results = HashMap<R, Long>()
override fun accept(
k: K,
v: V,
) {
val group = mapper.map(k, v)
val sum = results[group]
if (sum == null) {
results[group] = sumOf.map(k, v)
} else {
results[group] = sum + sumOf.map(k, v)
}
}
}
class BiCountIfCollector<K, V>(
val filter: BiFilter<K, V>,
) : BiConsumer<K, V> {
var count = 0
override fun accept(
k: K,
v: V,
) {
if (filter.filter(k, v)) count++
}
override fun forEach(consumer: BiConsumer<K, V>) {
cache.forEach(consumer)
}
}
@@ -24,7 +24,7 @@ import java.lang.ref.WeakReference
import java.util.concurrent.ConcurrentSkipListMap
import java.util.function.BiConsumer
class LargeSoftCache<K, V> {
class LargeSoftCache<K, V> : CacheOperations<K, V> {
private val cache = ConcurrentSkipListMap<K, WeakReference<V>>()
fun keys() = cache.keys
@@ -43,7 +43,7 @@ class LargeSoftCache<K, V> {
fun remove(key: K) = cache.remove(key)
fun size() = cache.size
override fun size() = cache.size
fun isEmpty() = cache.isEmpty()
@@ -78,7 +78,7 @@ class LargeSoftCache<K, V> {
key: K,
builder: (key: K) -> V,
): V {
val softRef = cache.get(key)
val softRef = cache[key]
val value = softRef?.get()
return if (value != null) {
@@ -89,20 +89,6 @@ class LargeSoftCache<K, V> {
}
}
fun createIfAbsent(
key: K,
builder: (key: K) -> V,
): Boolean {
val softRef = cache.get(key)
val value = softRef?.get()
return if (value != null) {
false
} else {
val newObject = builder(key)
cache.putIfAbsent(key, WeakReference(newObject)) == null
}
}
/**
* Proactively cleans up the cache by removing entries whose weakly referenced
* objects have been garbage collected. While `get` handles cleanup on access,
@@ -122,156 +108,8 @@ class LargeSoftCache<K, V> {
println("Cache cleanup completed. Remaining size: ${cache.size}")
}
fun forEach(consumer: BiConsumer<K, V>) {
innerForEach(consumer)
}
fun filter(consumer: BiFilter<K, V>): List<V> {
val runner = BiFilterCollector(consumer)
innerForEach(runner)
return runner.results
}
fun filterIntoSet(consumer: BiFilter<K, V>): Set<V> {
val runner = BiFilterUniqueCollector(consumer)
innerForEach(runner)
return runner.results
}
fun <R> map(consumer: BiNotNullMapper<K, V, R>): List<R> {
val runner = BiNotNullMapCollector(consumer)
innerForEach(runner)
return runner.results
}
fun <R> mapNotNull(consumer: BiMapper<K, V, R?>): List<R> {
val runner = BiMapCollector(consumer)
innerForEach(runner)
return runner.results
}
fun <R> mapNotNullIntoSet(consumer: BiMapper<K, V, R?>): Set<R> {
val runner = BiMapUniqueCollector(consumer)
innerForEach(runner)
return runner.results
}
fun <R> mapFlatten(consumer: BiMapper<K, V, Collection<R>?>): List<R> {
val runner = BiMapFlattenCollector(consumer)
innerForEach(runner)
return runner.results
}
fun <R> mapFlattenIntoSet(consumer: BiMapper<K, V, Collection<R>?>): Set<R> {
val runner = BiMapFlattenUniqueCollector(consumer)
innerForEach(runner)
return runner.results
}
fun maxOrNullOf(
filter: BiFilter<K, V>,
comparator: Comparator<V>,
): V? {
val runner = BiMaxOfCollector(filter, comparator)
innerForEach(runner)
return runner.maxV
}
fun sumOf(consumer: BiSumOf<K, V>): Int {
val runner = BiSumOfCollector(consumer)
innerForEach(runner)
return runner.sum
}
fun sumOfLong(consumer: BiSumOfLong<K, V>): Long {
val runner = BiSumOfLongCollector(consumer)
innerForEach(runner)
return runner.sum
}
fun <R> groupBy(consumer: BiNotNullMapper<K, V, R>): Map<R, List<V>> {
val runner = BiGroupByCollector(consumer)
innerForEach(runner)
return runner.results
}
fun <R> countByGroup(consumer: BiNotNullMapper<K, V, R>): Map<R, Int> {
val runner = BiCountByGroupCollector(consumer)
innerForEach(runner)
return runner.results
}
fun <R> sumByGroup(
groupMap: BiNotNullMapper<K, V, R>,
sumOf: BiNotNullMapper<K, V, Long>,
): Map<R, Long> {
val runner = BiSumByGroupCollector(groupMap, sumOf)
innerForEach(runner)
return runner.results
}
fun count(consumer: BiFilter<K, V>): Int {
val runner = BiCountIfCollector(consumer)
innerForEach(runner)
return runner.count
}
fun <T, U> associate(transform: (K, V) -> Pair<T, U>): Map<T, U> {
val runner = BiAssociateCollector(size(), transform)
innerForEach(runner)
return runner.results
}
fun <T, U> associateNotNull(transform: (K, V) -> Pair<T, U>?): Map<T, U> {
val runner = BiAssociateNotNullCollector(size(), transform)
innerForEach(runner)
return runner.results
}
fun <U> associateWith(transform: (K, V) -> U?): Map<K, U?> {
val runner = BiAssociateWithCollector(size(), transform)
innerForEach(runner)
return runner.results
}
fun <U> associateNotNullWith(transform: (K, V) -> U): Map<K, U> {
val runner = BiAssociateNotNullWithCollector(size(), transform)
innerForEach(runner)
return runner.results
}
private fun innerForEach(runner: BiConsumer<K, V>) {
cache.forEach(BiConsumerWrapper(this, runner))
}
fun joinToString(
separator: CharSequence = ", ",
prefix: CharSequence = "",
postfix: CharSequence = "",
limit: Int = -1,
truncated: CharSequence = "...",
transform: ((K, V) -> CharSequence)? = null,
): String {
val buffer = StringBuilder()
buffer.append(prefix)
var count = 0
forEach { key, value ->
val str = if (transform != null) transform(key, value) else ""
if (str.isNotEmpty()) {
if (++count > 1) buffer.append(separator)
if (limit < 0 || count <= limit) {
when {
transform != null -> buffer.append(str)
else -> buffer.append("$key $value")
}
} else {
return@forEach
}
}
}
if (limit >= 0 && count > limit) buffer.append(truncated)
buffer.append(postfix)
return buffer.toString()
override fun forEach(consumer: BiConsumer<K, V>) {
cache.forEach(BiConsumerWrapper(this, consumer))
}
class BiConsumerWrapper<K, V>(
@@ -290,338 +128,4 @@ class LargeSoftCache<K, V> {
}
}
}
fun interface BiFilter<K, V> {
fun filter(
k: K,
v: V,
): Boolean
}
class BiFilterCollector<K, V>(
val filter: BiFilter<K, V>,
) : BiConsumer<K, V> {
var results: ArrayList<V> = ArrayList()
override fun accept(
k: K,
v: V,
) {
if (filter.filter(k, v)) {
results.add(v)
}
}
}
class BiFilterUniqueCollector<K, V>(
val filter: BiFilter<K, V>,
) : BiConsumer<K, V> {
var results: HashSet<V> = HashSet()
override fun accept(
k: K,
v: V,
) {
if (filter.filter(k, v)) {
results.add(v)
}
}
}
fun interface BiMapper<K, V, R> {
fun map(
k: K,
v: V,
): R?
}
fun interface BiMapperNotNull<K, V, R> {
fun map(
k: K,
v: V,
): R
}
class BiMapCollector<K, V, R>(
val mapper: BiMapper<K, V, R?>,
) : BiConsumer<K, V> {
var results: ArrayList<R> = ArrayList()
override fun accept(
k: K,
v: V,
) {
val result = mapper.map(k, v)
if (result != null) {
results.add(result)
}
}
}
class BiAssociateCollector<K, V, T, U>(
val size: Int,
val mapper: BiMapperNotNull<K, V, Pair<T, U>>,
) : BiConsumer<K, V> {
var results: LinkedHashMap<T, U> = LinkedHashMap(size)
override fun accept(
k: K,
v: V,
) {
val pair = mapper.map(k, v)
results.put(pair.first, pair.second)
}
}
class BiAssociateNotNullCollector<K, V, T, U>(
val size: Int,
val mapper: BiMapper<K, V, Pair<T, U>?>,
) : BiConsumer<K, V> {
var results: LinkedHashMap<T, U> = LinkedHashMap(size)
override fun accept(
k: K,
v: V,
) {
val pair = mapper.map(k, v)
if (pair != null) {
results.put(pair.first, pair.second)
}
}
}
class BiAssociateWithCollector<K, V, U>(
val size: Int,
val mapper: BiMapper<K, V, U?>,
) : BiConsumer<K, V> {
var results: LinkedHashMap<K, U?> = LinkedHashMap(size)
override fun accept(
k: K,
v: V,
) {
results.put(k, mapper.map(k, v))
}
}
class BiAssociateNotNullWithCollector<K, V, U>(
val size: Int,
val mapper: BiMapper<K, V, U>,
) : BiConsumer<K, V> {
var results: LinkedHashMap<K, U> = LinkedHashMap(size)
override fun accept(
k: K,
v: V,
) {
val newValue = mapper.map(k, v)
if (newValue != null) {
results.put(k, newValue)
}
}
}
class BiMapUniqueCollector<K, V, R>(
val mapper: BiMapper<K, V, R?>,
) : BiConsumer<K, V> {
var results: HashSet<R> = HashSet()
override fun accept(
k: K,
v: V,
) {
val result = mapper.map(k, v)
if (result != null) {
results.add(result)
}
}
}
class BiMapFlattenCollector<K, V, R>(
val mapper: BiMapper<K, V, Collection<R>?>,
) : BiConsumer<K, V> {
var results: ArrayList<R> = ArrayList()
override fun accept(
k: K,
v: V,
) {
val result = mapper.map(k, v)
if (result != null) {
results.addAll(result)
}
}
}
class BiMapFlattenUniqueCollector<K, V, R>(
val mapper: BiMapper<K, V, Collection<R>?>,
) : BiConsumer<K, V> {
var results: HashSet<R> = HashSet()
override fun accept(
k: K,
v: V,
) {
val result = mapper.map(k, v)
if (result != null) {
results.addAll(result)
}
}
}
fun interface BiNotNullMapper<K, V, R> {
fun map(
k: K,
v: V,
): R
}
class BiNotNullMapCollector<K, V, R>(
val mapper: BiNotNullMapper<K, V, R>,
) : BiConsumer<K, V> {
var results: ArrayList<R> = ArrayList()
override fun accept(
k: K,
v: V,
) {
results.add(mapper.map(k, v))
}
}
fun interface BiSumOf<K, V> {
fun map(
k: K,
v: V,
): Int
}
class BiMaxOfCollector<K, V>(
val filter: BiFilter<K, V>,
val comparator: Comparator<V>,
) : BiConsumer<K, V> {
var maxK: K? = null
var maxV: V? = null
override fun accept(
k: K,
v: V,
) {
if (filter.filter(k, v)) {
if (maxK == null || comparator.compare(v, maxV) > 0) {
maxK = k
maxV = v
}
}
}
}
class BiSumOfCollector<K, V>(
val mapper: BiSumOf<K, V>,
) : BiConsumer<K, V> {
var sum = 0
override fun accept(
k: K,
v: V,
) {
sum += mapper.map(k, v)
}
}
fun interface BiSumOfLong<K, V> {
fun map(
k: K,
v: V,
): Long
}
class BiSumOfLongCollector<K, V>(
val mapper: BiSumOfLong<K, V>,
) : BiConsumer<K, V> {
var sum = 0L
override fun accept(
k: K,
v: V,
) {
sum += mapper.map(k, v)
}
}
class BiGroupByCollector<K, V, R>(
val mapper: BiNotNullMapper<K, V, R>,
) : BiConsumer<K, V> {
var results = HashMap<R, ArrayList<V>>()
override fun accept(
k: K,
v: V,
) {
val group = mapper.map(k, v)
val list = results[group]
if (list == null) {
val answer = ArrayList<V>()
answer.add(v)
results[group] = answer
} else {
list.add(v)
}
}
}
class BiCountByGroupCollector<K, V, R>(
val mapper: BiNotNullMapper<K, V, R>,
) : BiConsumer<K, V> {
var results = HashMap<R, Int>()
override fun accept(
k: K,
v: V,
) {
val group = mapper.map(k, v)
val count = results[group]
if (count == null) {
results[group] = 1
} else {
results[group] = count + 1
}
}
}
class BiSumByGroupCollector<K, V, R>(
val mapper: BiNotNullMapper<K, V, R>,
val sumOf: BiNotNullMapper<K, V, Long>,
) : BiConsumer<K, V> {
var results = HashMap<R, Long>()
override fun accept(
k: K,
v: V,
) {
val group = mapper.map(k, v)
val sum = results[group]
if (sum == null) {
results[group] = sumOf.map(k, v)
} else {
results[group] = sum + sumOf.map(k, v)
}
}
}
class BiCountIfCollector<K, V>(
val filter: BiFilter<K, V>,
) : BiConsumer<K, V> {
var count = 0
override fun accept(
k: K,
v: V,
) {
if (filter.filter(k, v)) count++
}
}
}