views:

282

answers:

2

I'm starting on a Scala application which uses Hibernate (JPA) on the back end. In order to load an object, I use this line of code:

val addr = s.load(classOf[Address], addr_id).asInstanceOf[Address];

Needless to say, that's a little painful. I wrote a helper class which looks like this:

import org.hibernate.Session

class DataLoader(s: Session) {
  def loadAddress(id: Long): Address = {
    return s.load(classOf[Address], id).asInstanceOf[Address];
  }
  ...
}

So, now I can do this:

val dl = new DataLoader(s)
val addr = dl loadAddress(addr_id)

Here's the question: How do I write a generic parametrized method which can load any object using this same pattern? i.e

val addr = dl load[Address](addr_id)

(or something along those lines.)

I'm new to Scala so please forgive anything here that's especially hideous.

+3  A: 

One method would be to do take advantage of the java.lang.Class.cast method in order to do something like:

def load[A](clazz: Class[A], id: Long)(implicit s: Session) : A 
       = clazz.cast(s.load(clazz, id))

Then the usage is as follows:

implicit val s = ...//get hibernate session
val addr = load(classOf[Address], 1)

It's not hugely different from what you have already but in order to access the class instance, you need to pass it in.

I'm pretty confident that you cannot safely do what you want with Manifests because they cannot supply the parametrized Class[Address] at compile-time which you need in order for the cast to work (they can only supply the erasure of Class[_]). I don't see any other mechanism for doing a cast from a Manifest

You can of course use asInstanceOf[A] instead of cast but this gets erased at compile-time to isInstanceOf[Object] and is therefore useless in terms of compile-time type checking (and hence unadvisable).

oxbow_lakes
Interesting. This makes things a little tidier... but you just taught me about a couple of new Scala constructs as well. Appreciate that.
Isaac Oates
Hah! I'll prove you wrong! ;-)
Daniel
By the way... `asInstanceOf[A]` gets erased? That doesn't make any sense. Furthermore, erasure happens at run-time, not compile-time. On the other hand, it never occured to me to use a `Class[A]` to do cast to `A`. Interesting.
Daniel
I agree with Daniel, there's no reason why Manifest shouldn't work here.
Jorge Ortiz
@Daniel - erasure happens at compile time. You can write code that casts to a parametric type (like A) but this type is erased at compile time to its upper bound, which in this case is Object. Hence the cast which actually happens at runtime is a (meaningless) cast to object. I'll defer to your wisdom on scala, but not java :-)
oxbow_lakes
@oxbow I'm not sure we agree on the meaning of compile-time here. The type returned by `load`, though an `Object` at run-time, is definitely syntactically checked for while compiling.
Daniel
No - you are wrong. The only compile-time "check" that is happening (in the case of the manifest solution) is the `asInstanceOf[A]` check. But that is actually meaningless for the reasons I've mentioned. For example, you could send a manifest of a completely different class in there and you'd have no compile-time errors. And possibly a runtime error far away from the source of the problem
oxbow_lakes
What I mean is that Scala will enforce that the object returned by `load` be treated as an `A` at compile time. At run-time, an exception may arise due to it _not_ being an `A` after all.
Daniel
Agreed - but it is important in that case that if a method appears to return a parametric type `A`, that this should fail fast due to the returned object not actually being an instance of `A`, rather than be allowed to pass out (because `A` has been erased to `Object`) only to cause some horrors later on.
oxbow_lakes
+4  A: 

Here it is:

import org.hibernate.Session
class DataLoader(s: Session) {
  def load[A](id: Long)(implicit m: Manifest[A]): A = {
    return s.load(m.erasure, id).asInstanceOf[A];
  }
}

EDIT -- Or, to ensure that any casting error -- as a result of hibernate returning the wrong object -- will happen inside load, like this:

import org.hibernate.Session
class DataLoader(s: Session) {
  def load[A](id: Long)(implicit m: Manifest[A]): A = {
    return m.erasure.asInstanceOf[Class[A]].cast(s.load(m.erasure, id));
  }
}

On Scala 2.8 you can also write it like this:

import org.hibernate.Session
class DataLoader(s: Session) {
  def load[A : Manifest](id: Long): A = {
    return s.load(manifest[A].erasure, id).asInstanceOf[A];
  }
}

You can combine it with an implicit session as well, as suggested by Chris:

def load[A](id: Long)(implicit m: Manifest[A], s: org.hibernate.Session): A = {
  return s.load(m.erasure, id).asInstanceOf[A];
}

Note that you can't combine context view notation (A : Manifest) with additional implicit parameters.

Daniel
@Daniel - your asInstanceOf cast is not really safe I'm afraid. Consider the case of two statements `val a = load[Address](1)` and `val b: Any = load[Address](2)`. In the 2nd case, no casts to the `Address` class are actually taking place (they are erased) and you are trusting Hibernate to actually return you an instance of the correct type (which, to be fair, it most certainly will).
oxbow_lakes
@oxbow I see. Your point being that a `Class.cast` will cause a run-time error inside the `load`, instead of being left with something that may cause trouble later?
Daniel
Yes - that is exactly it. The only "correct" cast going on here is outside the `load` method and inserted by the compiler because the type of the reference `a` is Address. If the `load` method is called and assigned to an `Any`, then no CCE happens and you may find that something breaks way down the line
oxbow_lakes
@oxbow Ok, then. I added a safer version, which, according to some tests I did, ought to work. The placement of `asInstanceOf` was particularly suprising.
Daniel