--- title: A simple timed cache in Kotlin --- ###### tags: `Kotlin` # A simple timed cache in Kotlin ```kotlin import org.slf4j.Logger import org.slf4j.helpers.NOPLogger import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit.MILLISECONDS interface Cache { fun get(key: String): String? fun put(key: String, value: String) } class TimedCache(var log: Logger = NOPLogger.NOP_LOGGER) : Cache { private var cacheTimeValidityInMillis: Long = 0 private val hashMap = ConcurrentHashMap<String, TimedEntry>() companion object { fun expiringEvery(duration: Long, timeUnit: TimeUnit) = TimedCache().apply { cacheTimeValidityInMillis = MILLISECONDS.convert(duration, timeUnit) } } override fun get(key: String): String? { val timedEntry = hashMap[key] if (timedEntry == null || timedEntry.isExpired()) { log.debug("cache miss for key $key") return null } return timedEntry.value } override fun put(key: String, value: String) { log.debug("caching $key with value $value") hashMap[key] = TimedEntry(value, cacheTimeValidityInMillis) } data class TimedEntry(val value: String, val maxDurationInMillis: Long) { private val creationTime: Long = now() fun isExpired() = (now() - creationTime) > maxDurationInMillis private fun now() = System.currentTimeMillis() } } ``` ## Tests ```kotlin import org.junit.jupiter.api.Test import shouldBe import java.util.concurrent.TimeUnit.SECONDS class TimedCacheTest { private val timedCache = TimedCache.expiringEvery(1, SECONDS) @Test fun `before the cache expiration the cached value is still available`() { timedCache.put("someKey", "someValue") Thread.sleep(ONE_SECOND / 2) timedCache.get("someKey") shouldBe "someValue" } @Test fun `after the cache expiration the cached value is no more available`() { timedCache.put("someKey", "someValue") Thread.sleep(ONE_SECOND + 1) timedCache.get("someKey") shouldBe null } @Test fun `putting again a value on an expired cache entry renews its time validity`() { timedCache.put("someKey", "someValue") Thread.sleep(ONE_SECOND + 1) timedCache.put("someKey", "anotherValue") timedCache.get("someKey") shouldBe "anotherValue" } companion object { private const val ONE_SECOND: Long = 1000 } } ``` ## A util function ```kotlin import org.assertj.core.api.Assertions.assertThat internal infix fun Any?.shouldBe(other: Any?) { assertThat(this).isEqualTo(other) } ```