Skip to content

Instantly share code, notes, and snippets.

@brikis98
Created June 22, 2013 21:57
Show Gist options
  • Save brikis98/5842766 to your computer and use it in GitHub Desktop.
Save brikis98/5842766 to your computer and use it in GitHub Desktop.
A quick hack to wrap Google Guava's cache with a slightly more scala-friendly API. The main goal is to have a getOrElseUpdate method that is actually thread safe, unlike the one on Scala's ConcurrentMap.
package com.linkedin.playplugins.common.util
import com.google.common.cache.{Cache => GuavaCache, CacheBuilder}
import play.api.Configuration
import Cache._
import java.util.concurrent.Callable
/**
* A Scala wrapper for a Google Guava cache. Exposes the basic underlying methods of a Guava cache and adds a
* getOrElseUpdate(key, value) method that lazily evaluates the value parameter only if the key is not already present
* in the cache.
*
* You may be asking, why not just use Scala's ConcurrentMap interface, which already has a getOrElseUpdate method?
*
* val cache = new ConcurrentHashMap().asScala
* cache.getOrElseUpdate("foo", "bar") // BAD idea
*
* The answer is because this method is inherited not from ConcurrentMap but from MapLike, and is NOT a thread safe
* (atomic) operation!
*
* @tparam K
* @tparam V
*/
class Cache[K <: AnyRef, V <: AnyRef](initialCapacity: Int = DEFAULT_INITIAL_CAPACITY, concurrencyLevel: Int = DEFAULT_CONCURRENCY_LEVEL) {
/**
* Overloaded constructor to create a cache from config values
*
* @param config
* @return
*/
def this(config: Configuration) = this(
config.getInt(CONFIG_KEY_INITIAL_CAPACITY).getOrElse(DEFAULT_INITIAL_CAPACITY),
config.getInt(CONFIG_KEY_CONCURRENCY_LEVEL).getOrElse(DEFAULT_CONCURRENCY_LEVEL)
)
private val cache: GuavaCache[K, V] = {
CacheBuilder
.newBuilder()
.initialCapacity(initialCapacity)
.concurrencyLevel(concurrencyLevel)
.build()
}
/**
* Optionally get the value associated with the given key
*
* @param key
* @return
*/
def get(key: K): Option[V] = {
Option(cache.getIfPresent(key))
}
/**
* Get the value associated with the given key. If no value is already associated, then associate the given value
* with the key and use it as the return value.
*
* Like Scala's ConcurrentMap, the value parameter will be lazily evaluated. However, unlike Scala's ConcurrentMap,
* this method is a thread safe (atomic) operation.
*
* @param key
* @param value
* @return
*/
def getOrElseUpdate(key: K, value: => V): V = {
cache.get(key, new Callable[V] {
def call(): V = value
})
}
/**
* Associate the given value with the given key
*
* @param key
* @param value
*/
def put(key: K, value: V) {
cache.put(key, value)
}
/**
* Remove the key and any associated value from the cache
*
* @param key
*/
def remove(key: K) {
cache.invalidate(key)
}
/**
* Remove all keys and values from the cache
*/
def clear() {
cache.invalidateAll()
}
/**
* Return how many items are in the cache
*
* @return
*/
def size: Long = {
cache.size()
}
}
object Cache {
val DEFAULT_INITIAL_CAPACITY = 16
val DEFAULT_CONCURRENCY_LEVEL = 16
val CONFIG_KEY_INITIAL_CAPACITY = "initialCapacity"
val CONFIG_KEY_CONCURRENCY_LEVEL = "concurrencyLevel"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment