Search code examples
genericsscalaguavaparameterized

Simple cache manager using Guava in Scala parameterized (generics) type error


I am pretty new to Scala and still do not understand generics that well. Consequently, I cannot figure out why the compiler hates me with type mismatch errors.

I am using Google's Guava library to create some simple caches represented as Scala's ConcurrentMap. I would like to keep track of the created caches using another Map of cache name to ConcurrentMap (the cache). Here is what I have so far which compiles but is missing the tracking of the caches (I've commented out the bits that fail):

import scala.collection.mutable.ConcurrentMap

trait CacheManager {

    def getCache[V <: AnyRef](
            cacheName: String,
            cacheListener: Option[CacheListener] = None): ConcurrentMap[String, V]

}


import scala.collection.JavaConversions._
import com.google.common.collect.MapMaker
import java.util.concurrent.{ConcurrentMap => JConcurrentMap, TimeUnit}
import org.slf4j.LoggerFactory
import com.google.common.cache.{RemovalNotification, RemovalListener, CacheBuilder}
import scala.collection.mutable.ConcurrentMap

class LocalCacheManager extends CacheManager {

    private val logger = LoggerFactory.getLogger(classOf[LocalCacheManager])


    private val caches /*: ConcurrentMap[String, ConcurrentMap[String, _ <: AnyRef]]*/ =
            asScalaConcurrentMap[String, ConcurrentMap[String, _ <: AnyRef]](
                new MapMaker().concurrencyLevel(4).makeMap[String, ConcurrentMap[String, _ <: AnyRef]]())

    def getCache[V <: AnyRef](cacheName: String, cacheListener: Option[CacheListener] = None) = {
//        caches.getOrElseUpdate(cacheName, {
            val cache = CacheBuilder.newBuilder()
                        .concurrencyLevel(4)
                        .softValues()
                        .expireAfterAccess(30, TimeUnit.MINUTES)
                        .build[String, V]()
            asScalaConcurrentMap[String, V](cache.asMap())
//        })
    }
}

Basically, if I try to add the Guava cache to caches (via the commented out caches.getOrElseUpdate) then the compiler complains with the following:

error: type mismatch;
found   : scala.collection.mutable.ConcurrentMap[String,_$1] where type _$1 <: AnyRef
required: scala.collection.mutable.ConcurrentMap[String,V]
caches.getOrElseUpdate(cacheName, {

Solution

  • Since you are providing type information when retrieving the cache, it is not necessary to try and maintain the wildcard typing. It's much simpler to type the values to AnyRef and then typecast to V at the end. The following compiles and should help. Also, there is no need to call asScalaConcurrentMap directly since it is well... implicit.

    import scala.collection.JavaConversions._
    import com.google.common.collect.MapMaker
    import java.util.concurrent.TimeUnit
    import com.google.common.cache.CacheBuilder
    import scala.collection.mutable.ConcurrentMap
    
    trait CacheListener // not sure what this is doing yet.
    
    trait CacheManager {
    
        def getCache[V <: AnyRef](
                cacheName: String,
                cacheListener: Option[CacheListener] = None): ConcurrentMap[String, V]
    
    }
    
    class LocalCacheManager extends CacheManager {
        private val caches: ConcurrentMap[String, ConcurrentMap[String, AnyRef]] =
                    new MapMaker().concurrencyLevel(4).makeMap[String, ConcurrentMap[String, AnyRef]]()
        def getCache[V <: AnyRef](cacheName: String, cacheListener: Option[CacheListener] = None) = 
          caches.getOrElseUpdate(cacheName, {
                  CacheBuilder.newBuilder()
                              .concurrencyLevel(4)
                              .softValues()
                              .expireAfterAccess(30, TimeUnit.MINUTES)
                              .asInstanceOf[CacheBuilder[String, AnyRef ]]
                              .build[String, AnyRef ]()
                              .asMap()
                  }).asInstanceOf[ConcurrentMap[String, V]]
    }