All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.blemale.scaffeine.Scaffeine.scala Maven / Gradle / Ivy

package com.github.blemale.scaffeine

import com.github.benmanes.caffeine
import com.github.benmanes.caffeine.cache.Scheduler
import com.github.benmanes.caffeine.cache.stats.StatsCounter

import java.util
import java.util.concurrent.{CompletableFuture, Executor}
import scala.collection.JavaConverters._
import scala.compat.java8.DurationConverters._
import scala.compat.java8.FunctionConverters._
import scala.compat.java8.FutureConverters._
import scala.concurrent.Future
import scala.concurrent.duration._

object Scaffeine {

  /** Constructs a new `Scaffeine` instance with default settings, including
    * strong keys, strong values, and no automatic eviction of any kind.
    *
    * @return
    *   a new instance with default settings
    */
  def apply(): Scaffeine[Any, Any] =
    Scaffeine(
      caffeine.cache.Caffeine
        .newBuilder()
        .asInstanceOf[caffeine.cache.Caffeine[Any, Any]]
    )

  /** Constructs a new `Scaffeine` instance with the settings specified in
    * `spec`.
    *
    * @param spec
    *   an instance of [[com.github.benmanes.caffeine.cache.CaffeineSpec]]
    * @return
    *   a new instance with the specification's settings
    */
  def apply(spec: caffeine.cache.CaffeineSpec): Scaffeine[Any, Any] =
    Scaffeine(
      caffeine.cache.Caffeine
        .from(spec)
        .asInstanceOf[caffeine.cache.Caffeine[Any, Any]]
    )

  /** Constructs a new `Scaffeine` instance with the settings specified in
    * `spec`.
    *
    * @param spec
    *   a [[java.lang.String]] in the format specified by
    *   [[com.github.benmanes.caffeine.cache.CaffeineSpec]]
    * @return
    *   a new instance with the specification's settings
    */
  def apply(spec: String): Scaffeine[Any, Any] =
    Scaffeine(
      caffeine.cache.Caffeine
        .from(spec)
        .asInstanceOf[caffeine.cache.Caffeine[Any, Any]]
    )

}

case class Scaffeine[K, V](underlying: caffeine.cache.Caffeine[K, V]) {

  /** Sets the minimum total size for the internal hash tables.
    *
    * @param initialCapacity
    *   minimum total size for the internal hash tables
    * @return
    *   this builder instance
    * @throws java.lang.IllegalArgumentException
    *   if initialCapacity
    * @throws java.lang.IllegalStateException
    *   if an initial capacity was already set
    */
  def initialCapacity(initialCapacity: Int): Scaffeine[K, V] =
    Scaffeine(underlying.initialCapacity(initialCapacity))

  /** Specifies the executor to use when running asynchronous tasks.
    *
    * @param executor
    *   the executor to use for asynchronous execution
    * @return
    *   this builder instance
    */
  def executor(executor: Executor): Scaffeine[K, V] =
    Scaffeine(underlying.executor(executor))

  /** Specifies the maximum number of entries the cache may contain.
    *
    * @param maximumSize
    *   the maximum size of the cache
    * @return
    *   this builder instance
    * @throws java.lang.IllegalArgumentException
    *   `size` is negative
    * @throws java.lang.IllegalStateException
    *   if a maximum size or weight was already set
    */
  def maximumSize(maximumSize: Long): Scaffeine[K, V] =
    Scaffeine(underlying.maximumSize(maximumSize))

  /** Specifies the maximum weight of entries the cache may contain. 

This * feature cannot be used in conjunction with [[Scaffeine.maximumSize]]. * * @param maximumWeight * the maximum total weight of entries the cache may contain * @return * this builder instance * @throws java.lang.IllegalArgumentException * if `maximumWeight` is negative * @throws java.lang.IllegalStateException * if a maximum weight or size was already set */ def maximumWeight(maximumWeight: Long): Scaffeine[K, V] = Scaffeine(underlying.maximumWeight(maximumWeight)) /** Specifies the weigher to use in determining the weight of entries. * * @param weigher * the weigher to use in calculating the weight of cache entries * @tparam K1 * key type of the weigher * @tparam V1 * value type of the weigher * @return * this builder instance * @throws java.lang.IllegalArgumentException * if `size` is negative * @throws java.lang.IllegalStateException * if a maximum size was already set */ def weigher[K1 <: K, V1 <: V](weigher: (K1, V1) => Int): Scaffeine[K1, V1] = Scaffeine(underlying.weigher(new caffeine.cache.Weigher[K1, V1] { override def weigh(key: K1, value: V1): Int = weigher(key, value) })) /** Specifies that each key (not value) stored in the cache should be wrapped * in a [[java.lang.ref.WeakReference]] (by default, strong references are * used).

* * @return * this builder instance * @throws java.lang.IllegalStateException * if the key strength was already set or the writer was set */ def weakKeys(): Scaffeine[K, V] = Scaffeine(underlying.weakKeys()) /** Specifies that each value (not key) stored in the cache should be wrapped * in a [[java.lang.ref.WeakReference]] (by default, strong references are * used).

This feature cannot be used in conjunction with * [[Scaffeine.buildAsync[K1<:K,V1<:V]()*]]. * * @return * this builder instance * @throws java.lang.IllegalStateException * if the value strength was already set */ def weakValues(): Scaffeine[K, V] = Scaffeine(underlying.weakValues()) /** Specifies that each value (not key) stored in the cache should be wrapped * in a [[java.lang.ref.SoftReference]] (by default, strong references are * used).

This feature cannot be used in conjunction with * [[Scaffeine.buildAsync[K1<:K,V1<:V]()*]]. * * @return * this builder instance * @throws java.lang.IllegalStateException * if the value strength was already set */ def softValues(): Scaffeine[K, V] = Scaffeine(underlying.softValues()) /** Specifies that each entry should be automatically removed from the cache * once a fixed duration has elapsed after the entry's creation, or the most * recent replacement of its value. * * @param duration * the length of time after an entry is created that it should be * automatically removed * @return * this builder instance * @throws java.lang.IllegalArgumentException * if `duration` is negative * @throws java.lang.IllegalStateException * if the time to live or time to idle was already set */ def expireAfterWrite(duration: FiniteDuration): Scaffeine[K, V] = Scaffeine(underlying.expireAfterWrite(duration.toJava)) /** Specifies that each entry should be automatically removed from the cache * once a fixed duration has elapsed after the entry's creation, the most * recent replacement of its value, or its last read. * * @param duration * the length of time after an entry is last accessed that it should be * automatically removed * @return * this builder instance * @throws java.lang.IllegalArgumentException * if `duration` is negative * @throws java.lang.IllegalStateException * if the time to idle or time to live was already set */ def expireAfterAccess(duration: FiniteDuration): Scaffeine[K, V] = Scaffeine(underlying.expireAfterAccess(duration.toJava)) /** Specifies that each entry should be automatically removed from the cache * once a duration has elapsed after the entry's creation, the most recent * replacement of its value, or its last read. * * @param create * the length of time an entry should be automatically removed from the * cache after the entry's creation. * @param update * the length of time an entry should be automatically removed from the * cache after the replacement of it's value. * @param read * the length of time an entry should be automatically removed from the * cache after the entry's last read. * @tparam K1 * the key type of the expiry. * @tparam V1 * the value type of the expiry. * @return * this builder instance * @throws java.lang.IllegalStateException * if expiration was already set or used with expiresAfterAccess or * expiresAfterWrite. */ def expireAfter[K1 <: K, V1 <: V]( create: (K1, V1) => FiniteDuration, update: (K1, V1, FiniteDuration) => FiniteDuration, read: (K1, V1, FiniteDuration) => FiniteDuration ): Scaffeine[K1, V1] = Scaffeine( underlying .expireAfter(new caffeine.cache.Expiry[K1, V1] { override def expireAfterCreate(key: K1, value: V1, currentTime: Long) : Long = create(key, value).toNanos override def expireAfterUpdate( key: K1, value: V1, currentTime: Long, currentDuration: Long ): Long = update(key, value, currentDuration.nanos).toNanos override def expireAfterRead( key: K1, value: V1, currentTime: Long, currentDuration: Long ): Long = read(key, value, currentDuration.nanos).toNanos }) .asInstanceOf[caffeine.cache.Caffeine[K1, V1]] ) /** Specifies that active entries are eligible for automatic refresh once a * fixed duration has elapsed after the entry's creation, or the most recent * replacement of its value. * * @param duration * the length of time after an entry is created that it should be * considered stale, and thus eligible for refresh * @return * this builder instance * @throws java.lang.IllegalArgumentException * if `duration` is negative * @throws java.lang.IllegalStateException * if the refresh interval was already set */ def refreshAfterWrite(duration: FiniteDuration): Scaffeine[K, V] = Scaffeine(underlying.refreshAfterWrite(duration.toJava)) /** Specifies a nanosecond-precision time source for use in determining when * entries should be expired or refreshed. By default, * `java.lang.System.nanoTime` is used. * * @param ticker * a nanosecond-precision time source * @return * this builder instance * @throws java.lang.IllegalStateException * if a ticker was already set */ def ticker(ticker: caffeine.cache.Ticker): Scaffeine[K, V] = Scaffeine(underlying.ticker(ticker)) /** Specifies a listener instance that caches should notify each time an entry * is removed for any [[com.github.benmanes.caffeine.cache.RemovalCause]]. * * @param removalListener * a listener that caches should notify each time an entry is removed * @tparam K1 * the key type of the listener * @tparam V1 * the value type of the listener * @return * this builder instance * @throws java.lang.IllegalStateException * if a removal listener was already set */ def removalListener[K1 <: K, V1 <: V]( removalListener: (K1, V1, caffeine.cache.RemovalCause) => Unit ): Scaffeine[K1, V1] = Scaffeine( underlying.removalListener(new caffeine.cache.RemovalListener[K1, V1] { override def onRemoval( key: K1, value: V1, cause: caffeine.cache.RemovalCause ): Unit = removalListener(key, value, cause) }) ) /** Specifies a listener instance that caches should notify each time an entry * is evicted. * * @param evictionListener * a listener that caches should notify each time an entry is being * automatically removed due to eviction * @tparam K1 * the key type of the listener * @tparam V1 * the value type of the listener * @return * this builder instance * @throws java.lang.IllegalStateException * if a removal listener was already set */ def evictionListener[K1 <: K, V1 <: V]( evictionListener: (K1, V1, caffeine.cache.RemovalCause) => Unit ): Scaffeine[K1, V1] = Scaffeine( underlying.evictionListener(new caffeine.cache.RemovalListener[K1, V1] { override def onRemoval( key: K1, value: V1, cause: caffeine.cache.RemovalCause ): Unit = evictionListener(key, value, cause) }) ) /** Enables the accumulation of * [[com.github.benmanes.caffeine.cache.stats.CacheStats]] during the * operation of the cache. * * @return * this builder instance */ def recordStats(): Scaffeine[K, V] = Scaffeine(underlying.recordStats()) /** Enables the accumulation of * [[com.github.benmanes.caffeine.cache.stats.CacheStats]] during the * operation of the cache. * * @param statsCounterSupplier * a supplier that returns a new * [[com.github.benmanes.caffeine.cache.stats.StatsCounter]] * @return * this builder instance */ def recordStats[C <: StatsCounter]( statsCounterSupplier: () => C ): Scaffeine[K, V] = Scaffeine(underlying.recordStats(asJavaSupplier(statsCounterSupplier))) /** Specifies the scheduler to use when scheduling routine maintenance based * on an expiration event. This augments the periodic maintenance that occurs * during normal cache operations to allow for the prompt removal of expired * entries regardless of whether any cache activity is occurring at that * time. By default the scheduler is disabled. * * @param scheduler * the scheduler that submits a task to the [[Scaffeine.executor*]] after a * given delay * @return * this builder instance */ def scheduler(scheduler: Scheduler): Scaffeine[K, V] = Scaffeine(underlying.scheduler(scheduler)) /** Builds a cache which does not automatically load values when keys are * requested. * * @tparam K1 * the key type of the cache * @tparam V1 * the value type of the cache * @return * a cache having the requested features */ def build[K1 <: K, V1 <: V](): Cache[K1, V1] = Cache(underlying.build()) /** Builds a cache, which either returns an already-loaded value for a given * key or atomically computes or retrieves it using the supplied `loader`. If * another thread is currently loading the value for this key, simply waits * for that thread to finish and returns its loaded value. Note that multiple * threads can concurrently load values for distinct keys. * * @param loader * the loader used to obtain new values * @param allLoader * the loader used to obtain new values in bulk, called by * [[LoadingCache.getAll(keys:Iterable[K])*]] * @param reloadLoader * the loader used to obtain already-cached values * @tparam K1 * the key type of the loader * @tparam V1 * the value type of the loader * @return * a cache having the requested features */ def build[K1 <: K, V1 <: V]( loader: K1 => V1, allLoader: Option[Iterable[K1] => Map[K1, V1]] = None, reloadLoader: Option[(K1, V1) => V1] = None ): LoadingCache[K1, V1] = LoadingCache( underlying.build( toCacheLoader( loader, allLoader, reloadLoader ) ) ) /** Builds a cache which does not automatically load values when keys are * requested unless a mapping function is provided. The returned * [[scala.concurrent.Future]] may be already loaded or currently computing * the value for a given key. If the asynchronous computation fails value * then the entry will be automatically removed. Note that multiple threads * can concurrently load values for distinct keys. * * @tparam K1 * the key type of the cache * @tparam V1 * the value type of the cache * @return * a cache having the requested features */ def buildAsync[K1 <: K, V1 <: V](): AsyncCache[K1, V1] = AsyncCache(underlying.buildAsync[K1, V1]()) /** Builds a cache, which either returns a [[scala.concurrent.Future]] already * loaded or currently computing the value for a given key, or atomically * computes the value asynchronously through a supplied mapping function or * the supplied `loader`. If the asynchronous computation fails then the * entry will be automatically removed. Note that multiple threads can * concurrently load values for distinct keys. * * @param loader * the loader used to obtain new values * @param allLoader * the loader used to obtain new values in bulk, called by * [[AsyncLoadingCache.getAll(keys:Iterable[K])*]] * @param reloadLoader * the loader used to obtain already-cached values * @tparam K1 * the key type of the loader * @tparam V1 * the value type of the loader * @return * a cache having the requested features * @throws java.lang.IllegalStateException * if the value strength is weak or soft */ def buildAsync[K1 <: K, V1 <: V]( loader: K1 => V1, allLoader: Option[Iterable[K1] => Map[K1, V1]] = None, reloadLoader: Option[(K1, V1) => V1] = None ): AsyncLoadingCache[K1, V1] = AsyncLoadingCache( underlying.buildAsync[K1, V1]( toCacheLoader( loader, allLoader, reloadLoader ) ) ) /** Builds a cache, which either returns a [[scala.concurrent.Future]] already * loaded or currently computing the value for a given key, or atomically * computes the value asynchronously through a supplied mapping function or * the supplied async `loader`. If the asynchronous computation fails then * the entry will be automatically removed. Note that multiple threads can * concurrently load values for distinct keys. * * @param loader * the loader used to obtain new values * @param allLoader * the loader used to obtain new values in bulk, called by * [[AsyncLoadingCache.getAll(keys:Iterable[K])*]] * @param reloadLoader * the loader used to obtain already-cached values * @tparam K1 * the key type of the loader * @tparam V1 * the value type of the loader * @throws java.lang.IllegalStateException * if the value strength is weak or soft */ def buildAsyncFuture[K1 <: K, V1 <: V]( loader: K1 => Future[V1], allLoader: Option[Iterable[K1] => Future[Map[K1, V1]]] = None, reloadLoader: Option[(K1, V1) => Future[V1]] = None ): AsyncLoadingCache[K1, V1] = AsyncLoadingCache( underlying.buildAsync[K1, V1]( toAsyncCacheLoader( loader, allLoader, reloadLoader ) ) ) private[this] def toCacheLoader[K1 <: K, V1 <: V]( loader: K1 => V1, allLoader: Option[Set[K1] => Map[K1, V1]], reloadLoader: Option[(K1, V1) => V1] ): caffeine.cache.CacheLoader[K1, V1] = allLoader match { case Some(l) => new CacheLoaderAdapter[K1, V1](loader, reloadLoader) { override def loadAll(keys: util.Set[_ <: K1]): util.Map[K1, V1] = l(keys.asScala.toSet).asJava } case None => new CacheLoaderAdapter[K1, V1](loader, reloadLoader) } private[this] def toAsyncCacheLoader[K1 <: K, V1 <: V]( loader: K1 => Future[V1], allLoader: Option[Set[K1] => Future[Map[K1, V1]]], reloadLoader: Option[(K1, V1) => Future[V1]] ): caffeine.cache.AsyncCacheLoader[K1, V1] = allLoader match { case Some(l) => new AsyncCacheLoaderAdapter[K1, V1](loader, reloadLoader) { override def asyncLoadAll( keys: util.Set[_ <: K1], executor: Executor ): CompletableFuture[util.Map[K1, V1]] = l(keys.asScala.toSet) .map(_.asJava)(DirectExecutionContext) .toJava .toCompletableFuture } case None => new AsyncCacheLoaderAdapter[K1, V1](loader, reloadLoader) } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy