dveim
dveim

Reputation: 3482

Restriction on Map key/value types, how to make them linked?

In Scala code, I have

val pendingServices = new ConcurrentHashMap[String, JMap[Class[_ <: Service], Service]]()

From the caller side, Java, using looks like

Service$.MODULE$.pendingServices().get("key").put(Foo.class, new Foo()); 

Also I am able to get instance of some Service class by

Service$.MODULE$.pendingServices().get("key").get(Foo.class)

or from Scala,

Service.pendingServices.get("key").get(classOf[Foo])

The problem: it is possible to put unrelated Servicees in the map, like

Service$.MODULE$.pendingServices().get("key").put(Bar.class, new Foo());

if Foo and Bar both extends Service. So, how can I restrict Class[_ <: Service and Service from the definition to share same Service ?

I want to demonstrate my coworkers some cases when Scala's type system really helps. Tried with type lambdas, but my type-fu is not strong enought.

Upvotes: 4

Views: 144

Answers (2)

Odomontois
Odomontois

Reputation: 16308

You could use such construction in the scala code

import scala.collection.mutable.{HashMap ⇒ MHMap,  SynchronizedMap}
import scala.reflect.{ClassTag, classTag}

class Service

case class Foo() extends Service
case class Bar() extends Service

object Service {
  object pendingServices {
    private val backing = (new MHMap[String, Map[Class[_], Service]] with SynchronizedMap)
        .withDefaultValue(Map.empty)

    def update[T <: Service : ClassTag](name: String, service: T): Unit =
      backing(name) += classTag[T].runtimeClass → service

    def apply[T <: Service : ClassTag](name: String): T =
      backing(name)(classTag[T].runtimeClass).asInstanceOf[T]
  }
}

in scala you could use it as

val foo = new Foo
val bar = new Bar

pendingServices("baz") = foo
pendingServices("baz") = bar

println(pendingServices[Foo]("baz")) // Foo()
println(pendingServices[Bar]("baz")) // Bar()

in java equivalent would be

final Foo foo = new Foo();
final Bar bar = new Bar();
Service.pendingServices$.MODULE$.update("baz", foo, ClassTag$.MODULE$.apply(Foo.class));
Service.pendingServices$.MODULE$.update("baz", bar, ClassTag$.MODULE$.apply(Bar.class));

System.out.println(Service.pendingServices$.MODULE$.<Foo>apply("baz", ClassTag$.MODULE$.apply(Foo.class))); // Foo()
System.out.println(Service.pendingServices$.MODULE$.<Bar>apply("baz", ClassTag$.MODULE$.apply(Bar.class))); // Bar()

It still does not restrict anything outside scala, but can work as demo

Upvotes: 3

Jiri Tousek
Jiri Tousek

Reputation: 12440

I think you'll have to wrap the map into some helper class. You could use something like (Java syntax):

public class ServiceRepository {

    private Map<Class<?>, Object> repo = new HashMap<>();

    public <T> void put(Class<T> clazz, T service) {
         repo.put(clazz, service);
    }

    public <T> T get(Class<T> clazz) {
        return (T) repo.get(clazz);
    }

}

Upvotes: 2

Related Questions