diff --git a/quartz/src/iosMain/kotlin/com/vitorpamplona/quartz/utils/cache/LargeCache.ios.kt b/quartz/src/iosMain/kotlin/com/vitorpamplona/quartz/utils/cache/LargeCache.ios.kt index 91ab979bf..791a7ace5 100644 --- a/quartz/src/iosMain/kotlin/com/vitorpamplona/quartz/utils/cache/LargeCache.ios.kt +++ b/quartz/src/iosMain/kotlin/com/vitorpamplona/quartz/utils/cache/LargeCache.ios.kt @@ -20,131 +20,172 @@ */ package com.vitorpamplona.quartz.utils.cache -// Need of find a Concurrent/ThreadSafe HashMap in iOS +import io.github.charlietap.cachemap.CacheMap +import io.github.charlietap.cachemap.cacheMapOf +import kotlinx.coroutines.runBlocking +import kotlin.collections.plus +import kotlin.collections.set + +// An implementation of a Threadsafe map, using CacheMap. +// Investigating a Swift-based alternative(for now) actual class LargeCache : ICacheOperations { + private val concurrentMap = cacheMapOf() + actual fun keys(): Set { - TODO("Not yet implemented") + return concurrentMap.keys } actual fun values(): Iterable { - TODO("Not yet implemented") + return concurrentMap.values } actual fun get(key: K): V? { - TODO("Not yet implemented") + return concurrentMap[key] } actual fun remove(key: K): V? { - TODO("Not yet implemented") + return concurrentMap.remove(key) } actual fun isEmpty(): Boolean { - TODO("Not yet implemented") + return concurrentMap.isEmpty() } actual fun clear() { + concurrentMap.clear() } actual fun containsKey(key: K): Boolean { - TODO("Not yet implemented") + return concurrentMap.containsKey(key) } actual fun put( key: K, value: V, ) { + concurrentMap.put(key, value) } actual fun getOrCreate( key: K, builder: (K) -> V, ): V { - TODO("Not yet implemented") + val value = concurrentMap.get(key) + + return if (value != null) { + value + } else { + val newObject = builder(key) + concurrentMap.put(key, newObject) + concurrentMap[key] ?: newObject + } } actual fun createIfAbsent( key: K, builder: (K) -> V, ): Boolean { - TODO("Not yet implemented") + return runBlocking { + val value = concurrentMap.get(key) + if (value != null) { + false + } else { + val newObject = builder(key) + concurrentMap.put(key, newObject) + concurrentMap[key] == null + } + } } actual override fun size(): Int { - TODO("Not yet implemented") + return concurrentMap.size } actual override fun forEach(consumer: ICacheBiConsumer) { - TODO("Not yet implemented") + concurrentMap.forEach { consumer.accept(it.key, it.value) } } actual override fun filter(consumer: CacheCollectors.BiFilter): List { - TODO("Not yet implemented") + return concurrentMap + .filter { consumer.filter(it.key, it.value) } + .values + .toList() } actual override fun filterIntoSet(consumer: CacheCollectors.BiFilter): Set { - TODO("Not yet implemented") + return concurrentMap + .filter { consumer.filter(it.key, it.value) } + .values + .toSet() } actual override fun map(consumer: CacheCollectors.BiNotNullMapper): List { - TODO("Not yet implemented") + return concurrentMap.map { consumer.map(it.key, it.value) } } actual override fun mapNotNull(consumer: CacheCollectors.BiMapper): List { - TODO("Not yet implemented") + return concurrentMap.mapNotNull { consumer.map(it.key, it.value) } } actual override fun mapNotNullIntoSet(consumer: CacheCollectors.BiMapper): Set { - TODO("Not yet implemented") + return mapNotNull(consumer).toSet() } actual override fun mapFlatten(consumer: CacheCollectors.BiMapper?>): List { - TODO("Not yet implemented") + return concurrentMap.flatMap { consumer.map(it.key, it.value) as Iterable } } actual override fun mapFlattenIntoSet(consumer: CacheCollectors.BiMapper?>): Set { - TODO("Not yet implemented") + return mapFlatten(consumer).toSet() } actual override fun maxOrNullOf( filter: CacheCollectors.BiFilter, comparator: Comparator, ): V? { - TODO("Not yet implemented") +// return concurrentMap.maxOfWithOrNull( +// comparator, +// selector = { +// if (filter.filter(it.key, it.value)) it.value else concurrentMap.getValue(it.key) +// } +// ) + return concurrentMap.maxOrNullOf(filter, comparator) } actual override fun sumOf(consumer: CacheCollectors.BiSumOf): Int { - TODO("Not yet implemented") + return concurrentMap.sumOf(consumer) +// return concurrentMap.map { consumer.map(it.key, it.value) }.sum() } actual override fun sumOfLong(consumer: CacheCollectors.BiSumOfLong): Long { - TODO("Not yet implemented") + return concurrentMap.sumOfLong(consumer) } actual override fun groupBy(consumer: CacheCollectors.BiNotNullMapper): Map> { - TODO("Not yet implemented") + return concurrentMap.groupBy(consumer) } actual override fun countByGroup(consumer: CacheCollectors.BiNotNullMapper): Map { - TODO("Not yet implemented") + return concurrentMap.countByGroup(consumer) } actual override fun sumByGroup( groupMap: CacheCollectors.BiNotNullMapper, sumOf: CacheCollectors.BiNotNullMapper, ): Map { - TODO("Not yet implemented") + return concurrentMap.sumByGroup(groupMap, sumOf) } actual override fun count(consumer: CacheCollectors.BiFilter): Int { - TODO("Not yet implemented") + return concurrentMap.count { consumer.filter(it.key, it.value) } } actual override fun associate(transform: (K, V) -> Pair): Map { - TODO("Not yet implemented") + return concurrentMap.associate(transform) } actual override fun associateWith(transform: (K, V) -> U?): Map { - TODO("Not yet implemented") + return concurrentMap.associateWith(transform) } actual override fun filter( @@ -152,7 +193,8 @@ actual class LargeCache : ICacheOperations { to: K, consumer: CacheCollectors.BiFilter, ): List { - TODO("Not yet implemented") + val transientList = concurrentMap.subMapAlt(from, to) + return transientList.filter { consumer.filter(it.key, it.value) }.values.toList() } actual override fun filterIntoSet( @@ -160,7 +202,7 @@ actual class LargeCache : ICacheOperations { to: K, consumer: CacheCollectors.BiFilter, ): Set { - TODO("Not yet implemented") + return filter(from, to, consumer).toSet() } actual override fun map( @@ -168,7 +210,8 @@ actual class LargeCache : ICacheOperations { to: K, consumer: CacheCollectors.BiNotNullMapper, ): List { - TODO("Not yet implemented") + val transientList = concurrentMap.subMapAlt(from, to) + return transientList.map { consumer.map(it.key, it.value) } } actual override fun mapNotNull( @@ -176,7 +219,7 @@ actual class LargeCache : ICacheOperations { to: K, consumer: CacheCollectors.BiMapper, ): List { - TODO("Not yet implemented") + return concurrentMap.subMapAlt(from, to).mapNotNull { consumer.map(it.key, it.value) } } actual override fun mapNotNullIntoSet( @@ -184,7 +227,7 @@ actual class LargeCache : ICacheOperations { to: K, consumer: CacheCollectors.BiMapper, ): Set { - TODO("Not yet implemented") + return mapNotNull(from, to, consumer).toSet() } actual override fun mapFlatten( @@ -192,7 +235,7 @@ actual class LargeCache : ICacheOperations { to: K, consumer: CacheCollectors.BiMapper?>, ): List { - TODO("Not yet implemented") + return concurrentMap.subMapAlt(from, to).flatMap { consumer.map(it.key, it.value) as Iterable } } actual override fun mapFlattenIntoSet( @@ -200,7 +243,7 @@ actual class LargeCache : ICacheOperations { to: K, consumer: CacheCollectors.BiMapper?>, ): Set { - TODO("Not yet implemented") + return mapFlatten(from, to, consumer).toSet() } actual override fun maxOrNullOf( @@ -209,7 +252,8 @@ actual class LargeCache : ICacheOperations { filter: CacheCollectors.BiFilter, comparator: Comparator, ): V? { - TODO("Not yet implemented") + val transient = concurrentMap.subMapAlt(from, to) + return transient.maxOrNullOf(filter, comparator) } actual override fun sumOf( @@ -217,7 +261,7 @@ actual class LargeCache : ICacheOperations { to: K, consumer: CacheCollectors.BiSumOf, ): Int { - TODO("Not yet implemented") + return concurrentMap.subMapAlt(from, to).sumOf(consumer) } actual override fun sumOfLong( @@ -225,7 +269,7 @@ actual class LargeCache : ICacheOperations { to: K, consumer: CacheCollectors.BiSumOfLong, ): Long { - TODO("Not yet implemented") + return concurrentMap.subMapAlt(from, to).sumOfLong(consumer) } actual override fun groupBy( @@ -233,7 +277,7 @@ actual class LargeCache : ICacheOperations { to: K, consumer: CacheCollectors.BiNotNullMapper, ): Map> { - TODO("Not yet implemented") + return concurrentMap.subMapAlt(from, to).groupBy(consumer) } actual override fun countByGroup( @@ -241,7 +285,7 @@ actual class LargeCache : ICacheOperations { to: K, consumer: CacheCollectors.BiNotNullMapper, ): Map { - TODO("Not yet implemented") + return concurrentMap.subMapAlt(from, to).countByGroup(consumer) } actual override fun sumByGroup( @@ -250,7 +294,7 @@ actual class LargeCache : ICacheOperations { groupMap: CacheCollectors.BiNotNullMapper, sumOf: CacheCollectors.BiNotNullMapper, ): Map { - TODO("Not yet implemented") + return concurrentMap.subMapAlt(from, to).sumByGroup(groupMap, sumOf) } actual override fun count( @@ -258,7 +302,7 @@ actual class LargeCache : ICacheOperations { to: K, consumer: CacheCollectors.BiFilter, ): Int { - TODO("Not yet implemented") + return concurrentMap.subMapAlt(from, to).count { consumer.filter(it.key, it.value) } } actual override fun associate( @@ -266,7 +310,7 @@ actual class LargeCache : ICacheOperations { to: K, transform: (K, V) -> Pair, ): Map { - TODO("Not yet implemented") + return concurrentMap.subMapAlt(from, to).associate(transform) } actual override fun associateWith( @@ -274,7 +318,7 @@ actual class LargeCache : ICacheOperations { to: K, transform: (K, V) -> U?, ): Map { - TODO("Not yet implemented") + return concurrentMap.subMapAlt(from, to).associateWith(transform) } actual override fun joinToString( @@ -285,6 +329,170 @@ actual class LargeCache : ICacheOperations { truncated: CharSequence, transform: ((K, V) -> CharSequence)?, ): String { - TODO("Not yet implemented") + 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() } } + +// Different subMap implementations below. Investigating their performance for now. + +fun CacheMap.subMapSlow(from: K, to: K, toInclusive: Boolean = true): Map { + val transientList = toList() + val transientSubList = transientList.subList( + fromIndex = transientList.indexOf(Pair(from, getValue(from))), + toIndex = transientList.indexOf(Pair(to, getValue(to))), + ) + val completeSubList = transientSubList + Pair(to, getValue(to)) + + return if (toInclusive) completeSubList.toMap() else transientSubList.toMap() +} + +fun CacheMap.subMapAlt(from: K, to: K, toInclusive: Boolean = true): Map { + val resultMap = hashMapOf() + val keySet = keys + val fromIndex = keySet.indexOf(from) + val toIndex = keySet.indexOf(to) + for (index in fromIndex..toIndex){ + val correspondingEntry = entries.elementAt(index) + resultMap[correspondingEntry.key] = correspondingEntry.value + } + + return resultMap +} + +/** + * The following functions below are (re)implementations for the ICacheOperations + * interface. A lot of it is copying and pasting, with modifications to make it work + * consistently. + */ + +fun Map.maxOrNullOf( + filter: CacheCollectors.BiFilter, + comparator: Comparator, +): V? { + var _maxK: K? = null + var _maxV: V? = null + + val maxK: K? = _maxK + val maxV: V? = _maxV + + forEach { + if (filter.filter(it.key, it.value)) { + if (_maxK == null || (_maxV != null && comparator.compare(it.value, _maxV) > 0)) { + _maxK = it.key + _maxV = it.value + } + } + } + + return maxV +} + +fun Map.sumOf( + consumer: CacheCollectors.BiSumOf, +): Int { + var sum = 0 + val totalSum = sum + forEach { sum += consumer.map(it.key, it.value) } + return totalSum +} + +fun Map.sumOfLong( + consumer: CacheCollectors.BiSumOfLong, +): Long { + var sum = 0L + val totalSum = sum + forEach { sum += consumer.map(it.key, it.value) } + return totalSum +} + +fun Map.groupBy( + consumer: CacheCollectors.BiNotNullMapper +): Map> { + val results = HashMap>() + forEach { + val group = consumer.map(it.key, it.value) + val list = results[group] + if (list == null) { + val answer = ArrayList() + answer.add(it.value) + results[group] = answer + } else { + list.add(it.value) + } + } + + return results +} + +fun Map.countByGroup( + consumer: CacheCollectors.BiNotNullMapper +): Map { + val results = HashMap() + forEach { + val group = consumer.map(it.key, it.value) + val count = results[group] + if (count == null) { + results[group] = 1 + } else { + results[group] = count + 1 + } + } + + return results +} + +fun Map.sumByGroup( + groupMap: CacheCollectors.BiNotNullMapper, + sumOf: CacheCollectors.BiNotNullMapper, +): Map { + val results = HashMap() + forEach { + val group = groupMap.map(it.key, it.value) + val sum = results[group] + if (sum == null) { + results[group] = sumOf.map(it.key, it.value) + } else { + results[group] = sum + sumOf.map(it.key, it.value) + } + } + + return results +} + +fun Map.associate(transform: (K, V) -> Pair): Map { + val results: LinkedHashMap = LinkedHashMap(size) + forEach { + val pair = transform(it.key, it.value) + results[pair.first] = pair.second + } + + return results +} + +fun Map.associateWith(transform: (K, V) -> U?): Map { + val results: LinkedHashMap = LinkedHashMap(size) + forEach { + results[it.key] = transform(it.key, it.value) + } + + return results +}