How can I store a reference to a set of static Java methods from Scala?

724 views Asked by At

In the context of Eclipse Databinding, there are quite a few Java classes that act as factories for IObservable objects. For instance, there's BeanObservables, PojoObservables, EMFObservables, etc., and they all implement a set of methods similar to

  public static IObservableValue observeValue(Object target, String property)

Now if we were in the Scala world, each of those factories would probably be a singleton object implementing a common trait, say, IObservableFactory, and I could, for instance, pass them around as implicit values to methods that would use them to create data bindings.

But as they are defined in Java, it seems I can do none of that. I can't write

val pojoObservableFact = PojoObservables

because PojoObservables is not recognized as being a value.

Is there any way to make the compiler "instantiate" a singleton that would correspond to the PojoObservables class in Scala? Or any workaround for it? I know I could write the singleton myself and forward all method calls, but this sounds a bit tedious.


Edit

To make it clearer: this is what I'm trying to do: define a method that creates the observable value using a passed factory.

def createObservable(fact: ObservableFactory, target: AnyRef, property: String) =
  fact.observeValue(target, property)

but I wouldn't know how to define ObservableFactory or how to actually pass PojoObservables, a Java class with only static methods, to this createObservable method.

4

There are 4 answers

1
Rex Kerr On BEST ANSWER

If no-one else comes up with a working answer, I don't think it would be all that hard to write a code generator based on the method signatures.

val a = classOf[String].getMethods.map(_.toString).filter(s =>
  s.contains("public") && s.contains("static")
)

will, for instance, pull out all the method signatures (for String in this case) as strings. Then

val b = a.map(_.split(" ").reverse.take(2).reverse)

will grab just the function signature with the return type split out front. Now

val c = b.map(_(1).dropWhile(_ != '(').drop(1).takeWhile(_ != ')').split(","))
val d = b.map(_(1).takeWhile(_ != '('))

will get the argument signatures and name of the function, respectively. We then need to convert the primitive types, which is pretty easy since we just need to capitalize the first letter (save for void which becomes unit):

def jprim2scala(s: String) = {
  val prim = List("boolean","byte","short","char","int","long","float","double")
  def arrayconvert(s: String): String = {
    if (s.endsWith("[]")) "Array["+arrayconvert(s.substring(0,s.length-2))+"]"
    else if (s=="void") "Unit"
    else if (prim contains s) {
      s.substring(0,1).toUpperCase + s.substring(1)
    }
    else s
  }
  arrayconvert(s)
}

def e = (b,c).zipped.map((bi,ci) => (jprim2scala(bi(0)), ci.map(jprim2scala)))

and finally you can put it all back together:

val f = (d,e).zipped.map((name,ei) => {
  val (ret,args) = ei
  val lastname = name.split('.').last
  "def "+lastname+"(" +
  (for ((a,i) <- args.zipWithIndex) yield ("a"+i+": "+a)).mkString(", ") +
  "): "+ret+" = "+name+"("+(0 until args.length).map("a"+_).mkString(",")+")"
})

Now you have a bunch of Scala code that you can put (by hand) in an appropriate singleton.

object Stringleton {
  // The contents of this is cut-and-paste f.map("  "+_).foreach(println)
  def valueOf(a0: java.lang.Object): java.lang.String = java.lang.String.valueOf(a0)
  def valueOf(a0: Array[Char]): java.lang.String = java.lang.String.valueOf(a0)
  def valueOf(a0: Array[Char], a1: Int, a2: Int): java.lang.String = java.lang.String.valueOf(a0,a1,a2)
  def valueOf(a0: Boolean): java.lang.String = java.lang.String.valueOf(a0)
  def valueOf(a0: Char): java.lang.String = java.lang.String.valueOf(a0)
  def valueOf(a0: Int): java.lang.String = java.lang.String.valueOf(a0)
  def valueOf(a0: Long): java.lang.String = java.lang.String.valueOf(a0)
  def valueOf(a0: Float): java.lang.String = java.lang.String.valueOf(a0)
  def valueOf(a0: Double): java.lang.String = java.lang.String.valueOf(a0)
  def copyValueOf(a0: Array[Char], a1: Int, a2: Int): java.lang.String = java.lang.String.copyValueOf(a0,a1,a2)
  def copyValueOf(a0: Array[Char]): java.lang.String = java.lang.String.copyValueOf(a0)
  def format(a0: java.lang.String, a1: Array[java.lang.Object]): java.lang.String = java.lang.String.format(a0,a1)
  def format(a0: java.util.Locale, a1: java.lang.String, a2: Array[java.lang.Object]): java.lang.String = java.lang.String.format(a0,a1,a2)
}

(Varargs aren't working here, incidentally--you'd need to fix those if they were present. You can do this by going back to the .isVarArgs method from the method itself (not its string representation) and converting the last Array[X] into X* and then calling it as ai: _*.)

The nice thing about this approach is that if there are common methods, you don't even need to use structural typing in order to pull them out--you can define your own trait that your wrapped methods inherit from! (And if you're especially ambitious, you can shoehorn almost-but-not-quite conforming methods to actually conform, e.g. to all have the same method name.)

1
Vasil Remeniuk On

Why not simply passing the factory method (or a set of methods, if needed)?

def createObservable(fact: (AnyRef, String) => IObservableValue, 
target: AnyRef, property: String) = fact(target, property)

createObservable(BeanObservables.createObservable, target, property)
4
Rustem Suniev On

One way is to use reflection:

object PojoObservables

val pojoObservableFact = PojoObservables.getClass.getConstructor()
pojoObservableFact.setAccessible(true)
pojoObservableFact.newInstance()

Now we can call the methods on PojoObservables.method...

3
Kevin Wright On

So taking the reference for PojoObservables from here, I can see the static method observeValue as you describe.

This is an easy one, Scala treats static methods on Java classes as though they were defined on a companion. You can either use this as though it were old-school Java:

import org.eclipse.core.databinding.beans.PojoObservables
...
val ov = PojoObservables.observeValue(target, property)

Or the new-school variant with static imports:

import org.eclipse.core.databinding.beans.PojoObservables._
...
val ov = observeValue(target, property)

You can then pass any such method around as a function object. The idea that a function somehow needs to be escorted by its interface is very Java-centric thinking, just reference whatever you need and supply as an argument to other higher-kinded functions :)