diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfo.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfo.kt index 3fac4efd4a..1d2655ac16 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfo.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfo.kt @@ -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 = emptyList(), ) fun relaySetupInfoBuilder( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfoClickableRow.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfoClickableRow.kt index 98da2d9219..ec65c11c5d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfoClickableRow.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfoClickableRow.kt @@ -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, + ) + } + } + } + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfoDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfoDialog.kt index e5b2b7b4aa..6ca31d0b1f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfoDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfoDialog.kt @@ -44,5 +44,6 @@ fun BasicRelaySetupInfoDialog( onDelete = onDelete, accountViewModel = accountViewModel, onClick = { nav.nav(Route.RelayInfo(item.relay.url)) }, + nav = nav, ) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfoModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfoModel.kt index 9f84c96d5a..89875455a1 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfoModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/common/BasicRelaySetupInfoModel.kt @@ -83,22 +83,24 @@ abstract class BasicRelaySetupInfoModel : ViewModel() { } } - fun clear() { - _relays.update { - val relayList = getRelayList() ?: emptyList() + open fun relayListBuilder(): List { + 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) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/connected/ConnectedRelayListViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/connected/ConnectedRelayListViewModel.kt index 9759533876..a76ebe9ee6 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/connected/ConnectedRelayListViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/connected/ConnectedRelayListViewModel.kt @@ -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 { + 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 = account.client .relayStatusFlow() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt index 971bd8b065..5e10e20752 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt @@ -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) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 95221f9107..65b0d6cb41 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -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" } \ No newline at end of file diff --git a/quartz/build.gradle b/quartz/build.gradle index 60c5ab93ca..f4e91c4d09 100644 --- a/quartz/build.gradle +++ b/quartz/build.gradle @@ -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 { diff --git a/quartz/src/androidTest/java/com/vitorpamplona/quartz/nip44Encryption/TestPackageClasses.kt b/quartz/src/androidTest/java/com/vitorpamplona/quartz/nip44Encryption/TestPackageClasses.kt index 40ee511f10..4262d36bea 100644 --- a/quartz/src/androidTest/java/com/vitorpamplona/quartz/nip44Encryption/TestPackageClasses.kt +++ b/quartz/src/androidTest/java/com/vitorpamplona/quartz/nip44Encryption/TestPackageClasses.kt @@ -32,61 +32,59 @@ data class V2( ) data class Valid( - @JsonProperty("get_conversation_key") - val getConversationKey: ArrayList = arrayListOf(), - @JsonProperty("get_message_keys") val getMessageKeys: GetMessageKeys? = GetMessageKeys(), - @JsonProperty("calc_padded_len") val calcPaddedLen: ArrayList> = arrayListOf(), - @JsonProperty("encrypt_decrypt") val encryptDecrypt: ArrayList = arrayListOf(), - @JsonProperty("encrypt_decrypt_long_msg") + @field:JsonProperty("get_conversation_key") val getConversationKey: ArrayList = arrayListOf(), + @field:JsonProperty("get_message_keys") val getMessageKeys: GetMessageKeys? = GetMessageKeys(), + @field:JsonProperty("calc_padded_len") val calcPaddedLen: ArrayList> = arrayListOf(), + @field:JsonProperty("encrypt_decrypt") val encryptDecrypt: ArrayList = arrayListOf(), + @field:JsonProperty("encrypt_decrypt_long_msg") val encryptDecryptLongMsg: ArrayList = arrayListOf(), ) data class Invalid( - @JsonProperty("encrypt_msg_lengths") val encryptMsgLengths: ArrayList = arrayListOf(), - @JsonProperty("get_conversation_key") - val getConversationKey: ArrayList = arrayListOf(), - @JsonProperty("decrypt") val decrypt: ArrayList = arrayListOf(), + @field:JsonProperty("encrypt_msg_lengths") val encryptMsgLengths: ArrayList = arrayListOf(), + @field:JsonProperty("get_conversation_key") val getConversationKey: ArrayList = arrayListOf(), + @field:JsonProperty("decrypt") val decrypt: ArrayList = 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 = 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, diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/limits/Limits.kt b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/limits/Limits.kt index 62d0b6da8a..75904c921f 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/limits/Limits.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/experimental/limits/Limits.kt @@ -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?, - @JsonProperty("blocked_event_kinds") - val blockedEventKinds: Set?, - @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>?, + @field:JsonProperty("can_write") val canWrite: Boolean?, + @field:JsonProperty("can_read") val canRead: Boolean?, + @field:JsonProperty("accepted_event_kinds") val acceptedEventKinds: Set?, + @field:JsonProperty("blocked_event_kinds") val blockedEventKinds: Set?, + @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>?, ) diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/pool/RelayPool.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/pool/RelayPool.kt index 47605b1c62..deb33b5dbe 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/pool/RelayPool.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/pool/RelayPool.kt @@ -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()) { diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/EventTemplate.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/EventTemplate.kt index 25a2196d67..3d801e9637 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/EventTemplate.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/EventTemplate.kt @@ -30,7 +30,7 @@ import com.vitorpamplona.quartz.nip01Core.jackson.JsonMapper import com.vitorpamplona.quartz.utils.TimeUtils class EventTemplate( - @JsonProperty("created_at") + @field:JsonProperty("created_at") val createdAt: Long, val kind: Int, val tags: TagArray, diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/addressables/Address.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/addressables/Address.kt index c2ad939015..8c5edc6d2d 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/addressables/Address.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/addressables/Address.kt @@ -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
{ +) : Comparable
, + Parcelable { fun toValue() = assemble(kind, pubKeyHex, dTag) fun countMemory(): Long = diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip47WalletConnect/Response.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip47WalletConnect/Response.kt index b895114ed7..4968410be0 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip47WalletConnect/Response.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip47WalletConnect/Response.kt @@ -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 } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip59Giftwrap/rumors/Rumor.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip59Giftwrap/rumors/Rumor.kt index 1a133acb3b..02ad68890e 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip59Giftwrap/rumors/Rumor.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip59Giftwrap/rumors/Rumor.kt @@ -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>?, val content: String?, diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip96FileStorage/actions/UploadResult.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip96FileStorage/actions/UploadResult.kt index 554210641a..a4b2d218b7 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip96FileStorage/actions/UploadResult.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip96FileStorage/actions/UploadResult.kt @@ -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 { diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip96FileStorage/info/ServerInfo.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nip96FileStorage/info/ServerInfo.kt index b1bd838464..7d773bf5c4 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip96FileStorage/info/ServerInfo.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nip96FileStorage/info/ServerInfo.kt @@ -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 = arrayListOf(), - @JsonProperty("tos_url") val - tosUrl: String? = null, - @JsonProperty("content_types") val - contentTypes: ArrayList = arrayListOf(), - @JsonProperty("plans") val - plans: Map = 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 = arrayListOf(), + @field:JsonProperty("tos_url") val tosUrl: String? = null, + @field:JsonProperty("content_types") val contentTypes: ArrayList = arrayListOf(), + @field:JsonProperty("plans") val plans: Map = 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 = arrayListOf(), - @JsonProperty("media_transformations") - val mediaTransformations: Map> = 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 = arrayListOf(), + @field:JsonProperty("media_transformations") val mediaTransformations: Map> = emptyMap(), ) diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/utils/CacheCollectors.kt b/quartz/src/main/java/com/vitorpamplona/quartz/utils/CacheCollectors.kt new file mode 100644 index 0000000000..5b54c8be7b --- /dev/null +++ b/quartz/src/main/java/com/vitorpamplona/quartz/utils/CacheCollectors.kt @@ -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( + private val filter: BiFilter, +) : BiConsumer { + val results: ArrayList = ArrayList() + + override fun accept( + k: K, + v: V, + ) { + if (filter.filter(k, v)) { + results.add(v) + } + } +} + +class BiFilterUniqueCollector( + private val filter: BiFilter, +) : BiConsumer { + val results: HashSet = HashSet() + + override fun accept( + k: K, + v: V, + ) { + if (filter.filter(k, v)) { + results.add(v) + } + } +} + +class BiMapCollector( + private val mapper: BiMapper, +) : BiConsumer { + val results: ArrayList = ArrayList() + + override fun accept( + k: K, + v: V, + ) { + val result = mapper.map(k, v) + if (result != null) { + results.add(result) + } + } +} + +class BiMapUniqueCollector( + private val mapper: BiMapper, +) : BiConsumer { + val results: HashSet = HashSet() + + override fun accept( + k: K, + v: V, + ) { + val result = mapper.map(k, v) + if (result != null) { + results.add(result) + } + } +} + +class BiMapFlattenCollector( + private val mapper: BiMapper?>, +) : BiConsumer { + val results: ArrayList = ArrayList() + + override fun accept( + k: K, + v: V, + ) { + val result = mapper.map(k, v) + if (result != null) { + results.addAll(result) + } + } +} + +class BiMapFlattenUniqueCollector( + private val mapper: BiMapper?>, +) : BiConsumer { + val results: HashSet = HashSet() + + override fun accept( + k: K, + v: V, + ) { + val result = mapper.map(k, v) + if (result != null) { + results.addAll(result) + } + } +} + +class BiNotNullMapCollector( + private val mapper: BiNotNullMapper, +) : BiConsumer { + val results: ArrayList = ArrayList() + + override fun accept( + k: K, + v: V, + ) { + results.add(mapper.map(k, v)) + } +} + +class BiMaxOfCollector( + private val filter: BiFilter, + private val comparator: Comparator, +) : BiConsumer { + 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( + private val mapper: CacheCollectors.BiSumOf, +) : BiConsumer { + private var _sum = 0 + val sum: Int get() = _sum + + override fun accept( + k: K, + v: V, + ) { + _sum += mapper.map(k, v) + } +} + +class BiSumOfLongCollector( + private val mapper: BiSumOfLong, +) : BiConsumer { + private var _sum = 0L + val sum: Long get() = _sum + + override fun accept( + k: K, + v: V, + ) { + _sum += mapper.map(k, v) + } +} + +class BiGroupByCollector( + private val mapper: BiNotNullMapper, +) : BiConsumer { + val results = HashMap>() + + override fun accept( + k: K, + v: V, + ) { + val group = mapper.map(k, v) + val list = results[group] + if (list == null) { + val answer = ArrayList() + answer.add(v) + results[group] = answer + } else { + list.add(v) + } + } +} + +class BiCountByGroupCollector( + private val mapper: BiNotNullMapper, +) : BiConsumer { + val results = HashMap() + + 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( + private val mapper: BiNotNullMapper, + private val sumOf: BiNotNullMapper, +) : BiConsumer { + val results = HashMap() + + 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( + private val filter: BiFilter, +) : BiConsumer { + private var _count = 0 + val count: Int get() = _count + + override fun accept( + k: K, + v: V, + ) { + if (filter.filter(k, v)) _count++ + } +} + +class BiAssociateCollector( + size: Int, + private val mapper: BiMapperNotNull>, +) : BiConsumer { + val results: LinkedHashMap = LinkedHashMap(size) + + override fun accept( + k: K, + v: V, + ) { + val pair = mapper.map(k, v) + results[pair.first] = pair.second + } +} + +class BiAssociateNotNullCollector( + size: Int, + private val mapper: BiMapper?>, +) : BiConsumer { + val results: LinkedHashMap = 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( + size: Int, + private val mapper: BiMapper, +) : BiConsumer { + val results: LinkedHashMap = LinkedHashMap(size) + + override fun accept( + k: K, + v: V, + ) { + results[k] = mapper.map(k, v) + } +} + +class BiAssociateNotNullWithCollector( + size: Int, + private val mapper: BiMapper, +) : BiConsumer { + val results: LinkedHashMap = 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 { + fun filter( + k: K, + v: V, + ): Boolean + } + + fun interface BiMapper { + fun map( + k: K, + v: V, + ): R? + } + + fun interface BiMapperNotNull { + fun map( + k: K, + v: V, + ): R + } + + fun interface BiNotNullMapper { + fun map( + k: K, + v: V, + ): R + } + + fun interface BiSumOf { + fun map( + k: K, + v: V, + ): Int + } + + fun interface BiSumOfLong { + fun map( + k: K, + v: V, + ): Long + } +} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/utils/CacheOperations.kt b/quartz/src/main/java/com/vitorpamplona/quartz/utils/CacheOperations.kt new file mode 100644 index 0000000000..24e0577079 --- /dev/null +++ b/quartz/src/main/java/com/vitorpamplona/quartz/utils/CacheOperations.kt @@ -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 { + fun forEach(consumer: BiConsumer) + + fun size(): Int + + fun filter(consumer: CacheCollectors.BiFilter): List { + val runner = BiFilterCollector(consumer) + forEach(runner) + return runner.results + } + + fun filterIntoSet(consumer: CacheCollectors.BiFilter): Set { + val runner = BiFilterUniqueCollector(consumer) + forEach(runner) + return runner.results + } + + fun map(consumer: CacheCollectors.BiNotNullMapper): List { + val runner = BiNotNullMapCollector(consumer) + forEach(runner) + return runner.results + } + + fun mapNotNull(consumer: CacheCollectors.BiMapper): List { + val runner = BiMapCollector(consumer) + forEach(runner) + return runner.results + } + + fun mapNotNullIntoSet(consumer: CacheCollectors.BiMapper): Set { + val runner = BiMapUniqueCollector(consumer) + forEach(runner) + return runner.results + } + + fun mapFlatten(consumer: CacheCollectors.BiMapper?>): List { + val runner = BiMapFlattenCollector(consumer) + forEach(runner) + return runner.results + } + + fun mapFlattenIntoSet(consumer: CacheCollectors.BiMapper?>): Set { + val runner = BiMapFlattenUniqueCollector(consumer) + forEach(runner) + return runner.results + } + + fun maxOrNullOf( + filter: CacheCollectors.BiFilter, + comparator: Comparator, + ): V? { + val runner = BiMaxOfCollector(filter, comparator) + forEach(runner) + return runner.maxV + } + + fun sumOf(consumer: CacheCollectors.BiSumOf): Int { + val runner = BiSumOfCollector(consumer) + forEach(runner) + return runner.sum + } + + fun sumOfLong(consumer: CacheCollectors.BiSumOfLong): Long { + val runner = BiSumOfLongCollector(consumer) + forEach(runner) + return runner.sum + } + + fun groupBy(consumer: CacheCollectors.BiNotNullMapper): Map> { + val runner = BiGroupByCollector(consumer) + forEach(runner) + return runner.results + } + + fun countByGroup(consumer: CacheCollectors.BiNotNullMapper): Map { + val runner = BiCountByGroupCollector(consumer) + forEach(runner) + return runner.results + } + + fun sumByGroup( + groupMap: CacheCollectors.BiNotNullMapper, + sumOf: CacheCollectors.BiNotNullMapper, + ): Map { + val runner = BiSumByGroupCollector(groupMap, sumOf) + forEach(runner) + return runner.results + } + + fun count(consumer: CacheCollectors.BiFilter): Int { + val runner = BiCountIfCollector(consumer) + forEach(runner) + return runner.count + } + + fun associate(transform: (K, V) -> Pair): Map { + val runner = BiAssociateCollector(size(), transform) + forEach(runner) + return runner.results + } + + fun associateWith(transform: (K, V) -> U?): Map { + 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() + } +} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/utils/LargeCache.kt b/quartz/src/main/java/com/vitorpamplona/quartz/utils/LargeCache.kt index bd61c05d19..962932c9af 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/utils/LargeCache.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/utils/LargeCache.kt @@ -23,7 +23,7 @@ package com.vitorpamplona.quartz.utils import java.util.concurrent.ConcurrentSkipListMap import java.util.function.BiConsumer -class LargeCache { +class LargeCache : CacheOperations { private val cache = ConcurrentSkipListMap() fun keys() = cache.keys @@ -34,7 +34,7 @@ class LargeCache { 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 { } } - fun forEach(consumer: BiConsumer) { - innerForEach(consumer) - } - - fun filter(consumer: BiFilter): List { - val runner = BiFilterCollector(consumer) - innerForEach(runner) - return runner.results - } - - fun filterIntoSet(consumer: BiFilter): Set { - val runner = BiFilterUniqueCollector(consumer) - innerForEach(runner) - return runner.results - } - - fun map(consumer: BiNotNullMapper): List { - val runner = BiNotNullMapCollector(consumer) - innerForEach(runner) - return runner.results - } - - fun mapNotNull(consumer: BiMapper): List { - val runner = BiMapCollector(consumer) - innerForEach(runner) - return runner.results - } - - fun mapNotNullIntoSet(consumer: BiMapper): Set { - val runner = BiMapUniqueCollector(consumer) - innerForEach(runner) - return runner.results - } - - fun mapFlatten(consumer: BiMapper?>): List { - val runner = BiMapFlattenCollector(consumer) - innerForEach(runner) - return runner.results - } - - fun mapFlattenIntoSet(consumer: BiMapper?>): Set { - val runner = BiMapFlattenUniqueCollector(consumer) - innerForEach(runner) - return runner.results - } - - fun maxOrNullOf( - filter: BiFilter, - comparator: Comparator, - ): V? { - val runner = BiMaxOfCollector(filter, comparator) - innerForEach(runner) - return runner.maxV - } - - fun sumOf(consumer: BiSumOf): Int { - val runner = BiSumOfCollector(consumer) - innerForEach(runner) - return runner.sum - } - - fun sumOfLong(consumer: BiSumOfLong): Long { - val runner = BiSumOfLongCollector(consumer) - innerForEach(runner) - return runner.sum - } - - fun groupBy(consumer: BiNotNullMapper): Map> { - val runner = BiGroupByCollector(consumer) - innerForEach(runner) - return runner.results - } - - fun countByGroup(consumer: BiNotNullMapper): Map { - val runner = BiCountByGroupCollector(consumer) - innerForEach(runner) - return runner.results - } - - fun sumByGroup( - groupMap: BiNotNullMapper, - sumOf: BiNotNullMapper, - ): Map { - val runner = BiSumByGroupCollector(groupMap, sumOf) - innerForEach(runner) - return runner.results - } - - fun count(consumer: BiFilter): Int { - val runner = BiCountIfCollector(consumer) - innerForEach(runner) - return runner.count - } - - fun associate(transform: (K, V) -> Pair): Map { - val runner = BiAssociateCollector(size(), transform) - innerForEach(runner) - return runner.results - } - - fun associateNotNull(transform: (K, V) -> Pair?): Map { - val runner = BiAssociateNotNullCollector(size(), transform) - innerForEach(runner) - return runner.results - } - - fun associateWith(transform: (K, V) -> U?): Map { - val runner = BiAssociateWithCollector(size(), transform) - innerForEach(runner) - return runner.results - } - - fun associateNotNullWith(transform: (K, V) -> U): Map { - val runner = BiAssociateNotNullWithCollector(size(), transform) - innerForEach(runner) - return runner.results - } - - private fun innerForEach(runner: BiConsumer) { - 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 { - fun filter( - k: K, - v: V, - ): Boolean - } - - class BiFilterCollector( - val filter: BiFilter, - ) : BiConsumer { - var results: ArrayList = ArrayList() - - override fun accept( - k: K, - v: V, - ) { - if (filter.filter(k, v)) { - results.add(v) - } - } - } - - class BiFilterUniqueCollector( - val filter: BiFilter, - ) : BiConsumer { - var results: HashSet = HashSet() - - override fun accept( - k: K, - v: V, - ) { - if (filter.filter(k, v)) { - results.add(v) - } - } - } - - fun interface BiMapper { - fun map( - k: K, - v: V, - ): R? - } - - fun interface BiMapperNotNull { - fun map( - k: K, - v: V, - ): R - } - - class BiMapCollector( - val mapper: BiMapper, - ) : BiConsumer { - var results: ArrayList = ArrayList() - - override fun accept( - k: K, - v: V, - ) { - val result = mapper.map(k, v) - if (result != null) { - results.add(result) - } - } - } - - class BiAssociateCollector( - val size: Int, - val mapper: BiMapperNotNull>, - ) : BiConsumer { - var results: LinkedHashMap = LinkedHashMap(size) - - override fun accept( - k: K, - v: V, - ) { - val pair = mapper.map(k, v) - results.put(pair.first, pair.second) - } - } - - class BiAssociateNotNullCollector( - val size: Int, - val mapper: BiMapper?>, - ) : BiConsumer { - var results: LinkedHashMap = 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( - val size: Int, - val mapper: BiMapper, - ) : BiConsumer { - var results: LinkedHashMap = LinkedHashMap(size) - - override fun accept( - k: K, - v: V, - ) { - results.put(k, mapper.map(k, v)) - } - } - - class BiAssociateNotNullWithCollector( - val size: Int, - val mapper: BiMapper, - ) : BiConsumer { - var results: LinkedHashMap = LinkedHashMap(size) - - override fun accept( - k: K, - v: V, - ) { - val newValue = mapper.map(k, v) - if (newValue != null) { - results.put(k, newValue) - } - } - } - - class BiMapUniqueCollector( - val mapper: BiMapper, - ) : BiConsumer { - var results: HashSet = HashSet() - - override fun accept( - k: K, - v: V, - ) { - val result = mapper.map(k, v) - if (result != null) { - results.add(result) - } - } - } - - class BiMapFlattenCollector( - val mapper: BiMapper?>, - ) : BiConsumer { - var results: ArrayList = ArrayList() - - override fun accept( - k: K, - v: V, - ) { - val result = mapper.map(k, v) - if (result != null) { - results.addAll(result) - } - } - } - - class BiMapFlattenUniqueCollector( - val mapper: BiMapper?>, - ) : BiConsumer { - var results: HashSet = HashSet() - - override fun accept( - k: K, - v: V, - ) { - val result = mapper.map(k, v) - if (result != null) { - results.addAll(result) - } - } - } - - fun interface BiNotNullMapper { - fun map( - k: K, - v: V, - ): R - } - - class BiNotNullMapCollector( - val mapper: BiNotNullMapper, - ) : BiConsumer { - var results: ArrayList = ArrayList() - - override fun accept( - k: K, - v: V, - ) { - results.add(mapper.map(k, v)) - } - } - - fun interface BiSumOf { - fun map( - k: K, - v: V, - ): Int - } - - class BiMaxOfCollector( - val filter: BiFilter, - val comparator: Comparator, - ) : BiConsumer { - 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( - val mapper: BiSumOf, - ) : BiConsumer { - var sum = 0 - - override fun accept( - k: K, - v: V, - ) { - sum += mapper.map(k, v) - } - } - - fun interface BiSumOfLong { - fun map( - k: K, - v: V, - ): Long - } - - class BiSumOfLongCollector( - val mapper: BiSumOfLong, - ) : BiConsumer { - var sum = 0L - - override fun accept( - k: K, - v: V, - ) { - sum += mapper.map(k, v) - } - } - - class BiGroupByCollector( - val mapper: BiNotNullMapper, - ) : BiConsumer { - var results = HashMap>() - - override fun accept( - k: K, - v: V, - ) { - val group = mapper.map(k, v) - - val list = results[group] - if (list == null) { - val answer = ArrayList() - answer.add(v) - results[group] = answer - } else { - list.add(v) - } - } - } - - class BiCountByGroupCollector( - val mapper: BiNotNullMapper, - ) : BiConsumer { - var results = HashMap() - - 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( - val mapper: BiNotNullMapper, - val sumOf: BiNotNullMapper, - ) : BiConsumer { - var results = HashMap() - - 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( - val filter: BiFilter, - ) : BiConsumer { - var count = 0 - - override fun accept( - k: K, - v: V, - ) { - if (filter.filter(k, v)) count++ - } + override fun forEach(consumer: BiConsumer) { + cache.forEach(consumer) } } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/utils/LargeSoftCache.kt b/quartz/src/main/java/com/vitorpamplona/quartz/utils/LargeSoftCache.kt index 50f539bd69..995c0701c8 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/utils/LargeSoftCache.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/utils/LargeSoftCache.kt @@ -24,7 +24,7 @@ import java.lang.ref.WeakReference import java.util.concurrent.ConcurrentSkipListMap import java.util.function.BiConsumer -class LargeSoftCache { +class LargeSoftCache : CacheOperations { private val cache = ConcurrentSkipListMap>() fun keys() = cache.keys @@ -43,7 +43,7 @@ class LargeSoftCache { 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 { 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 { } } - 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 { println("Cache cleanup completed. Remaining size: ${cache.size}") } - fun forEach(consumer: BiConsumer) { - innerForEach(consumer) - } - - fun filter(consumer: BiFilter): List { - val runner = BiFilterCollector(consumer) - innerForEach(runner) - return runner.results - } - - fun filterIntoSet(consumer: BiFilter): Set { - val runner = BiFilterUniqueCollector(consumer) - innerForEach(runner) - return runner.results - } - - fun map(consumer: BiNotNullMapper): List { - val runner = BiNotNullMapCollector(consumer) - innerForEach(runner) - return runner.results - } - - fun mapNotNull(consumer: BiMapper): List { - val runner = BiMapCollector(consumer) - innerForEach(runner) - return runner.results - } - - fun mapNotNullIntoSet(consumer: BiMapper): Set { - val runner = BiMapUniqueCollector(consumer) - innerForEach(runner) - return runner.results - } - - fun mapFlatten(consumer: BiMapper?>): List { - val runner = BiMapFlattenCollector(consumer) - innerForEach(runner) - return runner.results - } - - fun mapFlattenIntoSet(consumer: BiMapper?>): Set { - val runner = BiMapFlattenUniqueCollector(consumer) - innerForEach(runner) - return runner.results - } - - fun maxOrNullOf( - filter: BiFilter, - comparator: Comparator, - ): V? { - val runner = BiMaxOfCollector(filter, comparator) - innerForEach(runner) - return runner.maxV - } - - fun sumOf(consumer: BiSumOf): Int { - val runner = BiSumOfCollector(consumer) - innerForEach(runner) - return runner.sum - } - - fun sumOfLong(consumer: BiSumOfLong): Long { - val runner = BiSumOfLongCollector(consumer) - innerForEach(runner) - return runner.sum - } - - fun groupBy(consumer: BiNotNullMapper): Map> { - val runner = BiGroupByCollector(consumer) - innerForEach(runner) - return runner.results - } - - fun countByGroup(consumer: BiNotNullMapper): Map { - val runner = BiCountByGroupCollector(consumer) - innerForEach(runner) - return runner.results - } - - fun sumByGroup( - groupMap: BiNotNullMapper, - sumOf: BiNotNullMapper, - ): Map { - val runner = BiSumByGroupCollector(groupMap, sumOf) - innerForEach(runner) - return runner.results - } - - fun count(consumer: BiFilter): Int { - val runner = BiCountIfCollector(consumer) - innerForEach(runner) - return runner.count - } - - fun associate(transform: (K, V) -> Pair): Map { - val runner = BiAssociateCollector(size(), transform) - innerForEach(runner) - return runner.results - } - - fun associateNotNull(transform: (K, V) -> Pair?): Map { - val runner = BiAssociateNotNullCollector(size(), transform) - innerForEach(runner) - return runner.results - } - - fun associateWith(transform: (K, V) -> U?): Map { - val runner = BiAssociateWithCollector(size(), transform) - innerForEach(runner) - return runner.results - } - - fun associateNotNullWith(transform: (K, V) -> U): Map { - val runner = BiAssociateNotNullWithCollector(size(), transform) - innerForEach(runner) - return runner.results - } - - private fun innerForEach(runner: BiConsumer) { - 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) { + cache.forEach(BiConsumerWrapper(this, consumer)) } class BiConsumerWrapper( @@ -290,338 +128,4 @@ class LargeSoftCache { } } } - - fun interface BiFilter { - fun filter( - k: K, - v: V, - ): Boolean - } - - class BiFilterCollector( - val filter: BiFilter, - ) : BiConsumer { - var results: ArrayList = ArrayList() - - override fun accept( - k: K, - v: V, - ) { - if (filter.filter(k, v)) { - results.add(v) - } - } - } - - class BiFilterUniqueCollector( - val filter: BiFilter, - ) : BiConsumer { - var results: HashSet = HashSet() - - override fun accept( - k: K, - v: V, - ) { - if (filter.filter(k, v)) { - results.add(v) - } - } - } - - fun interface BiMapper { - fun map( - k: K, - v: V, - ): R? - } - - fun interface BiMapperNotNull { - fun map( - k: K, - v: V, - ): R - } - - class BiMapCollector( - val mapper: BiMapper, - ) : BiConsumer { - var results: ArrayList = ArrayList() - - override fun accept( - k: K, - v: V, - ) { - val result = mapper.map(k, v) - if (result != null) { - results.add(result) - } - } - } - - class BiAssociateCollector( - val size: Int, - val mapper: BiMapperNotNull>, - ) : BiConsumer { - var results: LinkedHashMap = LinkedHashMap(size) - - override fun accept( - k: K, - v: V, - ) { - val pair = mapper.map(k, v) - results.put(pair.first, pair.second) - } - } - - class BiAssociateNotNullCollector( - val size: Int, - val mapper: BiMapper?>, - ) : BiConsumer { - var results: LinkedHashMap = 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( - val size: Int, - val mapper: BiMapper, - ) : BiConsumer { - var results: LinkedHashMap = LinkedHashMap(size) - - override fun accept( - k: K, - v: V, - ) { - results.put(k, mapper.map(k, v)) - } - } - - class BiAssociateNotNullWithCollector( - val size: Int, - val mapper: BiMapper, - ) : BiConsumer { - var results: LinkedHashMap = LinkedHashMap(size) - - override fun accept( - k: K, - v: V, - ) { - val newValue = mapper.map(k, v) - if (newValue != null) { - results.put(k, newValue) - } - } - } - - class BiMapUniqueCollector( - val mapper: BiMapper, - ) : BiConsumer { - var results: HashSet = HashSet() - - override fun accept( - k: K, - v: V, - ) { - val result = mapper.map(k, v) - if (result != null) { - results.add(result) - } - } - } - - class BiMapFlattenCollector( - val mapper: BiMapper?>, - ) : BiConsumer { - var results: ArrayList = ArrayList() - - override fun accept( - k: K, - v: V, - ) { - val result = mapper.map(k, v) - if (result != null) { - results.addAll(result) - } - } - } - - class BiMapFlattenUniqueCollector( - val mapper: BiMapper?>, - ) : BiConsumer { - var results: HashSet = HashSet() - - override fun accept( - k: K, - v: V, - ) { - val result = mapper.map(k, v) - if (result != null) { - results.addAll(result) - } - } - } - - fun interface BiNotNullMapper { - fun map( - k: K, - v: V, - ): R - } - - class BiNotNullMapCollector( - val mapper: BiNotNullMapper, - ) : BiConsumer { - var results: ArrayList = ArrayList() - - override fun accept( - k: K, - v: V, - ) { - results.add(mapper.map(k, v)) - } - } - - fun interface BiSumOf { - fun map( - k: K, - v: V, - ): Int - } - - class BiMaxOfCollector( - val filter: BiFilter, - val comparator: Comparator, - ) : BiConsumer { - 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( - val mapper: BiSumOf, - ) : BiConsumer { - var sum = 0 - - override fun accept( - k: K, - v: V, - ) { - sum += mapper.map(k, v) - } - } - - fun interface BiSumOfLong { - fun map( - k: K, - v: V, - ): Long - } - - class BiSumOfLongCollector( - val mapper: BiSumOfLong, - ) : BiConsumer { - var sum = 0L - - override fun accept( - k: K, - v: V, - ) { - sum += mapper.map(k, v) - } - } - - class BiGroupByCollector( - val mapper: BiNotNullMapper, - ) : BiConsumer { - var results = HashMap>() - - override fun accept( - k: K, - v: V, - ) { - val group = mapper.map(k, v) - - val list = results[group] - if (list == null) { - val answer = ArrayList() - answer.add(v) - results[group] = answer - } else { - list.add(v) - } - } - } - - class BiCountByGroupCollector( - val mapper: BiNotNullMapper, - ) : BiConsumer { - var results = HashMap() - - 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( - val mapper: BiNotNullMapper, - val sumOf: BiNotNullMapper, - ) : BiConsumer { - var results = HashMap() - - 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( - val filter: BiFilter, - ) : BiConsumer { - var count = 0 - - override fun accept( - k: K, - v: V, - ) { - if (filter.filter(k, v)) count++ - } - } }